0x00 Qiling lab
通过qiling lab来学习qiling的使用,主要是一些hook技巧
https://www.shielder.com/blog/2021/07/qilinglab-release/
start
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| from qiling import * from qiling.const import QL_VERBOSE
def challenge1(ql: Qiling): pass;
if __name__ == "__main__": target=["qilinglab-aarch64"] rootfs="/Users/kazamayc/tools/qiling/examples/rootfs/arm64_linux" ql=Qiling(target,rootfs,verbose=QL_VERBOSE.OFF) challenge1(ql) ql.run()
|
1
| target remote 172.30.2.232:9999
|
challenge1 内存
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| _BYTE *__fastcall challenge1(_BYTE *a1)
_BYTE *result;
result = (_BYTE *)*(unsigned int *)((char *)&loc_1334 + 3); if ( result ) == 1337 ) { result = a1; *a1 = 1; } return result; }
|
1 2 3 4
| def challenge1(ql): ql.mem.map(0x1337//4096*4096, 4096, info = "[challenge1]") ql.mem.write(0x1337, ql.pack16(1337))
|
可以在内存中查找或者写入数据
参考:
https://docs.qiling.io/en/latest/memory/#map-a-memory-area
https://docs.qiling.io/en/latest/struct/
challenge2 系统调用
读取uname,通关条件是
1 2
| name.sysname=="QilingOS"; name.version=="ChallengeStart";
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| struct utsname { char sysname[65]; char nodename[65]; char release[65]; char version[65]; char machine[65]; char domainname[65]; };
__int64 __fastcall challenge2(_BYTE *a1) { unsigned int v3; int v4; int v5; int v6; struct utsname name; char s[16]; char v9[16]; __int64 v10;
if ( uname(&name) ) { perror("uname"); } else { strcpy(s, "QilingOS"); s[9] = 0; strcpy(v9, "ChallengeStart"); v9[15] = 0; v3 = 0; v4 = 0; while ( v5 < strlen(s) ) { if ( name.sysname[v5] == s[v5] ) ++v3; ++v5; } while ( v6 < strlen(v9) ) { if ( name.version[v6] == v9[v6] ) ++v4; ++v6; } if ( v3 == strlen(s) && v4 == strlen(v9) && v3 > 5 ) *a1 = 1; } return v10 ^ _stack_chk_guard; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| from qiling.const import QL_INTERCEPT def my_uname(ql, pStcUname, *args): ql.mem.write(pStcUname, b"QilingOS".ljust(65,b"\x00")) ql.mem.write(pStcUname + 65*3, b'ChallengeStart'.ljust(65, b'\x00')) return 0
def challenge2(ql): ql.os.set_syscall('uname', my_uname, QL_INTERCEPT.EXIT)
|
pStcUname
是一个指向一个字符数组的指针,是uname的参数,该数组用于存储 uname 系统调用返回的操作系统信息,包括系统名称、网络名称、版本等
*args
是在使用 Qiling 模拟系统调用时,需要将自定义的函数作为回调函数传递给 Qiling。在这种情况下, Qiling 框架将自动传递一些参数给自定义函数,例如系统调用号和系统调用参数。因此,在定义自定义函数时,为了避免这些参数引起错误,通常会使用可变长度参数 *args
来捕获和忽略这些参数。
还可以使用QL_INTERCEPT.CALL等对系统调用函数进行替代。
参考:
https://man7.org/linux/man-pages/man3/uname.3p.html
https://docs.qiling.io/en/latest/hijack/
challenge3 文件系统
1 2
| /dev/urandom == getrandom 且一个字节的随机数和其他的随机数都不一样
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| __int64 __fastcall challenge3(_BYTE *a1) { int v3; int i; int fd; char v6[8]; char buf[32]; char v8[32]; __int64 v9;
fd = open("/dev/urandom", 0); read(fd, buf, 0x20uLL); read(fd, v6, 1uLL); close(fd); getrandom(v8, 32LL, 1LL); v3 = 0; for ( i = 0; i <= 31; ++i ) { if ( buf[i] == v8[i] && buf[i] != v6[0] ) ++v3; } if ( v3 == 32 ) *a1 = 1; return v9 ^ _stack_chk_guard; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| from qiling.os.mapper import QlFsMappedObject class Fake_urandom(QlFsMappedObject): def read(self, size): if(size == 1): return b"\x02" else: return b"\x01" * size def close(self): return 0
def fake_getrandom(ql, buf, buflen, flags, *args): ql.mem.write(buf, b"\x01"*buflen) return 0
def challenge3(ql): ql.add_fs_mapper("/dev/urandom", Fake_urandom()) ql.os.set_syscall('getrandom', fake_getrandom, QL_INTERCEPT.EXIT)
|
challenge4 地址和寄存器
1 2 3 4 5 6 7
| void challenge4(check) { int i = 0; while (i < 0) { check = 1; i++; } }
|
b.lt 小于时跳转,所以设置W0的值为1就可以
1 2 3 4 5 6 7 8
| def loop_hook(ql): ql.arch.regs.write("w0", 0x1)
def challenge4(ql): base_addr = ql.mem.get_lib_base(ql.path) loop_enter = base_addr+0xFE0 ql.hook_address(loop_hook, loop_enter)
|
参考:
https://docs.qiling.io/en/latest/hook/
challenge5 外部函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| __int64 __fastcall challenge5(_BYTE *a1) { unsigned int v1; int i; int j; _DWORD v6[12]; __int64 v7;
v1 = time(0LL); srand(v1); for ( i = 0; i <= 4; ++i ) { v6[i] = 0; v6[i + 6] = rand(); } for ( j = 0; j <= 4; ++j ) { if ( v6[j] != v6[j + 6] ) { *a1 = 0; return v7 ^ _stack_chk_guard; } } *a1 = 1; return v7 ^ _stack_chk_guard; }
|
rand是库函数,不是系统调用,所以不能使用set_syscall,只能用set_api
我们只要让rand的返回值为0就可以了
使用set_api来 hook 函数的返回值时,不能使用 return
语句来修改返回值,必须要改寄存器
1 2 3 4 5
| def rand_hook(ql, *args): ql.arch.regs.x0 = 0 def challenge5(ql): ql.os.set_api("rand", rand_hook)
|
challenge6 地址
1 2 3 4 5 6 7
| def loop_bypass_hook(ql): ql.arch.regs.w0 = 0
def challenge6(ql): base_addr = ql.mem.get_lib_base(ql.path) loop_addr = base_addr + 0x1114 ql.hook_address(loop_bypass_hook, loop_addr)
|
challenge7 外部函数
1 2 3 4 5
| __int64 __fastcall challenge7(_BYTE *a1) { *a1 = 1; return sleep(0xFFFFFFFF); }
|
hook sleep函数有多种方法
用set_api,用一个空函数替换sleep
用set_api,把参数改了
用set_syscall,将nanosleep改了
1 2 3 4 5 6 7 8 9 10 11 12 13
| def fake_sleep1(ql, *args): return
def fake_sleep2(ql, *args): ql.arch.regs.write("w0", 0)
def hook_nanosleep(ql, *args): return 0
def challenge7(ql): ql.os.set_syscall('nanosleep', hook_nanosleep)
|
challenge8 结构体
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| _DWORD *__fastcall challenge8(__int64 a1) { _DWORD *result; _DWORD *v3;
v3 = malloc(0x18uLL); *(_QWORD *)v3 = malloc(0x1EuLL); v3[2] = 0x539; v3[3] = 0x3DFCD6EA; strcpy(*(char **)v3, "Random data"); result = v3; *((_QWORD *)v3 + 2) = a1; return result; }
|
是让我们去寻找一个类似的结构体
1 2 3 4 5
| struct something(0x18){ string_ptr -> malloc (0x1e) -> "Random data" long_int = 0x3DFCD6EA00000539 check_addr -> check; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import struct def search_heap1(ql):
nMagic = 0x3DFCD6EA00000539 pMagics = ql.mem.search(ql.pack64(nMagic))
for pMagic in pMagics: pHeap1 = pMagic - 8 heap1 = ql.mem.read(pHeap1, 24) pHeap2, _, pFlag = struct.unpack("QQQ", heap1)
if ql.mem.string(pHeap2) == "Random data":
ql.mem.write(pFlag, b"\x01") break
def challenge8(ql): base_addr = ql.mem.get_lib_base(ql.path) ql.hook_address(search_heap1, base_addr + 0x11DC)
|
hook一个具体地址。执行指定地址时将调用已注册的回调。
参考链接:
https://docs.qiling.io/en/latest/memory/
https://docs.qiling.io/en/latest/hook/
还有第二种方法,可以直接读取栈上的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| def search_heap2(ql): ''' 001011d0 e0 17 40 f9 ldr x0,[sp, #0x28] <---- heap structure on stack 001011d4 e1 0f 40 f9 ldr x1,[sp, #0x18] 001011d8 01 08 00 f9 str x1,[x0, #0x10] 001011dc 1f 20 03 d5 nop <----------------------- HOOK HERE 001011e0 fd 7b c3 a8 ldp x29=>local_30,x30,[sp], #0x30 001011e4 c0 03 5f d6 ret
''' heap_struct_addr = ql.unpack64(ql.mem.read(ql.arch.regs.sp + 0x28, 8))
heap_struct = ql.mem.read(heap_struct_addr, 24) some_string_addr, magic, check_addr = struct.unpack('QQQ', heap_struct)
ql.mem.write(check_addr, b"\x01") def challenge8(ql): base_addr = ql.mem.get_lib_base(ql.path) ql.hook_address(search_heap2, base_addr + 0x11DC)
|
challenge9 外部函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| __int64 __fastcall challenge9(bool *a1) { char *i; char dest[32]; char src[32]; __int64 v6;
strcpy(src, "aBcdeFghiJKlMnopqRstuVWxYz"); src[27] = 0; strcpy(dest, src); for ( i = dest; *i; ++i ) *i = tolower((unsigned __int8)*i); *a1 = strcmp(src, dest) == 0; return v6 ^ _stack_chk_guard; }
|
本题主要原理是在tolower把字符串转换成小写之前,使其失效即可。
1 2 3 4 5
| def tolower_hook(ql): return
def challenge9(ql): ql.os.set_api("tolower", tolower_hook)
|
challenge10 文件系统
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| __int64 __fastcall challenge10(_BYTE *a1) { int i; int fd; ssize_t v5; char buf[64]; __int64 v7;
fd = open("/proc/self/cmdline", 0); if ( fd != -1 ) { v5 = read(fd, buf, 0x3FuLL); if ( v5 > 0 ) { close(fd); for ( i = 0; v5 > i; ++i ) { if ( !buf[i] ) buf[i] = 32; } buf[v5] = 0; if ( !strcmp(buf, "qilinglab") ) *a1 = 1; } } return v7 ^ _stack_chk_guard; }
|
一种方法:hook文件系统
1 2 3 4 5 6 7 8 9 10
| class Fake_cmdline(QlFsMappedObject): def read(self, size): return b"qilinglab" def fstat(self): return -1 def close(self): return 0
def challenge10(ql): ql.add_fs_mapper("/proc/self/cmdline", Fake_cmdline())
|
第二种方法:本地创建文本
1
| $ echo -n "qilinglab" > fake_cmdline
|
1 2
| def challenge10(ql): ql.add_fs_mapper("/proc/self/cmdline", "./fake_cmdline")
|
第三种方法:无需编写任何代码
1 2
| $ mkdir -p ./my_rootfs/proc/self $ echo -n "qilinglab" > my_rootfs/proc/self/cmdline
|
但是上述方法我都没执行成功,于是我直接劫持了strcmp
1 2 3 4
| def strcmp_hook(ql): ql.arch.regs.x0 = 0 def challenge10(ql): ql.os.set_api("strcmp",strcmp_hook)
|
challenge11 CPU指令
1 2 3 4 5 6 7 8 9 10 11 12
| __int64 __fastcall challenge11(_BYTE *a1) { __int64 result;
result = 4919LL; if ( _ReadStatusReg(ARM64_SYSREG(3, 0, 0, 0, 0)) >> 16 == 4919 ) { result = (__int64)a1; *a1 = 1; } return result; }
|
其实可以直接hook 0x1400的x1为0x1337
1 2 3 4 5
| def fake_end(ql): ql.arch.regs.write("x1", 0x1337) def challenge11(ql): base_addr = ql.mem.get_lib_base(ql.path) ql.hook_address(fake_end, base_addr + 0x1400)
|
或者hook指令
1 2 3 4 5 6 7 8 9 10 11
| def fake_end2(ql, address, size): ''' 000013ec 00 00 38 d5 mrs x0,midr_el1 ''' if ql.mem.read(address, size) == b"\x00\x00\x38\xD5": ql.arch.regs.x0 = 0x1337 << 16 ql.arch.regs.arch_pc += 4 def challenge11(ql): ql.hook_code(fake_end2)
|