What is Qiling

From the author’s website:

Qiling is an advanced binary emulation framework, with the following features:

  • Cross platform: Windows, MacOS, Linux, BSD
  • Cross architecture: X86, X86_64, Arm, Arm64, Mips
  • Multiple file formats: PE, MachO, ELF
  • Emulate & sandbox machine code in a isolated environment
  • Supports cross architecture and platform debugging capabilities
  • Provide high level API to setup & configure the sandbox
  • Fine-grain instrumentation: allow hooks at various levels (instruction/basic-block/memory-access/exception/syscall/IO/etc)
  • Allow dynamic hotpatch on-the-fly running code, including the loaded library
  • True framework in Python, making it easy to build customized security analysis tools on top

Qiling is backed by Unicorn engine.

Or just saying shortly: this tool will allow you to run binary from one system to another, i.e you can run Windows PE on Mac.

Additionally you can also utilize its scripting ability to hook addresses, run only part of binary, etc. Check the references to view project’s GitHub page and others.

Simple demo

Let’s imagine that we have this simple task:

Task

Obviously flag is generated from the stack strings with some on-the-fly operations, we can very easly capture the flag by setting couple of breakpoints in the debugger at addresses: 0x804852e and 0x804853d and check the contents of eax (notice that second part of the flag actually replaces the 1st part) or we can write simple script using Qiling:

from qiling import *
from os import environ

EXEC_FILE = ["./task"]
ROOTFS = "{}/x86_linux".format(environ["QILING_ROOTFS"])
FLAG = []

def on_hook(ql : core.Qiling) -> None:
    addr_flag = ql.reg.eax
    FLAG.append(ql.mem.read(addr_flag, 0x13))

def my_sandbox(path, rootfs):
    ql = Qiling(path, rootfs)
    
    # remove systrace logs
    ql.filter = []

    ql.hook_address(on_hook, 0x804852e)
    ql.hook_address(on_hook, 0x804853d)
    ql.run()

    flag = FLAG[0][:0x12].decode() + FLAG[1][:7].decode()
    print(flag)

if __name__ == "__main__":
    my_sandbox(EXEC_FILE, ROOTFS)

Some explanation: Qiling for execution requires 2 things:

  • binary - what is obvious;
  • Rootfs - which should match target architecture.

In my case environment variable $QILING_ROOTFS points to $QILING_REPO_DIR/examples/rootfs, above mentioned is a linux x86 binary so we are selecting x68_linux:

from qiling import *
from os import environ

EXEC_FILE = ["./task"]
ROOTFS = "{}/x86_linux".format(environ["QILING_ROOTFS"])

We want to break hook at specified addresses, so we can achieve that by calling hook_address(callback, address) I decided to reuse twice the same function, but you can create separate functions for that:

ql.hook_address(on_hook, 0x804852e)
ql.hook_address(on_hook, 0x804853d) 

After everything is setup we can run the binary with ql.run().

Our hook method reads the value from eax register and saves it in global variable:

def on_hook(ql : core.Qiling) -> None:
    addr_flag = ql.reg.eax
    FLAG.append(ql.mem.read(addr_flag, 0x13))

Notice that we are reading twice the same amount of bytes (ql.mem.read(addr_flag, 0x13)), so we need to truncate these array before printing it on the screen:

    flag = FLAG[0][:0x12].decode() + FLAG[1][:7].decode()
    print(flag)

By default Qiling displays strace log, if you don’t want to see it or you want to see only logs from specific methods you can use filters (you need to change this variable before running the qiling):

# mute strace log
ql.filter = []

# display only "read" functions
ql.filter = ["read"]

Notice: the current version doesn’t have working properly filters, so you need to switch to dev branch.

Finally you can run it with python and see the flag:

flag

Closing notes

Qiling is definately great framework, very simple to use and very powerful. Definately worth to try :)

References

  1. Qiling Github
  2. Documentation