Skip to content

Commit bba26a5

Browse files
authored
Merge pull request #1075 from qilingframework/libfuzzer
Fuzzing evovled
2 parents 3e97d28 + 90ad04d commit bba26a5

File tree

12 files changed

+172
-101
lines changed

12 files changed

+172
-101
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 & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@
1717
$ rm -fr afl_outputs/default/
1818
"""
1919

20-
# This uses the new unicornafl, which no longer provides any Unicorn stuff so we have to import by our own.
21-
from unicornafl import *
22-
from unicorn import *
20+
# No more need for importing unicornafl, try ql.afl_fuzz instead!
2321

2422
import os
2523
import sys
@@ -30,6 +28,7 @@
3028
from qiling import Qiling
3129
from qiling.const import QL_VERBOSE
3230
from qiling.extensions import pipe
31+
from qiling.extensions.afl import ql_afl_fuzz
3332

3433
def main(input_file: str):
3534
mock_stdin = pipe.SimpleInStream(sys.stdin.fileno())
@@ -41,31 +40,18 @@ def main(input_file: str):
4140
stdout=None,
4241
stderr=None)
4342

44-
def place_input_callback(uc: Uc, input: bytes, persistent_round: int, data: Any) -> Optional[bool]:
43+
def place_input_callback(ql: Qiling, input: bytes, persistent_round: int) -> Optional[bool]:
4544
"""Called with every newly generated input.
4645
"""
4746

4847
ql.os.stdin.write(input)
4948

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

7056
# get image base address
7157
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

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/pipe.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,25 @@ def writable(self) -> bool:
4848
def readable(self) -> bool:
4949
return True
5050

51+
def seek(self, offset: int, origin: int = 0) -> int:
52+
# Imitate os.lseek
53+
raise OSError("Illega Seek")
54+
55+
def seekable(self) -> bool:
56+
return False
57+
5158
class SimpleStreamBase:
5259
def __init__(self, fd: int, *args):
5360
super().__init__(*args)
5461

5562
self.__fd = fd
63+
self.__closed = False
64+
65+
def close(self) -> None:
66+
self.__closed = True
67+
68+
def closed(self) -> bool:
69+
return self.__closed
5670

5771
def fileno(self) -> int:
5872
return self.__fd
@@ -84,3 +98,73 @@ def flush(self) -> None:
8498

8599
def writable(self) -> bool:
86100
return True
101+
102+
class SimpleBufferedStream(TextIO):
103+
"""Simple buffered IO.
104+
"""
105+
106+
def __init__(self):
107+
self.buff = bytearray()
108+
self.cur = 0
109+
110+
def seek(self, offset: int, origin: int = 0) -> int:
111+
if origin == 0: # SEEK_SET
112+
base = 0
113+
elif origin == 1: # SEEK_CUR
114+
base = self.cur
115+
else: # SEEK_END
116+
base = len(self.buff)
117+
118+
if base + offset >= len(self.buff):
119+
self.cur = len(self.buff)
120+
elif base + offset <= 0:
121+
self.cur = 0
122+
else:
123+
self.cur = base + offset
124+
125+
return self.cur
126+
127+
def tell(self) -> int:
128+
return self.cur
129+
130+
def read(self, n: int = -1) -> bytes:
131+
if n == -1:
132+
ret = self.buff[self.cur:]
133+
self.cur = len(self.buff)
134+
else:
135+
ret = self.buff[self.cur:self.cur + n]
136+
137+
if self.cur + n >= len(self.buff):
138+
self.cur = len(self.buff)
139+
else:
140+
self.cur = self.cur + n
141+
142+
return bytes(ret)
143+
144+
def readline(self, limit: int = -1) -> bytes:
145+
ret = bytearray()
146+
147+
while not (ret.endswith(b'\n') or len(ret) == limit):
148+
ret.extend(self.read(1))
149+
150+
return bytes(ret)
151+
152+
def write(self, s: bytes) -> int:
153+
self.buff = self.buff[:self.cur]
154+
self.buff.extend(s)
155+
156+
self.cur += len(s)
157+
158+
return len(s)
159+
160+
def flush(self) -> None:
161+
pass
162+
163+
def writable(self) -> bool:
164+
return True
165+
166+
def readable(self) -> bool:
167+
return True
168+
169+
def seekable(self) -> bool:
170+
return True

qiling/os/filestruct.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ def write(self, write_buf: bytes) -> int:
4343
def fileno(self) -> int:
4444
return self.__fd
4545

46+
def seek(self, lseek_offset: int, lseek_origin: int = os.SEEK_SET) -> int:
47+
return self.lseek(lseek_offset, lseek_origin)
48+
4649
def lseek(self, lseek_offset: int, lseek_origin: int = os.SEEK_SET) -> int:
4750
return os.lseek(self.__fd, lseek_offset, lseek_origin)
4851

0 commit comments

Comments
 (0)