[翻译]使用 IDAPython 写一个简单的x86模拟器
使用 IDAPython 写一个简单的x86模拟器
我经常遇到一些IDAPython脚本,注意到这些脚本通常使用低效或者错误的IDAPython的API来反汇编或者解码指令(比如,使用idc.GetMenem()或者idc.GetDisasm())。因此,在本篇文章中,我将阐述如何使用IDAPython的指令解码函数来编写一个非常简单的x86模拟器。本文的目的是通过编写这个模拟器来演示IDAPython API的正确使用方式。读完本文后,你应该就能使用IDAPpython解决一些相似的问题了。
目录
- 概览
- 反汇编程序
- 编写模拟器
- 指令解码快速入门
- 确定挑战函数的范围
- 模拟执行指令
概览
让我们从下边的列表展示的示例代码开始,其中包含12个挑战函数,这些函数在循环中被调用并且显示出运行结果。我们的目标编写一个能够不依赖第三方模拟器而静态的计算出函数的返回值的模拟器。
#include <stdio.h>
#include <cstdint>
#include <time.h>
#include <stdlib.h>
typedef uint64_t (*challenge_proto_t)(uint32_t, uint32_t);
//-------------------------------------------------------------------------
uint64_t challenge_1(uint32_t c1, uint32_t c2) // 39 operation(s)
{
uint64_t result;
__asm
{
pushad
mov eax, [c1]
mov edx, [c2]
not eax
dec edx
xor edx, eax
xor edx, eax
inc eax
not eax
sub edx, 0x27c12466
inc eax
dec edx
not edx
inc eax
add eax, 0x273804ca
xor edx, 0xaa5a1584
sub eax, edx
not edx
xor eax, 0xf94f7d8c
dec edx
dec eax
sub eax, edx
not edx
dec edx
sub edx, 0xd7b41b83
xor eax, 0xa551a9c7
add eax, eax
dec eax
inc eax
not eax
add edx, 0xa551b974
inc edx
dec edx
not edx
xor eax, 0x200d519
not edx
not eax
sub edx, 0xeb15b7ef
xor eax, 0xb2558b8c
xor eax, 0xda288d90
not eax
not edx
mov dword ptr[result], eax
mov dword ptr[result + 4], edx
popad
}
return result;
}
// .
// .
// challenge_2 .. challenge_12
// .
// .
challenge_proto_t challenge_funcs[] = {
challenge_1,
challenge_2,
challenge_3,
challenge_4,
challenge_5,
challenge_6,
challenge_7,
challenge_8,
challenge_9,
challenge_10,
challenge_11,
challenge_12
};
//-------------------------------------------------------------------------
int main(int argc, char *argv[])
{
if (argc < 4)
{
printf("challenge func[0..%d] challenge1-32 challenge2-32 -> result-64\n", _countof(challenge_funcs));
return -1;
}
uint32_t f = atol(argv[1]) % _countof(challenge_funcs);
uint32_t c1 = atol(argv[2]);
uint32_t c2 = atol(argv[3]);
printf("challenge_%d(%d, %d) -> %016I64X\n", f, c1, c2, challenge_funcs[f](c1, c2));
return 0;
}
在实际工作中,大家可能会发现自己就是处于这种类似的情况,希望将某个函数视为黑盒子,而不是将其反编译成伪代码。在这种情况下,有很多选择:
- 在运行期间使用Appcall然后直接调用挑战函数
- 使用反编译器将其算法转化为伪C语言代码然后重新编译成可以使用的代码,最后使用
- 得到挑战函数的反汇编代码,然后重新汇编,最后使用
- 使用模拟器框架(比如说Unicorn engine)来模拟挑战函数
先让我们来编译一下这个程序,然后使用参数c1=123和c2=456运行一下:
C:\>for /l %a in (0, 1, 11) do @test.exe %a 123 456 challenge_0(123, 456) -> 8FDCE2E203FCAAF2 challenge_1(123, 456) -> E0317E1AB061ED8B challenge_2(123, 456) -> A0A0E0C2279BE734 challenge_3(123, 456) -> 5D18D0A79D07D7D8 challenge_4(123, 456) -> 2583B4EEB62E6042 challenge_5(123, 456) -> D5261E0275AB9805 challenge_6(123, 456) -> F2B3282E143F7927 challenge_7(123, 456) -> 9B9B3CBB0169F4CD challenge_8(123, 456) -> EF51086C5D1AF235 challenge_9(123, 456) -> FC8A97125C0EA232 challenge_10(123, 456) -> EEAE8BEB7996D2E7 challenge_11(123, 456) -> 4F36E6A65AB03929
反汇编程序
先来反汇编一下测试程序然后定位到challenge_funcs函数列表,第一个挑战函数如下:
; ; The challenge functions as referenced from main() ; (12 functions) ; .rdata:0041749C 00 10 40 00 challenge_funcs dd offset sub_401000 .rdata:0041749C ; DATA XREF: _main+57↑r .rdata:004174A0 90 10 40 00 dd offset sub_401090 .rdata:004174A4 30 11 40 00 dd offset sub_401130 .rdata:004174A8 D0 11 40 00 dd offset sub_4011D0 .rdata:004174AC 80 12 40 00 dd offset sub_401280 .rdata:004174B0 30 13 40 00 dd offset sub_401330 .rdata:004174B4 A0 13 40 00 dd offset sub_4013A0 .rdata:004174B8 30 14 40 00 dd offset sub_401430 .rdata:004174BC C0 14 40 00 dd offset sub_4014C0 .rdata:004174C0 30 15 40 00 dd offset sub_401530 .rdata:004174C4 E0 15 40 00 dd offset sub_4015E0 .rdata:004174C8 80 16 40 00 dd offset sub_401680 ; ; The first challenge function ; .text:00401000 ; int __cdecl sub_401000(int c1, int c2) .text:00401000 sub_401000 proc near .text:00401000 ; CODE XREF: _main+65↓p .text:00401000 ; DATA XREF: .rdata:challenge_funcs↓o .text:00401000 .text:00401000 var_8= dword ptr -8 .text:00401000 var_4= dword ptr -4 .text:00401000 c1= dword ptr 8 .text:00401000 c2= dword ptr 0Ch .text:00401000 .text:00401000 55 push ebp .text:00401001 8B EC mov ebp, esp .text:00401003 83 EC 08 sub esp, 8 .text:00401006 53 push ebx .text:00401007 56 push esi .text:00401008 57 push edi .text:00401009 60 pusha .text:0040100A 8B 45 08 mov eax, [ebp+8] .text:0040100D 8B 55 0C mov edx, [ebp+c2] .text:00401010 F7 D0 not eax .text:00401012 4A dec edx .text:00401013 33 D0 xor edx, eax .text:00401015 33 D0 xor edx, eax .text:00401017 40 inc eax .text:00401018 F7 D0 not eax .text:0040101A 81 EA 66 24 C1 27 sub edx, 27C12466h .text:00401020 40 inc eax .text:00401021 4A dec edx .text:00401022 F7 D2 not edx . . . .text:00401076 F7 D2 not edx .text:00401078 89 45 F8 mov [ebp+var_8], eax .text:0040107B 89 55 FC mov [ebp+var_4], edx .text:0040107E 61 popa .text:0040107F 8B 45 F8 mov eax, [ebp+var_8] .text:00401082 8B 55 FC mov edx, [ebp+var_4] .text:00401085 5F pop edi .text:00401086 5E pop esi .text:00401087 5B pop ebx .text:00401088 8B E5 mov esp, ebp .text:0040108A 5D pop ebp .text:0040108B C3 retn .text:0040108B .text:0040108B sub_401000 endp .text:0040108B
可以轻松的定位到challenge_funcs表,因为这个表被main引用了。可以看见,第一个函数也包括其他所有的函数,具有非常独特的格式/模式,我们将基于该模式来设计模拟器。
注意:上传附件及图片大小不得大于30M。
⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途。
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。
复制成功
