Skip to content

Commit 1190a8c

Browse files
authored
Merge branch 'qilingframework:dev' into dev
2 parents c21bec6 + cf541bf commit 1190a8c

File tree

15 files changed

+233
-103
lines changed

15 files changed

+233
-103
lines changed

examples/fuzzing/dlink_dir815/dir815_mips32el_linux.py

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,10 @@
77

88
import os,sys
99

10-
# This is new. Instead of unicorn, we import unicornafl. It's the same Uc with some new `afl_` functions
11-
import unicornafl
12-
13-
# Make sure Qiling uses our patched unicorn instead of it's own, second so without instrumentation!
14-
unicornafl.monkeypatch()
15-
1610
sys.path.append("../../..")
1711
from qiling import *
1812
from qiling.const import QL_VERBOSE
13+
from qiling.extensions.afl import ql_afl_fuzz
1914

2015
def main(input_file, enable_trace=False):
2116

@@ -32,32 +27,17 @@ def main(input_file, enable_trace=False):
3227
verbose=QL_VERBOSE.DEBUG, env=env_vars,
3328
console = True if enable_trace else False)
3429

35-
def place_input_callback(uc, input, _, data):
30+
def place_input_callback(ql: Qiling, input: bytes, _: int):
3631
env_var = ("HTTP_COOKIE=uid=1234&password=").encode()
3732
env_vars = env_var + input + b"\x00" + (ql.path).encode() + b"\x00"
3833
ql.mem.write(ql.target_addr, env_vars)
3934

40-
4135
def start_afl(_ql: Qiling):
4236

4337
"""
4438
Callback from inside
4539
"""
46-
# We start our AFL forkserver or run once if AFL is not available.
47-
# This will only return after the fuzzing stopped.
48-
try:
49-
print("Starting afl_fuzz().")
50-
if not _ql.uc.afl_fuzz(input_file=input_file,
51-
place_input_callback=place_input_callback,
52-
exits=[ql.os.exit_point]):
53-
print("Ran once without AFL attached.")
54-
os._exit(0) # that's a looot faster than tidying up.
55-
except unicornafl.UcAflError as ex:
56-
# This hook trigers more than once in this example.
57-
# If this is the exception cause, we don't care.
58-
# TODO: Chose a better hook position :)
59-
if ex != unicornafl.UC_AFL_RET_CALLED_TWICE:
60-
raise
40+
ql_afl_fuzz(_ql, input_file=input_file, place_input_callback=place_input_callback, exits=[ql.os.exit_point])
6141

6242
addr = ql.mem.search("HTTP_COOKIE=uid=1234&password=".encode())
6343
ql.target_addr = addr[0]

examples/fuzzing/linux_x8664/fuzz_x8664_linux.py

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,7 @@
1717
$ rm -fr afl_outputs/default/
1818
"""
1919

20-
# This is new. Instead of unicorn, we import unicornafl. It's the same Uc with some new `afl_` functions
21-
import unicornafl as UcAfl
22-
23-
# Make sure Qiling uses our patched unicorn instead of it's own, second so without instrumentation!
24-
UcAfl.monkeypatch()
20+
# No more need for importing unicornafl, try ql.afl_fuzz instead!
2521

2622
import os
2723
import sys
@@ -32,6 +28,7 @@
3228
from qiling import Qiling
3329
from qiling.const import QL_VERBOSE
3430
from qiling.extensions import pipe
31+
from qiling.extensions.afl import ql_afl_fuzz
3532

3633
def main(input_file: str):
3734
mock_stdin = pipe.SimpleInStream(sys.stdin.fileno())
@@ -43,30 +40,18 @@ def main(input_file: str):
4340
stdout=None,
4441
stderr=None)
4542

46-
def place_input_callback(uc: UcAfl.Uc, input: bytes, persistent_round: int, data: Any) -> Optional[bool]:
43+
def place_input_callback(ql: Qiling, input: bytes, persistent_round: int) -> Optional[bool]:
4744
"""Called with every newly generated input.
4845
"""
4946

5047
ql.os.stdin.write(input)
5148

49+
return True
50+
5251
def start_afl(_ql: Qiling):
5352
"""Callback from inside.
5453
"""
55-
56-
# We start our AFL forkserver or run once if AFL is not available.
57-
# This will only return after the fuzzing stopped.
58-
try:
59-
if not _ql.uc.afl_fuzz(input_file=input_file, place_input_callback=place_input_callback, exits=[ql.os.exit_point]):
60-
_ql.log.warning("Ran once without AFL attached")
61-
os._exit(0)
62-
63-
except UcAfl.UcAflError as ex:
64-
# This hook triggers more than once in this example.
65-
# If this is the exception cause, we don't care.
66-
67-
# TODO: choose a better hook position :)
68-
if ex.errno != UcAfl.UC_AFL_RET_CALLED_TWICE:
69-
raise
54+
ql_afl_fuzz(_ql, input_file=input_file, place_input_callback=place_input_callback, exits=[ql.os.exit_point])
7055

7156
# get image base address
7257
ba = ql.loader.images[0].base
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/usr/bin/python3
2+
3+
from fuzzercorn import *
4+
from unicorn import *
5+
from qiling import Qiling
6+
from qiling.extensions import pipe
7+
8+
import sys, os, ctypes
9+
10+
class SimpleFuzzer:
11+
12+
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)
18+
ba = ql.loader.images[0].base
19+
try:
20+
# Only instrument the function `fun`, so we don't need to instrument libc and ld
21+
FuzzerCornFuzz(ql.uc, sys.argv, [ql.os.exit_point], self.place_input, self.init, UserData=ql, Ranges=[(ba + 0x1179, ba + 0x122B)], CountersCount=4096)
22+
except Exception as ex:
23+
os.abort() # Quick exit
24+
25+
def place_input(self, uc: Uc, data: ctypes.Array, ql: Qiling):
26+
ql.restore(self.snapshot)
27+
ql.os.stdin = pipe.SimpleInStream(1)
28+
ql.os.stdin.write(bytes(data))
29+
return 1
30+
31+
def init(self, uc: Uc, argv: list, ql: Qiling):
32+
ba = ql.loader.images[0].base
33+
ql.hook_address(callback=lambda x: os.abort(), address=ba + 0x1225) # ___stack_chk_fail
34+
ql.run(end=ba + 0x122c) # Run to main.
35+
36+
# Save a snapshot.
37+
self.snapshot = ql.save()
38+
return 0
39+
40+
41+
if __name__ == "__main__":
42+
# chmod +x ./libfuzzer_x8664_linux.py
43+
# ./libfuzzer_x8664_linux.py -jobs=6 -workers=6
44+
SimpleFuzzer().run()
45+

examples/fuzzing/qnx_arm/fuzz_arm_qnx.py

Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,15 @@
1010
afl-fuzz -i ./afl_inputs -o ./afl_outputs -m none -U -- python3 ./fuzz_x8664_linux.py @@
1111
"""
1212

13-
# This is new. Instead of unicorn, we import unicornafl. It's the same Uc with some new `afl_` functions
14-
import unicornafl
15-
16-
# Make sure Qiling uses our patched unicorn instead of it's own, second so without instrumentation!
17-
unicornafl.monkeypatch()
13+
# No more need for importing unicornafl, try ql.afl_fuzz instead!
1814

1915
import sys, os
2016
from binascii import hexlify
2117

2218
sys.path.append("../../..")
2319
from qiling import *
2420
from qiling.extensions import pipe
21+
from qiling.extensions.afl import ql_afl_fuzz
2522

2623
def main(input_file, enable_trace=False):
2724
mock_stdin = pipe.SimpleInStream(sys.stdin.fileno())
@@ -35,33 +32,17 @@ def main(input_file, enable_trace=False):
3532
# or this for output:
3633
# ... stdout=sys.stdout, stderr=sys.stderr)
3734

38-
def place_input_callback(uc, input, _, data):
35+
def place_input_callback(ql: Qiling, input: bytes, _: int):
3936
ql.os.stdin.write(input)
37+
return True
4038

4139
def start_afl(_ql: Qiling):
42-
"""
43-
Callback from inside
44-
"""
45-
# We start our AFL forkserver or run once if AFL is not available.
46-
# This will only return after the fuzzing stopped.
47-
try:
48-
#print("Starting afl_fuzz().")
49-
if not _ql.uc.afl_fuzz(input_file=input_file,
50-
place_input_callback=place_input_callback,
51-
exits=[ql.os.exit_point]):
52-
print("Ran once without AFL attached.")
53-
os._exit(0) # that's a looot faster than tidying up.
54-
except unicornafl.UcAflError as ex:
55-
# This hook trigers more than once in this example.
56-
# If this is the exception cause, we don't care.
57-
# TODO: Chose a better hook position :)
58-
if ex != unicornafl.UC_AFL_RET_CALLED_TWICE:
59-
raise
40+
ql_afl_fuzz(_ql, input_file=input_file, place_input_callback=place_input_callback, exits=[ql.os.exit_point])
6041

6142
LIBC_BASE = int(ql.profile.get("OS32", "interp_address"), 16)
6243

6344
# crash in case we reach SignalKill
64-
ql.hook_address(callback=lambda x: os.abort(), address=LIBC_BASE + 0x456d4)
45+
ql.hook_address(callback=lambda x: os.abort(), address=LIBC_BASE + 0x38170)
6546

6647
# Add hook at main() that will fork Unicorn and start instrumentation.
6748
main_addr = 0x08048aa0

examples/fuzzing/tenda_ac15/fuzz_tendaac15_httpd.py

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,10 @@
1313

1414
import os, pickle, socket, sys, threading
1515

16-
import unicornafl
17-
unicornafl.monkeypatch()
18-
1916
sys.path.append("../../../")
2017
from qiling import *
2118
from qiling.const import QL_VERBOSE
19+
from qiling.extensions.afl import ql_afl_fuzz
2220

2321
def patcher(ql):
2422
br0_addr = ql.mem.search("br0".encode() + b'\x00')
@@ -40,25 +38,10 @@ def place_input_callback(uc, input, _, data):
4038
ql.mem.write(target_address, input)
4139

4240
def start_afl(_ql: Qiling):
43-
4441
"""
4542
Callback from inside
4643
"""
47-
# We start our AFL forkserver or run once if AFL is not available.
48-
# This will only return after the fuzzing stopped.
49-
try:
50-
#print("Starting afl_fuzz().")
51-
if not _ql.uc.afl_fuzz(input_file=input_file,
52-
place_input_callback=place_input_callback,
53-
exits=[ql.os.exit_point]):
54-
print("Ran once without AFL attached.")
55-
os._exit(0) # that's a looot faster than tidying up.
56-
except unicornafl.UcAflError as ex:
57-
# This hook trigers more than once in this example.
58-
# If this is the exception cause, we don't care.
59-
# TODO: Chose a better hook position :)
60-
if ex != unicornafl.UC_AFL_RET_CALLED_TWICE:
61-
raise
44+
ql_afl_fuzz(_ql, input_file=input_file, place_input_callback=place_input_callback, exits=[ql.os.exit_point])
6245

6346
ql.hook_address(callback=start_afl, address=0x10930+8)
6447

examples/sality.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def hook_CreateFileA(ql: Qiling, address: int, params):
7575
else:
7676
return (-1)
7777
else:
78-
ret = _CreateFile(ql, address, params, "CreateFileA")
78+
ret = _CreateFile(ql, address, params)
7979
return ret
8080

8181
def _WriteFile(ql: Qiling, address: int, params):

qiling/core.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@
77
import ntpath, os, pickle, platform
88

99
# See https://stackoverflow.com/questions/39740632/python-type-hinting-without-cyclic-imports
10-
from typing import Dict, List, Union
10+
from typing import Callable, Dict, List, Union
1111
from typing import TYPE_CHECKING
1212

1313
from unicorn.unicorn import Uc
14+
1415
if TYPE_CHECKING:
1516
from .arch.register import QlRegisterManager
1617
from .arch.arch import QlArch
@@ -840,7 +841,6 @@ def stack_read(self, offset):
840841
def stack_write(self, offset, data):
841842
return self.arch.stack_write(offset, data)
842843

843-
844844
# Assembler/Diassembler API
845845
@property
846846
def assembler(self):

qiling/extensions/afl/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .afl import ql_afl_fuzz

qiling/extensions/afl/afl.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
from typing import List, Callable
2+
from qiling.core import Qiling
3+
from unicornafl import *
4+
from qiling.exception import QlErrorNotImplemented
5+
6+
def ql_afl_fuzz(ql: Qiling,
7+
input_file: str,
8+
place_input_callback: Callable[["Qiling", bytes, int], bool],
9+
exits: List[int],
10+
validate_crash_callback: Callable[["Qiling", bytes, int], bool] = None,
11+
always_validate: bool = False,
12+
persistent_iters: int = 1):
13+
""" Fuzz a range of code with afl++.
14+
This function wraps some common logic with unicornafl.uc_afl_fuzz.
15+
NOTE: If no afl-fuzz instance is found, this function is almost identical to ql.run.
16+
:param Qiling ql: The Qiling instance.
17+
:param str input_file: This usually is the input file name provided by the command argument.
18+
:param Callable place_input_callback: This callback is triggered every time a new child is
19+
generated. It returns True if the input is accepted, or the input would be skipped.
20+
:param list exits: All possible exits.
21+
:param Callable validate_crash_callback: This callback is triggered every time to check if we are crashed.
22+
:param bool always_validate: If this is set to False, validate_crash_callback will be only triggered if
23+
uc_emu_start (which is called internally by afl_fuzz) returns an error. Or the validate_crash_callback will
24+
be triggered every time.
25+
:param int persistent_iters: Fuzz how many times before forking a new child.
26+
:raises UcAflError: If something wrong happens with the fuzzer.
27+
"""
28+
29+
def _ql_afl_place_input_wrapper(uc, input_bytes, iters, data):
30+
(ql, cb, _) = data
31+
32+
return cb(ql, input_bytes, iters)
33+
34+
def _ql_afl_validate_wrapper(uc, input_bytes, iters, data):
35+
(ql, _, cb) = data
36+
37+
return cb(ql, input_bytes, iters)
38+
39+
data = (ql, place_input_callback, validate_crash_callback)
40+
try:
41+
# uc_afl_fuzz will never return non-zero value.
42+
uc_afl_fuzz(ql.uc,
43+
input_file=input_file,
44+
place_input_callback=_ql_afl_place_input_wrapper,
45+
exits=exits,
46+
validate_crash_callback=_ql_afl_validate_wrapper,
47+
always_validate=always_validate,
48+
persistent_iters=persistent_iters,
49+
data=data)
50+
except NameError as ex:
51+
raise QlErrorNotImplemented("unicornafl is not installed or AFL++ is not supported on this platform") from ex
52+
except UcAflError as ex:
53+
if ex.errno != UC_AFL_RET_CALLED_TWICE:
54+
# This one is special. Many fuzzing scripts start fuzzing in a Unicorn UC_HOOK_CODE callback and
55+
# starts execution on the current address, which results in a duplicate UC_HOOK_CODE callback. To
56+
# make unicornafl easy to use, we handle this siliently.
57+
#
58+
# For other exceptions, we raise them.
59+
raise

0 commit comments

Comments
 (0)