Qiling框架学习-Qilinglab

Qiling框架学习-QilinglabQiling 框架学习 Qilinglab 简介 Qiling 框架是一个超轻量级的 沙盒 适用于 Linux MacOS Windows FreeBSD DOS UEFI 和 MBR 它是建立在 Unicorn 引擎之上的二进制仿真框架 支持 x86 16 32 和 64 位 ARM ARM64 和 MIPS

大家好,我是讯享网,很高兴认识大家。

Qiling框架学习-Qilinglab

简介

Qiling框架是一个超轻量级的“沙盒”,适用于Linux、MacOS、Windows、FreeBSD、DOS、UEFI和MBR。它是建立在Unicorn引擎之上的二进制仿真框架,支持x86(16、32和64位)、ARM、ARM64和MIPS。麒麟框架也凭借Demigod支持Linux内核模块(.ko),微软视窗驱动(.sys)和苹果MacOS内核(.kext)。

然而,麒麟框架不是旨于构建另一个“沙盒”工具,而是为逆向工程设计的框架。因此,二进制检测和API是麒麟框架的主要及优先关注点。使用麒麟框架可以节省时间。拥有丰富API的麒麟框架将逆向工程及二进制代码检测快速的提升到了一个新的层次。

此外,麒麟框架还提供了对寄存器、内存、文件系统、操作系统和调试器的API访问。麒麟框架也提供了虚拟机级别的API,如保存和恢复执行状态。

安装

使用pip安装

pip3 install qiling 使用 pip 安装最新的开发版本 pip3 install --user https://github.com/qilingframework/qiling/archive/dev.zip 

讯享网

从github克隆框架手动安装

讯享网git clone https://github.com/qilingframework/qiling cd qiling python3 setup.py install 

另外不要忘记初始化 rootfs。

git submodule update --init --recursive 

qilinglab

首先使用file指令查看下载到的qilinglab程序
在这里插入图片描述
讯享网

基本使用模板

讯享网from qiling import * def challenge1(ql: Qiling): pass if __name__ == '__main__': path = ['qilinglab-aarch64'] # 可执行程序 rootfs = "/qiling/examples/rootfs/arm64_linux" # 机器文件系统的根 ql.verbose = 0 ql = Qiling(path, rootfs) ql.run() #如果需要其他共享库来模拟二进制文件,我们需要下载它们并将它们添加到我们的 rootfs 中 

执行二进制程序
在这里插入图片描述

输出挑战列表,并提示Some challenges will results in segfaults and infinite loops if they aren’t solved

main()–>start()函数,主要是输出挑战内容,调用challange X函数和checker函数对结果进行校验
start()
在这里插入图片描述

checker()
在这里插入图片描述

challange1

题目要求:在0X1337地址处写入1337

在这里插入图片描述

操作内存手册:https://docs.qiling.io/en/latest/memory/
麒麟提供了几种管理模拟内存空间的方法:
在这里插入图片描述

写入内存地址 ql.mem.write(address, data) 映射内存区域 在写入内存之前映射内存。info可以为空。 ql.mem.map(addr,size,info = [my_first_map]) 地址: 你需要对齐内存偏移量和地址以进行映射。 addr//size*size -> 0x7fefc9e0//4096*4096 大小: 应映射的内存量 此参数取决于操作系统;如果使用 linux 系统,请考虑至少使用 4096 的倍数进行对齐 

slove-challenge1

讯享网def challenge1(ql): ql.mem.map(0x1337//4096*4096,0x1000, info = "[challenge1]") ql.mem.write(0x1337, ql.pack16(1337)) # pack16(value) == struct.pack('H', value)  #struct.pack用于将Python的值根据格式符,转换为字符串,h表示short,l表示long 

运行结束后可以通过kill命令手动结束程序运行
在这里插入图片描述

challange2

题目要求:使uname系统调用返回正确的值

在这里插入图片描述

uname系统调用返回有关底层操作系统的信息,传入一个utsname结构体buffer让它填充

utsname结构体的相关信息参考
https://man7.org/linux/man-pages/man3/uname.3p.html
在这里插入图片描述

struct utsname { 
    char sysname[65]; char nodename[65]; char release[65]; char version[65]; char machine[65]; char domainname[65]; }; 

通过挑战条件为

讯享网uname.sysname == "QilingOS"; uname.version == "ChallengeStart"; 

需要通过qiling提供的系统调用uname,修改返回地uname结构体即可。
https://docs.qiling.io/en/latest/hijack/
在这里插入图片描述

劫持 POSIX 系统调用

POSIX 系统调用可以hook以允许用户修改其参数、更改返回值或完全替换其功能。当指定的系统调用即将被调用时,系统调用可以通过其名称或号码挂钩,并在一个或多个阶段被拦截;
进入系统调用前,可用于完全替换系统调用功能;
退出系统调用后,可用于篡改系统调用参数值,可用于篡改返回值;
QL_INTERCEPT.CALL| QL_INTERCEPT.ENTER | QL_INTERCEPT.EXIT

slove-challenge2
在这里插入图片描述

JOANSIVION大佬这里的方法是获取寄存器sp栈指针的地址,然后找到偏移量,就是结构体的指针地址,然后作为mem写入的起始地址
name的地址为 -0x1B0
然后0x1F0+name = 0x1F0-0x1B0 = 0x40

def hook_uname_on_exit(ql, pName, *args): #out_struct_addr = ql.arch.regs.sp + 0x40 #sysname_addr = out_struct_addr #ql.mem.write(sysname_addr, b'QilingOS\x00') #ql.mem.write(out_struct_addr + 65 * 3, b'ChallengeStart\x00') #使用这种方法定义函数时为def hook_uname_on_exit(ql, *args): ql.mem.write(pName, b'QilingOS\x00') ql.mem.write(pName + 65 * 3, b'ChallengeStart\x00') #通过 os.set_syscall 加上 QL_INTERCEPT.EXIT 参数,在调用结束后劫持 uname 的返回值,替换成验证的字符串 def challenge2(ql): ql.os.set_syscall('uname', hook_uname_on_exit, QL_INTERCEPT.EXIT) #系统调用可以通过其名称或号码来引用,通过引用其编号替换uname系统调用的等效替代 

在这里插入图片描述

challange3

题目要求:/dev/urandom 和 getrandom 相等

在这里插入图片描述

程序需要使/dev/urandom 和 getrandom 相等,且一个字节的随机数和其他的随机数都不一样。

getrandom的系统调用定义如下

讯享网ssize_t getrandom(void *buf, size_t buflen, unsigned int flags); /* The getrandom() system call fills the buffer pointed to by buf with up to buflen random bytes. These bytes can be used to seed user-space random number generators or for cryptographic purposes. */ 

对 getrandom的劫持与Challenge2一样

对 /dev/urandom的劫持要用到 QlFsMappedObject的add_fs_mapper,它可以实现将模拟环境中的路径劫持到主机上的路径或将读/写操作重定向到用户定义的对象。

在这里插入图片描述

slove-challenge3

class Fake_urandom(QlFsMappedObject): def read(self, size): # return a constant value upon reading if(size == 1): return b"\x02" # byUrandom else: return b"\x01" * size def fstat(self): # syscall fstat will ignore it if return -1 return -1 def close(self): return 0 def fake_getrandom(ql, buf, buflen, flags, *args, kw): ql.mem.write(buf, b"\x01"*buflen) ql.os.set_syscall_return(0) def challenge3(ql): ql.add_fs_mapper("/dev/urandom", Fake_urandom()) #将虚拟路径映射到用户定义的文件类型,该对象允许对交互进行更精细的控制 ql.os.set_syscall('getrandom', fake_getrandom, QL_INTERCEPT.EXIT) #同2系统调用 

在这里插入图片描述

challenge 4

题目要求:进入禁止的循环
IDA F5无效
在这里插入图片描述

查看汇编
在这里插入图片描述

loc_FD8()函数
LDR 将存储器地址为SP+0x20-8的半字数据读入寄存器w0
LDR 将存储器地址为SP+0x20-4的半字数据读入寄存器w1
CMP 比较W1和W0的值
B.LT 比较结果是大于,跳转loc_FC0()函数,否则不跳转
因为判断条件一直不成立,程序进入死循环,不会跳转循环语句
我们需要在比较前将W0值改为1
我们需要使用ql
https://docs.qiling.io/en/latest/hook/

slove-challenge4

讯享网def forbidden_loop_hook(ql): #hook_address 将 x0 改成比 x1 小即可 #ql.arch.regs.x0 = 1 #ql.arch.regs.write("x0", 1) ql.arch.regs.write("w0", 0x1) def challenge4(ql): # Get the module base address # https://github.com/qilingframework/qiling/blob/dev/qiling/profiles/linux.ql 可知 qiling 默认配置 linux64 加载基地址为 0x0 #base_addr = ql.mem.get_lib_base(ql.path) # Address we need to patch test_forbidden_loop_enter = 0x0 + 0xFE0 # cmp指令偏移是 0xFE0 # Place hook ql.hook_address(forbidden_loop_hook, test_forbidden_loop_enter) 

cmp指令偏移是 0xFE0
在这里插入图片描述

在这里插入图片描述

challenge5

题目要求:预测每次调用rand()函数的值

在这里插入图片描述

在第二个for循环中,存在判断语句rand()函数的值是否都相同为0,我们需要挟持rand()函数的值让它都是0

rand()是库函数,不是系统调用,所以不能用set_syscall,应该用set_api

参考Qiling文档Hijacking OS API (POSIX)
在这里插入图片描述

slove-challenge5

def rand_hook(ql, *args, kw): ql.arch.regs.x0 = 0 def challenge5(ql): ql.os.set_api("rand", rand_hook) 

在这里插入图片描述

这里为什么没有报sloved,排查问题也排查不出呀

challenge6

题目要求:避免无限循环

在这里插入图片描述

在这里插入图片描述

B.NE 表示不相等时直接向后跳转
程序不断的将1 mov至寄存器w0,然后cmp w0 和0的值,不相同时跳转,程序陷入无限死循环,我们需要在cmp前使w0=0.就可以跳出循环

slove-challenge6

讯享网def infinite_loop_bypass_hook(ql): ql.arch.regs.write("w0", 0x0) def challenge6(ql): # Address we need to patch # cmp_infinite_loop_addr = base_addr + 0x1118 = 0x0 + 0x1118 # Place hook ql.hook_address(infinite_loop_bypass_hook, 0x0 + 0x1118) 

在这里插入图片描述

challenge 7

题目要求:不要浪费时间等待sleep()函数

在这里插入图片描述

这里可以挟持sleep()函数,修改sleep的值

slove-challenge7

def fake_sleep(ql, *args): ql.arch.regs.write("w0", 0) #法二  #return def hook_nanosleep(ql: Qiling, *args, kwargs): # 注意参数列表 return def challenge7(ql): ql.os.set_api("sleep", fake_sleep) #法三 #ql.set_syscall('nanosleep', hook_nanosleep) 

这个challenge7解决后,程序恢复正常,开始输出各个challenge的信息

参考其他大佬的方法,这里还有几种解决方法:
1、挟持sleep()函数,将其替换为空函数
2、劫持系统调用,根据DEBUG信息,sleep其实是调用了nanosleep()

讯享网参考
https://man7.org/linux/man-pages/man3/sleep.3.html
https://man7.org/linux/man-pages/man2/nanosleep.2.html
	On Linux, sleep() is implemented via nanosleep(2). 

在这里插入图片描述

challenge8

题目要求:解包结构体并在目标地址写入内容

在这里插入图片描述

在这里插入图片描述

NOP 无操作

这个题目没咋看懂,函数调用了两次malloc()函数,结构体嵌套?
看了一下其他人的方法,是通过利用特殊字符串或者结构定位想要的指令地址
通过固定的 0x3DFCD6EA539 去找到结构体位置,进而修改 flag

qiling从内存搜索字符串
在这里插入图片描述

这里才理解了Q的原理

slove-challenge7

def search_heap1(ql): #从内存中搜索字符串 nMagic = 0x3DFCD6EA00000539 pMagics = ql.mem.search(ql.pack64(nMagic)) #内存可能出现了几次字符串,使用字符串“Random data”验证是否找到了正确的数据 for pMagic in pMagics: pHeap1 = pMagic - 8 heap1 = ql.mem.read(pHeap1, 24) pHeap2, _, pFlag = struct.unpack("Q", heap1) #比较地址和读到的字符串 if ql.mem.string(pHeap2) == "Random data": #找到结构体的位置然后写入1 ql.mem.write(pFlag, b"\x01") break def challenge8(ql): ql.hook_address(search_heap1, 0x0 + 0x11DC) # 0x11DC : nop 

在这里插入图片描述

这个题目确实有点云里雾里,后续再研究研究

challenge9

题目要求:修改字符串操作使得 iMpOsSiBlE 正确

在这里插入图片描述

tolower(int c) 把给定的字母转换为小写字母

解决的两个思路
1、在两个字符串比较前,让tolower()失效
2、直接修改strcmp(src, dest) == 0 的值

solve-challenge9

讯享网def fake_strcmp(ql, *args): ql.arch.regs.write("x0", 0) def fake_tolower(ql): return def challenge9(ql): #ql.os.set_api('strcmp', fake_strcmp) ql.os.set_api('tolower', fake_tolower) 

在这里插入图片描述

方法二同时会解决challenge2,后面看一下

challenge10

题目要求:伪造成 ’cmdline’ 文件来返回正确的内容
在这里插入图片描述

通过篡改/proc/self/cmdline 这个文件内容为”qilinglab”

解决的两个思路
1、像challenge3对 /dev/urandom的劫持的那样,通过 add_fs_mapper 映射到自定义实现或主机路径,它可以实现将模拟环境中的路径劫持到主机上的路径或将读/写操作重定向到用户定义的对象。
2、直接修改strcmp(buf,“qilinglab”) == 0 的值,这也是为啥challenge 9的方法二可以同时解决challenge 10

solve-challenge10

#未通过 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()) 

在这里插入图片描述

讯享网echo -n "qilinglab" > fake_cmdline 
ql.add_fs_mapper("/proc/self/cmdline", "./fake_cmdline") 

为啥大家这样解决都没问题,我challenge9无论采用ql.os.set_api(‘tolower’, fake_tolower)的方案,还是直接替换目标文件,后面这里一直通不过呀???
有无大佬这里能指点一下
最后只能采用挟持strcmp()函数的方案

challenge11

题目要求:绕过CPUID/MIDR_EL1检查

在这里插入图片描述

在这里插入图片描述

MRS CPUid指令,可以加载特殊功能寄存器的值到通用寄存器
aarch64的伪代码:

讯享网if ( _ReadStatusReg(ARM64_SYSREG(3, 0, 0, 0, 0)) >> 16 == 4919 ) { result = (__int64)a1; *a1 = 1; } 

这里是将CPU的信息保存到X0中,然后运行比较运算

简便方法:

def fake_end(ql): ql.arch.regs.write("x1", 0x1337) def challenge11(ql): ql.hook_address(fake_end, 0x0+ 0x1400) 

或者采用qiling的函数 hook_code(),可以hook CPU所有的指令,

在这里插入图片描述

法二:

讯享网def midr_el1_hook(ql, address, size): # opcode: \x00\x00\x38\xD5  if ql.mem.read(address, size) == b"\x00\x00\x38\xD5": # Write the expected value to x0 ql.arch.regs.x0 = 0x1337 << 16 # Go to next instruction # opcode take 4 bytes so next instruction will be pc + 4 ql.arch.regs.arch_pc += 4 def challenge11(ql): ql.hook_code(midr_el1_hook) 

在这里插入图片描述

完整代码

import struct from qiling import * # from unicorn.unicorn_const import UC_MEM_WRITE from qiling.const import * from qiling.os.mapper import QlFsMappedObject def challenge1(ql): #ql.mem.map(addr, size) must be page aligned ql.mem.map(0x1000, 0x1000, info = "[challenge1]") ql.mem.write(0x1337, ql.pack16(1337)) def hook_uname_on_exit(ql, *args): #sp = ql.arch.regs.sp out_struct_addr = ql.arch.regs.sp + 0x40 sysname_addr = out_struct_addr ql.mem.write(sysname_addr, b'QilingOS\x00') ql.mem.write(out_struct_addr + 65 * 3, b'ChallengeStart\x00') #ql.mem.write(pName, b'QilingOS\x00') #ql.mem.write(pName + 65 * 3, b'ChallengeStart\x00') def challenge2(ql): ql.os.set_syscall('uname', hook_uname_on_exit, QL_INTERCEPT.EXIT) #系统调用可以通过其名称或号码来引用,通过引用其编号替换uname系统调用的等效替代 class Fake_urandom(QlFsMappedObject): def read(self, size): if(size == 1): return b"\x02" # byUrandom else: return b"\x01" * size def fstat(self): # syscall fstat will ignore it if return -1 return -1 def close(self): return 0 def fake_getrandom(ql, buf, buflen, flags, *args, kw): ql.mem.write(buf, b"\x01"*buflen) ql.os.set_syscall_return(0) def challenge3(ql): ql.add_fs_mapper("/dev/urandom", Fake_urandom()) ql.os.set_syscall('getrandom', fake_getrandom, QL_INTERCEPT.EXIT) def forbidden_loop_hook(ql): #ql.arch.regs.x0 = 1 #hook_address 将 x0 改成比 x1 小即可 #ql.arch.regs.write("x0", 1) ql.arch.regs.write("w0", 0x1) def challenge4(ql): # Get the module base address # https://github.com/qilingframework/qiling/blob/dev/qiling/profiles/linux.ql 可知 qiling 默认配置 linux64 加载基地址为 0x0 #base_addr = ql.mem.get_lib_base(ql.path) # Address we need to patch test_forbidden_loop_enter = 0x0 + 0xFE0 # cmp指令偏移是 0xFE0 # Place hook ql.hook_address(forbidden_loop_hook, test_forbidden_loop_enter) def rand_hook(ql, *args, kw): ql.arch.regs.x0 = 0 def challenge5(ql): ql.os.set_api("rand", rand_hook) def infinite_loop_bypass_hook(ql): ql.arch.regs.write("w0", 0x0) def challenge6(ql): # Get the module base address #base_addr = ql.mem.get_lib_base(ql.path) #print(base_addr) # Address we need to patch # cmp_infinite_loop_addr = base_addr + 0x1118 # Place hook ql.hook_address(infinite_loop_bypass_hook, 0x0 + 0x1118) def fake_sleep(ql, *args): ql.arch.regs.write("w0", 0) def challenge7(ql): ql.os.set_api("sleep", fake_sleep) 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("Q", heap1) if ql.mem.string(pHeap2) == "Random data": ql.mem.write(pFlag, b"\x01") break return def challenge8(ql): #pBase = ql.mem.get_lib_base(ql.path) ql.hook_address(search_heap1, 0x0 + 0x11DC) # 0x11DC : nop #def fake_strcmp(ql, *args): #ql.arch.regs.write("x0", 0) def fake_tolower(ql): return def challenge9(ql): #ql.os.set_api('strcmp', fake_strcmp) ql.os.set_api("tolower", fake_tolower) class Fake_cmdline(QlFsMappedObject): def read(self, size): return b"qilinglab" def close(self): return 0 def challenge10(ql): ql.add_fs_mapper("/proc/self/cmdline", Fake_cmdline()) def fake_end(ql): ql.arch.regs.write("x1", 0x1337) def challenge11(ql): ql.hook_address(fake_end, 0x0+ 0x1400) if __name__ == '__main__': path = ["./qilinglab-aarch64"] rootfs = "/home/snjuxp/qiling/examples/rootfs/arm64_linux" ql = Qiling(path, rootfs, verbose=QL_VERBOSE.DEBUG) #ql = Qiling(path, rootfs) ql.verbose = 0 #ql.verbose = 4 # ql.mem.map_info() #ql.mem.get_formatted_mapinfo() challenge1(ql) challenge2(ql) challenge3(ql) challenge4(ql) challenge5(ql) challenge6(ql) challenge7(ql) challenge8(ql) challenge9(ql) challenge10(ql) challenge11(ql) ql.run() 

总结

这应该是农历新年的最后一篇文章了,同时也是2023新年的第一篇文章。今年学习了解了不少知识,射频安全,BLE,Fuzz,还有一些安全方案,2023年还有很多方向需要深入研究的,英语也需要多多练习口语,车联网安全的学习还任重而道远。2022的前半年都在疫情中度过,年末又恶感新冠,无论如何,希望2023身体健康。

参考:
https://docs.qiling.io/
https://bbs.kanxue.com/thread-268989.htm#msg_header_h2_13
https://joansivion.github.io/qilinglabs/
https://ryze-t.com/2022/09/08/Qiling%E6%A1%86%E6%9E%B6%E5%85%A5%E9%97%A8-QilingLab/
https://blog.csdn.net/Ga4ra/article/details/
https://github.com/badmonkey7/qilinglab-solution

小讯
上一篇 2025-04-06 08:29
下一篇 2025-03-30 17:40

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/113891.html