qiling icon indicating copy to clipboard operation
qiling copied to clipboard

Different behaviors caused by setting different binary paths

Open Heersin opened this issue 3 years ago • 0 comments

*Describe the bug

here is how my folder organized:

  • aarch64.py
  • qilinglab-aarch64 (binary file)
  • bins/
    • qilinglab-aarch64 (binary file, the same one)
  • examples/
    • rootfs/

when using the binary in the same folder as the Qiling's argv, everything works fine. and the output shows all the challenges are solved

if __name__ == "__main__":
    path = ["qilinglab-aarch64"]
    rootfs = "./examples/rootfs/arm64_linux/"

    ql = Qiling(path, rootfs)
    lab_solve(ql)
    ql.run()

If I copy it to a subfolder, and using the path of copied one which under "bins/" folder, it got stuck.

if __name__ == "__main__":
    path = ["bins/qilinglab-aarch64"]
    rootfs = "./examples/rootfs/arm64_linux/"

    ql = Qiling(path, rootfs)
    lab_solve(ql)
    ql.run()

Sample Code my script which reproduces the bug

# from qiling import Qiling
from qiling import *
from qiling.const import *
from qiling.os.mapper import QlFsMappedObject
import struct

# store 0x1337 to addr 0x1337
def challenge1(ql):
    mem_start = 0x1000
    map_size = 0x1000 # page aligned
    ql.mem.map(0x1000, 0x1000, info = "[challenge1]")
    ql.mem.write(0x1337, ql.pack16(1337))

# hook uname return 
# sysname == QilingOS 
# version == ChallengeStart
'''
struct utsname {
    char sysname[65];
    char nodename[65];
    char release[65];   
    char version[65];
    char machine[65];
    char domainname[65];
};
'''
def challenge2(ql):
    def hook_uname(ql, *args):
        '''
        ...
        00100d28 e0 03 01 91     add        x0,sp,#0x40 
        00100d2c a1 ff ff 97     bl         uname               int uname(utsname * __name)
        ...
        '''
        uname_ret_addr = ql.reg.sp + 0x40
        sysname_addr = uname_ret_addr
        version_addr = uname_ret_addr + 65 * 3

        ql.mem.write(sysname_addr, b"QilingOS\x00")
        ql.mem.write(version_addr, b"ChallengeStart\x00")

    ql.set_syscall("uname", hook_uname, QL_INTERCEPT.EXIT)

# hook /dev/urandom read single 32-byte data to be different from otheres
# hook /dev/urandom read 32 1-byte data to be the same with getrandom
# hook getrandom syscall
class FakeDevUrandom(QlFsMappedObject):
    def read(self, size):
        # always return 0x42
        if size == 1:
            return b"\x42"

        # others
        return b"\x24" * size

    def close(self):
        return 0

def challenge3(ql):
    def hook_getrandom(ql, buf, buf_len, flags, *args, **kw):
        ql.mem.write(buf, b"\x24" * buf_len)
        ql.os.set_syscall_return(0)

    ql.set_syscall("getrandom", hook_getrandom)
    ql.add_fs_mapper("/dev/urandom", FakeDevUrandom())

# 4. hook address to enter the loop
def challenge4(ql):
    def hook_loop_check(ql):
        ql.reg.w0 = 1

    base_addr = ql.mem.get_lib_base(ql.path)
    enter_loop_check = base_addr + 0xfe0
    ql.hook_address(hook_loop_check, enter_loop_check)

# 5. hook external function
# guess every call to rand()
def challenge5(ql):
    def rand_hook(ql, *args, **kw):
        ql.reg.x0 = 0

    # TODO : read from arg return
    ql.set_api("rand", rand_hook)

# 6. escape infinite loop blackhole
# hook address
def challenge6(ql):
    def bypass_loop_check(ql):
        ql.reg.w0 = 0

    base_addr = ql.mem.get_lib_base(ql.path)
    enter_loop_check = base_addr + 0x1114
    ql.hook_address(bypass_loop_check, enter_loop_check)

# 7. hook function 
# hook sleep 
def challenge7(ql):
    def hook_sleep(ql):
        print("[DEBUG]ESCAPE SLEEP")
        return
    ql.set_api("sleep", hook_sleep)

# 8. modify struct in memory
# hook address and operate mem
# search mem first 
'''
struct random_struct {
  char *some_string;
  __int64 magic;
  char *check_addr;
};
'''
def challenge8(ql):
    def search_struct_and_rewrite_chk(ql):
        MAGIC = 0x3DFCD6EA00000539
        magic_addrs = ql.mem.search(ql.pack64(MAGIC))

        for addr in magic_addrs:
            magic_t_addr = addr - 0x8
            magic_t = ql.mem.read(magic_t_addr, 24)
            str_addr, _, check_addr = struct.unpack('QQQ', magic_t)

            if ql.mem.string(str_addr) == "Random data":
                ql.mem.write(check_addr, b"\x01")
                break

    base_addr = ql.mem.get_lib_base(ql.path)
    end_of_c8 = base_addr + 0x11dc
    ql.hook_address(search_struct_and_rewrite_chk, end_of_c8)

# 9. hook function 
# fix function by hook 
def challenge9(ql):
    def hook_tolower(ql):
        print("[DEBUG]Do not tolower")
        return

    ql.set_api("tolower", hook_tolower)

# 10. filesys hook 
# fake [file] required by program
# read from /proc/self/cmdline and get qilinglab 
class FakeCmdline(QlFsMappedObject):
    def read(self, size):
        return b"qilinglab"

    def close(self):
        return 0

def challengeA(ql):
    ql.add_fs_mapper("/proc/self/cmdline", FakeCmdline())

# 11. instruction level hook 
def challengeB(ql):
    def hook_el1(ql, address, size):
        # 001013ec 00 00 38 d5     mrs        x0,midr_el1
        if ql.mem.read(address, size) == b"\x00\x00\x38\xD5":
            ql.reg.x0 = 0x1337 << 16
            ql.reg.arch_pc += 4

    ql.hook_code(hook_el1)

def lab_solve(ql):
    challenge1(ql)
    challenge2(ql)
    challenge3(ql)
    challenge4(ql)
    challenge5(ql)
    challenge6(ql)
    challenge7(ql)
    challenge8(ql)
    challenge9(ql)
    challengeA(ql)
    challengeB(ql)

if __name__ == "__main__":
    path = ["bins/qilinglab-aarch64"]
    rootfs = "./examples/rootfs/arm64_linux/"

    ql = Qiling(path, rootfs)
    lab_solve(ql)
    ql.run()

Expected behavior both of them could solve all the challenges

Screenshots the output after keyboard interrupt

Challenge 1: SOLVED
[=]     write(fd = 0x1, buf = 0x55555556a260, count = 0x14) = 0x14
[=]     mapping /dev/urandom
[=]     openat(fd = 0xffffffffffffff9c, path = 0x555555555860, flags = 0x0, mode = 0x0) = 0x3
[=]     read(fd = 0x3, buf = 0x80000000dc88, length = 0x20) = 0x20
[=]     read(fd = 0x3, buf = 0x80000000dc80, length = 0x1) = 0x1
[=]     close(fd = 0x3) = 0x0
[=]     getrandom(buf = 0x80000000dca8, buf_len = 0x20, flags = 0x1) = ?
Challenge 2: SOLVED
[=]     write(fd = 0x1, buf = 0x55555556a260, count = 0x14) = 0x14
Challenge 3: SOLVED
[=]     write(fd = 0x1, buf = 0x55555556a260, count = 0x14) = 0x14
[=]     gettimeofday(tv = 0x80000000dc58, tz = 0x0) = 0x0
Challenge 4: NOT SOLVED
[=]     write(fd = 0x1, buf = 0x55555556a260, count = 0x18) = 0x18
^CTraceback (most recent call last):
  File "qiling_play/lab/aarch64_solve.py", line 183, in <module>
    ql.run()
  File "qiling_play/venv/lib/python3.10/site-packages/qiling/core.py", line 730, in run
    self.os.run()
  File "qiling_play/venv/lib/python3.10/site-packages/qiling/os/linux/linux.py", line 149, in run
    self.ql.emu_start(self.ql.loader.elf_entry, self.exit_point, self.ql.timeout, self.ql.count)
  File "qiling_play/venv/lib/python3.10/site-packages/qiling/core.py", line 883, in emu_start
    raise self._internal_exception
  File "qiling_play/venv/lib/python3.10/site-packages/qiling/utils.py", line 159, in wrapper
    return func(*args, **kw)
  File "qiling_play/venv/lib/python3.10/site-packages/qiling/core_hooks.py", line 91, in _hook_trace_cb
    ret = hook.call(ql, addr, size)
  File "qiling_play/venv/lib/python3.10/site-packages/qiling/core_hooks_types.py", line 25, in call
    return self.callback(ql, *args)
  File "qiling_play/lab/aarch64_solve.py", line 158, in hook_el1
    if ql.mem.read(address, size) == b"\x00\x00\x38\xD5":
  File "qiling_play/venv/lib/python3.10/site-packages/qiling/os/memory.py", line 287, in read
    return self.ql.uc.mem_read(addr, size)
  File "qiling_play/venv/lib/python3.10/site-packages/unicorn/unicorn.py", line 555, in mem_read
    status = _uc.uc_mem_read(self._uch, address, data, size)
KeyboardInterrupt

Additional context about the challenge4, it requires to change the register value before cmp to enter a forbidden loop.

Decompiled code

void challenge4(char *check) {
    int i;
    i = 0;
    while (i < 0) {
        *check = 1;
        i = i + 1;
    }
}
    00100fe0 3f 00 00 6b     cmp        w1,w0           <--- HOOK HERE 
    00100fe4 eb fe ff 54     b.lt       LAB_00100fc0

so the idea is to set a hook function in cmp instruction address

# 4. hook address to enter the loop
def challenge4(ql):
    def hook_loop_check(ql):
        ql.reg.w0 = 1

    base_addr = ql.mem.get_lib_base(ql.path)
    enter_loop_check = base_addr + 0xfe0
    ql.hook_address(hook_loop_check, enter_loop_check)

I found the hook function hasn't been executed after debugging.

Heersin avatar Aug 09 '22 14:08 Heersin