Qiling For Malware Analysis: Part 1

4 minute read

Background

Qiling is an advanced binary emulation framework written in python and based on Unicorn engine.

It supports multiple platform (Windows, MacOS, Linux, BSD, UEFI) and multiple architectures (X86, X86_64, Arm, Arm64, MIPS).

Qiling is designed as a higher level framework, that leverages Unicorn to emulate CPU instructions, but Qiling understands OS as it has executable format loaders (for PE, MachO & ELF at the moment), dynamic linkers (so we can load & relocate shared libraries), syscall & IO handlers. For this reason, Qiling can run executable binaries that normally runs in native OS.

Installation

Due to distribution restrictions, Qiling doesn’t bundle Microsoft Windows DLL files and registry.

So for smooth installation, jump to a 64-bit windows machine and execute the following:

git clone https://github.com/qilingframework/qiling
cd qiling
examples\scripts\dllscollector.bat        # DLLs collector (requires admin privileges)

Sometimes the emulated program requires additional DLLs, you can copy them manually to "qiling/examples/rootfs/x8664_windows/Windows/System32" or "qiling/examples/rootfs/x86_windows/Windows/SysWOW64/" depending on the program architecture.

Now you can copy "qiling" folder to any machine you want (Windows, Linux, …) and complete the installation.

pip3 install -r requirements.txt    # sudo for Linux
python3 setup.py install            # sudo for Linux

Emulating a File

Emulating a binary file is as simple as that:

from qiling import *

# initialize emulator (x86-64 linux)
ql = Qiling(filename=["qiling/examples/rootfs/x8664_linux/bin/x8664_hello"], 
            rootfs="qiling/examples/rootfs/x8664_linux")
# start emulation
ql.run()

Qiling initialization constructor can take multiple arguments:

  • filename: binary file and its arguments, example: filename=[“test”,”-argv1”,”argv2”]

  • rootfs: virtual “/” folder, this is a “jail” file system when executing Qiling (target architecture)

  • env: environment variables, example: env={“SHELL”:”/bin/bash”,”HOME”:”/tmp”}

  • output: “default”, “debug”, “disasm”, “dump” where dump=(disam + debug)

The run() function can also take multiple arguments:

  • begin: start address of emulated code
  • end: end address of emulated code
  • timeout: emulation timeout (in microseconds)
  • count: maximum instruction count to be emulated

Now let’s run our first script:

........
brk(0x0)
brk(0x555555779000)
write(1,555555758260,14) = 0
Hello, World!

As you can see, Qiling outputs strace logs by default. You can disable them using filters.

# disable strace logs
ql.filter = []
# display only "open" logs
ql.filter = ["open"]

Emulating a Shellcode

To keep things simple, we will use this tiny shellcode:

shellcode = b"\x41\x4a" # inc ecx; dec edx

Next, let’s initialize Qiling.

# initialize emulator (x86 linux)
ql = Qiling(shellcoder=shellcode, 
            rootfs="qiling/examples/rootfs/x86_linux/",
            ostype="linux",
            archtype="x86",
            output="disasm")

Emulating shellcode is a little different than binary files, the initialization constructor takes additional arguments:

  • shellcoder: shellcode in binary format
  • rootfs: explained above

  • env: explained above

  • ostype: “linux”, “macos”, “windows”, “uefi”, “freebsd”

  • archtype: “x8664”, “x86”, “arm”, “arm64”, “mips”

  • output: explained above

Here we set the output to "disasm" to see the executed instructions.

The shellcode modifies the values of ECX and EDX registers, so let’s write some values to them before emulating.

# set machine registers
ql.reg.ecx = 0x3
ql.reg.edx = 0x7
# start emulation
ql.run()
# read machine registers
print("ecx = 0x{:x}".format(ql.reg.ecx))
print("edx = 0x{:x}".format(ql.reg.edx))

Let’s see the results:

[+] 0x11ff000      41          inc ecx
[+] 0x11ff001      4a          dec edx
ecx = 0x4
edx = 0x6

Hooking

Qiling supports a wide range of hooks such as hooking specific instructions and hooking memory read/write actions.

Let’s implement a basic disassembler with the help of Capstone and Qiling hooks.

Capstone is a multi-architecture disassembly framework, we can setup a code hook using Qiling to hook every instruction then use Capstone to disassemble the instructions.

from capstone import *
from qiling import *

# initialize emulator (x86 ARM)
ql = Qiling(["qiling/examples/rootfs/arm_linux/bin/arm_hello"],
             "qiling/examples/rootfs/arm_linux")
# hook every instruction
ql.hook_code(hook_callback)
# start emulation (timeout in microseconds)
ql.run(timeout=1000)

We can add a code hook by simply calling hook_code() with a callback function.

def hook_callback(ql, address, size):
    # read current instruction bytes
    data = ql.mem.read(address, size)
    # initialize Capstone
    md = Cs(CS_ARCH_ARM, CS_MODE_ARM)
    # disassemble current instruction
    for i in md.disasm(data, address):
        print("[*] 0x{:08x}: {} {}".format(i.address, i.mnemonic, i.op_str))

The callback function takes three arguments:

  • ql: our emulator object
  • address: the address of the current instruction
  • size: the size of the instruction in bytes

We can disassemble the current instruction using disasm() which takes two arguments (data to disassemble and a base address), here we are printing the instruction address, instruction mnemonic and instruction operands.

Let’s see the results:

[*] 0x047ba9e0: ldr sl, [pc, #0x94]
[*] 0x047ba9e4: ldr r4, [pc, #0x94]
[*] 0x047ba9e8: mov r0, sp
[*] 0x047ba9ec: bl #0x47bb154
[*] 0x0001030c: mov fp, #0
[*] 0x00010310: mov lr, #0
[*] 0x00010314: pop {r1}
..........

Conclusion

In this part we learned the basics of Qiling and how to emulate code for different architectures.

I really encourage you to read through Qiling Documentation to learn more about it’s amazing capabilities.

Code snippets can be found on my Github.

Categories:

Updated: