您的位置:首页 > 其它

自己的反汇编引擎——Intel指令编码(1)

2015-09-24 08:03 337 查看
写在前面:

学习Intel指令格式已经有近一个月了,本来想把整个反汇编引擎写完整之后再发布源代码和学习报告的,但是,最初的热情过后,剩下的就是辛苦劳动了,现在实在太累了,似乎有点写不下去的感觉了,所以我还是打算,边总结学习的过程,边完成整个反汇编引擎:一方面,希望论坛里对指令解码知识感兴趣的朋友,高手给些鼓励;另一方面,希望能和这些朋友们讨论程序中的bug,讨论整个反汇编引擎的架构(这个我会在后面的学习报告中详细说明我所了解的一些架构)。学习新的知识是一件很令人高兴和满足的事情,但是能和别人分享学习的经验,更令人快乐。

学习指令编码格式的好处有很多,我在这里提一些吧:

一、加深对指令的了解。并不是用汇编语言写出的程序速度就一定比其他高级语言快,或者说节省空间,现在大部分的编译器做得比一般的汇编语言初学者,甚至是有一定编程经验的人都好,对一些汇编指令有所了解后,可能利用这些指令写出符合特定条件的好的代码,不管是用在shellcode还是用在关键代码的性能优化方面都有好处。例如:Svin的教程中就有一个题目:

用四个byte实现下列的算法:(opcode hack)

IF ZF=1

inc eax

ELSE

mov al,40

再有,现在的高级语言因为执行效率的原因,一般都舍弃用leave和enter指令,然而这些指令有着空间的优势……等等,此外,学习了指令编码之后会对intel的寻址模式有一个更为深刻的了解。

二、学习了指令编码可以软件保护中的很多技巧如花指令等有更深刻的了解。

三、如果这些小的技巧实在是不值一提,那么如果想些一个虚拟机架构的话,就必须对这些指令有所了解。

……

由于我是一个菜鸟,所以有很多说不清楚的地方,还希望高手指正,毕竟,讨论才是学习永恒的主题。本来打算,把这些学习报告发在新手区的,但是看到这个版块有一个专题,就发到这个地方了。很多高手可能想自己学习研究,我会提前把我找到的所有的资料都列在附件中。

实验反汇编引擎介绍:(原代码下载:dasm.rar

引擎采用了最直观,当然也是最笨拙的方法,switch...case,代码虽然不够简练,但是执行效率和整体结构还是很清楚的,代码的解析和识别只剩下力气活了。

反汇编引擎目前的进度:

基本框架已经实现,能解析的指令大约200多条,2-byte的指令还不能解析,浮点指令和mmx指令的解析都还待完成。

(2008.10.22更新:已经能正确解析所有常用指令(除特权指令,浮点指令,mmx指令之外的指令))

(2008.10.24更新:改正了解析C4,C5指令的一个小bug)

测试程序:(CrackMe.exe为测试用的pe文件)

我用了不久前学习pe文件格式的时候写的pe文件解析代码,提取了.text中的数据作测试用,pe文件的解析部分代码很丑,大家感兴趣的话,可以只看反汇编部分的代码,代码没有加注释,我想我会在后续的学习报告中详细解释每一个部分的代码。

程序的运行方式:dasmMain.exe 待解析的pe文件 >out.txt 最好重定向到文件中看,输出的结构有点多。

由于有些指令不能识别,导致实际的反汇编代码跟正确的比有些混乱,但是大部分代码还是正确的,我测试的输出结果如图:



跟olly的结果比较一下:



对得不是太齐,但是如果以后做成GUI的形式,因该没有什么问题了。实际的效果大家可以用不同的pe文件测试,当然,程序只是读文件的.text区,没有任何分析,实际效果,大家还是要对照地址来检查。

学习资料:

1、首推Svin的教程,英文原版:tutorialof
opcode by svin.rar都是一些保存好的网页,大家可以权当链接使用。当然,论坛上也有翻译后的版本,大家可以找一找,不过还是推荐到原论坛看原帖。

2、The art of disassembly,英文原版:ArtOf
Disassembly.part1.rarArtOf Disassembly.part2.rarArtOf
Disassembly.part3.rar,当然论坛里也有中文版的。

3、罗聪的《学习Opcode教程》:learningopcode.rar

4、指令列表:codetable.rar,网上不同的版本很多,但是这个是我见到的做的最好的一份,实际的解码过程也是参照这个表做的,当然同时参考的少不了:

5、Intel® 64 and IA-32 Architectures Software Developer's Manual 2A Instruction Set Reference A-M.pdf

6、Intel® 64 and IA-32 Architectures Software Developer's Manual 2B Instruction Set Reference N-Z.pdf,上面两个就不传上来了,intel的网站上就有。

上面列出来的是能找到的所有关于intel指令编码的资料了,很多教程都不完整,或者没有实现一个真实的反汇编引擎,我想这也是为什么,我想把学习和实现反汇编引擎的经验写出来的原因之一,希望我能写出一个完整的学习过程来。

第一部分:总体介绍

反汇编引擎的目的就是要把机器码翻译成汇编语言的格式,主要的汇编格式有Intel格式、AT&T格式,一般在window环境中使用的大多数都是intel格式的汇编语言。这里从官方手册的介绍中总体介绍这两部分的内容,只有知道机器码的格式,汇编指令的格式,才能在其上架起一座桥梁——汇编或反汇编。这里我们习惯称汇编指令为Intruction operand,而称机器码为Intruction Opcode。

1.1 Intel汇编格式(Instruction operand)

在官方手册中intel汇编有着固定的格式:

label: mnemonic argument1, argument2, argument3

(1) lable:标签,表面意思就是这条指令的一个指代,实际代表着这条指令在内存中的起始位置。

(2) 助记符:用英语代表机器码的操作,汇编器会根据这个助记符寻找合适的机器码。

(3) argument1, argument2, argument3:实际上intel指令最多也只有三个操作码,当只有两个操作码的时候,第一个为目标操作码,第二个为源操作码。

1.2 intel机器码格式(Instruction Opcode)

汇编语言的格式反映了机器码的编码格式,直观地看,只要给汇编代码的每个部分都分配相应的字节就行了,例如:mnemonic两个字节,argument1-3分别4个字节,这样汇编语言与机器码之间真的就是直接对应的关系了,在这两个部分转换至需要维持一张简单的表就行了。但实际上,intel的指令体系为复杂指令系统(CISC),它这里的复杂绝非浪得虚名,由于以往的机器上内存是个很昂贵的设备,因此,intel的指令编码尽可能地利用了每一个bit,再加上兼容性的考虑,使得整个intel指令结构异常复杂。远远不是一个部分和另一个部分简单的映射那么简单。

物理上,CPU的逻辑运算单元只操作计算机中的两个对象:寄存器和内存。只要给每个寄存器一个编码,那么寄存器的辨别就很容易了,但是内存呢?物理上,内存是个一维的存储单元阵列,逻辑上内存被分成段,页之类的格式,要操作内存,那么指令就要给出操作内存的哪个(哪些)存储单元,这里“哪”指的是寻址模式,这里的“些”和“个”是指要操作的内存的大小,byte, word, dword……。除了这两个操作对象之外,还有一种对象,那就是立即数(immediate),物理上指令执行时,这个数字是在CPU中的,也就是CPU取得的指令中,这个数就已经在那里了。所有的指令编码都是围绕着这三个操作对象进行的,不同的是立即数不需要去找,寄存器简单的编码就行了,而内存不但需要指出其位置,还要指出其大小。此外,还有一些辅助的操作说明,比如是否重复一些操作等等。

看一下intel的确切的指令格式:


prefix部分是指令操作的一些辅助说明,如果先不看prefix部分,其他部分的表面涵义是很明确的:opcode编码了进行什么样的操作,跟汇编格式里面的mnemonic对应,CPU知道了什么操作之后就会寻找操作的对象,是寄存器还是内存?ModR/M部分就给出了操作的对象,R是register,M是memory,而Mod指示了到底是寄存器还是内存。如果ModR/M的字节数足够大的话,那么或许就不需要后面的两个部分了,实际上ModR/M只有一个字节,能编码所有的寄存器,却不能编码所有的内存寻址模式,intel使用后面两个部分来辅助ModR/M完成确切的内存定位——SIB和displacement。寻址方式跟CPU对内存的管理密切相关,intel的寻址方式很多,但全部都编码到了SIB和displacement之中。这部分到SIB部分再详细介绍。内存寻址后面就跟了最后一个操作对象Immediate。

指令编码的整个结构还是很清楚的,但也可以看到,每一个部分都有小的子结构,代表着不同的涵义。反汇编就是要读懂机器码的每个部分,然后翻译成汇编格式。在后面的各个部分将把我对各个部分的了解都写出来。



(简单说明一下,关于汇编指令和机器码之间的对应关系[并非一一对应],可以看看Svin的教程opcode#1和罗聪的《学习Opcode》,我想我主要关注的是反汇编引擎的实现细节,这些知识是重要的,但是既然前辈高手都已经写得很清楚了,就没有必要再重复了。如果真的想了解学习汇编指令格式,无论如何,这两份教程是一定要认真看的。)



1.3 调试实验环境的简单说明:

就像Svin在教程的开头就写的那样,学习指令格式,最重要的就是实验,动手,看实际的效果。实际上,要写出反汇编程序出来,首先得学会自己查表手工翻译指令。不同反汇编引擎的结构不一样,但是都是建立在对Intel指令结构各个部分的理解之上的,而要想熟悉各个部分,必须亲自动手,要多动手(查找资料的时候看到论坛里有些朋友也想实现自己的反汇编引擎,但是不知什么原因却没有动手,其实动手后就会发现,一切都很简单,如果不求代码的优美,我这样的菜鸟都能写出一个)。

(1)指令察看:

如果想知道一个汇编指令对应的机器码,或者说一些机器码对应的汇编指令,最简单的办法就是使用现有的工具,首推Ollydbg。Svin给出了一个简单的程序,在Ollydgb中当作“白纸”来用(当然也可以随便打开一个pe文件),可以在上面随便输入汇编指令,或机器码,查看对应的翻译。(程序源代码和可执行程序下载:blank.rar

可以在汇编栏双击随便输入一些指令,机器码部分就会显示相应的机器码。或者Ctrl+E在机器码部分随便输入一些机器码,可以在汇编栏看到对应的反汇编指令,大家可以动手做一下。

(2)反编译器测试框架:

用C语言构架吧,想要测试自己的反汇编引擎是否正确工作,首先得假设一个调试环境。可以使用shellcode中的方式,假设待反编译的指令位于一个字符串中:Code[] = "\X90\X90\X90\X90"...然后在程序中现解析这些数据,看看效果。

目前,根据上图定义的指令格式可以写出指令的一个结构体来,所有的指令理论上都能解析并存放到这个指令结构的各个部分,这是最直观的定义。

代码:

typedef struct _INSTRUCTION

{

/* prefixes */

char RepeatPrefix;

char SegmentPrefix;

char OperandPrefix;

char AddressPrefix;

/* opcode */

unsigned int Opcode;

/* ModR/M */

char ModRM;

/* SIB */

char SIB;

/* Displacement */

unsigned int Displacement;

/* Immediate */

unsigned int Immediate;

/* Linear address of this instruction */

unsigned int LinearAddress;

} INSTRUCTION, *PINSTRUCTION;

各个部分的大小都是根据指令结构中最大的字节数定义的。

我们再定义一下,反汇编的程序Disassemble(),最直观的就是输入指令的起始地址,返回下一条指令的起始地址(很多反汇编引擎都是返回指令的长度,但返回下一条指令的起始地址更直观),把指令的解析结果放在INSTRUCTION中,把反编译出来的字符串存放在一个缓冲区中。可以这样定义:

unsigned char *Disassemble(unsigned char *Code, PINSTRUCTION Instruction, char *InstructionStr);

InstructionStr为汇编指令格式,按照intel汇编指令格式的定义我们可以定义如下字符串prefix mnemonic operand1, operand1, operand3,然后把解析的结果分别放入这些字符串中,最后把这些字符串组组合起来就得到了最后的指令。

我们可以仿照The Art of Disassemlby中介绍的,先写出一个字节读取显示的框架程序(源代码下载:dasm_frame.rar)。

运行结果如图所示:

下面将按照intel指令格式分五个部分

(Prefixes, Opcode, Mod/RM, SIB+Displacement, Immediate)分别介绍各个部分的结构和解析方法,最后再介绍如何利用这些部分的解析子程序解析不同的指令,最终实现一个反汇编引擎。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: