capstone、keystone与unicorn-1

capstone

加坡南洋理工大学团队在 Blackhat USA 2014 上发布的一个反汇编引擎。

安装: pip install capstone

基础示例

capstone 的 API 使用起来非常简单,因此使用该框架编写工具非常容易。 下面的代码反汇编一些 x86机器码,并打印出汇编语句。

1
2
3
4
5
6
7
from capstone import *
CODE = b"\x55\x48\x8b\x05\xb8\x13\x00\x00"
md = Cs(CS_ARCH_X86, CS_MODE_64)
for i in md.disasm(CODE, 0x1000):
print("0x%x:\t%s\t%s" %(i.address, i.mnemonic, i.op_str))
#0x1000: push rbp
#0x1001: mov rax, qword ptr [rip + 0x13b8]
1
2
3
4
5
6
7
8
from capstone import *
CODE = b"\x55\x48\x8b\x05\xb8\x13\x00\x00"
md = Cs(CS_ARCH_X86, CS_MODE_64)
for insn in md.disasm(CODE, 0x1000):
print("0x%x:\t%s\t%s" %(insn.address, insn.mnemonic, insn.op_str))

#0x1000: push rbp
#0x1001: mov rax, qword ptr [rip + 0x13b8]

以下是每一行代码的解释:

第 1 行:导入 Python 模块 capstone。

第 3 行:要反汇编的原始二进制代码。

第 5 行:使用类 Cs 为 capstone 初始化 Python 类。 需要给这个类两个参数:硬件架构和硬件模式。

在此示例中,我们要反汇编 x86 体系结构的 64 位代码。

第 6 行:使用上面创建的 Cs 类实例的方法 disasm() 反汇编二进制代码。 disasm 的第二个参数是第一

条指令的地址,在本例中为 0x1000。 默认情况下,disasm 反汇编所有代码,直到没有更多代码或遇

到不可解释的指令。 disasm 返回一个类型为 CsInsn 的指令列表,这里的 for 循环会迭代这个列表。

第 7 行:打印出每条指令的一些内部信息。 CsInsn 类公开了反汇编指令的所有内部信息。 下面介绍了

此类中一些最常用的属性。

///想起来了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def hex_to_file(input_file, output_file):
# 从文件中读取十六进制字符串
with open(input_file, 'r') as f:
hex_string = f.read().strip()

# 将十六进制字符串转换为字节序列
bytes_data = bytes.fromhex(hex_string)

# 将字节序列写入文件
with open(output_file, 'wb') as f:
f.write(bytes_data)

input_file = "11.txt"
output_file = "output" # 输出文件为二进制文件,因为十六进制是字节数据

hex_to_file(input_file, output_file)

image-20240229202523536

更高效的 API

上个示例中使用 disasm() 方法获取 CsInsn 对象。这提供了可用于反汇编指令的完整信息。但是,如果我们只需要地址、大小、助记符和 op_str 等基本数据,我们可以使用更轻量级的 API disasm_lite 。

下面是 disasm_lite 示例

1
2
3
4
5
from capstone import *
CODE = b"\x55\x48\x8b\x05\xb8\x13\x00\x00"
md = Cs(CS_ARCH_X86, CS_MODE_64)
for address, size, mnemonic, op_str in md.disasm_lite(CODE, 0x1000):
print("0x%x:\t%s\t%s" %(address, mnemonic, op_str))

架构和模式

image-20240229204045467

image-20240229204054519

1
2
3
4
5
from capstone import *
CODE = b"\x56\x34\x21\x34\xc2\x17\x01\x00"
md = Cs(CS_ARCH_MIPS, CS_MODE_MIPS64 + CS_MODE_LITTLE_ENDIAN)
for i in md.disasm(CODE, 0x1000):
print("%x:\t%s\t%s" %(i.address, i.mnemonic, i.op_str))

keystone

pip install keystone-engine

1
2
3
4
5
6
7
from keystone import *
code = "mov eax, 3 \n" \
"sub edx, eax \n" \
"jmp edx"
ks = Ks(KS_ARCH_X86, KS_MODE_32)
encoding, count = ks.asm(code)
print("%s (number of statements: %u)" %(encoding, count))

每行代码的注释:

第 1 行:在使用之前导入 keystone 模块。

第 3-5 行:要编译的汇编字符串。 此示例中的代码是 x86 32 位,采用 Intel 格式。 可以用 ; 或

\n 分隔此字符串中的汇编指令。

第 7 行:使用类 Ks 初始化 keystone。 这个类接受 2 个参数:硬件架构和硬件模式。 此示例处理

x86 体系结构的 32 位代码。

第 8 行:使用方法 asm 编译汇编指令。 该函数返回一个编码字节列表,以及 keystone 在编译过

程中处理的输入语句的数量。

第 9 行:打印出指令编码和处理的汇编语句数。

默认情况下,keystone 接受采用 Intel 语法的 x86 汇编。 如果要处理 x86 AT&T 语法,可以简单地切换到语法 AT&T,如下所示。

1
2
3
4
5
6
7
8
from keystone import *
code = "movl $3, %eax \n" \
"sub %eax, %edx \n" \
"jmp *%edx"
ks = Ks(KS_ARCH_X86, KS_MODE_32)
ks.syntax = KS_OPT_SYNTAX_ATT # 添加此行
encoding, count = ks.asm(code)
print("%s (number of statements: %u)" %(encoding, count))

unicorn

unicorn 引擎的用处:

  • 从恶意软件中调用一个有趣的函数,而不会创建有害的进程

  • 解决 CTF 赛题

  • 模糊测试

  • 模拟执行混淆代码

任务 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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 当运行这个程序时,我们可以注意到它自己会打印 flag,但速度非常慢,flag 的每个下一个字节的计算速度越来越慢。这意味着有必要优化程序以获得 flag
from unicorn import *
from unicorn.x86_const import *
from capstone import *
# 这是一个递归函数,我们可以通过 unicorn 来对其进行优化。首先导入 unicorn 模块并实例化一个 Uc 对象:
mu = Uc(UC_ARCH_X86,UC_MODE_64)
# 要使用 unicorn,我们需要手动初始化虚拟内存。 对于这个二进制文件,需要在某处编写代码并分配一个堆栈。二进制文件的基址是 0x400000。 假设堆栈将从地址 0x0 开始,大小为 1024*1024。
# 我们可以通过调用 mem_map 方法来映射一片内存。
BASE = 0x400000
STACK_ADDR = 0x0
STACK_SIZE = 1024 * 1024
mu.mem_map(BASE, 1024 * 1024)
mu.mem_map(STACK_ADDR, STACK_SIZE)
# 现在,我们需要在基地址加载二进制文件,就像加载程序一样,然后将 RSP 设置为指向堆栈的末尾
mu.mem_write(BASE, open("./fibonacci", 'rb').read())
mu.reg_write(UC_X86_REG_RSP, STACK_ADDR + STACK_SIZE - 8)
# 来添加一个 hook 监听模拟过程,看看是哪个语句产生了内存访问错误:
def hook_code(mu, address, size, user_data):
for ins in cs.disasm(mu.mem_read(address, size), 0, 1):
print(hex(address), ins.mnemonic, ins.op_str)
mu.hook_add(UC_HOOK_CODE, hook_code)
# 我们可以启动仿真并运行我们的代码,但需要知道起始地址是什么以及仿真器应该在哪里停止。考虑从地址 0x4004E0 开始模拟代码(main 函数首地址),结尾可以是 0x400575:
mu.emu_start(0x4004E0, 0x400575)
# 运行却产生了报错:
'''Traceback (most recent call last):
File "solve.py", line 15, in <module>
mu.emu_start(0x4004E0, 0x400575)
File "python38\lib\site-packages\unicorn\unicorn.py", line 547, in emu_start
raise UcError(status)
unicorn.unicorn.UcError: Invalid memory read (UC_ERR_READ_UNMAPPED)'''
'''这段代码添加了一个钩子。我们定义了自己的函数 hook_code,它在每条指令的仿真之前被调用。它需
要以下参数:
Uc 实例
指令地址
指令大小
用户数据(我们可以在 hook_add() 的可选参数中传递这个值)'''
# 添加的在里面了
'''0x4004e0 push rbp
0x4004e1 push rbx
0x4004e2 xor esi, esi
0x4004e4 mov ebp, 0x4007e1
0x4004e9 xor ebx, ebx
0x4004eb sub rsp, 0x18
0x4004ef mov rdi, qword ptr [rip + 0x200b42]

image-20240229212524751

1
2
3
4
5
6
def hook_code(mu, address, size, user_data):
if address in [0x4004EF, 0x4004F6, 0x400502, 0x40054F, 0x400560]:
mu.reg_write(UC_X86_REG_RIP, address + size)
if address == 0x400560:
value = mu.reg_read(UC_X86_REG_RDI)
print(chr(value))