0x00 前言 第一次接触模拟执行这个东西,从QilingFramework开始上手试试看,后面还要学Qemu、Unicorn、Unidbg等
0x01 解 使用
1 git clone https://github.com/qilingframework/qiling.git --recursiv
克隆整个仓库
由于x86_64
和aarch64
题目内容,而我使用的是x86_64
平台,因此为了体现模拟执行的强大,以下都使用aarch64
的文件来解
之后使用模板如下
1 2 3 4 5 6 7 8 9 10 11 12 13 from qiling import *from qiling.const import QL_VERBOSEdef challenge1 (ql: Qiling ): pass if __name__ == '__main__' : path = ['qilinglab/qilinglab-aarch64' ] rootfs = "./examples/rootfs/arm64_linux" ql = Qiling(path, rootfs,verbose=QL_VERBOSE.OFF) challenge1(ql) ql.run()
Challenge 1: Store 1337 at pointer 0x1337. 1 2 3 4 5 6 7 8 9 10 11 12 _BYTE *__fastcall challenge1 (_BYTE *a1) { _BYTE *result; result = (_BYTE *)*(unsigned int *)((char *)&loc_1334 + 3 ); if ( *(_DWORD *)((char *)&loc_1334 + 3 ) == 1337 ) { result = a1; *a1 = 1 ; } return result; }
将值1337
放入地址0x1337
中,然而实际上并不能保证程序加载基地址,这里需要用
1 ql.mem.map (0x1000 , 0x1000 , info='[challenge]' )
映射一块内存 ,需要注意的是,Qiling底层就是用的Unicorn Engine,内存映射时,要4k对齐 。
之后向里面写内容 就可以了,注意写的时候内容要进行打包
1 2 3 def challenge1 (ql: Qiling ): ql.mem.map (0x1000 ,0x1000 ) ql.mem.write(0x1337 ,ql.pack16(1337 ))
这就能过第一个了
Challenge 2: Make the ‘uname’ syscall return the correct values. 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 __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; }
这边是需要让uname
这个syscall
返回的内容与设置的相同,设置的值为sysname==QilingOS
且version==ChallengeStart
uname
这个函数传入一个utsname
的结构体的地址,填充后返回
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <sys/utsname.h> #define _UTSNAME_SYSNAME_LENGTH 65 struct utsname { char sysname[_UTSNAME_SYSNAME_LENGTH]; char nodename[_UTSNAME_NODENAME_LENGTH]; char release[_UTSNAME_RELEASE_LENGTH]; char version[_UTSNAME_VERSION_LENGTH]; char machine[_UTSNAME_MACHINE_LENGTH]; #if _UTSNAME_DOMAIN_LENGTH - 0 # ifdef __USE_GNU char domainname[_UTSNAME_DOMAIN_LENGTH]; # else char __domainname[_UTSNAME_DOMAIN_LENGTH]; # endif #endif };
Qiling提供了在系统调用返回时进行hook
的功能 。
首先需要知道返回值被存储在了哪个寄存器(因为这是ARM64
,所以跟X64
不一样)
根据汇编
1 2 3 4 5 ... name= -0x1B0 ... ADD X0, SP, #0x1F0+name ; add r2, r1, #2 (r2 = r1 + 2) BL .uname
可以知道返回值由SP
和0x1F0+name
的值相加得到,被存在了X0
寄存器,所以劫持地址为
1 x0 = ql.arch.regs.sp + 0x1F0 - 0x1B0
之后就是写内容,注意要按照结构体的顺序来写,sysname
和version
中间的nodename
和release
要跳过不去动
1 2 3 4 5 6 7 def hook_uname_func (ql: Qiling,*args ): x0 = ql.arch.regs.sp + 0x1F0 - 0x1B0 ql.mem.write(x0,b'QilingOS\x00' ) ql.mem.write(x0+65 *3 ,b'ChallengeStart\x00' ) def challenge2 (ql: Qiling ): ql.os.set_syscall("uname" , hook_uname_func, QL_INTERCEPT.EXIT)
Challenge 3: Make ‘/dev/urandom’ and ‘getrandom’ “collide”. 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, 0x20 uLL); 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; }
挑战三需要让open
函数中获取的值与getrandom
函数中的值一致,且/dev/urandom
中的最后一个值需要与getrandom
中的任意一个值不同
要同时实现两个需求,需要更改getrandom
函数的返回值和文件的内容
首先使用ql.os.set_syscall
来劫持getrandom
函数,与上一个挑战不同,这次用到的ql.os.set_syscall
最后传入的参数是QL_INTERCEPT.CALL
,即在函数被调用时进行劫持,运行指定的函数而不运行原先的函数,这时,由于参数会相应地进行传入,因此要确保接受参数一致(有默认值除外)
1 2 3 def hook_getrandom_func (ql: Qiling,buf,buflen ): ql.mem.write(buf,b'\x00' *buflen) ql.os.set_syscall_return(0 )
之后要保证/dev/urandom
内容一致,使用到自定义文件系统 的功能,先定义一个FakeUrandom
类,然后将内容填入read
函数中,完成对read
函数的重定向(close
函数默认就行,fstat
函数可以不要,注意读取单字节时要与读取多字节不同)
1 2 3 4 5 6 7 8 class FakeUrandom (QlFsMappedObject ): def read (self, size: int ) -> bytes : if size == 1 : return b'\xE9' return b"\x00" *size def close (self ) -> int : return 0
最终整合如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class FakeUrandom (QlFsMappedObject ): def read (self, size: int ) -> bytes : if size == 1 : return b'\xE9' return b"\x00" *size def close (self ) -> int : return 0 def hook_getrandom_func (ql: Qiling,buf,buflen ): ql.mem.write(buf,b'\x00' *buflen) ql.os.set_syscall_return(0 ) def challenge3 (ql: Qiling ): ql.os.set_syscall("getrandom" , hook_getrandom_func) ql.add_fs_mapper("/dev/urandom" , FakeUrandom())
Challenge 4: Enter inside the “forbidden” loop. 这个函数IDA无法解析,只剩下一个return 0
手动逆一下
1 2 3 4 5 6 7 8 9 void challenge4 (_BYTE *v15) { int i = 0 ; int j = 0 ; while (i<j){ *v15 = 1 ; i++; } return 0 ; }
搞了一个进不去的循环,然后在里面进行判断变量的赋值,因此要进这个函数需要通过hook
来改变寄存器内容进入这个函数,Qiling是有提供这个功能 的
这边需要hook
跳转前的位置,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def hook_FE4 (ql: Qiling ): ''' .text:0000000000000FE0 3F 00 00 6B CMP W1, W0 .text:0000000000000FE4 EB FE FF 54 B.LT loc_FC0 ''' ql.arch.regs.W0 = 1 def challenge4 (ql: Qiling ): base_addr = ql.mem.get_lib_base(os.path.split(ql.path)[-1 ]) enter_loop = base_addr + 0xfe0 ql.hook_address(hook_FE4,enter_loop)
Challenge 5: Guess every call to rand(). 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; }
这边直接hook
函数rand()
,让其一直返回0
即可
1 2 3 4 5 6 7 8 def hook_rand_func (ql: Qiling ): ''' 返回值存在w0中 ''' ql.arch.regs.W0=0 def challenge5 (ql:Qiling ): ql.os.set_api('rand' ,hook_rand_func, QL_INTERCEPT.CALL)
由于challenge6卡死循环,所以没解决之前challenge5的内容不显示
Challenge 6: Avoid the infinite loop. 进去就是一个死循环,只能靠看汇编解决
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 .text:00000000000010F0 ; =============== S U B R O U T I N E ======================================= .text:00000000000010F0 .text:00000000000010F0 .text:00000000000010F0 ; void challenge6() .text:00000000000010F0 EXPORT challenge6 .text:00000000000010F0 challenge6 ; CODE XREF: start+1C0↓p .text:00000000000010F0 .text:00000000000010F0 var_18= -0x18 .text:00000000000010F0 var_5= -5 .text:00000000000010F0 var_4= -4 .text:00000000000010F0 .text:00000000000010F0 ; __unwind { .text:00000000000010F0 FF 83 00 D1 SUB SP, SP, #0x20 .text:00000000000010F4 E0 07 00 F9 STR X0, [SP,#0x20+var_18] .text:00000000000010F8 FF 1F 00 B9 STR WZR, [SP,#0x20+var_4] .text:00000000000010FC 20 00 80 52 MOV W0, #1 .text:0000000000001100 E0 6F 00 39 STRB W0, [SP,#0x20+var_5] .text:0000000000001104 03 00 00 14 B loc_1110 .text:0000000000001104 .text:0000000000001108 ; --------------------------------------------------------------------------- .text:0000000000001108 .text:0000000000001108 loc_1108 ; CODE XREF: challenge6+2C↓j .text:0000000000001108 20 00 80 52 MOV W0, #1 .text:000000000000110C E0 1F 00 B9 STR W0, [SP,#0x20+var_4] .text:000000000000110C .text:0000000000001110 .text:0000000000001110 loc_1110 ; CODE XREF: challenge6+14↑j .text:0000000000001110 E0 6F 40 39 LDRB W0, [SP,#0x20+var_5] .text:0000000000001114 00 1C 00 12 AND W0, W0, #0xFF .text:0000000000001118 1F 00 00 71 CMP W0, #0 .text:000000000000111C 61 FF FF 54 B.NE loc_1108 .text:000000000000111C .text:0000000000001120 E0 07 40 F9 LDR X0, [SP,#0x20+var_18] .text:0000000000001124 21 00 80 52 MOV W1, #1 .text:0000000000001128 01 00 00 39 STRB W1, [X0] .text:000000000000112C 1F 20 03 D5 NOP .text:0000000000001130 FF 83 00 91 ADD SP, SP, #0x20 ; ' ' .text:0000000000001134 C0 03 5F D6 RET .text:0000000000001134 ; } // starts at 10F0 .text:0000000000001134 .text:0000000000001134 ; End of function challenge6
根据汇编可以知道,首先在0x10FC
处将1
存进了栈中,然后在0x1110
处取了出来,AND
了之后拿去和0
做比较,比较不等则一直循环
由于hook
操作是在指令执行前确定的,所以可以在0x1114
处或0x1118
处将W0
改为0
,处理方法和challenge4类似
1 2 3 4 5 6 7 8 9 10 11 12 13 def hook_1118 (ql: Qiling ): ''' .text:0000000000001114 00 1C 00 12 AND W0, W0, #0xFF .text:0000000000001118 1F 00 00 71 CMP W0, #0 .text:000000000000111C 61 FF FF 54 B.NE loc_1108 ''' ql.arch.regs.w0 = 0 def challenge6 (ql: Qiling ): base_addr = ql.mem.get_lib_base(os.path.split(ql.path)[-1 ]) stop_loop = base_addr + 0x1118 ql.hook_address(hook_1118,stop_loop)
此时challenge5的结果可以正常打印,但challenge6的没打印
Challenge 7: Don’t waste time waiting for ‘sleep’. 1 2 3 4 5 __int64 __fastcall challenge7 (_BYTE *a1) { *a1 = 1 ; return sleep(0xFFFFFFFF ); }
方法很多,此处列三种
1.将sleep
过掉,直接返回
1 2 3 4 5 def hook_sleep_func (ql: Qiling ): return def challenge7 (ql: Qiling ): ql.os.set_api('sleep' ,hook_sleep_func)
2.在函数sleep
调用前把w0
改为0
就行
1 2 3 4 5 6 7 def hook_1154 (ql: Qiling ): ql.arch.regs.w0=0 def challenge7 (ql: Qiling ): base_addr = ql.mem.get_lib_base(os.path.split(ql.path)[-1 ]) hook_sleep = base_addr + 0x1154 ql.hook_address(hook_1154,hook_sleep)
3.将底层的nanosleep
过掉
1 2 3 4 5 def hook_nanosleep_func (ql: Qiling ): return def challenge7 (ql: Qiling ): ql.os.set_syscall('nanosleep' ,hook_nanosleep_func)
总之舞台很大,而且challenge7过了之后后面的内容都能打印了
Challenge 8: Unpack the struct and write at the target address. 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 (0x18 uLL); *(_QWORD *)v3 = malloc (0x1E uLL); v3[2 ] = 1337 ; v3[3 ] = 1039980266 ; strcpy (*(char **)v3, "Random data" ); result = v3; *((_QWORD *)v3 + 2 ) = a1; return result; }
根据伪代码与分析,可以知道结构体如下:
1 2 3 4 5 6 typedef struct { char *str; uint32_t v1; uint32_t v2; int64_t check; }
这边有两种方法可以完成挑战
1.在函数汇编的ret
前可以找到结构体存储的位置,从而对结构体内容进行修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 def hook_11DC (ql: Qiling ): ''' .text:00000000000011D0 E0 17 40 F9 LDR X0, [SP,#0x30+var_8] .text:00000000000011D4 E1 0F 40 F9 LDR X1, [SP,#0x30+var_18] .text:00000000000011D8 01 08 00 F9 STR X1, [X0,#0x10] .text:00000000000011DC 1F 20 03 D5 NOP .text:00000000000011E0 FD 7B C3 A8 LDP X29, X30, [SP+0x30+var_30],#0x30 .text:00000000000011E4 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 ) string_addr, magic, check_addr = struct.unpack('QQQ' , heap_struct) ql.mem.write(check_addr, b"\x01" ) def challenge8 (ql: Qiling ): base_addr = ql.mem.get_lib_base(os.path.split(ql.path)[-1 ]) hook_struct = base_addr + 0x11DC ql.hook_address(hook_11DC,hook_struct)
2.使用Qiling提供的强大的search功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 def hook_11DC (ql: Qiling ): MAGIC = 0x3DFCD6EA00000539 magic_addrs = ql.mem.search(ql.pack64(MAGIC)) for magic_addr in magic_addrs: candidate_heap_struct_addr = magic_addr - 8 candidate_heap_struct = ql.mem.read(candidate_heap_struct_addr, 24 ) string_addr, _ , check_addr = struct.unpack('QQQ' , candidate_heap_struct) if ql.mem.string(string_addr) == "Random data" : ql.mem.write(check_addr, b"\x01" ) break def challenge8 (ql: Qiling ): base_addr = ql.mem.get_lib_base(os.path.split(ql.path)[-1 ]) hook_struct = base_addr + 0x11DC ql.hook_address(hook_11DC,hook_struct)
Challenge 9: Fix some string operation to make the iMpOsSiBlE come true. 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; }
直接hook
掉tolower
函数就行
Challenge 10: Fake the ‘cmdline’ line file to return the right content. 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, 0x3F uLL); 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; }
参考challenge3就可以了
1 2 3 4 5 6 7 8 9 class Fakecmdline (QlFsMappedObject ): def read (self, size: int ) -> bytes : return b"qilinglab" def close (self ) -> int : return 0 def challenge10 (ql: Qiling ): ql.add_fs_mapper("/proc/self/cmdline" , Fakecmdline())
Challenge 11: Bypass CPUID/MIDR_EL1 checks. 这题X86/64
和aarch64
的实现方式不同,都做一遍
首先是ARM64 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; }
这里用到了一个ARM架构的特殊寄存器midr_el1
以及配套的汇编指令MRS
,对应的函数为AArch64.SysRegRead
而这个midr_el1
的使用例可以看这个
1 .text:00000000000013EC 00 00 38 D5 MRS X0, #0, c0, c0, #0
其作用是读取机器信息,这条完整的MRS
指令每一位的含义如下
31
30
29
28
27
26
25
24
23
22
21
20
19
18
17
16
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
1
1
0
1
0
1
0
1
0
0
1
1
o0
op1
op1
op1
CRn
CRn
CRn
CRn
CRm
CRm
CRm
CRm
op2
op2
op2
Rt
Rt
Rt
Rt
Rt
需要注意第十九位o0
实际上是两位 ,只是高位都为1 就省略掉了,即它的值为2或3
这条指令中的o0
、op1
、CRn
、CRm
、op2
的组合用于指示从哪个寄存器中取值,而Rt
表示取到哪个寄存器去
简单来说,指令MRS
就是从midr_el1
读取了信息然后存到了X0
里面,然后就对X0
的内容进行了判断
所以要过这关需要让X0
的值变为指定内容
这里用到Qiling提供的hook_code
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 def midr_el1_hook (ql: Qiling, address, size ): ''' .text:00000000000013EC 00 00 38 D5 MRS X0, #0, c0, c0, #0 ''' 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: Qiling ): ql.hook_code(midr_el1_hook)
但是hook_code函数每一条指令都会执行一次,这使得效率非常的低,因此考虑进行分段操作
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 def midr_el1_hook (ql: Qiling, address, size ): ''' .text:00000000000013EC 00 00 38 D5 MRS X0, #0, c0, c0, #0 ''' 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: Qiling ): mem_map = ql.mem.map_info for entry in mem_map: start, end, flags, label, _ = entry if os.path.split(ql.path)[-1 ] in label and flags == 5 : start_hook = start end_hook = end break ql.hook_code(midr_el1_hook, begin=start_hook, end=end_hook)
当然也可以直接hook掉MRS所在的地址
1 2 3 4 5 6 7 8 9 10 def midr_el1_hook (ql: Qiling ): ql.arch.regs.X0 = 0x1337 << 16 ql.arch.regs.arch_pc += 4 def challenge11 (ql: Qiling ): base_addr = ql.mem.get_lib_base(os.path.split(ql.path)[-1 ]) MRS_instruction = base_addr+0x13EC ql.hook_address(midr_el1_hook,MRS_instruction)
再看看X86/64 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 unsigned __int64 __fastcall challenge11 (_BYTE *a1) { int v7; int v8; char s[4 ]; char v10[4 ]; char v11[4 ]; unsigned __int64 v12; v12 = __readfsqword(0x28 u); _RAX = 0x40000000 LL; __asm { cpuid } v7 = _RCX; v8 = _RDX; if ( __PAIR64__(_RBX, _RCX) == 0x696C6951614C676E LL && (_DWORD)_RDX == 538976354 ) *a1 = 1 ; sprintf (s,"%c%c%c%c" ,(unsigned int )_RBX,(unsigned int )((int )_RBX >> 8 ),(unsigned int )((int )_RBX >> 16 ),(unsigned int )((int )_RBX >> 24 )); sprintf (v10,"%c%c%c%c" ,(unsigned int )v7,(unsigned int )(v7 >> 8 ),(unsigned int )(v7 >> 16 ),(unsigned int )(v7 >> 24 )); sprintf (v11,"%c%c%c%c" ,(unsigned int )v8,(unsigned int )(v8 >> 8 ),(unsigned int )(v8 >> 16 ),(unsigned int )(v8 >> 24 )); return __readfsqword(0x28 u) ^ v12; }
这里用到了一个cpuid
的汇编指令,可以看看这个 ,介绍了不同功能码下cpuid
返回的内容以及返回内容所在的寄存器
不过这里的功能吗是什么都不影响需要hook掉返回的值的操作
根据汇编
1 2 3 4 5 6 7 8 9 10 11 12 13 .text:0000000000001191 89 D0 mov eax, edx .text:0000000000001193 89 DE mov esi, ebx .text:0000000000001195 89 75 D0 mov [rbp+var_30], esi .text:0000000000001198 89 4D CC mov [rbp+var_34], ecx .text:000000000000119B 89 45 D4 mov [rbp+var_2C], eax .text:000000000000119E 81 7D D0 51 69 6C 69 cmp [rbp+var_30], 696C6951h .text:00000000000011A5 75 19 jnz short loc_11C0 .text:00000000000011A5 .text:00000000000011A7 81 7D CC 6E 67 4C 61 cmp [rbp+var_34], 614C676Eh .text:00000000000011AE 75 10 jnz short loc_11C0 .text:00000000000011AE .text:00000000000011B0 81 7D D4 62 20 20 20 cmp [rbp+var_2C], 20202062h .text:00000000000011B7 75 07 jnz short loc_11C0
需要更改的寄存器有EDX
、EBX
、ECX
可以使用hook_code
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.edx=0x20202062 ql.arch.regs.ebx=0x696C6951 ql.arch.regs.ecx=0x614C676E ql.arch.regs.rip += 2 def challenge11 (ql: Qiling ): ql.hook_code(hook_cpuid)
也可以用hook_address
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 ): base_addr = ql.mem.get_lib_base(os.path.split(ql.path)[-1 ]) cpuid_instruction = base_addr+0x1191 ql.hook_address(hook_cpuid,cpuid_instruction)
Challenge Finished! 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 Welcome to QilingLab. Here is the list of challenges: 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. Checking which challenge are solved... Note: Some challenges will results in segfaults and infinite loops if they aren' t solved.Challenge 1: SOLVED Challenge 2: SOLVED Challenge 3: SOLVED Challenge 4: SOLVED Challenge 5: SOLVED Challenge 6: SOLVED Challenge 7: SOLVED Challenge 8: SOLVED Challenge 9: SOLVED Challenge 10: SOLVED Challenge 11: SOLVED You solved 11/11 of the challenges
整个代码文件如下
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 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 from qiling import *from qiling.const import *from qiling.os.mapper import QlFsMappedObjectimport osimport structdef challenge1 (ql: Qiling ): ql.mem.map (0x1000 ,0x1000 ) ql.mem.write(0x1337 ,ql.pack16(1337 )) def hook_uname_func (ql: Qiling,*args ): x0 = ql.arch.regs.sp + 0x1F0 - 0x1B0 ql.mem.write(x0,b'QilingOS\x00' ) ql.mem.write(x0+65 *3 ,b'ChallengeStart\x00' ) def challenge2 (ql: Qiling ): ql.os.set_syscall("uname" , hook_uname_func, QL_INTERCEPT.EXIT) class FakeUrandom (QlFsMappedObject ): def read (self, size: int ) -> bytes : if size == 1 : return b'\xE9' return b"\x00" *size def close (self ) -> int : return 0 def hook_getrandom_func (ql: Qiling,buf,buflen ): ql.mem.write(buf,b'\x00' *buflen) ql.os.set_syscall_return(0 ) def challenge3 (ql: Qiling ): ql.os.set_syscall("getrandom" , hook_getrandom_func) ql.add_fs_mapper("/dev/urandom" , FakeUrandom()) def hook_FE4 (ql: Qiling ): ''' .text:0000000000000FE0 3F 00 00 6B CMP W1, W0 .text:0000000000000FE4 EB FE FF 54 B.LT loc_FC0 ''' ql.arch.regs.W0 = 1 def challenge4 (ql: Qiling ): base_addr = ql.mem.get_lib_base(os.path.split(ql.path)[-1 ]) enter_loop = base_addr + 0xfe0 ql.hook_address(hook_FE4,enter_loop) def hook_rand_func (ql: Qiling ): ''' 返回值存在w0中 ''' ql.arch.regs.W0=0 def challenge5 (ql:Qiling ): ql.os.set_api('rand' ,hook_rand_func, QL_INTERCEPT.CALL) def hook_1118 (ql: Qiling ): ''' .text:0000000000001114 00 1C 00 12 AND W0, W0, #0xFF .text:0000000000001118 1F 00 00 71 CMP W0, #0 .text:000000000000111C 61 FF FF 54 B.NE loc_1108 ''' ql.arch.regs.w0 = 0 def challenge6 (ql: Qiling ): base_addr = ql.mem.get_lib_base(os.path.split(ql.path)[-1 ]) stop_loop = base_addr + 0x1118 ql.hook_address(hook_1118,stop_loop) def hook_nanosleep_func (ql: Qiling ): return def challenge7 (ql: Qiling ): ql.os.set_syscall('nanosleep' ,hook_nanosleep_func) def hook_11DC (ql: Qiling ): MAGIC = 0x3DFCD6EA00000539 magic_addrs = ql.mem.search(ql.pack64(MAGIC)) for magic_addr in magic_addrs: candidate_heap_struct_addr = magic_addr - 8 candidate_heap_struct = ql.mem.read(candidate_heap_struct_addr, 24 ) string_addr, _ , check_addr = struct.unpack('QQQ' , candidate_heap_struct) if ql.mem.string(string_addr) == "Random data" : ql.mem.write(check_addr, b"\x01" ) break def challenge8 (ql: Qiling ): base_addr = ql.mem.get_lib_base(os.path.split(ql.path)[-1 ]) hook_struct = base_addr + 0x11DC ql.hook_address(hook_11DC,hook_struct) def hook_tolower_func (ql: Qiling ): return def challenge9 (ql: Qiling ): ql.os.set_api('tolower' ,hook_tolower_func) class Fakecmdline (QlFsMappedObject ): def read (self, size: int ) -> bytes : return b"qilinglab" def close (self ) -> int : return 0 def challenge10 (ql: Qiling ): ql.add_fs_mapper("/proc/self/cmdline" , Fakecmdline()) def midr_el1_hook (ql: Qiling, address, size ): ''' .text:00000000000013EC 00 00 38 D5 MRS X0, #0, c0, c0, #0 ''' 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: Qiling ): mem_map = ql.mem.map_info for entry in mem_map: start, end, flags, label, _ = entry if os.path.split(ql.path)[-1 ] in label and flags == 5 : start_hook = start end_hook = end break ql.hook_code(midr_el1_hook, begin=start_hook, end=end_hook) if __name__ == '__main__' : path = ['qilinglab/qilinglab-aarch64' ] rootfs = "./examples/rootfs/arm64_linux" ql = Qiling(path, rootfs,verbose= QL_VERBOSE.OFF) challenge1(ql) challenge2(ql) challenge3(ql) challenge4(ql) challenge5(ql) challenge6(ql) challenge7(ql) challenge8(ql) challenge9(ql) challenge10(ql) challenge11(ql) ql.run()