0%

qiling 学习 (1)

qiling 是一个基于 Unicorn 开发的二进制文件仿真工具,与 Unicorn 不同,qiling 的功能更加完善更加易用。下面将对该 Python 库的主要 API 进行总结与学习。

A. 类 Qiling

这是 qiling 模拟器主类,将完成仿真的大部分主要操作。

A.1 构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def __init__(
self,
argv: Sequence[str] = [],
rootfs: str = r'.',
env: MutableMapping[AnyStr, AnyStr] = {},
code: Optional[bytes] = None,
ostype: Optional[QL_OS] = None,
archtype: Optional[QL_ARCH] = None,
verbose: QL_VERBOSE = QL_VERBOSE.DEFAULT,
profile: Optional[Union[str, Mapping]] = None,
console: bool = True,
log_file: Optional[str] = None,
log_override: Optional['Logger'] = None,
log_plain: bool = False,
multithread: bool = False,
filter: Optional[str] = None,
stop: QL_STOP = QL_STOP.NONE,
*,
endian: Optional[QL_ENDIAN] = None,
thumb: bool = False,
libcache: bool = False
):
...

一些常用参数的含义:

  • argv:命令的具体内容,对于控制台的命令,需要以空格分开并保存在列表中。如要执行/bin/cat flag.txt则需要传入['/bin/cat', 'flag.txt']
  • rootfs:本次模拟的根目录,默认为当前目录。
  • env:环境变量,传入字典即可。
  • code:自定义代码,不能和argv同时指定,这个参数主要是用于执行机器码字节序列。
  • ostype:操作系统,传入QL_OS类型。
  • archtype:处理器架构,传入QL_ARCH类型。
  • verbose:输出信息的详细程度,如调试级别会比默认级别输出更多信息。传入QL_VERBOSE类型。
  • profile:配置项,可以保存在文件中传入文件名,也可以传入字典。
  • console:是否运行在终端,将输出内容显示在终端。
  • log_file:日志的输出文件。
  • log_plain:是否输出去除颜色的日志内容,当日志被保存到文件中时常用。
  • multithread:是否使用多线程。
  • filter:根据正则表达式筛选指定日志输出。
  • endian:端序。
  • thumb:模拟 ARM 架构时是否为 thumb 模式。

这里与程序运行过程中关系最大的参数之一就是profile,我们可以在这里定义堆栈的地址、mmap的输出地址、网络配置等,profile参数如果为文件名,后缀应为.ql,文件实际格式为yaml。下面是默认的 Linux 配置文件:

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
[CODE]
# ram_size 0xa00000 is 10MB
ram_size = 0xa00000
entry_point = 0x1000000


[OS64]
stack_address = 0x7ffffffd0000
stack_size = 0x30000
load_address = 0x555555554000
interp_address = 0x7ffff7dd5000
mmap_address = 0x7fffb7dd6000
vsyscall_address = 0xffffffffff600000


[OS32]
stack_address = 0x7ff0d000
stack_size = 0x30000
load_address = 0x56555000
interp_address = 0x047ba000
mmap_address = 0x90000000


[KERNEL]
uid = 1000
gid = 1000
pid = 1996


[MISC]
current_path = /


[NETWORK]
# override the ifr_name field in ifreq structures to match the hosts network interface name.
# that fixes certain socket ioctl errors where the requested interface name does not match the
# one on the host. comment out to avoid override
ifrname_override = eth0

# To use IPv6 or not, to avoid binary double bind. ipv6 and ipv4 bind the same port at the same time
bindtolocalhost = True
# Bind to localhost
ipv6 = False

非常令人不解的是,官方文档并没有详细给出 profile 配置文件中包含哪些有效的项。翻遍了整个互联网,居然没有人对 qiling 配置文件的所有选项进行总结,要想知道某个配置的名字,只能参考现有的 .ql 文件,外加直接搜索 qiling 源代码中的相关用法。加载中需要使用的项在仓库的loader目录下不同格式的二进制文件加载过程代码中可以找到,下面简单总结一下查到的所有配置文件项列表(可能不全):

A.2. profile 配置文件项

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
所有格式通用:(os/os.py、os/posix/posix.py)
[CODE](Qiling构造函数需传入code参数)
entry_point: 程序入口点
ram_size: RAM的大小
[MISC]
current_path: 当前路径(OS类型需要为支持POSIX的,包括Linux、FreeBSD、QNX、macOS、Windows、DOS)
[KERNEL]
uid: uid号和euid号
gid: gid号和egid号
pid: pid号
[NETWORK]
ipv6: 布尔值,是否支持ipv6
bindtolocalhost: 布尔值,是否绑定到本机IP
ifrname_override: 主机网络设备映射

BLOB:(无操作系统的Bare Metal二进制文件)
[CODE]
heap_size: 堆空间大小

DOS:
[COM]
start_cs: 初始CS值(要求后缀名为.DOS_COM)
start_ip: 初始IP值(要求后缀名为.DOS_COM)
start_sp: 初始SP值(要求后缀名为.DOS_COM)
stack_size: 栈大小
[KERNEL]
ticks_per_second: 时钟频率

ELF:
[OS{字长bit数}]
stack_address: 栈地址
stack_size: 栈大小
load_address: 加载地址(需要为重定向文件)
interp_address: Linux加载器地址
mmap_address: mmap返回的地址
vsyscall_address: vsyscall地址(需要为OS64)

MacOS:
[OS64]
stack_address: 栈地址
stack_size: 栈大小
vmmap_trap_address: vmmap内存映射地址
heap_address: 堆地址
heap_size: 堆大小
mmap_address: mmap返回的地址
[LOADER]
slide
dyld_slide

MCU:


PE:
[HARDWARE]
number_processors: 处理器数量
[SYSTEM]
productType: 产品类型
majorVersion: 大版本号
minorVersion: 小版本号
VER_SERVICEPACKMAJOR: 系统包大版本
language: 系统语言(调用Windows SDK API)
permission: 权限
[OS{字长bit数}]
KI_USER_SHARED_DATA:内核用户共享数据地址
stack_address: 栈地址
stack_size: 栈大小
image_address: 程序的加载地址
dll_address: 系统等DLL文件加载地址
entry_point: 程序入口
[VOLUME]
name: 卷名
serial_number: 卷序列号
type: 卷类型
sectors_per_cluster: 每个簇的分区数量
bytes_per_sector: 每个分区包含的字节数量
number_of_free_clusters: 可用簇数量
number_of_clusters: 簇总数
[PATH]
systemdrive: 系统驱动
???: 其他任意Windows环境变量
[USER]
language: 用户语言(Windows SDK API)
[NETWORK]
dns_response_ip: DNS服务器IP
[PROCESSES]
csrss.exe: csrss.exe的pid
[KERNEL]
parent_pid: 父进程pid
[REGISTRY]
???: 任意注册表项

PE_UEFI:
[DXE]
heap_address: 堆地址
heap_size: 堆大小
stack_address: 栈地址
stack_size: 栈大小
image_address: EFI文件的加载地址
[SMM]
smram_size: SMM执行模式下的SMRAM大小
smram_base: SMM执行模式下的SMRAM基址
heap_address: SMM堆地址
heap_size: SMM堆大小
image_address: SMM加载基地址
[LOADED_IMAGE_PROTOCOL]
Guid: 加载的image的guid
[HOB_LIST]
Guid: Hand-Off Block,用于数据交接块的guid

下面,我们尝试模拟一下 Linux 系统的 ASLR 保护机制加载一个 ELF 文件,并在 main 函数开头暂停,输出该 ELF 文件的内存布局情况。

A.3 第一次试运行

在第一次运行的过程中,发现了一些问题,下面逐一进行解决。

测试代码:

1
2
3
4
5
6
#include <stdio.h>

int main(){
puts("Hello, Qiling !!!");
puts("Greetings from CoLin");
}

为了模拟 ASLR 内存保护,我们需要对 libc 的加载地址、ELF 的加载地址以及堆栈的地址进行一定程度的随机化。由于 ASLR 要求每一次执行的内存地址都不同,因此我们不能将配置文件写死在 ql 文件之中,只能动态生成到字典中。

而对于 Qiling 类构造函数的参数,需要注意rootfs选项。这里一定要将根目录设置为 ‘/’,如果设置为默认值,那么 qiling 将无法找到加载 ELF 的 ld.so 文件以及 libc.so 文件。

随后,开启执行发现,Qiling 没有完成一些较新的 Linux 系统调用实现。笔者的 libc 版本为 2.35,在加载器加载时会调用 334 号系统调用,而 Qiling 没有实现这个系统调用的替代,因此导致该系统调用无法正常运行,加载器判定 libc 版本不够。因此下面改为静态编译后运行。

脚本:

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

def callback(ql: qiling.Qiling):
print('\n'.join(ql.mem.get_formatted_mapinfo()))

if __name__ == '__main__':
profile = {
"OS64": {}
}

# 0x5500_0000_0000 ~ 0x56ff_ffff_f000
profile['OS64']['stack_address'] = 0x8000_0000_0000 - 0x30000 - (randint(0, 0x8000_0000) << 12)
profile['OS64']['stack_size'] = 0x30000
profile['OS64']['mmap_address'] = profile['OS64']['stack_address'] - 0x1000_0000

print("profile: ")
for key, value in profile['OS64'].items():
print("{}: {}".format(key, hex(value)))

elf = ELF('./hello_qiling')

emu = qiling.Qiling(
argv=['./hello_qiling'],
rootfs='/',
ostype=qiling.const.QL_OS.LINUX,
archtype=qiling.const.QL_ARCH.X8664,
profile=profile,
)

print(hex(elf.symbols['main']))
emu.hook_address(callback, elf.symbols['main'])
emu.run()

这里hook_address即在main函数首地址添加 hook。回调函数的第一个参数一定要为 Qiling 对象。ql.mem.get_formatted_mapinfo用于获取工整的内存映射信息字符串。

输出结果:

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
profile: 
stack_address: 0x7c76907a4000
stack_size: 0x30000
mmap_address: 0x7c76807a4000
[*] '/home/colin/Desktop/misc/CTF-playground/qiling/qiling_learn/hello_qiling'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
0x401775
Start End Perm Label Image
000000000000030000 - 000000000000031000 rwx [GDT]
000000000000400000 - 000000000000401000 r-- hello_qiling /home/colin/Desktop/misc/CTF-playground/qiling/qiling_learn/hello_qiling
000000000000401000 - 000000000000498000 r-x hello_qiling /home/colin/Desktop/misc/CTF-playground/qiling/qiling_learn/hello_qiling
000000000000498000 - 0000000000004c1000 r-- hello_qiling /home/colin/Desktop/misc/CTF-playground/qiling/qiling_learn/hello_qiling
0000000000004c1000 - 0000000000004c5000 r-- hello_qiling /home/colin/Desktop/misc/CTF-playground/qiling/qiling_learn/hello_qiling
0000000000004c5000 - 0000000000004cd000 rw- hello_qiling /home/colin/Desktop/misc/CTF-playground/qiling/qiling_learn/hello_qiling
0000000000004cd000 - 0000000000004cf000 rwx [hook_mem]
0000000000004cf000 - 0000000000004d0000 rwx [brk]
0000000000004d0000 - 0000000000004f1000 rwx [brk]
0000007c76907a4000 - 0000007c76907d4000 rwx [stack]
00ffffffffff600000 - 00ffffffffff601000 rwx [vsyscall]
Hello, Qiling !!!
Greetings from CoLin
[=] arch_prctl(code = 0x3001, addr = 0x7c76907d3e00) = -0x1 (EPERM)
[=] brk(inp = 0x0) = 0x4cf000
[=] brk(inp = 0x4cfdc0) = 0x4d0000
[=] arch_prctl(code = 0x1002, addr = 0x4cf3c0) = 0x0
[=] set_tid_address(tidptr = 0x4cf690) = 0x2419
[=] set_robust_list(head_ptr = 0x4cf6a0, head_len = 0x18) = 0x0
[!] 0x44ac4f: syscall ql_syscall_rseq number = 0x14e(334) not implemented
[=] uname(buf = 0x7c76907d3b90) = 0x0
[=] prlimit64(pid = 0x0, res = 0x3, new_limit = 0x0, old_limit = 0x7c76907d3d10) = 0x0
[=] readlink(pathname = 0x4aea98, buf = 0x7c76907d2c50, bufsize = 0x1000) = 0x48
[=] getrandom(buf = 0x4cc1f0, buflen = 0x8, flags = 0x1) = 0x8
[=] brk(inp = 0x4f1000) = 0x4f1000
[=] mprotect(start = 0x4c1000, mlen = 0x4000, prot = 0x1) = 0x0
[=] newfstatat(dirfd = 0x1, path = 0x4af37d, buf_ptr = 0x7c76907d3b00, flags = 0x1000) = -0x1 (EPERM)
[=] write(fd = 0x1, buf = 0x4d0500, count = 0x27) = 0x27
[=] exit_group(code = 0x0) = ?

对于静态编译的程序,其 ELF 加载地址是固定的,但我们对栈地址完成了随机化处理。

A.4 Hooks

Qiling 支持多种程序 Hook,包括内存读写访问、基本块、代码片段、特定指令(只支持 syscall、in、out 等少数几种系统相关指令,Qiling 的一大缺点)。下面使用上面的静态编译程序进行测试,脚本如下:

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
import qiling.const
import qiling.core_hooks_types

from pwn import *

def callback_address(ql: qiling.Qiling):
print("*** Address Hook Callback Function ***")

def callback_code(ql: qiling.Qiling, address: int, inst_size: int):
print(f"*** Code Hook Callback Function: at {address:#x}, instruction size = {inst_size} ***")

def callback_basicblock(ql: qiling.Qiling, address: int, inst_size: int):
print(f"*** Basic Block Hook Callback Function: at {address:#x}, instruction size = {inst_size} ***")

def callback_memoryFetch(ql: qiling.Qiling, access_type: int, address: int, memory_size: int, value: int):
print(f"*** Memory Fetch Hook Callback Function: at {address:#x}, memory size = {memory_size} ***")

if __name__ == '__main__':
profile = {
"OS64": {}
}

# 0x5500_0000_0000 ~ 0x56ff_ffff_f000
profile['OS64']['stack_address'] = 0x8000_0000_0000 - 0x30000 - (randint(0, 0x8000_0000) << 12)
profile['OS64']['stack_size'] = 0x30000
profile['OS64']['mmap_address'] = profile['OS64']['stack_address'] - 0x1000_0000

print("profile: ")
for key, value in profile['OS64'].items():
print("{}: {}".format(key, hex(value)))

elf = ELF('./hello_qiling')

emu = qiling.Qiling(
argv=['./hello_qiling'],
rootfs='/',
ostype=qiling.const.QL_OS.LINUX,
archtype=qiling.const.QL_ARCH.X8664,
profile=profile,
)

hooks: list[qiling.core_hooks_types.HookRet] = [
emu.hook_address(callback_address, elf.symbols['main']),
emu.hook_code(callback_code, begin=elf.symbols['main'] + 4, end=elf.symbols['main'] + 8),
emu.hook_block(callback_basicblock, begin=elf.symbols['main'] + 0x17, end=elf.symbols['main'] + 0x26),
emu.hook_mem_read(callback_memoryFetch, begin=0x498016, end=0x49802b)
]
emu.run()

这里的hook_code相当于给某个范围内的所有指令前添加 Hook,在不传入beginend参数的情况下,默认是在整个内存空间操作,即对所有指令添加 Hook。hook_block则是在某个范围内对新找到的基本块添加 Hook。不同的 Hook 的回调函数的参数不太一样,对于内存访问 Hook,其参数包含访问类型、访问地址、访问大小、写入(如果访问为写入操作)的值。当然在创建 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
41
42
43
44
45
profile: 
stack_address: 0x784745fb3000
stack_size: 0x30000
mmap_address: 0x784735fb3000
[*] '/home/colin/Desktop/misc/CTF-playground/qiling/qiling_learn/hello_qiling'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
[=] arch_prctl(code = 0x3001, addr = 0x784745fe2e00) = -0x1 (EPERM)
[=] brk(inp = 0x0) = 0x4cf000
[=] brk(inp = 0x4cfdc0) = 0x4d0000
[=] arch_prctl(code = 0x1002, addr = 0x4cf3c0) = 0x0
[=] set_tid_address(tidptr = 0x4cf690) = 0x4b1c
[=] set_robust_list(head_ptr = 0x4cf6a0, head_len = 0x18) = 0x0
[!] 0x44ac4f: syscall ql_syscall_rseq number = 0x14e(334) not implemented
[=] uname(buf = 0x784745fe2b90) = 0x0
[=] prlimit64(pid = 0x0, res = 0x3, new_limit = 0x0, old_limit = 0x784745fe2d10) = 0x0
[=] readlink(pathname = 0x4aea98, buf = 0x784745fe1c50, bufsize = 0x1000) = 0x48
[=] getrandom(buf = 0x4cc1f0, buflen = 0x8, flags = 0x1) = 0x8
[=] brk(inp = 0x4f1000) = 0x4f1000
[=] mprotect(start = 0x4c1000, mlen = 0x4000, prot = 0x1) = 0x0
[=] newfstatat(dirfd = 0x1, path = 0x4af37d, buf_ptr = 0x784745fe2b00, flags = 0x1000) = -0x1 (EPERM)
[=] write(fd = 0x1, buf = 0x4d0500, count = 0x27) = 0x27
[=] exit_group(code = 0x0) = ?
*** Address Hook Callback Function ***
*** Code Hook Callback Function: at 0x401779, instruction size = 1 ***
*** Code Hook Callback Function: at 0x40177a, instruction size = 3 ***
*** Code Hook Callback Function: at 0x40177d, instruction size = 7 ***
*** Memory Fetch Hook Callback Function: at 0x498010, memory size = 8 ***
*** Memory Fetch Hook Callback Function: at 0x498018, memory size = 8 ***
*** Memory Fetch Hook Callback Function: at 0x498020, memory size = 8 ***
*** Memory Fetch Hook Callback Function: at 0x498028, memory size = 8 ***
*** Basic Block Hook Callback Function: at 0x40178c, instruction size = 15 ***
*** Memory Fetch Hook Callback Function: at 0x498016, memory size = 8 ***
*** Memory Fetch Hook Callback Function: at 0x49801e, memory size = 8 ***
*** Memory Fetch Hook Callback Function: at 0x498020, memory size = 8 ***
*** Memory Fetch Hook Callback Function: at 0x498028, memory size = 8 ***
*** Memory Fetch Hook Callback Function: at 0x498016, memory size = 8 ***
*** Memory Fetch Hook Callback Function: at 0x49801e, memory size = 8 ***
*** Memory Fetch Hook Callback Function: at 0x498026, memory size = 4 ***
*** Basic Block Hook Callback Function: at 0x40179b, instruction size = 7 ***
Hello, Qiling !!!
Greetings from CoLin

A.5 runtime 关键数据

runtime 数据包括寄存器值、内存值等。在 pwndbg 中,每一次程序执行中断后都会显示当前 RIP 前后的汇编代码、寄存器值以及堆栈内容。在 qiling 中,这些同样可以实现。

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
import qiling
import qiling.core_hooks_types
from random import randint
from pwn import *

context.arch = "amd64"
watch_regs = ['rax', 'rbx', 'rcx', 'rdx', 'rsi', 'rdi', 'rsp', 'rbp',
'r8', 'r9', 'r10', 'r11', 'r12', 'r13', 'r14', 'r15']

def callback_code(ql: qiling.Qiling, address: int, inst_size: int):
current_instruction = ql.mem.read(address, inst_size)
assembly = repr(next(ql.arch.disassembler.disasm(current_instruction, 0)))

print(f"{address:#x}: {assembly}")

print("*** REGISTERS ***")
for reg in watch_regs:
print(f"{reg}\t{ql.arch.regs.read(reg):16x}")

print("*** STACK ***")
for i in range(10):
print(f"{ql.arch.regs.read('rsp') + i * 8:x}\t{ql.arch.stack_read(i * 8):16x}")

input()

if __name__ == '__main__':
profile = {
"OS64": {}
}

# 0x5500_0000_0000 ~ 0x56ff_ffff_f000
profile['OS64']['stack_address'] = 0x8000_0000_0000 - 0x30000 - (randint(0, 0x8000_0000) << 12)
profile['OS64']['stack_size'] = 0x30000
profile['OS64']['mmap_address'] = profile['OS64']['stack_address'] - 0x1000_0000

emu = qiling.Qiling(
argv=['./hello_qiling'],
profile=profile
)

hook: qiling.core_hooks_types.HookRet = emu.hook_code(callback_code)
emu.run()

如上脚本所示,这里的hook_code仅传入了回调函数参数,这使得程序在每执行一条汇编指令后都会调用一次回调函数。在回调函数中,可以通过ql.mem_read方法读取内存中的任意地址处的任意长度值(write 即为写入),返回值为字节数组(bytearray)。在 qiling 中提供了调用 capstone 库进行反汇编的 API:ql.arch.disassembler.disasm,第一个参数为字节数组,第二个参数为偏移量。

对于寄存器值,可以直接通过ql.arch.regs.read传入寄存器名获取寄存器的值(write 即为写入)。如果需要查询当前架构的所有寄存器,可以通过ql.arch.regs.register_mapping获取。

对于堆栈,qiling 也特别设计了 API 便于操作。ql.arch.stack_read用于读取以rsp为基地址的任意偏移量的堆栈值(读取 4/8 字节,取决于字长)。stack_write为写入指定偏移处,stack_pushstack_pop即为直接修改rsp的值,在程序执行外完成 push 和 pop 操作。

部分输出:

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
0x40166f: <CsInsn 0x0 [67e8db130000]: call 0x13e1>
*** REGISTERS ***
rax 0
rbx 0
rcx 0
rdx 7f69841ede78
rsi 1
rdi 401775
rsp 7f69841ede60
rbp 0
r8 0
r9 0
r10 0
r11 0
r12 0
r13 0
r14 0
r15 0
*** STACK ***
7f69841ede60 7f69841ede68
7f69841ede68 0
7f69841ede70 1
7f69841ede78 7f69841edff0
7f69841ede80 0
7f69841ede88 0
7f69841ede90 10
7f69841ede98 78bfbfd
7f69841edea0 6
7f69841edea8 1000

A.6 内存

1
2
3
4
5
6
7
8
9
def callback_address(ql: qiling.Qiling):
address_to_map = 0x123456780000
if ql.mem.is_mapped(address_to_map, 0x2000):
return
ql.mem.map(address_to_map, 0x2000, UC_PROT_READ | UC_PROT_WRITE)
ql.mem.protect(address_to_map, 0x1000, UC_PROT_READ)
addresses: list[int] = ql.mem.search(re.compile(b"[a-zA-Z0-9]{4,}\0"), 0x400000, 0x500000)
print('\n'.join(f"{hex(x)} {ql.mem.string(x)}" for x in addresses))
ql.mem.unmap(address_to_map, 0x2000)

qiling 支持对内存进行映射、解除映射、搜索等操作。如上,ql.mem.is_mapped可查询某地址开始往后一段空间是否已经被映射,ql.mem.protect用于修改某段内存的权限,ql.mem.search可查询某段内存中的值并返回所有查询结果,查询可使用字节数组或正则表达式。ql.mem.string可便捷地提取内存中某个地址开始的字符串,但字符串中不可包含不可打印字符。

部分输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
0x4ad2c7 ENOCSI
0x4ad2ce EL2HLT
0x4ad2d5 EBADE
0x4ad2db EBADR
0x4ad2e1 EXFULL
0x4ad2e8 ENOANO
0x4ad2ef EBADRQC
0x4ad2f7 EBADSLT
0x4ad2ff EBFONT
0x4ad306 ENONET
0x4ad30d ENOPKG
0x4ad314 EADV

A.7 pack

qiling 支持整数类型与字符数组的相互转化,甚至还能够将字符数组转化为 C 语言的结构体。字符数组与结构体的转化是通过 Python 自带模块实现的,规则参考 Python 文档

1
2
3
4
5
6
7
8
print(emu.pack8(0x12))          # unpack 将字符数组转化为整数
print(emu.pack16(0x1234))
print(emu.pack32(0x12345678))
print(emu.pack64(0x1234567890abcdef))
print(emu.pack8s(-0x12)) # 有符号值
print(emu.pack16s(-0x1234))
print(emu.pack32s(-0x12345678))
print(emu.pack64s(-0x1234567890abcdef))

输出:

1
2
3
4
5
6
7
8
b'\x12'
b'4\x12'
b'xV4\x12'
b'\xef\xcd\xab\x90xV4\x12'
b'\xee'
b'\xcc\xed'
b'\x88\xa9\xcb\xed'
b'\x112To\x87\xa9\xcb\xed'