Skip to content

Commit 8e7fdf8

Browse files
authored
Merge pull request #1080 from elicn/qiling-next
qiling-next
2 parents 91a4732 + 5b4613d commit 8e7fdf8

File tree

168 files changed

+3257
-3030
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

168 files changed

+3257
-3030
lines changed

examples/crackme_x86_linux.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,11 @@
1616

1717
class Solver:
1818
def __init__(self, invalid: bytes):
19-
mock_stdin = pipe.SimpleInStream(sys.stdin.fileno())
20-
mock_stdout = pipe.NullOutStream(sys.stdout.fileno())
21-
2219
# create a silent qiling instance
23-
self.ql = Qiling([rf"{ROOTFS}/bin/crackme_linux"], ROOTFS,
24-
verbose=QL_VERBOSE.OFF, # thwart qiling logger output
25-
stdin=mock_stdin, # take over the input to the program using a fake stdin
26-
stdout=mock_stdout) # disregard program output
20+
self.ql = Qiling([rf"{ROOTFS}/bin/crackme_linux"], ROOTFS, verbose=QL_VERBOSE.OFF)
21+
22+
self.ql.os.stdin = pipe.SimpleInStream(sys.stdin.fileno()) # take over the input to the program using a fake stdin
23+
self.ql.os.stdout = pipe.NullOutStream(sys.stdout.fileno()) # disregard program output
2724

2825
# execute program until it reaches the 'main' function
2926
self.ql.run(end=0x0804851b)
@@ -32,7 +29,7 @@ def __init__(self, invalid: bytes):
3229
#
3330
# since the emulation halted upon entering 'main', its return address is there on
3431
# the stack. we use it to limit the emulation till function returns
35-
self.replay_starts = self.ql.reg.arch_pc
32+
self.replay_starts = self.ql.arch.regs.arch_pc
3633
self.replay_ends = self.ql.stack_read(0)
3734

3835
# instead of restarting the whole program every time a new flag character is guessed,

examples/crackme_x86_windows.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,10 @@ def instruction_count(ql: Qiling, address: int, size: int, user_data):
1414
user_data[0] += 1
1515

1616
def get_count(flag):
17-
mock_stdin = pipe.SimpleStringBuffer()
18-
mock_stdout = pipe.SimpleStringBuffer()
17+
ql = Qiling(["rootfs/x86_windows/bin/crackme.exe"], "rootfs/x86_windows", verbose=QL_VERBOSE.OFF)
1918

20-
ql = Qiling(["rootfs/x86_windows/bin/crackme.exe"], "rootfs/x86_windows",
21-
verbose=QL_VERBOSE.OFF,
22-
stdin=mock_stdin,
23-
stdout=mock_stdout)
19+
ql.os.stdin = pipe.SimpleStringBuffer()
20+
ql.os.stdout = pipe.SimpleStringBuffer()
2421

2522
ql.os.stdin.write(bytes("".join(flag) + "\n", 'utf-8'))
2623
count = [0]

examples/crackme_x86_windows_auto.py

Lines changed: 57 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,64 @@
88

99
from qiling import Qiling
1010
from qiling.extensions import pipe
11+
from qiling.const import QL_INTERCEPT, QL_VERBOSE
12+
from qiling.os.windows.api import HWND, UINT, LONG
13+
14+
def hook_DialogBoxParamA_onexit(ql: Qiling, address: int, params, retval: int):
15+
# extract lpDialogFunc value
16+
# [see arguments list at 'qiling/os/windows/dlls/user32.py' -> 'hook_DialogBoxParamA']
17+
lpDialogFunc = params['lpDialogFunc']
18+
19+
def call_DialogFunc(ql: Qiling):
20+
# we would like to resume from the exact same address that used to invoke
21+
# this hook. in order to prevent an endless loop of hook invocations, we
22+
# remove the hook through its handle.
23+
hh.remove()
24+
25+
WM_COMMAND = 0x111
26+
IDS_APPNAME = 1001
27+
28+
# [steps #3 and #4]
29+
# set up the arguments and call the address passed through the lpDialogFunc
30+
# param. make sure it resumes back to where we were.
31+
ql.os.fcall.call_native(lpDialogFunc, (
32+
(HWND, 0),
33+
(UINT, WM_COMMAND),
34+
(UINT, IDS_APPNAME),
35+
(LONG, 0),
36+
), ql.arch.regs.arch_pc)
37+
38+
# get DialogBoxParamA return address; should be the first item on the stack
39+
retaddr = ql.arch.stack_read(0)
40+
41+
# we would like to call DialogFunc as soon as DialogBoxParamA returns, so we
42+
# hook its return address. once it returns, 'call_DialogFunc' will be invoked.
43+
hh = ql.hook_address(call_DialogFunc, retaddr)
44+
45+
def our_sandbox(path: str, rootfs: str):
46+
ql = Qiling([path], rootfs, verbose=QL_VERBOSE.DEFAULT)
47+
48+
# this crackme's logic lies within the function passed to DialogBoxParamA through
49+
# the lpDialogFunc parameter. normally DialogBoxParamA would call the function
50+
# passed through that parameter, but Qiling's implementation for it doesn't do
51+
# that.
52+
#
53+
# to solve this crackme and force the "success" dialog to show, we will:
54+
# 1. set up a mock stdin and feed it with the correct flag
55+
# 1. hook DialogBoxParamA to see where its lpDialogFunc param points to
56+
# 2. set up a valid set of arguments DialogFunc expects to see
57+
# 3. call it and see it greets us with a "success" message
58+
59+
# [step #1]
60+
# set up a mock stdin and feed it with mocked keystrokes
61+
ql.os.stdin = pipe.SimpleInStream(sys.stdin.fileno())
62+
ql.os.stdin.write(b'Ea5yR3versing\n')
63+
64+
# [step #2]
65+
# intercept DialogBoxParamA on exit
66+
ql.os.set_api('DialogBoxParamA', hook_DialogBoxParamA_onexit, QL_INTERCEPT.EXIT)
1167

12-
def force_call_dialog_func(ql: Qiling):
13-
# get DialogFunc address
14-
lpDialogFunc = ql.unpack32(ql.mem.read(ql.reg.esp - 0x8, 4))
15-
# setup stack for DialogFunc
16-
ql.stack_push(0)
17-
ql.stack_push(1001)
18-
ql.stack_push(273)
19-
ql.stack_push(0)
20-
ql.stack_push(0x0401018)
21-
# force EIP to DialogFunc
22-
ql.reg.eip = lpDialogFunc
23-
24-
def our_sandbox(path, rootfs):
25-
ql = Qiling(path, rootfs, stdin=pipe.SimpleInStream(sys.stdin.fileno()))
26-
27-
ql.os.stdin.write(b"Ea5yR3versing\n")
28-
ql.hook_address(force_call_dialog_func, 0x00401016)
2968
ql.run()
3069

3170
if __name__ == "__main__":
32-
# Flag is : Ea5yR3versing
33-
our_sandbox(["rootfs/x86_windows/bin/Easy_CrackMe.exe"], "rootfs/x86_windows")
71+
our_sandbox(r"rootfs/x86_windows/bin/Easy_CrackMe.exe", r"rootfs/x86_windows")

examples/crackme_x86_windows_setcallback.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@
1010

1111
def force_call_dialog_func(ql: Qiling):
1212
# get DialogFunc address
13-
lpDialogFunc = ql.unpack32(ql.mem.read(ql.reg.esp - 0x8, 4))
13+
lpDialogFunc = ql.unpack32(ql.mem.read(ql.arch.regs.esp - 0x8, 4))
1414
# setup stack for DialogFunc
1515
ql.stack_push(0)
1616
ql.stack_push(1001)
1717
ql.stack_push(273)
1818
ql.stack_push(0)
1919
ql.stack_push(0x0401018)
2020
# force EIP to DialogFunc
21-
ql.reg.eip = lpDialogFunc
21+
ql.arch.regs.eip = lpDialogFunc
2222

2323
def my_sandbox(path, rootfs):
2424
ql = Qiling(path, rootfs)

examples/crackme_x86_windows_unpatch.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@
1010

1111
def force_call_dialog_func(ql: Qiling):
1212
# get DialogFunc address
13-
lpDialogFunc = ql.unpack32(ql.mem.read(ql.reg.esp - 0x8, 4))
13+
lpDialogFunc = ql.unpack32(ql.mem.read(ql.arch.regs.esp - 0x8, 4))
1414
# setup stack for DialogFunc
1515
ql.stack_push(0)
1616
ql.stack_push(1001)
1717
ql.stack_push(273)
1818
ql.stack_push(0)
1919
ql.stack_push(0x0401018)
2020
# force EIP to DialogFunc
21-
ql.reg.eip = lpDialogFunc
21+
ql.arch.regs.eip = lpDialogFunc
2222

2323
def our_sandbox(path, rootfs):
2424
ql = Qiling(path, rootfs)

examples/doogie_8086_crack.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def echo_key(ql: Qiling, key):
119119

120120
def show_once(ql: Qiling, key):
121121
klen = len(key)
122-
ql.reg.ax = klen
122+
ql.arch.regs.ax = klen
123123
ql.mem.write(0x87F4, key)
124124
# Partial exectution to skip input reading
125125
ql.run(begin=0x801B, end=0x803d)
@@ -133,7 +133,7 @@ def third_stage(keys):
133133
"rootfs/8086",
134134
console=False)
135135
ql.add_fs_mapper(0x80, QlDisk("rootfs/8086/doogie/doogie.DOS_MBR", 0x80))
136-
ql.set_api((0x1a, 4), set_required_datetime, QL_INTERCEPT.EXIT)
136+
ql.os.set_api((0x1a, 4), set_required_datetime, QL_INTERCEPT.EXIT)
137137
hk = ql.hook_code(stop, begin=0x8018, end=0x8018)
138138
ql.run()
139139
ql.hook_del(hk)
@@ -172,10 +172,10 @@ def read_until_zero(ql: Qiling, addr):
172172

173173
def set_required_datetime(ql: Qiling):
174174
ql.log.info("Setting Feburary 06, 1990")
175-
ql.reg.ch = BIN2BCD(19)
176-
ql.reg.cl = BIN2BCD(1990%100)
177-
ql.reg.dh = BIN2BCD(2)
178-
ql.reg.dl = BIN2BCD(6)
175+
ql.arch.regs.ch = BIN2BCD(19)
176+
ql.arch.regs.cl = BIN2BCD(1990%100)
177+
ql.arch.regs.dh = BIN2BCD(2)
178+
ql.arch.regs.dl = BIN2BCD(6)
179179

180180
def stop(ql, addr, data):
181181
ql.emu_stop()
@@ -187,7 +187,7 @@ def first_stage():
187187
console=False)
188188
ql.add_fs_mapper(0x80, QlDisk("rootfs/8086/doogie/doogie.DOS_MBR", 0x80))
189189
# Doogie suggests that the datetime should be 1990-02-06.
190-
ql.set_api((0x1a, 4), set_required_datetime, QL_INTERCEPT.EXIT)
190+
ql.os.set_api((0x1a, 4), set_required_datetime, QL_INTERCEPT.EXIT)
191191
# A workaround to stop the program.
192192
hk = ql.hook_code(stop, begin=0x8018, end=0x8018)
193193
ql.run()

examples/extensions/idaplugin/custom_script.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ def __init__(self):
55
pass
66

77
def _show_context(self, ql:Qiling):
8-
registers = [ k for k in ql.reg.register_mapping.keys() if type(k) is str ]
8+
registers = [ k for k in ql.arch.regs.register_mapping.keys() if type(k) is str ]
99
for idx in range(0, len(registers), 3):
1010
regs = registers[idx:idx+3]
11-
s = "\t".join(map(lambda v: f"{v:4}: {ql.reg.__getattr__(v):016x}", regs))
11+
s = "\t".join(map(lambda v: f"{v:4}: {ql.arch.regs.__getattr__(v):016x}", regs))
1212
ql.log.info(s)
1313

1414
def custom_prepare(self, ql:Qiling):

examples/fuzzing/linux_x8664/fuzz_x8664_linux.py

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,50 +17,53 @@
1717
$ rm -fr afl_outputs/default/
1818
"""
1919

20-
# No more need for importing unicornafl, try ql.afl_fuzz instead!
20+
# No more need for importing unicornafl, try afl.ql_afl_fuzz instead!
2121

2222
import os
2323
import sys
2424

25-
from typing import Any, Optional
25+
from typing import Optional
2626

2727
sys.path.append("../../..")
2828
from qiling import Qiling
2929
from qiling.const import QL_VERBOSE
3030
from qiling.extensions import pipe
31-
from qiling.extensions.afl import ql_afl_fuzz
31+
from qiling.extensions import afl
3232

3333
def main(input_file: str):
34-
mock_stdin = pipe.SimpleInStream(sys.stdin.fileno())
35-
3634
ql = Qiling(["./x8664_fuzz"], "../../rootfs/x8664_linux",
37-
verbose=QL_VERBOSE.OFF, # keep qiling logging off
38-
console=False, # thwart program output
39-
stdin=mock_stdin, # redirect stdin to our mock to feed it with incoming fuzzed keystrokes
40-
stdout=None,
41-
stderr=None)
35+
verbose=QL_VERBOSE.OFF, # keep qiling logging off
36+
console=False) # thwart program output
37+
38+
# redirect stdin to our mock to feed it with incoming fuzzed keystrokes
39+
ql.os.stdin = pipe.SimpleInStream(sys.stdin.fileno())
4240

4341
def place_input_callback(ql: Qiling, input: bytes, persistent_round: int) -> Optional[bool]:
44-
"""Called with every newly generated input.
42+
"""Feed generated stimuli to the fuzzed target.
43+
44+
This method is called with every fuzzing iteration.
4545
"""
4646

47+
# feed fuzzed input to our mock stdin
4748
ql.os.stdin.write(input)
4849

50+
# signal afl to proceed with this input
4951
return True
5052

51-
def start_afl(_ql: Qiling):
52-
"""Callback from inside.
53+
def start_afl(ql: Qiling):
54+
"""Have Unicorn fork and start instrumentation.
5355
"""
54-
ql_afl_fuzz(_ql, input_file=input_file, place_input_callback=place_input_callback, exits=[ql.os.exit_point])
56+
57+
afl.ql_afl_fuzz(ql, input_file=input_file, place_input_callback=place_input_callback, exits=[ql.os.exit_point])
5558

5659
# get image base address
5760
ba = ql.loader.images[0].base
5861

59-
# make process crash whenever __stack_chk_fail@plt is about to be called.
62+
# make the process crash whenever __stack_chk_fail@plt is about to be called.
6063
# this way afl will count stack protection violations as crashes
6164
ql.hook_address(callback=lambda x: os.abort(), address=ba + 0x1225)
6265

63-
# set a hook on main() to let unicorn fork and start instrumentation
66+
# set afl instrumentation [re]starting point. we set it to 'main'
6467
ql.hook_address(callback=start_afl, address=ba + 0x122c)
6568

6669
# okay, ready to roll

examples/fuzzing/linux_x8664/libfuzzer_x8664_linux.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,7 @@
1010
class SimpleFuzzer:
1111

1212
def run(self):
13-
ql = Qiling(["./x8664_fuzz"], "../../rootfs/x8664_linux",
14-
console=False, # No output
15-
stdin=None,
16-
stdout=None,
17-
stderr=None)
13+
ql = Qiling(["./x8664_fuzz"], "../../rootfs/x8664_linux", console=False)
1814
ba = ql.loader.images[0].base
1915
try:
2016
# Only instrument the function `fun`, so we don't need to instrument libc and ld

examples/fuzzing/qnx_arm/fuzz_arm_qnx.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,13 @@
2121
from qiling.extensions.afl import ql_afl_fuzz
2222

2323
def main(input_file, enable_trace=False):
24-
mock_stdin = pipe.SimpleInStream(sys.stdin.fileno())
24+
ql = Qiling(["./arm_fuzz"], "../../rootfs/arm_qnx", console=enable_trace)
2525

26-
ql = Qiling(["./arm_fuzz"], "../../rootfs/arm_qnx",
27-
stdin=mock_stdin,
28-
stdout=1 if enable_trace else None,
29-
stderr=1 if enable_trace else None,
30-
console = True if enable_trace else False)
26+
ql.os.stdin = pipe.SimpleInStream(sys.stdin.fileno())
3127

32-
# or this for output:
33-
# ... stdout=sys.stdout, stderr=sys.stderr)
28+
if not enable_trace:
29+
ql.os.stdout = pipe.NullOutStream(sys.stdout.fileno())
30+
ql.os.stderr = pipe.NullOutStream(sys.stderr.fileno())
3431

3532
def place_input_callback(ql: Qiling, input: bytes, _: int):
3633
ql.os.stdin.write(input)
@@ -50,7 +47,7 @@ def start_afl(_ql: Qiling):
5047

5148
if enable_trace:
5249
# The following lines are only for `-t` debug output
53-
md = ql.create_disassembler()
50+
md = ql.arch.disassembler
5451
count = [0]
5552

5653
def spaced_hex(data):

0 commit comments

Comments
 (0)