qiling
qiling copied to clipboard
Different behaviors caused by setting different binary paths
*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.