-
Notifications
You must be signed in to change notification settings - Fork 737
/
Copy pathcrackme_x86_windows.py
120 lines (86 loc) · 4.09 KB
/
crackme_x86_windows.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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#!/usr/bin/env python3
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#
import sys
sys.path.append("..")
from qiling import Qiling
from qiling.const import QL_VERBOSE
from qiling.extensions import pipe
ROOTFS = r"rootfs/x86_windows"
class Solver:
def __init__(self, invalid: bytes):
# create a silent qiling instance
self.ql = Qiling([rf"{ROOTFS}/bin/crackme.exe"], ROOTFS, verbose=QL_VERBOSE.DISABLED)
self.ql.os.stdin = pipe.SimpleInStream(sys.stdin.fileno()) # take over the input to the program using a fake stdin
self.ql.os.stdout = pipe.NullOutStream(sys.stdout.fileno()) # disregard program output
# execute program until it reaches the part that asks for input
self.ql.run(end=0x401030)
# record replay starting and ending points.
#
# since the emulation halted upon entering a function, its return address is there on
# the stack. we use it to limit the emulation till function returns
self.replay_starts = self.ql.arch.regs.arch_pc
self.replay_ends = self.ql.stack_read(0)
# instead of restarting the whole program every time a new flag character is guessed,
# we will restore its state to the latest point possible, fast-forwarding a good
# amount of start-up code that is not affected by the input.
#
# here we save the state just when the read input function is about to be called so we
# could use it to jumpstart the initialization part and get to the read input immediately
self.jumpstart = self.ql.save() or {}
# calibrate the replay instruction count by running the code with an invalid input
# first. the instruction count returned from the calibration process will be then
# used as a baseline for consequent replays
self.best_icount = self.__run(invalid)
def __run(self, input: bytes) -> int:
icount = [0]
def __count_instructions(ql: Qiling, address: int, size: int):
icount[0] += 1
# set a hook to fire up every time an instruction is about to execute
hobj = self.ql.hook_code(__count_instructions)
# feed stdin with input
self.ql.os.stdin.write(input + b'\n')
# resume emulation till function returns
self.ql.run(begin=self.replay_starts, end=self.replay_ends)
hobj.remove()
return icount[0]
def replay(self, input: bytes) -> bool:
"""Restore state and replay with a new input.
Returns an indication to execution progress: `True` if a progress
was made, `False` otherwise
"""
# restore program's state back to the starting point
self.ql.restore(self.jumpstart)
# resume emulation and count emulated instructions
curr_icount = self.__run(input)
# the larger part of the input is correct, the more instructions are expected to be executed. this is true
# for traditional loop-based validations like strcmp or memcmp which bails as soon as a mismatch is found:
# more correct characters mean more loop iterations - thus more executed instructions.
#
# if we got a higher instruction count, it means we made a progress in the right direction
if curr_icount > self.best_icount:
self.best_icount = curr_icount
return True
return False
def progress(msg: str) -> None:
print(msg, end='\r', file=sys.stderr, flush=True)
def main():
flag = bytearray(b'BJWXB_CTF{********-****-****-****-************}')
indices = (i for i, ch in enumerate(flag) if ch == ord('*'))
# uppercase hex digits
charset = '0123456789ABCDEF'
progress('Initializing...')
solver = Solver(flag)
for i in indices:
for ch in charset:
flag[i] = ord(ch)
progress(f'Guessing... {flag.decode()}')
if solver.replay(flag):
break
else:
raise RuntimeError('no match found')
print(f'\nFlag found!')
if __name__ == "__main__":
main()
# expected flag: BJWXB_CTF{C5307D46-E70E-4038-B6F9-8C3F698B7C53}