qiling icon indicating copy to clipboard operation
qiling copied to clipboard

Emulation Stops When Placing Input Via ql.uc.afl_fuzz

Open tulipszs opened this issue 3 years ago • 6 comments

Describe the bug When I attempt to feed input to crash a simple program via afl_fuzz place_input_callback, emulation seems to exit right after the callback is invoked. If I manually hook at the same location in program execution, and place the input, emulation continues and I observe the expected crash. The crash input file is 411 'A' characters.

Simple Test Case

#include <stdio.h>

int main(int argc, char **argv){
    char buffer[80];

    printf("Hi, feed me data...\n");
    scanf("%s", buffer);
    printf("Nom nom nom nom: %s\n", buffer);

    return 0;
}

Placing input via afl_fuzz

#!/usr/bin/env python3
import os,sys

# This is new. Instead of unicorn, we import unicornafl. It's the same Uc with some new `afl_` functions
import unicornafl

# Make sure Qiling uses our patched unicorn instead of it's own, second so without instrumentation!
unicornafl.monkeypatch()

from qiling import *
from qiling.const import *
from qiling.extensions import pipe


from IPython import embed

def main(input_file):

    print("Creating Mock stdin")
    mock_stdin = pipe.SimpleInStream(sys.stdin.fileno())

    print("Initializing Qiling")
    ql = Qiling(["./main"], "./rootfs",
                verbose=QL_VERBOSE.DISASM,
                stdin=mock_stdin)


    def place_input_callback(uc, input, _, data):
        print("Placing input")
        bytes = ql.os.stdin.write(input)
        print(f"Wrote {bytes} bytes")



    def start_afl(_ql: Qiling):

        """
        Callback from inside
        """
        # We start our AFL forkserver or run once if AFL is not available.
        # This will only return after the fuzzing stopped.
        if not _ql.uc.afl_fuzz(input_file=input_file,
                    place_input_callback=place_input_callback,
                    exits=[ql.os.exit_point]):
            os._exit(0)  # that's a looot faster than tidying up.

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

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

    # # set a hook on main() to let unicorn fork and start instrumentation
    ql.hook_address(callback=start_afl, address=ba + 0x1169)


    ql.run()
    os._exit(0)


if __name__ == "__main__":
    main(sys.argv[1])

Output

$ python3 fuzz.py afl_inputs/crash_input
[=]	00007fffb7df7b7d [[syscall_mmap]       + 0x021b7d]  48 8b 05 24 93 3c 00          mov                  rax, qword ptr [rip + 0x3c9324]
[=]	00007fffb7df7b84 [[syscall_mmap]       + 0x021b84]  48 8b 74 24 08                mov                  rsi, qword ptr [rsp + 8]
[=]	00007fffb7df7b89 [[syscall_mmap]       + 0x021b89]  8b 7c 24 14                   mov                  edi, dword ptr [rsp + 0x14]
[=]	00007fffb7df7b8d [[syscall_mmap]       + 0x021b8d]  48 8b 10                      mov                  rdx, qword ptr [rax]
[=]	00007fffb7df7b90 [[syscall_mmap]       + 0x021b90]  48 8b 44 24 18                mov                  rax, qword ptr [rsp + 0x18]
[=]	00007fffb7df7b95 [[syscall_mmap]       + 0x021b95]  ff d0                         call                 rax
[=]	0000555555555169 [main                 + 0x000169]  55                            push                 rbp
Placing input
Wrote 411 bytes
[=]	0000555555555169 [main                 + 0x000169]  55                            push                 rbp
$ 

Placing input manually

#!/usr/bin/env python3
import os,sys

# This is new. Instead of unicorn, we import unicornafl. It's the same Uc with some new `afl_` functions
import unicornafl

# Make sure Qiling uses our patched unicorn instead of it's own, second so without instrumentation!
unicornafl.monkeypatch()

from qiling import *
from qiling.const import *
from qiling.extensions import pipe


from IPython import embed

def main(input_file):

    print("Creating Mock stdin")
    mock_stdin = pipe.SimpleInStream(sys.stdin.fileno())

    print("Initializing Qiling")
    ql = Qiling(["./main"], "./rootfs",
                verbose=QL_VERBOSE.DISASM,
                stdin=mock_stdin)
    

    def place_input_callback_no_afl(ql):
        print("Placing input")
        with open(input_file, "rb") as f:
            # embed()
            bytes = ql.os.stdin.write(f.read())
            print(f"Wrote {bytes} bytes")


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

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

    # # set a hook on main() to let unicorn fork and start instrumentation
    ql.hook_address(callback=place_input_callback_no_afl, address=ba + 0x1169)

    ql.run()
    os._exit(0)


if __name__ == "__main__":
    main(sys.argv[1])

Output

$ python3 fuzz_no_afl.py afl_inputs/crash_input
[=]	00007fffb7e3af2b [[syscall_mmap]       + 0x064f2b]  64 48 33 0c 25 28 00 00 00    xor                  rcx, qword ptr fs:[0x28]
[=]	00007fffb7e3af34 [[syscall_mmap]       + 0x064f34]  75 08                         jne                  0x7fffb7e3af3e
[=]	00007fffb7e3af36 [[syscall_mmap]       + 0x064f36]  48 81 c4 d8 00 00 00          add                  rsp, 0xd8
[=]	00007fffb7e3af3d [[syscall_mmap]       + 0x064f3d]  c3                            ret                  
[=]	00005555555551cc [main                 + 0x0001cc]  b8 00 00 00 00                mov                  eax, 0
[=]	00005555555551d1 [main                 + 0x0001d1]  48 8b 55 f8                   mov                  rdx, qword ptr [rbp - 8]
[=]	00005555555551d5 [main                 + 0x0001d5]  64 48 2b 14 25 28 00 00 00    sub                  rdx, qword ptr fs:[0x28]
[=]	00005555555551de [main                 + 0x0001de]  74 05                         je                   0x5555555551e5
[=]	00005555555551e0 [main                 + 0x0001e0]  e8 5b fe ff ff                call                 0x555555555040
Aborted (core dumped)
$

Less Verbose Comparison

$ python3 fuzz_no_afl.py afl_inputs/crash_input
[+]	0x00007ffff7df1f41: mmap(addr = 0x7fffb81c3000, length = 0x3ae0, prot = 0x3, flags = 0x32, fd = 0xffffffff, pgoffset = 0x0) = 0x7fffb81c3000
[+]	0x00007ffff7df1ed5: close(fd = 0x3) = 0x0
[+]	mmap - mapping needed for 0x0
[+]	mmap - addr range  0x7fffb81c7000 - 0x7fffb81c8fff: 
[+]	0x00007ffff7df1f41: mmap(addr = 0x0, length = 0x2000, prot = 0x3, flags = 0x22, fd = 0xffffffff, pgoffset = 0x0) = 0x7fffb81c7000
[+]	0x00007ffff7dd6022: arch_prctl(code = 0x1002, addr = 0x7fffb81c7f00) = 0x0
[+]	0x00007ffff7df1ff5: mprotect(start = 0x7fffb81bd000, mlen = 0x4000, prot = 0x1) = 0x0
[+]	0x00007ffff7df1ff5: mprotect(start = 0x555555557000, mlen = 0x1000, prot = 0x1) = 0x0
[+]	0x00007ffff7df1ff5: mprotect(start = 0x7ffff7ffc000, mlen = 0x1000, prot = 0x1) = 0x0
Placing input
Wrote 411 bytes
[+]	fstat write completed
[+]	0x00007fffb7ee57c1: fstat(fd = 0x1, buf_ptr = 0x80000000dbb0) = 0x0
[+]	0x00007fffb7eec4b7: brk(inp = 0x0) = 0x55555555b000
[+]	0x00007fffb7eec4b7: brk(inp = 0x55555557c000) = 0x55555557c000
[+]	write() CONTENT: 'Hi, feed me data...\n'
Hi, feed me data...
[+]	0x00007fffb7ee6152: write(fd = 0x1, buf = 0x55555555b260, count = 0x14) = 0x14
[+]	fstat write completed
[+]	0x00007fffb7ee57c1: fstat(fd = 0x0, buf_ptr = 0x80000000d430) = 0x0
[+]	read() CONTENT: b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n'
[+]	0x00007fffb7ee607f: read(fd = 0x0, buf = 0x55555555b670, length = 0x400) = 0x19b
[+]	write() CONTENT: 'Nom nom nom nom: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n'
Nom nom nom nom: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[+]	0x00007fffb7ee6152: write(fd = 0x1, buf = 0x55555555b260, count = 0x1ac) = 0x1ac
Aborted (core dumped)



$ python3 fuzz.py afl_inputs/crash_input
[+]	mmap - MAP_FIXED, mapping not needed
[+]	mmap - addr range  0x7fffb81c3000 - 0x7fffb81c6fff: 
[+]	0x00007ffff7df1f41: mmap(addr = 0x7fffb81c3000, length = 0x3ae0, prot = 0x3, flags = 0x32, fd = 0xffffffff, pgoffset = 0x0) = 0x7fffb81c3000
[+]	0x00007ffff7df1ed5: close(fd = 0x3) = 0x0
[+]	mmap - mapping needed for 0x0
[+]	mmap - addr range  0x7fffb81c7000 - 0x7fffb81c8fff: 
[+]	0x00007ffff7df1f41: mmap(addr = 0x0, length = 0x2000, prot = 0x3, flags = 0x22, fd = 0xffffffff, pgoffset = 0x0) = 0x7fffb81c7000
[+]	0x00007ffff7dd6022: arch_prctl(code = 0x1002, addr = 0x7fffb81c7f00) = 0x0
[+]	0x00007ffff7df1ff5: mprotect(start = 0x7fffb81bd000, mlen = 0x4000, prot = 0x1) = 0x0
[+]	0x00007ffff7df1ff5: mprotect(start = 0x555555557000, mlen = 0x1000, prot = 0x1) = 0x0
[+]	0x00007ffff7df1ff5: mprotect(start = 0x7ffff7ffc000, mlen = 0x1000, prot = 0x1) = 0x0
Placing input
Wrote 411 bytes

Expected behavior I expect the program to crash under afl_fuzz. This manifests as AFL successfully starting, but not discovering program flaws since the emulation halts prior to the target processing malicious input.

Main function listing (so addresses are included) 2022-01-18-211234_871x610_scrot

tulipszs avatar Jan 19 '22 02:01 tulipszs

That works as intended. You need afl-fuzz to spin up an AFL server.

wtdcode avatar Jan 19 '22 18:01 wtdcode

@wtdcode the program doesn't crash when emulating under afl_fuzz though, which seems incorrect. The place_input_callback halts emulation if the afl server is not running?

When I emulate with afl-fuzz, I get no crashes, despite a crashing input being a part of the corpus. My hunch is that the program quits emulating before processing the input, and as such never crashes.

tulipszs avatar Jan 19 '22 18:01 tulipszs

@wtdcode the program doesn't crash when emulating under afl_fuzz though, which seems incorrect. The place_input_callback halts emulation if the afl server is not running?

When I emulate with afl-fuzz, I get no crashes, despite a crashing input being a part of the corpus. My hunch is that the program quits emulating before processing the input, and as such never crashes.

I suppose you are using unicornafl 2.0, right?

wtdcode avatar Jan 19 '22 19:01 wtdcode

In [1]: import unicornafl as uc

In [2]: uc.__version__
Out[2]: '1.0.3'

No, should I be? I can update and report back.

tulipszs avatar Jan 19 '22 19:01 tulipszs

In [1]: import unicornafl as uc

In [2]: uc.__version__
Out[2]: '1.0.3'

No, should I be? I can update and report back.

Yes, the new unicornafl 2.x could have a max of 40% speedup in some cases.

wtdcode avatar Jan 19 '22 22:01 wtdcode

Hello, does the fuzzing works properly with you when you update to unicornafl2 ???

KA2010 avatar Feb 14 '22 03:02 KA2010

Close for now.

We updated the codebase for Qiling and Unicorn since this issue being posted.

Feel free to try the latest version.

xwings avatar Oct 06 '22 03:10 xwings