如何在 Python 中使用 Unicorn 正确模拟 x86?

How can I properly emulate x86 with Unicorn in Python?

背景/我正在努力完成的事情的解释

我目前正在从事一个小的恶意软件分析项目,并试图实现我使用 Unicorn 编写的字符串解密器。为了压缩内容并使代码更易于审查,我在下面从我较大的代码库中制作了一个较小的示例。

我正在做的是提取代表小字符串解密例程的 x86 片段。有一系列的 mov 指令最终被异或生成明文字符串。我已经注释掉了应该产生的字符串值。在下面的示例中,未注释的 X86_CODE64 指令被模拟,但仅在我从堆栈地址读取时导致 hpe.com 。 (提示:要查看输出,asdf.txt 上的 运行 个字符串)我希望看到 apple.comhpe.com

问题

根据下面的代码,是否有我做错/根本没有做的事情会导致以下代码片段无法正确解密字符串?

免责声明:这是我第一次使用独角兽,所以如果我表达不清晰或解释有困难,我提前道歉!

#!/usr/bin/python

from __future__ import print_function
from unicorn import *
from unicorn.x86_const import *

# code to be emulated
# Strings should include apple.com and hpe.com
X86_CODE64 = b'\xc7D$<\xa9GY\x01\xc7D$@\xa2XQ/\x8bD$<\x8aD\x84\xc0u\x19H\x8b\xcb\x8bD\x8c<5\xc17</\x89D\x8c<H\xff\xc1H\x83\xf9\x02r\xeaE3\xc0H\x8dT$<H\x8b\xcf\xe8<\xd2\xfe\xff\x88]\xa4\xc7E\xa8\x86/\x00v\xc7E\xac\x82q\x13u\xc7E\xb0\x8a_p\x1a\x8bE\xa8\x8aE\xa4\x84\xc0u\x19H\x8b\xcb\x8bD\x8d\xa85\xe7_p\x1a'

# Strings should be svchost.exe
#X86_CODE64 = b"\xba\xe7_p\x1a\xc7D$|\x94)\x13r\xc7E\x80\x88,\x044\xc7E\x84\x82'\x15:\x89U\x88\x8bD$|\x8aD$x\x84\xc0u\x16H\x8b\xcf\x8bD\x8c|3\xc2"

# Strings should be apple.com
#X86_CODE64 = b'\xc7E\xa8\x86/\x00v\xc7E\xac\x82q\x13u\xc7E\xb0\x8a_p\x1a\x8bE\xa8\x8aE\xa4\x84\xc0u\x19H\x8b\xcb\x8bD\x8d\xa85\xe7_p\x1a'


# Set up Unicorn
ADDRESS = 0x10000000
STACK_ADDRESS = 0x90000
mu = Uc(UC_ARCH_X86, UC_MODE_64)
mu.mem_map(ADDRESS, 4 * 1024 * 1024)
mu.mem_map(STACK_ADDRESS, 4096*10)

# Write code to memory
mu.mem_write(ADDRESS, X86_CODE64)
# Initialize Stack for functions
mu.reg_write(UC_X86_REG_ESP, STACK_ADDRESS + 4096)
mu.reg_write(UC_X86_REG_EDX, 0x0000)

# Run the code
try:
    mu.emu_start(ADDRESS, ADDRESS + len(X86_CODE64), timeout=10000)
except UcError as e:
    pass

#a = mu.mem_read(ADDRESS, 4 * 1024 * 1024)
#print(a)
b = mu.mem_read(STACK_ADDRESS, 4096*10)

with open('asdf.txt', 'ab') as fp:
    fp.write(b)

这段代码几乎没有问题。

首先,您可能永远不想像在 except 中至少在顶层编写 pass 那样吞下所有异常。至少为了知道是否发生了意外,将它们写入控制台是件好事。如果你这样做,你会注意到 unicorn 在代码执行期间抛出一个 Invalid memory fetch (UC_ERR_FETCH_UNMAPPED)

如果您分析字节,您会注意到第一个代码中间有一个奇怪的调用

40: e8 3c d2 fe ff          call   0xfffffffffffed281

这个调用是在解密 hpe.com 之后,unicorn 停止执行代码并且永远不会到达代码的第二部分。在 unicorn 中可能有更好的方法来处理这个问题,但现在让我们只调用 nop(将 5 个字节替换为 5x\x90)。这仍然不会产生预期的 apple.com 字符串,因为这段代码有更多问题。第二部分(调用后)不使用 RSP,而是使用 RBP,并且您没有在代码中设置它。

所以我们需要添加:

mu.reg_write(UC_X86_REG_EBP, STACK_ADDRESS + 4096)

还有一个问题。您正在为 64 位设置独角兽,但您初始化了 32 位寄存器 - ESPEDX。这是故意的吗?在您的情况下,这可能不是问题,但您可能应该初始化 64 位 regs。

添加 RBP 以设置为某个堆栈地址后,您仍然看不到第二个字符串,因为代码剪得太早了。最后的指令是read & xor

6a: 8b 44 8d a8             mov    eax,DWORD PTR [ebp+ecx*4-0x58]
6e: 35 e7 5f 70 1a          xor    eax,0x1a705fe7

但是没有存储,没有递增到下一部分,也没有循环。

可能是你复制的字节太少了。如果我们添加那些缺失的字节,那么:89448da8 用于存储 (mov DWORD PTR [rbp+rcx*4-0x58],eax),48ffc1 用于 inc rcx4883f903 用于 cmp rcx, 0x3,最后 72ea 对于 jb -0x16.

因此,您的第一个代码总共遗漏了以下字节 89448da848ffc14883f90372ea(+ nop call)以及

❯ python3 program.py
❯ strings asdf.txt
apple.com
hpe.com

你得到了预期的结果。

简要检查了第二个和第三个代码,似乎没有 call 但它们也缺少商店、公司和循环部分。