如何实现反汇编?
实现反汇编第一次写这样的文章,由于用过的汇编指令有限,对有的IntelIA汇编指令的理解并
不是很透彻,所以在下的文章和程序中如果有不正确的理解和BUG,敬请指出。
下面的反汇编是按照Intelx86的体系结构来实现的,实现的是绝大部分的单字节指
令的翻译,对于二字节指令和一些MMX,SSE,SSE2指令没有实现,不过可以在现有程
序框架中进行扩充。
文章和程序的参考资料有:
1、Intel的IA-32IntelArchitectureSoftwareDeveloper'sManualV2:Instruction
SetReference;
2、《应用程序调试技术》一书附带的光盘中的调试器WDBG程序;
3、GDB源码中的反汇编部分;
如果有以上资料,特别是Intel的这本书,可以对照着看下面的原理部分,因为文章
中的表格都是查这个书上的,表格我就不画了,太多了,呵呵,偷个懒,:)。
一、反汇编的原理
1、Intel的IA-32指令的结构:
IntelIA的指令由以下几个部分组成:
前缀——1字节,所有指令的前缀分成4组(组1:LOCK和REP,组2:忽略的段前缀,
组3:忽略操作数大小的前缀,组4:忽略地址大小的前缀);
操作码——1、2或3字节,2字节指令是第一个指令码为0x0f,而一般3字节指令的第
3字节是ModR/M的一部分,具体见下文;
ModR/M字节——1字节,这个字节指示了后面跟随的操作数的形式,它分成3个部分:
第7-6位是Mod部分,第5-3位是Reg部分,第2-0位是R/M部分,其中Mod和R/M部分结合
指示了指令操作数的寻址方式,而Reg部分主要是指示用到的寄存器,这个字节的解
释要和主操作码结合起来,我认为这是翻译指令中最主要的部分;
SIB字节——1字节,除了上面这个ModR/M字节外,有时候指令还需要一个字节来补充
指操作数的寻址方式,这个字节也分成3个部分:第7-6位是scale部分,第5-3位是index
部分,第2-0位是base部分;
偏移量——1、2或4字节;
立即数——1、2或4字节;
2、操作数的解码:
反汇编就是把二进制字节流翻译成汇编代码的字符串,而汇编代码由操作码和操作数
两部分组成。在翻译过程中,以指令的操作码为索引从相应的数组来输出对应的指令
字符串,而接下来最主要的就是对操作数的翻译,所有的操作数都可以用:
寻址方式+操作数类型
来表示,寻址方式有A(直接地址),C(ModR/M字节的REG部分指示一个控制寄存器),D(
ModR/M字节的REG部分指示一个调试寄存器),E(ModR/M字节指示的操作数是一个通用
寄存器或一个内存地址),F(EFLAGS寄存器),G(ModR/M字节的REG部分指示一个通用寄
存器),I(立即数),J(指令包含一个相对地址),M(ModR/M字节指示一个内存地址),O(没
有ModR/M字节,后面的字或双字为偏移地址),P(ModR/M字节的REG部分指示一个4字节
MMX寄存器),Q(ModR/M字节指示的操作数是一个MMX寄存器或一个内存地址),R(ModR/
M字节的Mod部分指示一个通用寄存器),S(ModR/M字节的REG部分指示一个段寄存器),
T(ModR/M字节的REG部分指示一个测试寄存器),V(ModR/M字节的REG部分指示一个128
位XMM寄存器),W(ModR/M字节指示的操作数是一个128位XMM寄存器或一个内存地址),
X(内存地址由DS:SI寄存器对表示),Y(内存地址由ES:DI寄存器对表示);
操作数类型主要有b(字节),d(双字节),w(字),v(字或双字,根据操作数大小定)。
根据以上的解释就可以查看指令表了,表可以参考上面Intel的那本书中的附录A中的
TableA-1到TableA-20,对ModR/M字节的翻译可以参考第2章中的表Table2-1到Table
2-3,根据这些表就可以写出反汇编所用的数据结构了。
3、下面举个例子来说明如何根据这些表来进行反汇编:
如果二进制序列是0x03,0x70,0xe8,则操作码是0x03,查指令表TableA-2是"addGv,
Ev",第2个字节0x70是ModR/M字节,二进制是01110000,则Mod部分是1,REG部分是6,R/
M部分是0,查ModR/M字节表Table2-2得到Gv操作数是esi,Ev操作数是[eax+disp8],disp8
是8位的偏移量,则读下一个字节0xe8,由于这个字节是负数,取相反值是0x18,则Ev操
作数是[eax-0x18],所以输出的最终指令是"addesi,[eax-0x18]"。
4、扩展指令:
除了单字节指令和二字节指令外还有一部分指令是三字节的,这些指令称作扩展指令
,扩展指令是把ModR/M字节当作操作码的一部分,根据指令属于的不同group和REG部
分的值来查表TableA-4翻译指令的,如操作码为0x80-0x83的指令就是属于group1
,然后根据REG的值来确定操作码的字符串,如REG为5则是"sub"指令。
5、浮点数指令:
浮点数指令不知道为什么叫逃逸码指令(escapeopcodeinstructions),呵呵。它是
主操作码为0xd8-0xdf的指令,并且用这个主操作码分成几组,每组指令中又根据ModR
/M字节的值分成值在0x00-0xbf范围中和不在这个范围中两部分,在这个范围中的指令
根据ModR/M字节字节中的REG部分进行索引,得到相应的指令;不在这个范围中的指令
则按照ModR/M字节的值查找相应的表得到指令,ModR/M字节中的高4位是行号,低4位是
列号。
二、程序说明
以下是一个反汇编的程序和它的测试程序
disasm.h反汇编需要的数据结构的定义
disasm.c实现反汇编的主要函数
main.c使用以上反汇编函数的测试程序
程序中有详细的注解,这里是注解以外的一些说明。
1、程序实现的主要是单字节指令的翻译,二字节部分(也就是指令如0x0f22等这些
以0x0f为第一个字节的指令)在程序中屏蔽掉了(因为对比了VC等DEBUGER反汇编出
来的程序,好象都是只把程序翻译成单字节指令),但是在disasm.h中二字节部分的
数据结构还是写出来了,只不过在disasm.c中把这部分的实现注释了;
2、关于跳转指令
程序中的跳转指令有两种:一是绝对地址,是直接写出seg:offset的(如0xea指令)
;二是相对地址,是以读入数据的开始地址加上地址字节的值为最终地址,在一般的
DEBUGER中,某个函数经过重定位被读入某个特定的地址,则在这个函数中的跳转指
令即以这个函数的开始地址为基准,加上地址值为最终的地址值,而函数的开始地址
往往好象是从符号表中读出的,由于这个程序中没有程序符号表的相关部分,所以在
测试程序中,反汇编出来的跳转指令地址值就是以input数组的开始地址为基准计算
的,所以在实际使用时(如作为DEBUGER中的一部分时)也许对这个部分还要做些修
改;
3、关于扩展指令的部分
在程序中这部分的指令的解释不全,数据结构也只简单的写了个主要的部分,如果有
兴趣的读者可以扩展相应的数据结构和程序来完成所有的指令解释。如果要解释所有
的扩展指令需要把grouptab数组结构重新定义,扩展成可以解释操作码和操作数的形
式,类似于esc_tab结构那样;
4、关于浮点数指令的部分
当操作码在0xD8-0xDF时,是浮点数指令,在程序中解释了大部分的浮点数指令,不
过由于没用这些指令编过程序,所以只能按照书上写的和VC的DEBUGER的结果来写了
,也许不全,但可以进行扩充;
5、关于测试程序
程序是在windows2000下用VC6.0编译的,不过是用标准C写的,所以可以移植到LINUX下
,不过也许有些地方要改动。测试程序是打开一个可执行程序,然后读出一定偏移的
二进制码,翻译后写入另一个文件中,这个程序可以结合符号表来找到代码段进行翻
译,不过我没有试过。程序可以结合VC带的工具dumpbin来看,就是用dumpbin/disasm
命令生成那个可执行程序的反汇编文件,然后看一定偏移处的反汇编结果和程序生成
的结果是否一样。如用命令行得到的反汇编文件中偏移为0x00401110处的汇编程序有
下面一段:
00401110:55pushebp
00401111:8BECmovebp,esp
00401113:83EC40subesp,40h
00401116:53pushebx
00401117:56pushesi
00401118:57pushedi
00401119:8D7DC0leaedi,[ebp-40h]
0040111C:B910000000movecx,10h
00401121:B8CCCCCCCCmoveax,0CCCCCCCCh
00401126:F3ABrepstosdwordptr[edi]
则在main.c中设置offset=0x1110,生成的结果文件中可以得到如下一段:
pushebp
movebp,esp
subesp,0x40
pushebx
pushesi
pushedi
leaedi,[ebp-0x40]
movecx,0x10
moveax,0xcccccccc
repstosdwordptr[edi]
当然格式不如它的好看了,呵呵,不过格式可以调整的,:)。
http://www.smth.edu.cn/bbsanc.php?path=%2Fgroups%2Fcomp.faq%2FCrack%2Faboutcrack%2Fasm%2FM.1053718961.p0
本文地址:http://www.45fan.com/dnjc/71235.html