我经常遇到一些IDAPython脚本,注意到这些脚本通常使用低效或者错误的IDAPython的API来反汇编或者解码指令(比如,使用idc.GetMenem()
或者idc.GetDisasm()
)。因此,在本篇文章中,我将阐述如何使用IDAPython的指令解码函数来编写一个非常简单的x86模拟器。本文的目的是通过编写这个模拟器来演示IDAPython API的正确使用方式。读完本文后,你应该就能使用IDAPpython解决一些相似的问题了。
让我们从下边的列表展示的示例代码开始,其中包含12个挑战函数,这些函数在循环中被调用并且显示出运行结果。我们的目标编写一个能够不依赖第三方模拟器而静态的计算出函数的返回值的模拟器。
在实际工作中,大家可能会发现自己就是处于这种类似的情况,希望将某个函数视为黑盒子,而不是将其反编译成伪代码。在这种情况下,有很多选择:
先让我们来编译一下这个程序,然后使用参数c1=123
和c2=456
运行一下:
先来反汇编一下测试程序然后定位到challenge_funcs
函数列表,第一个挑战函数如下:
可以轻松的定位到challenge_funcs
表,因为这个表被main
引用了。可以看见,第一个函数也包括其他所有的函数,具有非常独特的格式/模式,我们将基于该模式来设计模拟器。
可以清楚的看见一条pusha
指令(0x401009
)后边跟着两条寄存器赋初值的指令(0x40100A
和0x40100D
),然后有一系列针对这两个寄存器的操作(从0x401010
到0x401076
),最后在popa
指令恢复所有的寄存器钱,结果被赋值到了局部变量中(0x401078
和0x40107B
)。
我们将根据这个代码模式来编写一个小小的函数使其能够识别计算过程的代码序列,然后,编写另一个函数来根据给定的范围来进行计算最后返回值。
本节将实现两个函数:
在正式开始前,先来定义一些脚本需要用到的一些全局变量:
我们从之前的反汇编中可以推导出挑战函数表以及它的大小,所以此处定义了一个RESULT
变量来存储使用参数c1=123
和c2=456
运行挑战函数之后的结果,该结果将被用来验证模拟是否正确。
为了模拟表中所有的挑战函数,可以这么做:
输出是这样的:
使用IDAPython的idautils.DecodeInstruction()
函数来解码指令:
如果解码失败,该函数返回None
,如果解码成功,我们将会得到一个包含该指令及其操作数的指令对象。
以下是比较重要的指令属性:
你可能会想知道opcode和它的itype
之间到底是什么关系。其实很简单,在IDA中,开源数据库处理器模块负责根据opcode来填充itype
字段。在IDA SDK中,你可以找到一个allins.hpp
的头文件。该头文件包含了所有支持的处理器模块的枚举数据其中包含了受支持的所有指令:
不知道为什么,反正NN_
前缀用来表示x86/x64处理器上的指令。
以下是一个解码以及检查指令类型的简单的例子:
通过与idaapi.NN_xxxx
常量一一比较,可以直观的了解解码的指令是什么。
至于操作数,可以通过访问inst.Operands[]
或者inst.OpN
来访问。要获取被解码指令使用的操作数数量不应依赖Operands
数组的长度,因为它总是被解析成UA_MAXOP==8
(参阅ida.hpp)。因此应该使用遍历每个操作数并检查操作数的类型是否是o_void
类型。
操作数的定义是ua.hpp
中的op_t
结构。
以下是一些op_t
的成员:
以下是受支持的操作数类型(o_xxx
):
还有一些操作数成员的含义因操作数的类型而异:
当操作数的类型是o_reg
或者o_phrase
的时候,op_reg
/op_phrase
值包含了对应寄存器的枚举值。就像NN_xxx
专有标签,IDA SDK同样提供了寄存器的名称常量,以及其对应的值;但是,这只适用于x86
/x64
处理器模块。我从intel.hpp
中摘抄了一部分:
不幸的是,这些值并没有被IDAPython导出,但是至少我们知道了足够多的信息来定义下边的一些数据:
以下是完全“反汇编”指令的另一个例子:
这涵盖了指令解码原则。 有关更多信息,请参阅头文件intel.hpp
,allins.hpp
,ua.hpp
和idp.hpp
。
之前我们想出了如何通过挑战函数表来去顶挑战函数地址的方法。现在让我们来写一个使用指令解码来确定需要被模拟的指令的范围的函数吧。
请注意:我使用IDAPython的FindBinary()
函数但是那样违背了本文的目的。出于演示的目的,我仅使用该函数来找到问题中的代码模式:
解码指令的基本模式是在每次成功的解码之后通过inst.size
(在此例中是ea
变量)预先解码地址。之后,应该检查指令的itype
然后检查相应的操作数。
请注意,在stage #2的时候,我开始向后解码。要想正确的在反汇编列表中向后解码,可以使用idc.PrevHead()
函数来获取前边定义的指令(参考第37行)的起始地址。来测试一下:
在上一步中,我们设法获得了模拟执行的开始和结束地址范围,现在,来写一个极简的模拟函数吧(仅支持有限的指令 NOT
,DEC
,INC
,XOR
,SUB
,ADD
):
当函数开始运行的时候,将使用有初始化值的寄存器来填充regs
字典,该字典使用op.reg
作为key,其他的未赋初始化值的寄存器将会被置为0。模拟函数将会进入一个循环中解码每一条指令,它将检查每一条指令的类型(来确定什么操作需要被模拟)以及该指令的操作数(来确定如何得到需要的值)。循环结束之后,将返回一个64-bit的值。
我们可以将模拟函数返回的值和之前刚开始运行时得到的值进行比较来验证模拟的是否正确。
我希望这篇文章对你有所帮助,如果想提出问题或者文中出现错误,请不要犹豫,联系我。可以从这里下载到本文中所使用的文件:文件下载:密码123
原文地址:http://0xeb.net/2018/02/writing-a-simple-x86-emulator-with-idapython/
本文由看雪论坛zplusplus翻译
#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;
}
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
;
; 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
[注意]APP应用上架合规检测服务,协助应用顺利上架!
最后于 2018-3-11 10:21
被zplusplus编辑
,原因: 上传附件