Qiling study note. It mainly talk about a program about some challenges. When you overcome it, you will master this skill.

Qiling框架学习

 前几天在看雪公众号上看到这篇文章:Qiling框架分析实战,当时简单看了看开头,应该是IOT漏洞利用的一个执行框架,感觉和我之后想干的很契合,所以打算学习学习。

Qiling 框架是基于 unicorn 的多架构平台模拟执行框架,能够在模拟执行的基础上提供统一的分析 API,可以进行插桩分析、快照、系统调用和API劫持等操作。Joansivion提供了能够针对Qiling框架进行学习的程序,并提供了相应的writeup。他提供的 writeup 是 arm 架构的。本文也是对其提供的程序进行分析,但是提供的题解是x86_64的。

 Joansivion提供的程序包括11个挑战,分为x86_64版本与aarch64版本。本文主要针对x86_64,其挑战如下:

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
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.

挑战1:将 1337 存储在指针 0x1337 处。
挑战2:使'uname'系统调用返回正确的值。
挑战3:使 '/dev/urandom' 和 'getrandom' '碰撞'。
挑战4:进入'禁止'循环。
挑战5:猜测对 rand() 的每次调用。
挑战6:避免无限循环。
挑战7:不要浪费时间等待'sleep'。
挑战8:解压结构体并写入目标地址。
挑战9:修复一些字符串操作以使iMpOsSiBlE成为现实。
挑战10:伪造'cmdline'行文件以返回正确的内容。
挑战11:绕过'CPUID/MIDR_EL1'检查。

 使用qiling运行一下x86_64,显示错误:

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
from qiling import *

if __name__ == '__main__':
path = ["qilinglab-x86_64"]
rootfs = "./qiling/examples/rootfs/x8664_linux"
ql = Qiling(path, rootfs)
ql.run()
# [=] brk(inp = 0x0) = 0x55555575a000
# [=] uname(buf = 0x80000000d9b0) = 0x0
# [=] access(path = 0x7ffff7df6082, mode = 0x0) = -0x1 (EPERM)
# [=] access(path = 0x7ffff7df8dd0, mode = 0x4) = -0x1 (EPERM)
# [=] openat(fd = 0xffffff9c, path = 0x7ffff7df6428, flags = 0x80000, mode = 0x0) = -0x2 (ENOENT)
# [=] openat(fd = 0xffffff9c, path = 0x80000000d340, flags = 0x80000, mode = 0x0) = -0x2 (ENOENT)
# [=] stat(path = 0x80000000d340, buf_ptr = 0x80000000d400) = -0x2 (ENOENT)
# [=] openat(fd = 0xffffff9c, path = 0x80000000d340, flags = 0x80000, mode = 0x0) = -0x2 (ENOENT)
# [=] stat(path = 0x80000000d340, buf_ptr = 0x80000000d400) = -0x2 (ENOENT)
# [=] openat(fd = 0xffffff9c, path = 0x80000000d340, flags = 0x80000, mode = 0x0) = -0x2 (ENOENT)
# [=] stat(path = 0x80000000d340, buf_ptr = 0x80000000d400) = -0x2 (ENOENT)
# [=] openat(fd = 0xffffff9c, path = 0x80000000d340, flags = 0x80000, mode = 0x0) = -0x2 (ENOENT)
# [=] stat(path = 0x80000000d340, buf_ptr = 0x80000000d400) = -0x2 (ENOENT)
# [=] openat(fd = 0xffffff9c, path = 0x80000000d340, flags = 0x80000, mode = 0x0) = -0x2 (ENOENT)
# [=] stat(path = 0x80000000d340, buf_ptr = 0x80000000d400) = -0x2 (ENOENT)
# [=] openat(fd = 0xffffff9c, path = 0x80000000d340, flags = 0x80000, mode = 0x0) = -0x2 (ENOENT)
# [=] stat(path = 0x80000000d340, buf_ptr = 0x80000000d400) = -0x2 (ENOENT)
# [=] openat(fd = 0xffffff9c, path = 0x80000000d340, flags = 0x80000, mode = 0x0) = -0x2 (ENOENT)
# [=] stat(path = 0x80000000d340, buf_ptr = 0x80000000d400) = -0x2 (ENOENT)
# [=] openat(fd = 0xffffff9c, path = 0x80000000d340, flags = 0x80000, mode = 0x0) = -0x2 (ENOENT)
# [=] stat(path = 0x80000000d340, buf_ptr = 0x80000000d400) = -0x2 (ENOENT)
# [=] openat(fd = 0xffffff9c, path = 0x80000000d340, flags = 0x80000, mode = 0x0) = -0x2 (ENOENT)
# [=] stat(path = 0x80000000d340, buf_ptr = 0x80000000d400) = -0x2 (ENOENT)
# [=] openat(fd = 0xffffff9c, path = 0x80000000d340, flags = 0x80000, mode = 0x0) = -0x2 (ENOENT)
# [=] stat(path = 0x80000000d340, buf_ptr = 0x80000000d400) = -0x2 (ENOENT)
# ...
# [x] 0000007ffff7ffc000 - 0000007ffff7ffd000 r-- ld-linux-x86-64.so.2 /home/wd/Desktop/qiling/qiling/examples/rootfs/x8664_linux/lib64/ld-linux-x86-64.so.2
# [x] 0000007ffff7ffd000 - 0000007ffff7fff000 rw- ld-linux-x86-64.so.2 /home/wd/Desktop/qiling/qiling/examples/rootfs/x8664_linux/lib64/ld-linux-x86-64.so.2
# [x] 0000007ffffffde000 - 00000080000000e000 rwx [stack]
# [x] 00ffffffffff600000 - 00ffffffffff601000 rwx [vsyscall]
# UcError: Invalid memory read (UC_ERR_READ_UNMAPPED)
  • brk(inp = 0x0) = 0x55555575a000:通过brk()系统调用请求增加堆内存的结果,其中inp参数为0,表示请求增加0字节的堆内存,返回值0x55555575a000表示堆内存的起始地址。

  • uname(buf = 0x80000000d9b0) = 0x0:通过uname()系统调用获取系统信息的结果,其中buf参数表示用于存储系统信息的缓冲区的地址,返回值0x0表示操作成功。

  • access(path = 0x7ffff7df6082, mode = 0x0) = -0x1 (EPERM):通过access()系统调用检查文件访问权限的结果,其中path参数表示要检查的文件路径,mode参数表示要检查的权限,返回值-0x1表示操作失败,错误码为EPERM,表示权限不允许。
  • openat(fd = 0xffffff9c, path = 0x7ffff7df6428, flags = 0x80000, mode = 0x0) = -0x2 (ENOENT):通过openat()系统调用打开文件的结果,其中fd参数表示文件描述符,path参数表示要打开的文件路径,flags参数表示打开文件的方式和行为,mode参数表示文件的权限,返回值-0x2表示操作失败,错误码为ENOENT,表示文件不存在。
  • stat(path = 0x80000000d340, buf_ptr = 0x80000000d400) = -0x2 (ENOENT):通过stat()系统调用获取文件状态的结果,其中path参数表示要获取状态的文件路径,buf_ptr参数表示用于存储状态信息的缓冲区的地址,返回值-0x2表示操作失败,错误码为ENOENT,表示文件不存在。该行出现了多次,表明程序尝试多次获取同一个文件的状态,但均返回了文件不存在的错误。

 查看一下qilinglab-x86_64的头部信息(-h代表头部。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
(base) wd@ubuntu:$ readelf -h qilinglab-x86_64 
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0xa80
Start of program headers: 64 (bytes into file)
Start of section headers: 15840 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 29
Section header string table index: 28

 从data字段可以看出是小端序,从flags字段可以看出未裁剪符号表。

 查看一下文件类型,并显示符号链接的类型与其指向的文件。

1
2
(base) wd@ubuntu:~/Desktop/qiling$ file -L qilinglab-x86_64 
qilinglab-x86_64: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=76164e6b494c1af9d9f746e2dc7d3663cc23525c, not stripped

 使用ida7.7查看qilinglab-x86_64。函数逻辑如下:

(1)循环初始化数组v13所有元素为0,如果完成对应challenge1就会改变v13对应数组的值,再用checker进行一次检查v13数组元素是否为0。最终,程序会输出完成的挑战数量,也就是数组v13中为1的个数。

0x00 challenge1-修改内存地址

image-20230706203020940

 其中[rax]=v13[0],即我们想让[0x1337] = 0x539 = 1337。在Qiling中可以以多种方法编写字节序列:

1
2
3
4
5
6
# 返回4字节的大端序字节序列,其中包含整数值 0x12345678 的二进制表示
# ">I"参数指定了这个字节序列的格式,其中">"表示大端序,"<"表示小端序,"I"表示一个32位无符号整数
ql.pack(">I", 0x12345678)

# 这个函数专门用于 16 位整数
ql.pack16(0x1234)

 因此,可以编写challenage1-wp,将[0x1337]指向的内存为1337

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from qiling import *

def challenge1(ql):
# 以0x1000为起始,映射0x1000大小的数据,映射的大小必须是页大小的倍数,这里的页大小4096字节
# info是对这部分内存做的一个标记,后续可以用这个标记来定位
ql.mem.map(0x1000, 0x1000, info = '[challenge1]')
# 将整数值 1337 转换为一个16位字节写入内存地址 0x1337 所指定的内存位置,小端输入
ql.mem.write(0x1337, ql.pack16(1337))

if __name__ == '__main__':
path = ["qilinglab-x86_64"]
rootfs = "./qiling/examples/rootfs/x8664_linux"
ql = Qiling(path, rootfs)
challenge1(ql)
ql.run()

0x01 challenge2-hook系统调用

image-20230706212206713

 通过分析,此函数的逻辑为:

(1)运行uname函数,将返回结果保存到name结构体中,如果成功获取的话返回值为0。

(2)判断name.sysname=="QilingOS"name.version=="ChallengeStart",就可以完成此挑战。

 那么,我们要Hook函数uname,在返回之前改sysnameversion。有4hook方式:

(1)QL_INTERCEPT.EXIT:在系统调用执行完成之后立即执行hook函数。

(2)QL_INTERCEPT.ENTER:在系统调用执行之前执行hook函数。

(3)QL_INTERCEPT.EXIT_TREE:在系统调用执行完成并返回后,执行完其他所有系统调用的hook函数之后,再执行当前系统调用的hook函数。

(4)QL_INTERCEPT.EXIT_ALL:在系统调用执行完成并返回后,执行所有系统调用的hook函数,并清除这些hook函数。

 补充:系统调用返回结构体型数据时,会将结构体的地址存放在寄存器rdi中。uname返回的结构体为:

1
2
3
4
5
6
7
8
struct utsname {
char sysname[65];
char nodename[65];
char release[65];
char version[65];
char machine[65];
char domainname[65];
};

 可以写脚本如下:

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
from qiling import *
# 导入Qiling中的常量
from qiling.const import *

def my_uname_on_exit_hook(ql, *args):
# 获得rdi
rdi = ql.arch.regs.rdi
# 输出rdi地址
print(f"utsname address: {hex(rdi)}")
# sysname
ql.mem.write(rdi, b'QilingOS\x00')
# version
ql.mem.write(rdi + 65 * 3, b'ChallengeStart\x00')

def challenge2(ql):
# 使用QL_INTERCEPT.EXIT
# 在系统调用执行完成之后立即执行hook函数
# 用my_uname_on_exit_hook替换了uname
ql.os.set_syscall("uname", my_uname_on_exit_hook, QL_INTERCEPT.EXIT)

def challenge1(ql):
...

if __name__ == '__main__':
path = ["./qiling/examples/rootfs/x8664_linux/qilinglab-x86_64"]
rootfs = "./qiling/examples/rootfs/x8664_linux"
ql = Qiling(path, rootfs)
challenge1(ql)
challenge2(ql)
ql.run()

0x02 challenge3-自定义文件

image-20230706215408991

 分析此函数:保证getrandom函数拿到的数据v7与文件/dev/urandom拿到的数据buf相同,且buf[i]!=v5。其中,getrandom是用系统调用获得随机数,此函数用法为:

1
2
3
4
5
6
7
8
9
10
11
ret = getrandom(ql, buf, buflen, flags)

buf:指向缓冲区的指针,用于存储读取到的随机数据。
buflen:要从系统熵池读取的字节数。
flags:
(1)0:如果系统熵池中没有足够的熵,getrandom 会阻塞直到有足够的熵可用。
(2)GRND_NONBLOCK(为 1):getrandom 在系统熵池中没有足够的熵时,会立即返回错误而不是阻塞。
(3)GRND_RANDOM(为 2):尝试从 /dev/random 获取随机数据,而不是从 /dev/urandom 获取。这个选项会导致 getrandom 的行为更加谨慎,可能会在熵不足时阻塞。
ret:
(1)如果成功获取随机数据,getrandom 返回实际读取的字节数。
(2)如果出错,返回-1。

 而/dev/urandom 是一个 Linux 系统中的特殊文件,它是一个伪随机数发生器设备文件,用于生成随机数。在qiling中使用 ql.add_fs_mapper("/dev/urandom", "/dev/urandom") 将宿主机中的 /dev/urandom (后面的) 设备文件映射到qiling虚拟机中的 /dev/urandom (前面的) 文件上,以便为虚拟机中的程序提供随机数服务。

wp如下:

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
from qiling import *
# 导入Qiling模拟器中的常量
from qiling.const import *
# 使用QlFsMappedObject自定义文件对象,以实现"/dev/urandom"
from qiling.os.mapper import QlFsMappedObject

# 模拟/dev/urandom
class FakeUrandom(QlFsMappedObject):

def read(self, size: int) -> bytes:
if size == 1:
# v5为\x42
return b"\x42"
else:
# buf为\x41 * size
return b"\x41" * size

def close(self) -> int:
return 0

# 模拟getrandom
def hook_getrandom(ql, buf, buflen, flags):
if buflen == 32:
data = b'\x41' * buflen
ql.mem.write(buf, data)
ql.os.set_syscall_return(buflen)
else:
ql.os.set_syscall_return(-1)

def challenge3(ql):
# 将/dev/urandom文件以FakeUrandom()替换
ql.add_fs_mapper(r'/dev/urandom', FakeUrandom())
# 将getrandom函数以hook_getrandom替换
ql.os.set_syscall("getrandom", hook_getrandom)

def challenge2(ql):
...

def challenge1(ql):
...

if __name__ == '__main__':
path = ["./qiling/examples/rootfs/x8664_linux/qilinglab-x86_64"]
rootfs = "./qiling/examples/rootfs/x8664_linux"
ql = Qiling(path, rootfs)
challenge1(ql)
challenge2(ql)
challenge3(ql)
ql.run()

0x03 challenge4-hook某地址

image-20230707120024772

 程序逻辑如下:初始时设置tmp1=tmp2=0,比较tmp2<tmp1是否成立,若成立则挑战成功。那么我们就要patch [rbp+tmp1]为1。

 可以编写wp为:

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
import os
from qiling import *

# 修改eax的值
def enter_forbidden_loop_hook(ql):
ql.arch.regs.eax = 1

def challenge4(ql):
"""
.text:0000555555554E40 mov eax, [rbp+tmp1]
.text:0000555555554E43 cmp [rbp+tmp2], eax ; 若tmp2<tmp1则成功 <-- 在运行此命令前hook eax,使得eax = 1
.text:0000555555554E46 jl short loc_555555554E35 ; challenge4成功
"""
# 根据文件路径查找已经加载的文件,获取对应文件的基地址
# os.path.split(ql.path)[-1]为文件名
base = ql.mem.get_lib_base(os.path.split(ql.path)[-1])
hook_addr = base + 0xE43
# 当执行流程到达hook_addr时,该函数将被调用,此时hook_addr处的代码还未被执行。
ql.hook_address(enter_forbidden_loop_hook, hook_addr)


if __name__ == '__main__':
path = ["./qiling/examples/rootfs/x8664_linux/qilinglab-x86_64"]
rootfs = "./qiling/examples/rootfs/x8664_linux"
ql = Qiling(path, rootfs)
challenge4(ql)
ql.run()

0x04 challenge5-hook某函数

image-20230707121805531

 函数逻辑如下:将v5[i]=0,v5[i+8]=rand(),0<=i<=4。检验时,要求v5[i]=v5[i+8]。思路是把rand()函数hook掉,感觉有点像challenge2,不同的是,challenge2hook的系统调用,而challenge5hook的函数调用rand,所以使用函数ql.os.set_apiwp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
from qiling import *

def my_rand_on_exit_hook(ql, *args):
ql.arch.regs.rax = 0

def challenge5(ql):
ql.os.set_api('rand', my_rand_on_exit_hook)
if __name__ == '__main__':
path = ["./qiling/examples/rootfs/x8664_linux/qilinglab-x86_64"]
rootfs = "./qiling/examples/rootfs/x8664_linux"
ql = Qiling(path, rootfs)
challenge5(ql)
ql.run()

 需要注意的是,此时程序不会回显,因为要解决后面的题目。

0x05 challenge6-修改寄存器

image-20230707123259692

 程序一直循环,只有当var_5==0时,才会跳出,我们打算将程序执行到0x0000555555554F12时修改eax=0。其wp如下:

1
2
3
4
5
6
def hook_rax(ql):
ql.arch.regs.rax = 0
def challenge6(ql):
base = ql.mem.get_lib_base(os.path.split(ql.path)[-1])
hook_addr = base + 0xF16
ql.hook_address(hook_rax, hook_addr)

0x06 challenge7-hook函数调用

image-20230709215819402

 程序一直sleep,很难返回,我们要hook sleep函数,如下所示:

1
2
3
4
def hook_sleep(ql):
return 0
def challenge7(ql):
ql.os.set_api('sleep', hook_sleep)

0x07 challenge8-修改结构体的值

image-20230709220031016

 这是一个结构体,其整理后的内容如下:

1
2
3
4
| 8 bytes | 30-byte memory, save "Random data" |
| 4 bytes | 1337 |
| 4 bytes | 1039980266 |
| 8 bytes | a1 |

 结构体定义如下:

1
2
3
4
5
6
typedef struct {
char *string_ptr; // 8 字节的指针,指向字符串 "Random data" 所在的内存
uint32_t value1; // 4 字节的整数,值为 1337
uint32_t value2; // 4 字节的整数,值为 1039980266
int64_t a1; // 8 字节的整数,值为传入的参数 a1
} CustomStruct;

 我们需要修改*a1=1,那么wp可以这么写:

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
import struct

def challenge8_hook(ql):

# 在内存中寻找1337和1039980266所在的内存
MAGIC = 0x3DFCD6EA00000539

# 转换MAGIC成为字节序列
magic_addrs = ql.mem.search(ql.pack64(MAGIC))

# 找到好多对应的地址
for magic_addr in magic_addrs:

# MAGIC距离结构体头指针距离为 8 bytes
candidate_heap_struct_addr = magic_addr - 8
# 获取结构体地址结构
candidate_heap_struct = ql.mem.read(candidate_heap_struct_addr, 24)
# struct.unpack() 是一个 Python 函数,用于将字节序列解包为多个值,解包 candidate_heap_struct 地址结构。
# 'QQQ' 是一个格式字符串,表示要解包的数据结构包含3个64位(8 字节)无符号整数。
# string_addr, _ , check_addr:只关注 string_addr 与 check_addr,最后得到相应地址
string_addr, _ , check_addr = struct.unpack('QQQ', candidate_heap_struct)
# 如果找到对应的地址
if ql.mem.string(string_addr) == "Random data":
# 修改*a1为1
ql.mem.write(check_addr, b"\x01")
break

def challenge8(ql):
base_addr = ql.mem.get_lib_base(os.path.split(ql.path)[-1])
# 程序运行结束时的地址
end_of_challenge8 = base_addr + 0xFB5
ql.hook_address(challenge8_hook, end_of_challenge8)

0x08 challenge9-hook函数调用2

image-20230709222307435

 把aBcdeFghiJKlMnopqRstuVWxYz变成小写,并检查srcdest是否相等,若相等则挑战成功。目测应该是要hook strcmp函数。wp如下,其hooktolower函数,让它什么也不做:

1
2
3
4
def hook_tolower(ql):
return 0
def challenge9(ql):
ql.os.set_api('tolower', hook_tolower)

0x09 challenge10-自定义文件对象

image-20230709222727406

 读取/proc/self/cmdline文件,并比较读取的字符串是否为"qilinglab"

 注:/proc/self/cmdline是一个在Linux系统中的特殊文件,它提供了当前进程(即访问/proc/self/cmdline的进程)的命令行参数信息。假如我们使用命令./my_program arg1 arg2 arg3启动一个程序时,读取/proc/self/cmdline的过程在 ./my_program 中,/proc/self/cmdline文件的内容将是:./my_program\0arg1\0arg2\0arg3\0

 按照challenge3,可以写wp

1
2
3
4
5
6
7
8
class Fake_cmdline(QlFsMappedObject):
def read(self, expected_len):
return b'qilinglab'
def close(self):
return 0

def challenge10(ql):
ql.add_fs_mapper('/proc/self/cmdline', Fake_cmdline())

 博客中此wp在运行后并没有解题成功,博主说是在hook /proc/self/cmdline 这里存在问题,所以他写了个脚本单独调试以下cmdline读取程序:

c 程序:用来读取cmdline,文件名:test_cmdline

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
#include <stdio.h>
#include <stdlib.h>

int main() {
FILE *file;
char ch;

// 打开/proc/self/cmdline文件
file = fopen("/proc/self/cmdline", "r");
if (file == NULL) {
printf("无法打开/proc/self/cmdline文件。\n");
exit(EXIT_FAILURE);
}
printf("读取/proc/self/cmdline:\n");
// 逐个字符读取文件内容并输出到终端
while ((ch = fgetc(file)) != EOF) {
// 将空字符替换为换行符以提高可读性
if (ch == '\0') {
putchar('\n');
} else {
putchar(ch);
}
}

// 关闭文件
fclose(file);
return 0;
}

 结果博主发现:用c语言读取程序存在问题。但是我没有看出来有什么问题(:最终经过代码审计(好长的步骤),发现qiling-1.4.3可以正常运行wp

0x10 challenge11-绕过CPUID校验

image-20230709225245426

 要让if的判断顺利通过,if ( __PAIR64__(_RBX, _RCX) == 0x696C6951614C676ELL && (_DWORD)_RDX == 538976354 ) 的含义为:

(1)rbxrcx两个32位寄存器组成的64位值等于0x696C6951614C676ELL

(2)RDX寄存器的低32位等于538976354

 因此我们跳过cpuid指令,并修改寄存器即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def hook_cpuid(ql, address, size):
# CPUID指令的机器码是 0F A2
if ql.mem.read(address, size) == b'\x0F\xA2':
regs = ql.arch.regs
regs.ebx = 0x696C6951
regs.ecx = 0x614C676E
regs.edx = 0x20202062
# 跳过cpuid指令防止被cpuid篡改
regs.rip += 2

def challenge11(ql):
begin, end = 0, 0
for info in ql.mem.map_info:
print(info)
if info[2] == 5 and 'qilinglab-x86_64' in info[3]:
begin, end = info[:2]
print(f"{begin} -> {end}")

ql.hook_code(hook_cpuid, begin=begin, end=end)

留言

2023-07-06

© 2024 wd-z711

⬆︎TOP