Binary exploitation basic 2
寫得很爛,所以不知道有哪些會是錯的🫠
- objdump -R
- 可以得知binary裡有用到的function, 左邊的offset就是他的GOT table

- 也可以用readelf -St
去查看GOT的位址

每個function的.plt第一行都是jmp QWORD PTR [rip+0x00000]是從一個memory裡拿值出來然後jump上去
左邊尚未call gets, 由於lazy binding的機制所以它都會放成那個function+6的地方, 右邊run過再看他的GOT table就發現已經被填回去了, 所以造成之後再call這個function的時候他會直接從裡面取值, 不用再跑resolve


ROP gadget
- 是一個片段可以執行的code
- 結尾是ret instruction
- call, jmp…任何可以繼續控制流程的方法
ROP
- 適用於NX有打開以至於不能使用shellcode的方式
- 在既有的執行區域(code segment)去尋找gadget, 然後運用這些得到的gadget去組成一長串的ROP chain(return adddress chain)
- 在function return的時候, 會拿走第一個return address, 在這時候rsp就會指向第二個return address, 並跳到第一個return address去執行, 接著會執行到ret instruction, 而第二個return address也在開始的時候就被放好了, 做到繼續control flow, 反覆執行來達到return oriented programming類型的攻擊
- 利用許多片段執行行為的gadget, 來串出任意代碼執行, 藉此繞過NX保護機制
- Control Register
- Gadget - pop
; ret - 通常不會有完美的gadget可用並且是ret結尾
- 例如要控制rax, 沒有pop rax; ret gadget也大機率不會有mov rax, <希望的值>
- 假設存在, pop rdi; ret與mov rax; ret, 組合出控制rax值得payload
- 或是找到xor rax, rax; ret與inc rax, ret, 先將rax清零在一直加一到想要的值
- 存在overflow或任何成功control rip的前提下:
- NX關的情況下, 可以填入執行execve(“/bin/sh”, 0, 0)shellcode並且去嘗試控制rip跳到shellcode
- 但如果NX開啟的話, 就可以透過ROP的方式, 堆疊出執行execve(“/bin/sh”, 0, 0)行為的ROP Chain
要pop rsi為/bin/sh\0的時候不是存地址而是直接放8個bytes因為要把string給存到memory裡面, 然後之後就會把rsi(/bin/sh\0)的值送到rdi, 並且需要放置在一個可寫的區塊
後面會再做pop rdx, 0; pop rsi, 0把裡面的值清空, 以達到execve想要的效果, 最後在pop rax, 0x3b就是execve的rax, 然後再就可以syscall了, 這樣就可以成功的get shell了
- 如果要mov rdi, rsi的gadget要找: mov qword ptr [rdi], rsi ; ret
ret2plt
- 如果要串的function本身就有在binary裡, 像是 , binary裡本身就有puts(), 這樣的情況就可以直接用ROP放好參數直接使用, 不用用ROP去堆全部的行為
- 在PIE關閉的情況下, 即使不知道library function address(因為ASLR), 也可以透過return到.plt上使用這個function
ret2libc
- bypass ASLR
- 假如知道了library被map到的隨機起始地址(base add.ress), 就可以計算出libc的function address, 就能去調用library中的函式
- 找出libc的隨機base
- 透過information leak的漏洞, 去leak出memory上面的內容, 獲得屬於libc的segment的address
- 這個address是隨機的base address加上一個固定位移的offset(libc版本不同offset就會不同)
- leak - offset = base_address
- 假如說洩漏出的printf@got內容為0x7fd0f9e57e80, 而已知的libc version為2.27, 我們就可以使用靜態分析(像是readelf -s等等)去得知printf function的library function在library中的offset是0x64e80, 扣掉offset後就可以得到執行libc的隨機base address是0x7fd0f9e57e80 - 0x64e80 = 0x7fd0f9df3000
- 得知base_address之後就可以透過加上offset的方式去得知其他function的address, 來call這個function, 並且結合ROP chain等等
- system() = libc_base_address + system_offset = 0x7fd0f9df3000 + 0x4f440 = 0x7fd0f9e42440
- function在libc的offset結尾的三個bytes都不會random, 都是固定的
由於ASLR所以每次leak出來的地址都是不固定的



如果資料夾裡有Dockerfile裡面有給ubuntu的版本可以這樣去尋找對應的libc版本

要找一個binary裡面的GOT的offset可以使用objdump -R <binary>
我們通常會找的目標就是__libc_start_main的GOT, 因為他是最早被執行的, 也代表一定已經被resolve過了
這個意思就代表我在執行puts(libc_start_got);
所以這個時候我就可以把__libc_start_main的address給leak出來, 到後面也會發現因為ASLR的機制導致每次leak出來的address都不一樣, 拿到leak之後要再只能在當下執行的程式使用, 因為等到下一次就會不一樣了

如果要取得libc的offset可以使用readelf -s readelf -s /lib/x86_64-linux-gnu/<libc version>
進入docker容器之後要記得使用下列指令才能正常運作readelf, objdump等等相關指令
1 | apt update |

system的base_address就等於我們已經leak出來的值再去加上這個libc的system的offset
由於我們在前面已經使用過一個gadget了所以後面想要再串一個屬於system的gadget就只能ret2main然後繼續使用原本拿到的gadget
會發現已經跳回main了, 所以就可以繼續使用使用先前使用的gadget


用strings -tx去找這個libc的/bin/sh字串

都做好了之後會發現它會crash
是因為要跟16bytes對齊, 那要怎麼做才可以處理, 就是要透過ret多往後8個bytes

還有建議直接載入libc檔案用ELF讓他自己去找function的位置
差點被環境搞死…

ret2libc放棄🫠
反正自己試了一遍不行看著yuawn再做一次還是不行, 所以就放棄, 應該是環境因素
最後的失敗exploit:
1 | from pwn import * |
demo
Q: 這個程式碼裡面有甚麼漏洞

A: information leak
可以看到我再做第三次輸入的時候他後面會噴出怪怪的東西

接下來可以看看gdb發生的事
這個是buf的位址

可以發現我框起來的部分全部都是buf, 有0x100的空間, 到了df90就是rbp跟rsp, 可以發現我的buf裡面它裡面不是乾淨的, 而是放了一堆address, 是因為在function prologue的時候會sub 0x100的空間出來, 程式在compiler的時候不會去清乾淨裡面的值, 不過在很多時候不會遇到, 因為我們再輸入的時候可能會接換行, 或者是library的function基本上是底層的syscall在包一層, 在包一層的過程會有一些行為, 例如append null byte, 雖然只overflow一個byte並且不能控制那個byte的內容, 但是他可以做很多事情, 最後還可以開shell, 只是一個overflow一個byte, 而且那個byte只能是null byte, 就可以讓你開shell

可以看到裡面有一個看起來很像libc的address

沒截好

開aslr去測試位移的值是否一樣(aslr on)

在run一次

可以發現它們的offset都是一樣的
那我就可以利用這個機制去填入很多個byte去把我想要的值給leak出來
舉例來說:

這裡可以看到buf+0x18是libc的address
所以我們就可以填入0x18個bytes去leak出這個address的值
demo:
1 | from pwn import * |
result:

然後前面有提到offset都是固定的, 所以只要把每次leak出來的值 - offset就可以得到library的base
Stack Pivoting
-
ROP需要很多的空間去放置一長串的ROP Chain,有時候沒有那麼多的空間可以用,可能很短甚至只能控制第一次rip(function pointer等等),沒有足夠的空間可以拿來放ROP的Payload
-
如果找到其他地方有足夠的空間放置ROP Payload,這時候就可以透過比較少的gadget數, 把stack搬移到ROP Payload的位置,在去做ROP,這個手法就是stack vioting或是stack migration
-
leave; ret
- overflow的時候將rbp填成ROP chain的address - 8
- return address填leave; ret gadget
-
pop rsp; ret
-
手動找針對各種當下情況的gadget
ret2csu
- 位於__libc_csu_init函式裡,為compiler編進去的function
- 尾部有一片段code,很適合拿來控制register放置參數,以及control flow
- __libc_csu_init

- 透過控制rbp rbx r12 r13 r14 r15 registers的值,跳至gadget開頭,r13 r14 r15,分別放置前三個參數rdi rsi rdx,此部分解決了很少找到pop rdx gadget,ROP很難控制第三個參數的問題
- 控制r12 rbx來指定任意記憶體位置call[r12+rbx*8]
- 將rbx設為0,將rbp設為1,在call完後使rbx==rbp,jne不會take,而繼續執行後面的連續pop register,這樣就可以重複使用達成任意ROP