我只是在编写一个PE文件parsing器的过程中,我已经达到了我想parsing和解释PE文件中的实际代码,我假设存储为x86操作码。
例如,DLL中的每个导出都指向函数将存储在内存中的RVAs(相对虚拟偏移量),而且我写了一个函数将这些RVA转换为物理文件偏移量。
问题是,这些是真正的操作码,还是其他的东西?
它依赖于编译器/链接器如何将函数存储在文件中,或者是一个或两个字节的X86操作码。
作为一个例子,Windows 7 DLL“BWContextHandler.dll”包含四个函数,这些函数被加载到内存中,使得它们在系统中可用。 第一个导出的函数是'DllCanUnloadNow',它位于文件中的偏移量0x245D处。 该数据的前四个字节是: 0xA1 0x5C 0xF1 0xF2
那么这些是一两个字节的操作码,还是其他的东西?
如果任何人都可以提供有关如何检查这些信息,将不胜感激。
谢谢!
在进一步阅读之后,通过IDA的演示版运行这个文件,我想我是正确的,第一个字节是0xA1,是一个字节的操作码,意思是mov eax。 我从这里得到了: http : //ref.x86asm.net/geek32.html#xA1 ,我认为这是正确的。
但是,对于后面的字节如何构成指令的其余部分,我有点困惑。 从我知道的x86汇编程序中,移动指令需要两个参数,即目的地和源,因此指令将(东西)移动到eax寄存器中,并且假设某些东西出现在下面的字节中。 但是我不知道如何阅读这些信息:)
x86编码是复杂的多字节编码,您不能简单地在指令表中找到一行来解码它,因为它在RISC(MIPS / SPARC / DLX)中。 一个指令甚至可以有16个字节的编码:1-3字节操作码+多个前缀(包括多字节VEX )+多个字段来编码立即数或存储器地址,偏移,缩放(imm,ModR / M和SIB; moffs)。 有时候有几十个操作码可以用于单个助记符。 而且,对于几种情况,在同一个asm行(“inc eax”= 0x40和= 0xff 0xc0)中有两种可能的编码方式。
一个字节的操作码,意思是mov eax。 我从这里得到了: http : //ref.x86asm.net/geek32.html#xA1 ,我认为这是正确的。
我们来看看桌子上的情况:
宝 flds; 助记符; op1; op2; grp1; grp2; 描述
A1; W; MOV; eAX; Ov; gen; datamov; 移动;
(提示:不要使用geek32表,切换到http://ref.x86asm.net/coder32.html#xA1 – 具有较少解码的字段,例如“A1 MOV eAX moffs16 / 32 Move”)
有op1和op2列,用于操作数的http://ref.x86asm.net/#column_op 。 第一个A1操作码总是eAX
,第二个(op2)是Ov。 根据表http://ref.x86asm.net/#Instruction-Operand-Codes :
O / moffs Original指令没有ModR / M字节; 操作数的偏移量在指令中被编码为一个字,双字或四字(取决于地址大小属性)。 不能使用基址寄存器,变址寄存器或比例因子(仅MOV(A0,A1,A2,A3))。
所以,在A1操作码之后,内存偏移被编码。 我认为,x86(32位模式)有32位偏移量。
PS:如果你的任务是解析PE而不是发明反汇编,使用libdisasm或者libudis86之类的x86反汇编库。
PPS:对于原来的问题:
问题是,这些是真正的操作码,还是其他的东西?
是的“A1 5C F1 F2 05 B9 5C F1 F2 05 FF 50 0C F7 D8 1B C0 F7 D8 C3 CC CC CC CC CC”是x86机器码。
反汇编是困难的,特别是由Visual Studio编译器生成的代码,特别是对于x86程序。 有几个问题:
指令是可变长度的,可以从任何偏移量开始。 一些架构需要指令对齐。 不是x86。 如果你从地址0开始阅读,那么你将得到不同的结果,然后如果你开始读取偏移量1.你必须知道有效的“起始位置”(函数入口点)是什么。
可执行文本文本部分中的所有地址都不是代码。 有些是数据。 Visual Studio将放置“跳转表”(用于实现switch语句的数组)在读取它们的过程中的文本部分。 将数据误解为代码将导致您产生不正确的拆卸。
你不能有完美的组合,将与所有可能的程序工作。 程序可以修改自己。 在这些情况下,你必须运行该程序来了解它的功能,最终导致“停止问题”。 你可以期望的最好的解体是“最”程序。
通常用于试图解决这些问题的算法被称为“递归下降”拆分。 它的作用类似于递归下降解析器,它以一个已知的“入口点”(exe的“main”方法或者一个dll的所有输出)开始,然后开始反汇编。 其他入口点在拆卸期间被发现。 例如,给定一个“调用”指令,目标将被认为是一个入口点。 解散程序会反复拆卸发现的入口点,直到找不到更多的入口点。
但是,这种技术有一些问题。 它不会找到只能通过间接执行的代码。 在窗口上,一个很好的例子是处理SEH异常。 调度给他们的代码实际上是在操作系统内部的,所以递归下降解体不会找到它们,也不会反汇编它们。 然而,它们往往可以通过增加模式识别的递归下降(启发式匹配)来检测。
机器学习可以用来自动识别模式,但是许多拆卸器(如IDA pro)使用手写模式并取得了很大的成功。
无论如何,如果你想拆卸x86代码,你需要阅读Intel手册 。 有很多需要支持的场景。 指令中的相同位模式可以根据修饰符,前缀,处理器的隐式状态等以各种不同的方式进行解释。这些都在手册中进行了介绍。 首先阅读第一卷的前几部分。这将贯穿基本的执行环境。 大部分你需要的东西是在第二卷。