前言
此Lab用于快速熟悉Qiling
框架的主要功能,其拥有两个版本x86-64
以及aarch64
两个版本,获取可以访问下面的页面
Shielder - QilingLab – Release
其包含11个挑战:
1 2 3 4 5 6 7 8 9 10 11 Challenge 1: Store 1337 at pointer 0x1337. Challenge 2: Make the 'uname' syscall return the correct values. Challenge 3: Make '/dev/urandom' and 'getrandom' "collide". Challenge 4: Enter inside the "forbidden" loop. Challenge 5: Guess every call to rand(). Challenge 6: Avoid the infinite loop. Challenge 7: Don't waste time waiting for 'sleep'. Challenge 8: Unpack the struct and write at the target address. Challenge 9: Fix some string operation to make the iMpOsSiBlE come true. Challenge 10: Fake the 'cmdline' line file to return the right content. Challenge 11: Bypass CPUID/MIDR_EL1 checks.
使用IDA打开看到的也是没有去除对应的符号信息,也没有混淆,可以比较直观的看到对应的检测:
基本用法
根据互联网上的资料以及项目中对应的说明文档,我们需要使用到rootfs
中的东西,其为Qiling框架测试所需要的文件
我们使用以下命令进行clone Qiling项目的框架(使用--recursiv
来循环拉取子项目):
1 git clone https://github.com/qilingframework/qiling.git --recursiv
之后我们在后续中使用到的脚本模板如下:
1 2 3 4 5 6 7 8 9 10 11 from qiling import *def challenge1 (ql: Qiling ): pass if __name__ == '__main__' : path = ['qilinglab-x86_64' ] rootfs = "./qiling/examples/rootfs/x8664_linux" ql = Qiling(path, rootfs) challenge1(ql) ql.run()
Challenge1:修改内存
1 2 3 4 5 void __fastcall challenge1 (_BYTE *a1) { if ( MEMORY[0x1337 ] == 1337 ) *a1 = 1 ; }
可以看出来需要我们在地址为0x1337处存放一个值为1337,对此我们需要使用以下代码来进行映射一块内存:
1 2 3 4 5 ql.mem.map (0x1000 , 0x1000 , info='[challenge1]' )
因为 Qiling 框架底层上还是使用的 Unicorn 那一套,所以还是需要按 4k 进行对齐
内存操作相关参考:Memory - Qiling Framework Documentation
我们需要写入内存的时候使用以下代码:
1 ql.mem.write(address, data)
需要注意的是,当我们写入数据的时候需要将对应的数据进行打包,在这个挑战中我们需要将其打包为16位数据无符号,即两字节:
当我们需要使用有符号的时候在对应数字后面加一个s即可,如ql.pack16s
则是有符号的short类型数据
打包与解包参考:Pack and Unpack - Qiling Framework Documentation
对应解题脚本如下:
1 2 3 def Challenge1 (ql: Qiling ): ql.mem.map (0x1000 ,0x1000 ,info="[Challenge 1]" ) ql.mem.write(0x1337 , ql.pack16(1337 ))
可以看到已经成功解决(此时运行于 Windows 环境下)
可以在main函数的运行代码处加上下面的代码来开启于关闭对应调试模式
Challenge2:修改系统调用
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 unsigned __int64 __fastcall challenge2 (_BYTE *a1) { unsigned int v2; int v3; int v4; int v5; struct utsname name ; char s[10 ]; char v8[24 ]; unsigned __int64 v9; v9 = __readfsqword(0x28 u); if ( uname (&name) ) { perror ("uname" ); } else { strcpy (s, "QilingOS" ); s[9 ] = 0 ; strcpy (v8, "ChallengeStart" ); v8[15 ] = 0 ; v2 = 0 ; v3 = 0 ; while ( v4 < strlen (s) ) { if ( name.sysname[v4] == s[v4] ) ++v2; ++v4; } while ( v5 < strlen (v8) ) { if ( name.version[v5] == v8[v5] ) ++v3; ++v5; } if ( v2 == strlen (s) && v3 == strlen (v8) && v2 > 5 ) *a1 = 1 ; } return __readfsqword(0x28 u) ^ v9; }
系统通过uname
来进行获取系统信息,通过对比系统信息来进行校验。我们可以在uname
所获取的信息为一个utsname
的结构体,我们可以在IDA中进行看到对应的结构信息
对应的我们可以在系统调用返回的时候进行Hook,打到我们想要的功能
劫持系统调用参考文档:Hijack - Qiling Framework Documentation
HOOK操作参考文档:Hook - Qiling Framework Documentation
寄存器相关的操作参考文档:Register - Qiling Framework Documentation
在上述的代码中我们进行简单的调试可以看到对应uname
获取信息后存储在rdi
寄存器中,那么我们的目标便是修改uname
执行完毕后的rdi
所指向的地址空间
那么对应的Hook代码如下(运行于Linux下):
1 2 3 4 5 6 7 8 9 10 11 12 13 from qiling import *from qiling.const import *from qiling.os.const import *def hook_rdi (ql: Qiling, *args ): rdi = ql.arch.regs.rdi ql.mem.write(rdi, b'QilingOS\x00' ) ql.mem.write(rdi + 65 * 3 , b'ChallengeStart\x00' ) def Challenge2 (ql: Qiling ): ql.os.set_syscall('uname' , hook_rdi, QL_INTERCEPT.EXIT)
之后我们可以尝试运行对应脚本,观察输出(后续运行均于 Window WSL 下)
Challenge3:劫持文件系统&系统调用
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 unsigned __int64 __fastcall challenge3 (_BYTE *a1) { int v2; int i; int fd; char v5; char buf[32 ]; char v7[40 ]; unsigned __int64 v8; v8 = __readfsqword(0x28 u); fd = open ("/dev/urandom" , 0 ); read (fd, buf, 0x20 uLL); read (fd, &v5, 1uLL ); close (fd); getrandom (v7, 32LL , 1LL ); v2 = 0 ; for ( i = 0 ; i <= 31 ; ++i ) { if ( buf[i] == v7[i] && buf[i] != v5 ) ++v2; } if ( v2 == 32 ) *a1 = 1 ; return __readfsqword(0x28 u) ^ v8; }
可以看到对应程序从/dev/urandom
获取了随机数,之后再通过getrandom()
来获取随机数,之后要求满足上述对应的关系,同时有一个随机数和其他的都不一样
Qiling提供了QlFsMappedObject去自定义文件系统,例:read,write等
同样可以参考系统劫持的文档:Hijack - Qiling Framework Documentation
那么对于这道题的核心便是我们自己手动实现一个read,并且Hook函数getrandom
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from qiling import *from qiling.const import *from qiling.os.const import *from qiling.os.mapper import QlFsMappedObjectclass my_uradom (QlFsMappedObject ): def read (self, size ): if size == 1 : return b'\x50' else : return b'\x00' * size def close (self ): return 0 def hook_getrandom (ql:Qiling, buf, size, flags ): ql.mem.write(buf, b'\x00' * size) ql.os.set_syscall_return(0 )def Challenge3 (ql:Qiling ): ql.os.set_syscall('getrandom' , hook_getrandom, QL_INTERCEPT.CALL) ql.add_fs_mapper('/dev/urandom' , my_uradom())
Challenge4:Hook地址
1 2 3 4 __int64 challenge4 () { return 0LL ; }
在IDA的反编译中我们无法看到对应的伪代码,但是当我们切换到汇编界面的时候可以看到对应的代码:
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 .text:0000560C8A000E1D public challenge4 .text:0000560C8A000E1D challenge4 proc near ; CODE XREF: start+18F↓p .text:0000560C8A000E1D .text:0000560C8A000E1D var_18= qword ptr -18h .text:0000560C8A000E1D var_8= dword ptr -8 .text:0000560C8A000E1D var_4= dword ptr -4 .text:0000560C8A000E1D .text:0000560C8A000E1D ; __unwind { // 560C8A000000 .text:0000560C8A000E1D push rbp .text:0000560C8A000E1E mov rbp, rsp .text:0000560C8A000E21 mov [rbp+var_18], rdi .text:0000560C8A000E25 mov [rbp+var_8], 0 .text:0000560C8A000E2C mov [rbp+var_4], 0 .text:0000560C8A000E33 jmp short loc_560C8A000E40 .text:0000560C8A000E33 .text:0000560C8A000E35 ; --------------------------------------------------------------------------- .text:0000560C8A000E35 .text:0000560C8A000E35 loc_560C8A000E35: ; CODE XREF: challenge4+29↓j .text:0000560C8A000E35 mov rax, [rbp+var_18] .text:0000560C8A000E39 mov byte ptr [rax], 1 .text:0000560C8A000E3C add [rbp+var_4], 1 .text:0000560C8A000E3C .text:0000560C8A000E40 .text:0000560C8A000E40 loc_560C8A000E40: ; CODE XREF: challenge4+16↑j .text:0000560C8A000E40 mov eax, [rbp+var_8] .text:0000560C8A000E43 cmp [rbp+var_4], eax .text:0000560C8A000E46 jl short loc_560C8A000E35 .text:0000560C8A000E46 .text:0000560C8A000E48 nop .text:0000560C8A000E49 pop rbp .text:0000560C8A000E4A retn .text:0000560C8A000E4A ; } // starts at 560C8A000E1D .text:0000560C8A000E4A .text:0000560C8A000E4A challenge4 endp
可以明显的看到程序会给eax
赋值为 0,之后在jl
处不执行对应的跳转,对应的我们在加载偏移为0xE43
处将eax
的值进行Hook,修改为 1,之后让其进入循环,将我们的Check
赋值为 1
对应的脚本如下:
1 2 3 4 5 6 7 def hook_eax (ql:Qiling ): ql.arch.regs.eax = 1 def Challenge4 (ql:Qiling ): libc_base = ql.mem.get_lib_base(ql.path) hook_addr = libc_base + 0xE43 ql.hook_address(hook_eax, hook_addr)
在使用ql.path
时候有个天坑,当使用此方法时需要注意你初始化Qiling的文件路径,下面为正确示范:
1 2 3 path = ["qilinglab-x86_64" ] rootfs = "./qiling/examples/rootfs/x8664_linux" ql = Qiling(path, rootfs)
下面为错误示范:
1 2 3 path = ["./qilinglab-x86_64" ] rootfs = "./qiling/examples/rootfs/x8664_linux" ql = Qiling(path, rootfs)
运行脚本后结果如下:
Challenge5:Hook外部函数
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 unsigned __int64 __fastcall challenge5 (_BYTE *a1) { unsigned int v1; int i; int j; int v5[14 ]; unsigned __int64 v6; v6 = __readfsqword(0x28 u); v1 = time (0LL ); srand (v1); for ( i = 0 ; i <= 4 ; ++i ) { v5[i] = 0 ; v5[i + 8 ] = rand (); } for ( j = 0 ; j <= 4 ; ++j ) { if ( v5[j] != v5[j + 8 ] ) { *a1 = 0 ; return __readfsqword(0x28 u) ^ v6; } } *a1 = 1 ; return __readfsqword(0x28 u) ^ v6; }
可以明显的看出来需要我们将rand()
进行Hook处理,将对应的返回值修改为 0,对此我们可以使用下面的代码来进行Hook处理
1 ql.set_api(Hook函数名, 回调函数, QL_INTERCEPT.CALL)
对此我们的脚本如下:
1 2 3 4 5 def hook_rand (ql: Qiling ): ql.reg.rax = 0 def challenge5 (ql: Qiling ): ql.set_api('rand' , hook_rand)
运行后因为Challenge6为死循环,会卡住对应的输出。所以没有相关的回显
Challenge6:突破死循环
1 2 3 4 5 void challenge6 () { while ( 1 ) ; }
我们切换至汇编可以看到其逻辑:
我们需要做的便是在其循环处修改al
的值,让其跳出循环,解题脚本与Challenge4 相似:
1 2 3 4 5 6 def hook_while_true (ql: Qiling ): ql.reg.rax = 0 def challenge6 (ql: Qiling ): libc_base = ql.mem.get_lib_base(ql.path) ql.hook_address(hook_while_true, libc_base + 0xF16 )
此题也没有回显,因为在Challenge7中存在有一个Sleep函数,会拖很久的时间
Challenge7:解决睡觉难题
1 2 3 4 5 unsigned int __fastcall challenge7 (_BYTE *a1) { *a1 = 1 ; return sleep (0xFFFFFFFF ); }
可以看到其有一个超长的 Sleep 函数,对于这个我们有几种修改方式:
修改sleep
的参数值,减少睡眠时间
通过 Hook 自己实现 API sleep
修改系统调用nanosleep()
直接返回,因为sleep()
底层调用的nanosleep()
# man 3 sleep
NOTES On Linux, sleep() is implemented via nanosleep(2). See the nanosleep(2)
man page for a discussion of the clock used.
对于方法一,我们可以看到是传入了edi
作为对应sleep
的参数,那么我们修改参数其实就是要修改edi
的值,脚本如下:
1 2 3 4 5 6 7 8 9 10 11 def hook_edi (ql:Qiling ): ql.arch.regs.rdi = 0 def Challenge7 (ql: Qiling ): libc_base = ql.mem.get_lib_base(ql.path) hook_addr = libc_base + 0xF3C ql.hook_address(hook_edi, hook_addr) ql.set_api('sleep' , hook_edi, QL_INTERCEPT.ENTER)
对于方法二,我们将对应的sleep
函数进行掉包成我们自己的:
1 2 3 4 5 def hook_sleep (ql: Qiling ): return def Challenge7 (ql: Qiling ): ql.set_api('sleep' , hook_sleep)
对于方法三 Hook 系统调用即可:
1 2 3 4 5 def hook_nanosleep (ql: Qiling, *args, **kwargs ): return def Challenge7 (ql: Qiling ): ql.os.set_syscall('nanosleep' , hook_nanosleep)
Challenge 8: 解析结构体,往正确地址写入值
1 2 3 4 5 6 7 8 9 10 11 void __fastcall challenge8 (__int64 a1) { _DWORD *v1; v1 = malloc (0x18 uLL); *v1 = malloc (0x1E uLL); v1[2 ] = 1337 ; v1[3 ] = 1039980266 ; strcpy (*v1, "Random data" ); *(v1 + 2 ) = a1; }
大概可以看出来是一个结构体数据,我们在IDA中创建一个结构体,将v1
的类型设置为我们所创建的结构体数据
之后修改v1
的类型,可以看到整个代码好看多了:
1 2 3 4 5 6 7 8 9 10 void __fastcall challenge8 (__int64 a1) { struc_1 *v1; v1 = malloc (24uLL ); v1->some_string = malloc (0x1E uLL); v1->magic = 0x3DFCD6EA00000539 LL; strcpy (v1->some_string, "Random data" ); v1->check_addr = a1; }
对应需要用到结构体的解包相关知识以及字节顺序、大小、对齐方式三种
结构体解包文档参考:Pack and Unpack - Qiling Framework Documentation
字节顺序、大小、对齐方式参考文档:
struct — Interpret bytes as packed binary data — Python 3.12.1 documentation
struct — Interpret bytes as packed binary data — Python 3.12.1 documentation
struct — Interpret bytes as packed binary data — Python 3.12.1 documentation
我们简单的调试一下可以看到对应的rax
可以看到其保存了结构体的首地址信息,那么我们需要将rax+0x10
处的地址里的数据进行写入为 1
同时我们可以开启调试模式来进行观察所找到的数据是否正确(即开启ql.verbose = 1
),对应脚本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 def hook_mem (ql:Qiling ): rax = ql.arch.regs.rax ql.log.info(f"\u001b[31m[+] rax: {hex (rax)} \u001b[0m" ) data = ql.mem.read(rax, 24 ) ql.log.info(f"\u001b[31m[+] data: {data.hex ()} \u001b[0m" ) str_addr, magic_num, check_addr = struct.unpack("QQQ" , data) ql.log.info(f"\u001b[31m[+] str_addr: {hex (str_addr)} \u001b[0m" ) ql.mem.read(str_addr, 0x10 ) ql.log.info(f"\u001b[31m[+] str: {ql.mem.string(str_addr)} \u001b[0m" ) ql.log.info(f"\u001b[31m[+] magic_num: {hex (magic_num)} \u001b[0m" ) ql.log.info(f"\u001b[31m[+] check_addr: {hex (check_addr)} \u001b[0m" ) ql.mem.write(check_addr, b'\x01' ) check = ql.mem.read(check_addr, 8 ) ql.log.info(f"\u001b[31m[+] check: {check.hex ()} \u001b[0m" )def Challenge8 (ql: Qiling ): libc_base = ql.mem.get_lib_base(ql.path) hook_addr = libc_base + 0x00FB5 ql.log.info(f"\u001b[31m[+] hook_addr: {hook_addr} \u001b[0m" ) ql.hook_address(hook_mem, hook_addr)
输出时以\u001b[31m
开头,\u001b[0m
结尾会将中间内容转换为红色,更多颜色可以参考 ANSI 码,此处不多描述
之后我们可以看到以下界面:
总感觉 Qiling 自带的 Debug 有点没用?
总的来说开启了 Debug 后还是得自己插桩写入 log,同时 log 也没点颜色…得自己设置一个颜色才好看的多,还方便找
对于上面的内容其实还一种解题方式,可以在内存中搜索对应数据信息,因为题目给的magic
数字以及对应标志字符串Random data
可以很快的帮助我们在内存里面找到对应的信息,脚本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def search_mem_to_find_struct (ql: Qiling ): MAGIC = ql.pack64(0x3DFCD6EA00000539 ) candidate_addrs = ql.mem.search(MAGIC) for addr in candidate_addrs: stru_addr = addr - 8 stru = ql.mem.read(stru_addr, 24 ) string_addr, _, check_addr = struct.unpack('QQQ' , stru) if ql.mem.string(string_addr) == 'Random data' : ql.mem.write(check_addr, b'\x01' ) break def Challenge8 (ql: Qiling ): base = ql.mem.get_lib_base(ql.path) ql.hook_address(search_mem_to_find_struct, base + 0xFB5 )
Challenge 9: 修改字符串函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void __fastcall challenge9 (bool *a1) { char *i; char dest[32 ]; char src[40 ]; unsigned __int64 v4; v4 = __readfsqword(0x28 u); strcpy (src, "aBcdeFghiJKlMnopqRstuVWxYz" ); src[27 ] = unk_562F9A0016C0; strcpy (dest, src); for ( i = dest; *i; ++i ) *i = tolower (*i); *a1 = strcmp (src, dest) == 0 ; }
可以直观的看到对应的我们需要将一个转小写的函数tolower()
进行 Hook 将其内容不做处理通过下面的strcmp()
函数,当然我们也可以 Hook strcmp()
这个函数,让其直接返回 0
1 2 3 4 5 def hook_tolower (ql: Qiling, *args ): return def Challenge9 (ql: Qiling ): ql.os.set_api('tolower' , hook_tolower, QL_INTERCEPT.CALL)
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 28 unsigned __int64 __fastcall challenge10 (_BYTE *a1) { int i; int fd; ssize_t v4; char buf[72 ]; unsigned __int64 v6; v6 = __readfsqword(0x28 u); fd = open ("/proc/self/cmdline" , 0 ); if ( fd != -1 ) { v4 = read (fd, buf, 0x3F uLL); if ( v4 > 0 ) { close (fd); for ( i = 0 ; v4 > i; ++i ) { if ( !buf[i] ) buf[i] = 32 ; } buf[v4] = 0 ; if ( !strcmp (buf, "qilinglab" ) ) *a1 = 1 ; } } return __readfsqword(0x28 u) ^ v6; }
这个感觉上又回到了Challenge3 ,同样是要求我们 Hook 文件系统,那么还是一样的我们可以自己实现一个cmdline
的读取与关闭
1 2 3 4 5 6 7 8 9 10 11 12 class My_cmdline (QlFsMappedObject ): def read (self, size ): if size == 63 : return b'qilinglab' else : return b'\x00' * size def close (self ): return 0 def Challenge10 (ql: Qiling ): ql.add_fs_mapper('/proc/self/cmdline' , My_cmdline)
还有其他的省事的办法,比如可以将我们自己的文件通过ql.add_fs_mapper(源文本, Hook的自定义文本)
来进行替换
Challenge 11: 指令Hook
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void __fastcall challenge11 (_BYTE *a1) { int v6; int v7; char s[4 ]; char v9[4 ]; char v10[4 ]; unsigned __int64 v11; v11 = __readfsqword(0x28 u); _RAX = 0x40000000 LL; __asm { cpuid } v6 = _RCX; v7 = _RDX; if ( __PAIR64__(_RBX, _RCX) == 0x696C6951614C676E LL && _RDX == 538976354 ) *a1 = 1 ; sprintf (s, "%c%c%c%c" , _RBX, (_RBX >> 8 ), (_RBX >> 16 ), (_RBX >> 24 )); sprintf (v9, "%c%c%c%c" , v6, (v6 >> 8 ), (v6 >> 16 ), (v6 >> 24 )); sprintf (v10, "%c%c%c%c" , v7, (v7 >> 8 ), (v7 >> 16 ), (v7 >> 24 )); }
通过观察汇编我们可以看到对应的相当是实现对寄存器的修改,那么相当于是我们需要对cpuid
这个指令进行 Hook 处理
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 .text:0000562F9A001175 C7 45 CC 00 00 00 00 mov [rbp+var_34], 0 .text:0000562F9A00117C C7 45 D0 00 00 00 00 mov [rbp+var_30], 0 .text:0000562F9A001183 C7 45 D4 00 00 00 00 mov [rbp+var_2C], 0 .text:0000562F9A00118A B8 00 00 00 40 mov eax, 40000000h .text:0000562F9A00118F 0F A2 cpuid .text:0000562F9A001191 89 D0 mov eax, edx .text:0000562F9A001193 89 DE mov esi, ebx .text:0000562F9A001195 89 75 D0 mov [rbp+var_30], esi .text:0000562F9A001198 89 4D CC mov [rbp+var_34], ecx .text:0000562F9A00119B 89 45 D4 mov [rbp+var_2C], eax .text:0000562F9A00119E 81 7D D0 51 69 6C 69 cmp [rbp+var_30], 696C6951h .text:0000562F9A0011A5 75 19 jnz short loc_562F9A0011C0 .text:0000562F9A0011A5 .text:0000562F9A0011A7 81 7D CC 6E 67 4C 61 cmp [rbp+var_34], 614C676Eh .text:0000562F9A0011AE 75 10 jnz short loc_562F9A0011C0 .text:0000562F9A0011AE .text:0000562F9A0011B0 81 7D D4 62 20 20 20 cmp [rbp+var_2C], 20202062h .text:0000562F9A0011B7 75 07 jnz short loc_562F9A0011C0 .text:0000562F9A0011B7 .text:0000562F9A0011B9 48 8B 45 B8 mov rax, [rbp+var_48] .text:0000562F9A0011BD C6 00 01 mov byte ptr [rax], 1 .text:0000562F9A0011BD .text:0000562F9A0011C0 loc_562F9A0011C0: ; CODE XREF: challenge11+4C↑j .text:0000562F9A0011C0 8B 45 D0 mov eax, [rbp+var_30] .text:0000562F9A0011C3 C1 F8 18 sar eax, 18h .text:0000562F9A0011C6 89 C7 mov edi, eax .text:0000562F9A0011C8 8B 45 D0 mov eax, [rbp+var_30] .text:0000562F9A0011CB C1 F8 10 sar eax, 10h .text:0000562F9A0011CE 89 C6 mov esi, eax .text:0000562F9A0011D0 8B 45 D0 mov eax, [rbp+var_30] .text:0000562F9A0011D3 C1 F8 08 sar eax, 8 .text:0000562F9A0011D6 89 C1 mov ecx, eax .text:0000562F9A0011D8 8B 55 D0 mov edx, [rbp+var_30] .text:0000562F9A0011DB 48 8D 45 DB lea rax, [rbp+s] .text:0000562F9A0011DF 41 89 F9 mov r9d, edi .text:0000562F9A0011E2 41 89 F0 mov r8d, esi .text:0000562F9A0011E5 48 8D 35 F2 04 00 00 lea rsi, format ; "%c%c%c%c" .text:0000562F9A0011EC 48 89 C7 mov rdi, rax ; s .text:0000562F9A0011EF B8 00 00 00 00 mov eax, 0 .text:0000562F9A0011F4 E8 37 F8 FF FF call _sprintf
按照之前修改寄存器的方式进行绕过,则我们可以写出如下脚本:
1 2 3 4 5 6 7 8 9 def hook_cpuid (ql: Qiling, *args ): ql.arch.regs.ebx = 0x696C6951 ql.arch.regs.ecx = 0x614C676E ql.arch.regs.edx = 0x20202062 def Challenge11 (ql: Qiling ): libc_base = ql.mem.get_lib_base(ql.path) hook_addr = libc_base + 0x1191 ql.hook_address(hook_cpuid, hook_addr)
但是实际上我们还可以对cpuid
处指令进行 Hook,对应实现代码如下:
1 2 3 4 5 6 7 8 9 def hook_cpuid (ql: Qiling, address, size ): if ql.mem.read(address, size) == b'\x0F\xA2' : ql.arch.regs.ebx = 0x696C6951 ql.arch.regs.ecx = 0x614C676E ql.arch.regs.edx = 0x20202062 ql.arch.regs.rip += 2 def Challenge11 (ql: Qiling ): ql.hook_code(hook_cpuid)
运行后我们可以看到以下内容:
至此我们的全部挑战已经完成,对应的完整脚本如下:
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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 from qiling import *from qiling.const import *from qiling.os.const import *from qiling.os.mapper import QlFsMappedObjectimport structdef Challenge1 (ql: Qiling ): ql.mem.map (0x1000 ,0x1000 ,info="[Challenge 1]" ) ql.mem.write(0x1337 , ql.pack16(1337 ))def hook_rdi (ql: Qiling, *args ): rdi = ql.arch.regs.rdi ql.mem.write(rdi, b'QilingOS\x00' ) ql.mem.write(rdi + 65 * 3 , b'ChallengeStart\x00' ) def Challenge2 (ql: Qiling ): ql.os.set_syscall('uname' , hook_rdi, QL_INTERCEPT.EXIT)class My_uradom (QlFsMappedObject ): def read (self, size ): if size == 1 : return b'\x50' else : return b'\x00' * size def close (self ): return 0 def hook_getrandom (ql:Qiling, buf, size, flags ): ql.mem.write(buf, b'\x00' * size) ql.os.set_syscall_return(0 )def Challenge3 (ql:Qiling ): ql.os.set_syscall('getrandom' , hook_getrandom, QL_INTERCEPT.CALL) ql.add_fs_mapper('/dev/urandom' , My_uradom())def hook_eax (ql:Qiling ): ql.arch.regs.eax = 1 def Challenge4 (ql:Qiling ): libc_base = ql.mem.get_lib_base(ql.path) ql.log.info(f"ql_path: {ql.path} " ) ql.log.info(f"libc_base: {libc_base} " ) hook_addr = libc_base + 0xE43 ql.hook_address(hook_eax, hook_addr) def hook_rand (ql:Qiling, *args ): ql.arch.regs.rax = 0 def Challenge5 (ql:Qiling ): ql.os.set_api('rand' , hook_rand, QL_INTERCEPT.CALL)def hook_while_true (ql: Qiling ): ql.arch.regs.rax = 0 def challenge6 (ql: Qiling ): libc_base = ql.mem.get_lib_base(ql.path) ql.hook_address(hook_while_true, libc_base + 0xF16 )def hook_sleep (ql: Qiling, *args ): ql.os.set_syscall_return(0 )def hook_edi (ql:Qiling ): ql.arch.regs.rdi = 0 def Challenge7 (ql: Qiling ): libc_base = ql.mem.get_lib_base(ql.path) hook_addr = libc_base + 0xF3C ql.hook_address(hook_edi, hook_addr) def hook_mem (ql:Qiling ): rax = ql.arch.regs.rax ql.log.info(f"\u001b[31m[+] rax: {hex (rax)} \u001b[0m" ) data = ql.mem.read(rax, 24 ) ql.log.info(f"\u001b[31m[+] data: {data.hex ()} \u001b[0m" ) str_addr, magic_num, check_addr = struct.unpack("QQQ" , data) ql.log.info(f"\u001b[31m[+] str_addr: {hex (str_addr)} \u001b[0m" ) ql.mem.read(str_addr, 0x10 ) ql.log.info(f"\u001b[31m[+] str: {ql.mem.string(str_addr)} \u001b[0m" ) ql.log.info(f"\u001b[31m[+] magic_num: {hex (magic_num)} \u001b[0m" ) ql.log.info(f"\u001b[31m[+] check_addr: {hex (check_addr)} \u001b[0m" ) ql.mem.write(check_addr, b'\x01' ) check = ql.mem.read(check_addr, 8 ) ql.log.info(f"\u001b[31m[+] check: {check.hex ()} \u001b[0m" )def Challenge8 (ql: Qiling ): libc_base = ql.mem.get_lib_base(ql.path) hook_addr = libc_base + 0x00FB5 ql.log.info(f"\u001b[31m[+] hook_addr: {hook_addr} \u001b[0m" ) ql.hook_address(hook_mem, hook_addr) def hook_tolower (ql: Qiling, *args ): return def Challenge9 (ql: Qiling ): ql.os.set_api('tolower' , hook_tolower, QL_INTERCEPT.CALL)class My_cmdline (QlFsMappedObject ): def read (self, size ): if size == 63 : return b'qilinglab' else : return b'\x00' * size def close (self ): return 0 def Challenge10 (ql: Qiling ): ql.add_fs_mapper('/proc/self/cmdline' , My_cmdline)def hook_cpuid (ql: Qiling, *args ): ql.arch.regs.ebx = 0x696C6951 ql.arch.regs.ecx = 0x614C676E ql.arch.regs.edx = 0x20202062 def Challenge11 (ql: Qiling ): libc_base = ql.mem.get_lib_base(ql.path) hook_addr = libc_base + 0x1191 ql.hook_address(hook_cpuid, hook_addr)if __name__ == "__main__" : path = ["qilinglab-x86_64" ] rootfs = "./qiling/examples/rootfs/x8664_linux" ql = Qiling(path, rootfs) Challenge1(ql) Challenge2(ql) Challenge3(ql) Challenge4(ql) Challenge5(ql) challenge6(ql) Challenge7(ql) Challenge8(ql) Challenge9(ql) Challenge10(ql) Challenge11(ql) ql.verbose = 0 ql.run()
写的比较丑陋,大家看看就行
总结
总的来说Qiling Lab
相当于是带着学习入门使用的一个过程,带着我们学习了内存的修改、地址的Hook、文件系统的劫持、系统函数,外部函数的Hook修改。基本上把Qiling
框架所常用的都用到了。
在此学习过程中更多的时候还是需要参考官方的手册文档来进行,通过多练来达到熟悉的作用。
菜 就多练
输不起 就别玩
以前是以前
现在是现在
你要是一直拿以前当做现在
哥们儿 你怎么不拿你刚出生的时候对比啊
——广君