Skip to content

Commit 6e74077

Browse files
committed
Improve and comment examples
1 parent fa804dc commit 6e74077

File tree

4 files changed

+151
-72
lines changed

4 files changed

+151
-72
lines changed

examples/crackme_x86_linux.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -89,21 +89,26 @@ def replay(self, input: bytes) -> bool:
8989

9090
return False
9191

92+
def progress(msg: str) -> None:
93+
print(msg, end='\r', file=sys.stderr, flush=True)
94+
9295
def main():
93-
idx_list = (1, 4, 2, 0, 3)
94-
flag = [0] * len(idx_list)
96+
flag = bytearray(b'*****')
97+
indices = (1, 4, 2, 0, 3)
9598

96-
solver = Solver(bytes(flag))
99+
# all possible flag characters (may be reduced to uppercase and digits to save time)
100+
charset = string.printable
97101

98-
for idx in idx_list:
102+
progress('Initializing...')
103+
solver = Solver(flag)
99104

100-
# bruteforce all possible flag characters
101-
for ch in string.printable:
102-
flag[idx] = ord(ch)
105+
for i in indices:
106+
for ch in charset:
107+
flag[i] = ord(ch)
103108

104-
print(f'Guessing... [{"".join(chr(ch) if ch else "_" for ch in flag)}]', end='\r', file=sys.stderr, flush=True)
109+
progress(f'Guessing... {flag.decode()}')
105110

106-
if solver.replay(bytes(flag)):
111+
if solver.replay(flag):
107112
break
108113

109114
else:
@@ -113,3 +118,5 @@ def main():
113118

114119
if __name__ == "__main__":
115120
main()
121+
122+
# expected flag: L1NUX

examples/crackme_x86_windows.py

Lines changed: 105 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -10,47 +10,111 @@
1010
from qiling.const import QL_VERBOSE
1111
from qiling.extensions import pipe
1212

13-
def instruction_count(ql: Qiling, address: int, size: int, user_data):
14-
user_data[0] += 1
15-
16-
def get_count(flag):
17-
ql = Qiling(["rootfs/x86_windows/bin/crackme.exe"], "rootfs/x86_windows", verbose=QL_VERBOSE.OFF)
18-
19-
ql.os.stdin = pipe.SimpleStringBuffer()
20-
ql.os.stdout = pipe.SimpleStringBuffer()
21-
22-
ql.os.stdin.write(bytes("".join(flag) + "\n", 'utf-8'))
23-
count = [0]
24-
25-
ql.hook_code(instruction_count, count)
26-
ql.run()
27-
28-
print(ql.os.stdout.read().decode('utf-8'), end='')
29-
print(" ============ count: %d ============ " % count[0])
30-
31-
return count[0]
32-
33-
def solve():
34-
# BJWXB_CTF{C5307D46-E70E-4038-B6F9-8C3F698B7C53}
35-
prefix = list("BJWXB_CTF{C5307D46-E70E-4038-B6F9-8C3F698B7C")
36-
flag = list("\x00" * 100)
37-
base = get_count(prefix + flag)
38-
i = 0
39-
40-
try:
41-
for i in range(len(flag)):
42-
for j in "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-{}":
43-
flag[i] = j
44-
data = get_count(prefix + flag)
45-
if data > base:
46-
base = data
47-
print("\n\n\n>>> FLAG: " + "".join(prefix + flag) + "\n\n\n")
48-
break
49-
if flag[i] == "}":
13+
ROOTFS = r"rootfs/x86_windows"
14+
15+
class Solver:
16+
def __init__(self, invalid: bytes):
17+
# create a silent qiling instance
18+
self.ql = Qiling([rf"{ROOTFS}/bin/crackme.exe"], ROOTFS, verbose=QL_VERBOSE.OFF)
19+
20+
self.ql.os.stdin = pipe.SimpleInStream(sys.stdin.fileno()) # take over the input to the program using a fake stdin
21+
self.ql.os.stdout = pipe.NullOutStream(sys.stdout.fileno()) # disregard program output
22+
23+
# execute program until it reaches the part that asks for input
24+
self.ql.run(end=0x401030)
25+
26+
# record replay starting and ending points.
27+
#
28+
# since the emulation halted upon entering a function, its return address is there on
29+
# the stack. we use it to limit the emulation till function returns
30+
self.replay_starts = self.ql.arch.regs.arch_pc
31+
self.replay_ends = self.ql.stack_read(0)
32+
33+
# instead of restarting the whole program every time a new flag character is guessed,
34+
# we will restore its state to the latest point possible, fast-forwarding a good
35+
# amount of start-up code that is not affected by the input.
36+
#
37+
# here we save the state just when the read input function is about to be called so we
38+
# could use it to jumpstart the initialization part and get to the read input immediately
39+
self.jumpstart = self.ql.save() or {}
40+
41+
# calibrate the replay instruction count by running the code with an invalid input
42+
# first. the instruction count returned from the calibration process will be then
43+
# used as a baseline for consequent replays
44+
self.best_icount = self.__run(invalid)
45+
46+
def __run(self, input: bytes) -> int:
47+
icount = [0]
48+
49+
def __count_instructions(ql: Qiling, address: int, size: int):
50+
icount[0] += 1
51+
52+
# set a hook to fire up every time an instruction is about to execute
53+
hobj = self.ql.hook_code(__count_instructions)
54+
55+
# feed stdin with input
56+
self.ql.os.stdin.write(input + b'\n')
57+
58+
# resume emulation till function returns
59+
self.ql.run(begin=self.replay_starts, end=self.replay_ends)
60+
61+
hobj.remove()
62+
63+
return icount[0]
64+
65+
def replay(self, input: bytes) -> bool:
66+
"""Restore state and replay with a new input.
67+
68+
Returns an indication to execution progress: `True` if a progress
69+
was made, `False` otherwise
70+
"""
71+
72+
# restore program's state back to the starting point
73+
self.ql.restore(self.jumpstart)
74+
75+
# resume emulation and count emulated instructions
76+
curr_icount = self.__run(input)
77+
78+
# the larger part of the input is correct, the more instructions are expected to be executed. this is true
79+
# for traditional loop-based validations like strcmp or memcmp which bails as soon as a mismatch is found:
80+
# more correct characters mean more loop iterations - thus more executed instructions.
81+
#
82+
# if we got a higher instruction count, it means we made a progress in the right direction
83+
if curr_icount > self.best_icount:
84+
self.best_icount = curr_icount
85+
86+
return True
87+
88+
return False
89+
90+
def progress(msg: str) -> None:
91+
print(msg, end='\r', file=sys.stderr, flush=True)
92+
93+
def main():
94+
flag = bytearray(b'BJWXB_CTF{********-****-****-****-************}')
95+
indices = (i for i, ch in enumerate(flag) if ch == ord('*'))
96+
97+
# uppercase hex digits
98+
charset = '0123456789ABCDEF'
99+
100+
progress('Initializing...')
101+
solver = Solver(flag)
102+
103+
for i in indices:
104+
for ch in charset:
105+
flag[i] = ord(ch)
106+
107+
progress(f'Guessing... {flag.decode()}')
108+
109+
if solver.replay(flag):
50110
break
51-
print("SOLVED!!!")
52-
except KeyboardInterrupt:
53-
print("STOP: KeyboardInterrupt")
111+
112+
else:
113+
raise RuntimeError('no match found')
114+
115+
print(f'\nFlag found!')
54116

55117
if __name__ == "__main__":
56-
solve()
118+
main()
119+
120+
# expected flag: BJWXB_CTF{C5307D46-E70E-4038-B6F9-8C3F698B7C53}

examples/hello_arm_linux_custom_syscall.py

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,37 @@
99
from qiling import Qiling
1010
from qiling.const import QL_VERBOSE
1111

12-
def my_syscall_write(ql: Qiling, write_fd, write_buf, write_count, *args, **kw):
13-
regreturn = 0
14-
12+
# customized system calls always use the same arguments list as the original ones,
13+
# but with a Qiling instance as first argument
14+
def my_syscall_write(ql: Qiling, fd: int, buf: int, count: int):
1515
try:
16-
buf = ql.mem.read(write_buf, write_count)
17-
ql.log.info("\n+++++++++\nmy write(%d,%x,%i) = %d\n+++++++++" % (write_fd, write_buf, write_count, regreturn))
18-
ql.os.fd[write_fd].write(buf)
19-
regreturn = write_count
16+
# read data from emulated memory
17+
data = ql.mem.read(buf, count)
18+
19+
# select the emulated file object that corresponds to the requested
20+
# file descriptor
21+
fobj = ql.os.fd[fd]
22+
23+
# write the data into the file object, if it supports write operations
24+
if hasattr(fobj, 'write'):
25+
fobj.write(data)
2026
except:
21-
regreturn = -1
22-
ql.log.info("\n+++++++++\nmy write(%d,%x,%i) = %d\n+++++++++" % (write_fd, write_buf, write_count, regreturn))
27+
ret = -1
28+
else:
29+
ret = count
2330

24-
return regreturn
31+
ql.log.info(f'my_syscall_write({fd}, {buf:#x}, {count}) = {ret}')
2532

33+
return ret
2634

2735
if __name__ == "__main__":
28-
ql = Qiling(["rootfs/arm_linux/bin/arm_hello"], "rootfs/arm_linux", verbose=QL_VERBOSE.DEBUG)
29-
# Custom syscall handler by syscall name or syscall number.
30-
# Known issue: If the syscall func is not be implemented in qiling, qiling does
31-
# not know which func should be replaced.
32-
# In that case, you must specify syscall by its number.
36+
ql = Qiling([r'rootfs/arm_linux/bin/arm_hello'], r'rootfs/arm_linux', verbose=QL_VERBOSE.DEBUG)
37+
38+
# replacing a system call with a custom implementation.
39+
# note that system calls may be referred to either by their name or number.
3340
ql.os.set_syscall(0x04, my_syscall_write)
3441

35-
# set syscall by syscall name
36-
#ql.os.set_syscall("write", my_syscall_write)
42+
# an possible alternative: refer to a syscall by its name
43+
#ql.os.set_syscall('write', my_syscall_write)
3744

3845
ql.run()

examples/hello_arm_linux_debug.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@
99
from qiling import Qiling
1010
from qiling.const import QL_VERBOSE
1111

12-
def run_sandbox(path, rootfs, verbose):
13-
ql = Qiling(path, rootfs, verbose = verbose)
12+
if __name__ == "__main__":
13+
ql = Qiling([r'rootfs/arm_linux/bin/arm_hello'], r'rootfs/arm_linux', verbose=QL_VERBOSE.DEBUG)
14+
1415
ql.debugger = "qdb" # enable qdb without options
16+
17+
# other possible alternatives:
1518
# ql.debugger = "qdb::rr" # switch on record and replay with rr
1619
# ql.debugger = "qdb:0x1030c" # enable qdb and setup breakpoin at 0x1030c
17-
ql.run()
1820

19-
if __name__ == "__main__":
20-
run_sandbox(["rootfs/arm_linux/bin/arm_hello"], "rootfs/arm_linux", QL_VERBOSE.DEBUG)
21+
ql.run()

0 commit comments

Comments
 (0)