Open Wenzel opened 1 year ago
---upd---
It seems, that if there are unused-bytes in payload data before place WHERE redq-dict C-string must be put -- kAFL can't save such testcase to corpus correct.
In next harness between 0xdeadbeef
constant and C-String "PWNED!!!"
two unused bytes present.
94 │ if (len >= 4) {
95 │
96 │ /* set a byte in the bitmap to guide your fuzzer */
97 │ //((uint8_t *)trace_buffer)[0] = 0x1;
98 │ if (payload_buffer->data[0] == '!') {
99 │
100 │ //((uint8_t *)trace_buffer)[1] = 0x1;
101 │ if (payload_buffer->data[1] == 'N') {
102 │
103 │ //((uint8_t *)trace_buffer)[2] = 0x1;
104 │ if (payload_buffer->data[2] == 'Y') {
105 │
106 │ //((uint8_t *)trace_buffer)[3] = 0x1;
107 │ if (payload_buffer->data[3] == 'X') {
108 │
109 │ //((uint8_t *)trace_buffer)[4] = 0x1;
110 │ /* Notifiy the hypervisor and the fuzzer that a "crash" has
111 │ * occured. Also a string is passed by this hypercall (this is
112 │ * currently not supported by AFL++-Nyx) */
113 │ if (*(int*)&(payload_buffer->data[4]) == 0xdeadbeef) {
114 │ if (!strcmp(&(payload_buffer->data[10]), "PWNED!!!")) {
115 │ hprintf("PWNED\n");
116 │ char *magic;
117 │ magic = &(payload_buffer->data[20]);
118 │ if (!memcmp(magic, "RedQueen", sizeof("RedQueen"))) {
119 │ void **crash = 0xdeadbeef;
120 │ *crash = 0xcafefeed;
121 │ kAFL_hypercall(HYPERCALL_KAFL_PANIC_EXTENDED,
122 │ (uintptr_t) "Something went wrong\n");
123 │ }
124 │ }
125 │ }
126 │ }
127 │
128 │ }
129 │
130 │ }
131 │
132 │ }
133 │
134 │ }
Backend pass that code and increase edges count up to 59, but:
(.venv) |root@w00t|:{/usr/src/kAFL/kafl/examples/linux-user/forkserver} #_ hexyl /dev/shm/kafl_root/corpus/regular/payload_00007
┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
│00000000│ 21 4e 59 58 ef be ad de ┊ │!NYX××××┊ │
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
(.venv) |root@w00t|:{/usr/src/kAFL/kafl/examples/linux-user/forkserver} #_ hexyl /dev/shm/kafl_root/corpus/regular/payload_00008
┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
│00000000│ 21 4e 59 58 ef be ad de ┊ │!NYX××××┊ │
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
In case of next branches:
113 │ if (*(int*)&(payload_buffer->data[4]) == 0xdeadbeef) {
114 │ if (!strcmp(&(payload_buffer->data[8]), "PWNED!!!")) {
all works as expected
(.venv) |root@w00t|:{/usr/src/kAFL/kafl/examples/linux-user/forkserver} #_ hexyl /dev/shm/kafl_root/corpus/regular/payload_00008
┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
│00000000│ 21 4e 59 58 ef be ad de ┊ 50 57 4e 45 44 21 21 21 │!NYX××××┊PWNED!!!│
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
I have some experience with AFL's cmplog backend stuff, in qemu userspace. But such sort of behaviour is highly discouraged, so i think i need some help. Thanks.
Please, can anybody test this behaviour and discard or approve it? In my setup it seems that when any space between parts of payload present -- it became unpassable constrain for kAFL.
poke @il-steffen any chance you can have a quick at this redqueen issue ? thanks !
So, the magic string was found and successfully injected to input, the harness react by crashing, as expected, and kAFL log this. But not new path in corpus/regular neither correct (crashing) payloads in corpus/crashes are present.
Thanks for debugging this. Can you check the logic on frontend side as well? :-)
The Redqueen analysis + first mutation stage is here: https://github.com/IntelLabs/kafl.fuzzer/blob/master/kafl_fuzzer/worker/state_logic.py#L346
The "state logic" implements the mutation pipeline of the workers. For the Redqueen stage, it first performs coloring + tracing using execute_redqueen()
, which skips the crash + feedback analysis disabled (using execute_naked() function). Qemu writes the relevant traces as files in the workdir/redqueeen-something/ folder where they are picked up by the frontend (Redqueen "InfoGatherer" stuff). It then runs the proposals using the normal "execute()" call where crashes + new coverage is analyzed normally. There is also a "havoc" variant of this, called as part of the havoc stage, where it simply uses tokens found by Redqueen as a dictionary: https://github.com/IntelLabs/kafl.fuzzer/blob/master/kafl_fuzzer/worker/state_logic.py#L473
I am not sure if the initial stage should have found the input already, but in principle the colorizer will first do some input expansion so the unused bytes should have been passed. If the trace logic found the compare, you should be able to see it written to the traces, where it is picked up by the InfoGatherer stuff.
Your log indicates that the input was eventually found in the later havoc stage using the RQ-dictionary:
Worker-00 RQ-Dict: attempting b'PWNED!!!\x00Somethi' Worker-00 RedQ-Dict: Have performed 0 iters (???????) Received new input (exit=crash): b'!NYX\xef\xbe\xad\xde'ndings: <0, 0, 0>
However, the supposedly crashing payload also seems wrong here. This "received new input" is what the worker eventually hands off to the manager process as the crashing input, and the manager will store it after checking that the bitmap is unique. But it seems that even the worker/self.execute() got the wrong input here, so maybe the execution is out of sync somehow?
I suspect your issue is related to how the redqueen mutator handles the strings. This was python2 code once which has a messed up way of handling strings, and we didn't really have a lot of tests for migrating to python3 except that it still seemed to work.
Kindly appreciated for reply, guys.
I was able to move on a bit with strings.
|root@w00t|:{/usr/src/kAFL} #_ find /dev/shm/kafl_root/corpus/regular/
/dev/shm/kafl_root/corpus/regular/
/dev/shm/kafl_root/corpus/regular/payload_00008
/dev/shm/kafl_root/corpus/regular/payload_00007
/dev/shm/kafl_root/corpus/regular/payload_00006
/dev/shm/kafl_root/corpus/regular/payload_00005
/dev/shm/kafl_root/corpus/regular/payload_00004
/dev/shm/kafl_root/corpus/regular/payload_00001
/dev/shm/kafl_root/corpus/regular/payload_00002
/dev/shm/kafl_root/corpus/regular/payload_00003
|root@w00t|:{/usr/src/kAFL} #_ hexyl /dev/shm/kafl_root/corpus/regular/payload_00008
┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
│00000000│ 21 4e 59 58 ef be ad de ┊ 00 00 50 57 4e 45 44 21 │!NYX××××┊00PWNED!│
│00000010│ 21 21 00 50 ┊ │!!0P ┊ │
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
There were several issues, at least.
0) https://github.com/IntelLabs/kafl.fuzzer/blob/82c87450bc6812773be8b051b465ec010aaf8710/kafl_fuzzer/worker/state_logic.py#L477C13-L477C13
why limitation of 256?
1) Redqueen's data was append to the dict with junky null bytes in front and in tail.
https://github.com/IntelLabs/kafl.fuzzer/blob/82c87450bc6812773be8b051b465ec010aaf8710/kafl_fuzzer/technique/redqueen/cmp.py#L193
Should we .strip(b'\x00')
before processing?
2) Sometimes even just zero-bytes were append to the dict.
https://github.com/IntelLabs/kafl.fuzzer/blob/82c87450bc6812773be8b051b465ec010aaf8710/kafl_fuzzer/technique/havoc_handler.py#L284
should we check len(.strip(b'\x00')) > 0
here?
3) As was told here https://github.com/IntelLabs/kafl.fuzzer/blob/82c87450bc6812773be8b051b465ec010aaf8710/kafl_fuzzer/technique/havoc_handler.py#L297C1-L297C83 variable and out-of-bound offsets -- very good idea, because without them we couldn't discover that the target has/need more large payload, etc.
Btw, in CMPLOG impl of qemuafl there was very cool stuff, which split ints to bytes. I don't sure if it possible to do that trick with intel-pt (but think it should), if it isn't done yet?
4) https://github.com/IntelLabs/kafl.fuzzer/blob/82c87450bc6812773be8b051b465ec010aaf8710/kafl_fuzzer/technique/havoc_handler.py#L318 if we got static dict we would never use rq?
5) https://github.com/IntelLabs/kafl.fuzzer/blob/82c87450bc6812773be8b051b465ec010aaf8710/kafl_fuzzer/technique/havoc_handler.py#L284C2-L284C2 What about uint16_t ? two bytes? And what about integers (with nullbytes) at all? If i understand right the dict is one for all types of rq findings?
Atm it pass first strcmp() and totally stacked on second memcmp().
124 │ if (*(int*)&(payload_buffer->data[4]) == 0xdeadbeef) {
125 │ if (!strcmp(&(payload_buffer->data[10]), "PWNED!!!")) {
126 │ hprintf("PWNED\n");
127 │ char *magic;
128 │ magic = &(payload_buffer->data[20]);
129 │ if (!memcmp(magic, "RedQueen", sizeof("RedQueen"))) {
130 │ void **crash = 0xdeadbeef;
131 │ *crash = 0xcafefeed;
132 │ kAFL_hypercall(HYPERCALL_KAFL_PANIC_EXTENDED,
133 │ (uintptr_t) "Something went wrong\n");
134 │ }
135 │ }
136 │ }
137 │ }
138 │
and now we got:
|root@w00t|:{/usr/src/kAFL} #_ grep -rn PWNED /dev/shm/kafl_root/
/dev/shm/kafl_root/hprintf_03.log:1:PWNED
grep: /dev/shm/kafl_root/aux_buffer_9: binary file matches
grep: /dev/shm/kafl_root/aux_buffer_8: binary file matches
grep: /dev/shm/kafl_root/aux_buffer_7: binary file matches
grep: /dev/shm/kafl_root/aux_buffer_6: binary file matches
grep: /dev/shm/kafl_root/aux_buffer_5: binary file matches
grep: /dev/shm/kafl_root/aux_buffer_4: binary file matches
grep: /dev/shm/kafl_root/aux_buffer_2: binary file matches
grep: /dev/shm/kafl_root/aux_buffer_1: binary file matches
grep: /dev/shm/kafl_root/aux_buffer_0: binary file matches
grep: /dev/shm/kafl_root/payload_6: binary file matches
grep: /dev/shm/kafl_root/payload_1: binary file matches
grep: /dev/shm/kafl_root/snapshot/fast_snapshot.mem_dump: binary file matches
grep: /dev/shm/kafl_root/corpus/regular/payload_00008: binary file matches
|root@w00t|:{/usr/src/kAFL} #_ grep -arn RedQueen /dev/shm/kafl_root/
/dev/shm/kafl_root/snapshot/fast_snapshot.mem_dump:91177:RedQueenSomething went wrong
/dev/shm/kafl_root/snapshot/fast_snapshot.mem_dump is too big to perform strings | sort -u
:-)
Strange is that rq stop appending new vars in dict.
Worker-07 RedQ-Dict: Have performed 0 iters
Worker-06 HAVOC times: afl: 7.0, splice: 0.0, grim: 0.0, rdmsa: 0.0 0, 0>
perf updated for node 3: 0.12 => 0.24
Worker-06 __perform_rq_dictt: rq_dict len 2, [{4203020: {b'\xde\xad\xbe\xef'}, 4203045: {b'PWNED!!!\x00PWNED\n\x00'}}];
payload_array len 8, [b'!NYX\xef\xbe\xad\xde']
Worker-06 RedQ-Dict: Have performed 0 iters
Worker-00 HAVOC times: afl: 4.7, splice: 11.2, grim: 0.0, rdmsa: 0.00, 0>
perf updated for node 1: 0.13 => 0.54
Worker-00 __perform_rq_dictt: rq_dict len 0, [{}];
payload_array len 4, [b'\xa2\x8a\xbaE']
Worker-00 RedQ-Dict: Have performed 0 iters
Worker-02 HAVOC times: afl: 4.3, splice: 10.6, grim: 0.0, rdmsa: 0.00, 0>
perf updated for node 2: 0.12 => 0.38
Worker-02 __perform_rq_dictt: rq_dict len 0, [{}];
payload_array len 4, [b'!\x03\xf4F']
Worker-02 RedQ-Dict: Have performed 0 iters
Worker-04 HAVOC times: afl: 4.6, splice: 11.4, grim: 0.0, rdmsa: 0.00, 0>
perf updated for node 5: 0.12 => 0.38
Worker-04 __perform_rq_dictt: rq_dict len 0, [{}];
payload_array len 4, [b'!NYt']
Worker-04 RedQ-Dict: Have performed 0 iters
Worker-01 HAVOC times: afl: 7.6, splice: 18.4, grim: 0.0, rdmsa: 0.00, 0>
perf updated for node 8: 0.23 => 0.33
Worker-01 __perform_rq_dictt: rq_dict len 0, [{}];
payload_array len 2, [b'!\x03']
Worker-01 RedQ-Dict: Have performed 0 iters
Worker-06 HAVOC times: afl: 2.8, splice: 6.8, grim: 0.0, rdmsa: 0.0 0, 0>
perf updated for node 7: 0.14 => 0.60
Worker-06 __perform_rq_dictt: rq_dict len 2, [{4203020: {b'\xde\xad\xbe\xef'}, 4203045: {b'PWNED!!!\x00PWNED\n\x00'}}];
payload_array len 4, [b'!N4\xd4']
Worker-06 RedQ-Dict: Have performed 0 iters
Worker-05 HAVOC times: afl: 4.8, splice: 11.7, grim: 0.0, rdmsa: 0.0
perf updated for node 4: 0.13 => 0.34
Worker-05 __perform_rq_dictt: rq_dict len 0, [{}];
payload_array len 4, [b'!NYX']
Worker-05 RedQ-Dict: Have performed 0 iters
Worker-07 HAVOC times: afl: 5.0, splice: 12.4, grim: 0.0, rdmsa: 0.00, 0>
perf updated for node 8: 0.23 => 0.33
Worker-07 __perform_rq_dictt: rq_dict len 0, [{}];
payload_array len 20, [b'!NYX\xef\xbe\xad\xde\x00\x00PWNED!!!\x00P']
Worker-07 RedQ-Dict: Have performed 0 iters
Worker-08 HAVOC times: afl: 9.7, splice: 23.5, grim: 0.0, rdmsa: 0.00, 0>
perf updated for node 6: 0.13 => 0.26
Worker-08 __perform_rq_dictt: rq_dict len 0, [{}];
payload_array len 8, [b'!NYX\xef\xbe\xad\xde']
Worker-08 RedQ-Dict: Have performed 0 iters
Worker-02 HAVOC times: afl: 4.3, splice: 10.5, grim: 0.0, rdmsa: 0.0
perf updated for node 2: 0.12 => 0.38
Worker-02 __perform_rq_dictt: rq_dict len 0, [{}];
payload_array len 4, [b'\xa2\x8a\xbaE']
Worker-02 RedQ-Dict: Have performed 0 iters
Worker-01 HAVOC times: afl: 10.7, splice: 0.0, grim: 0.0, rdmsa: 0.00, 0>
perf updated for node 3: 0.12 => 0.24
Worker-01 __perform_rq_dictt: rq_dict len 0, [{}];
payload_array len 4, [b'!\x03\xf4F']
Worker-01 RedQ-Dict: Have performed 0 iters
Worker-00 HAVOC times: afl: 4.7, splice: 11.4, grim: 0.0, rdmsa: 0.0
perf updated for node 1: 0.13 => 0.54
Worker-00 __perform_rq_dictt: rq_dict len 0, [{}];
payload_array len 4, [b'!NYt']
Worker-00 RedQ-Dict: Have performed 0 iters
Worker-04 HAVOC times: afl: 4.6, splice: 11.4, grim: 0.0, rdmsa: 0.00, 0>
perf updated for node 5: 0.12 => 0.38
Worker-04 __perform_rq_dictt: rq_dict len 0, [{}];
payload_array len 2, [b'!\x03']
Worker-04 RedQ-Dict: Have performed 0 iters
Worker-06 HAVOC times: afl: 4.8, splice: 11.9, grim: 0.0, rdmsa: 0.00, 0>
perf updated for node 4: 0.13 => 0.34
Worker-06 __perform_rq_dictt: rq_dict len 2, [{4203020: {b'\xde\xad\xbe\xef'}, 4203045: {b'PWNED!!!\x00PWNED\n\x00'}}];
payload_array len 4, [b'!N4\xd4']
Worker-06 RedQ-Dict: Have performed 0 iters
Worker-09 HAVOC times: afl: 9.6, splice: 23.7, grim: 0.0, rdmsa: 0.00, 0>
perf updated for node 6: 0.13 => 0.26
Worker-09 __perform_rq_dictt: rq_dict len 0, [{}];
payload_array len 4, [b'!NYX']
Worker-09 RedQ-Dict: Have performed 0 iters
Worker-08 HAVOC times: afl: 4.2, splice: 10.3, grim: 0.0, rdmsa: 0.00, 0>
perf updated for node 7: 0.14 => 0.60
Worker-08 __perform_rq_dictt: rq_dict len 0, [{}];
payload_array len 20, [b'!NYX\xef\xbe\xad\xde\x00\x00PWNED!!!\x00P']
Worker-08 RedQ-Dict: Have performed 0 iters
Worker-02 HAVOC times: afl: 3.0, splice: 7.4, grim: 0.0, rdmsa: 0.0 0, 0>
perf updated for node 1: 0.13 => 0.54
Worker-02 __perform_rq_dictt: rq_dict len 0, [{}];
payload_array len 8, [b'!NYX\xef\xbe\xad\xde']
Whew, thanks!
Suggestion @Wenzel: Lets collect/create a couple redqueen trace files that we load into RedqueenInfoGatherer() and call get_proposals() on. Then fix up some of this code to generate good inputs.
The kafl debug tool has some very old, very unlikely to work, code for this: https://github.com/IntelLabs/kafl.fuzzer/blob/master/kafl_fuzzer/debug/core.py#L321
Hi guys, any upd on this? @il-steffen @Wenzel
Hi @kotee4ko,
this issue has been in my TODO list for a while, unfortunately it's not trivial to investigate and solve. On top of that i'm not familiar with RedQueen's internals and implementation details on kAFL. I can try to allocate some time on Friday, and estimate how much work is needed.
I'd like to point out that I really appreciate the quality of your bug report though, thank you for this.
I reach some troubles with redqueen feature: it doesn't handle
strcmp()
calls on linux userspace target.Launch command:
Some debugging of qemu-nyx approve, that redqueen strcmp() hooks are reached and works as expected, or, at least, see strcmp() calls is target and could access guest args.
The target, as always, is simple, statically built, small harness with several branches to test integers and strings extractions.
full backtrace:
But kafl doesn't insert findings to corpus.
---upd--- After some time fuzz session log next in stdout:
So, the magic string was found and successfully injected to input, the harness react by crashing, as expected, and kAFL log this. But not new path in corpus/regular neither correct (crashing) payloads in corpus/crashes are present.
Any advices? Thanks.
Originally posted by @kotee4ko in https://github.com/IntelLabs/kAFL/issues/12#issuecomment-1646964469