自写反汇编引擎(二)
资料:
1.intel指令格式与长度反汇编引擎ADE32分析(https://bbs.pediy.com/thread-54180.htm)
2.编写自己的反汇编引擎(https://bbs.pediy.com/thread-128411.htm)
在上一篇文章中我们已经讲解来看一下基础的IA-32指令,那么在这篇文章我们将具体来实现反汇编引擎的编写,首先上效果图(只是完成了一些基本的内容,其实什么也没有)
接下来,让我们正式开始:
一、编写前准备
在编写引擎的整个中都需要用到的一个东西,定义一个好的结构体有助于我们梳理编写思路,那具体需要定义在结构体中定义那些成员呐?我希望的是能根据自己的具体需要去考虑,毕竟每个人的思路都不一样,虽然我们能借鉴别人的程序,但思路还是我们自己的,也就是说程序是死的,而人是活的(笑),而我将其中需要用到的每一个元素都定义在了结构体中,所以在我编写的时候,我只需要去查看结构体中对应的成员,就可以让整个思路如同一条线路一样在我脑中很清晰的呈现。
typedef struct _INSTRUCTION
{
BYTE RepeatPrefix; //重复指令前缀
BYTE SegmentPrefix; //段前缀
BYTE OperandPrefix; //操作数大小前缀0x66
BYTE AddressPrefix; //地址大小前缀0x67
BYTE Opcode1; //opcode1
BYTE Opcode2; //opcode2
BYTE Opcode3; //opcode3
BYTE Modrm; //modrm
BYTE E; //E区域在前
BYTE G; //G区域在前
BYTE Size; //判断大小(8/16/32)=(1/2/3)
BYTE Sib; //sib
BYTE GRP;
union
{
BYTE DispByte;
WORD DispWord;
DWORD DispDword;
}Displacement; //位移联合体
union
{
BYTE ImmByte;
WORD ImmWord;
DWORD ImmDword;
}Immediate; //立即数联合体
BYTE InstructionBuf[32]; //保存指令代码
DWORD InstrucCompil[32]; //保存汇编指令
DWORD Identification[32]; //指令表示,1表示字符,2表示0x16(8),3表示0x16(16),4表示0x16(32)
DWORD dwInstructionLen; //返回指令长度
}INSTRUCTION, *PINSTRUCTION;
typedef struct _REGISTER
{
BYTE _0;
BYTE _1;
BYTE _2;
BYTE _3;
BYTE _4;
BYTE _5;
BYTE _6;
BYTE _7;
}REGISTER, *REGIETER;
在这里我就根据了自己的需要定义了两个结构体,当然因为我属于菜鸡,所以定义的结构体不是很美观(笑),但我还是希望大家看一下每个成员的具体作用,特别是最后的几个数组、dwInstructionLen和REGISTER结构体,我需要强调一下,这个几个成员是非常重要的,我们如何打印出机器码与对应的汇编代码都与这几个成员有关。(具体的作用我之后会详细讲解)
二、填充opcode表
这一点就是我主要借鉴的地方了,具体思路也非常简单,我们在上一篇文章讲到了指令的组成结构,那么这里就是需要将这些结构进行区分与定义,我们给每一个属性定义一个标识,再将opcode表中的内容划分为不同的标识组合,那么在我们查询指令的时候,就可以直接查询这些标识了,这是非常利于我们编写代码的,任何一种标识所对应的属性,我们都能一目了然,这也就大大提升了我们的效率,这一部分网上有许多的文章进行了详细的讲解,我就不再赘述了(其实是我的理解还十分浅显,远远不如网上的大神们,就不在这里献丑了)。在本篇文章的开篇处给出的资料相信会对你有一个很好的启发,而我就在列举一下我的标识就行了。
#define ModRM 0x00000001 //含有ModRM
#define Imm8 0x00000002 //后面跟着1字节立即数
#define Imm16 0x00000004 //后面跟着2字节立即数
#define Imm66 0x00000008 //后面跟着立即数(Immediate),立即数长度得看是否有0x66前缀
#define Addr67 0x00000010 //后面跟着偏移量(Displacement),偏移量长度得看是否有0x67前缀
#define OneByte 0x00000020 //只有1个字节,这1个字节独立成一个指令
#define TwoOpcode 0x00000040 //0x0F,2个opcode
#define ThreeOpCode0F38 0x00000080 //0x0F38,3个opcode
#define ThreeOpCode0F3A 0x00000100 //0x0F3A,3个opcode
#define Grp 0x00000200 //Group表opcode
#define Reserved 0x00000400 //保留
#define MustHave66 0x00000800 //必须有0x66前缀,只在opcode38/3A表中有这样的指令
#define MustHaveF2 0x00001000 //目前只有一个指令是必须有0xF2前缀的:0FF0
#define MustHavePrefix 0x00002000 //必须有前缀
#define MustNo66 0x00004000 //必须没有0x66前缀,在扫描指令时出现66则取标志指令看是否有此标志,有则直接返回1,说明此66前缀是多余的
#define MustNoF2 0x00008000 //意义同上
#define MustNoF3 0x00010000 //意义同上
#define StringInstruction 0x00020000 //指令重复前缀0xF2 0xF3(REPNE REP/REPE)在Opcode1表中只能与下面7组字符串指令组合,
// 0xA4: 0xA5: MOVS
// 0xA6: 0xA7: CMPS
// 0xAE: 0xAF: SCAS
// 0xAC: 0xAD: LODS
// 0xAA: 0xAB: STOS
// 0x6C: 0x6D: INS
// 0x6E: 0x6F: OUTS
#define Uxx 0x00040000 //rm用于寻XMM,只能在mod==11时才可以解码,可能的opcode: 66 0F 50/C5/D7/F7 F2 OF D6
#define Nxx 0x00080000 //rm用于寻MMX,只能在mod==11时才可以解码,可能的opcode: OF C5/D7/F7 F3 OF D6
//Nxx没用,因为在Uxx里面已经全部包括了Nxx的可能
#define Mxx 0x00100000 //mod != 11时才可解码
#define Rxx 0x00200000 //mod == 11时才可解码
#define PreSegment 0x00400000 //段前缀
#define PreOperandSize66 0x00800000 //指令大小前缀0x66
#define PreAddressSize67 0x01000000 //地址大小前缀0x67
#define PreLockF0 0x02000000 //锁前缀0xF0
#define PreRep 0x04000000 //重复前缀
#define Prefix (PreSegment+PreOperandSize66+PreAddressSize67+PreLockF0+PreRep)
#define FPUOpCode 0x08000000 //FPU
static DWORD OneOpcode[256] = {
/*0 1 2 3 4 5 6 7 8 9 A B C D E F*/
/*0*/ ModRM, ModRM, ModRM, ModRM, Imm8, Imm66, OneByte, OneByte, ModRM, ModRM, ModRM, ModRM, Imm8, Imm66, OneByte, TwoOpcode,
/*1*/ ModRM, ModRM, ModRM, ModRM, Imm8, Imm66, OneByte, OneByte, ModRM, ModRM, ModRM, ModRM, Imm8, Imm66, OneByte, OneByte,
/*2*/ ModRM, ModRM, ModRM, ModRM, Imm8, Imm66, PreSegment, OneByte, ModRM, ModRM, ModRM, ModRM, Imm8, Imm66, PreSegment, OneByte,
/*3*/ ModRM, ModRM, ModRM, ModRM, Imm8, Imm66, PreSegment, OneByte, ModRM, ModRM, ModRM, ModRM, Imm8, Imm66, PreSegment, OneByte,
/*4*/ OneByte, OneByte, OneByte, OneByte, OneByte, OneByte, OneByte, OneByte, OneByte, OneByte, OneByte, OneByte, OneByte, OneByte, OneByte, OneByte,
/*5*/ OneByte, OneByte, OneByte, OneByte, OneByte, OneByte, OneByte, OneByte, OneByte, OneByte, OneByte, OneByte, OneByte, OneByte, OneByte, OneByte,
/*6*/ OneByte, OneByte, ModRM, ModRM, PreSegment, PreSegment, PreOperandSize66, PreAddressSize67, Imm66, ModRM + Imm66, Imm8, ModRM + Imm8, OneByte + StringInstruction, OneByte + StringInstruction, OneByte + StringInstruction, OneByte + StringInstruction,
/*7*/ Imm8, Imm8, Imm8, Imm8, Imm8, Imm8, Imm8, Imm8, Imm8, Imm8, Imm8, Imm8, Imm8, Imm8, Imm8, Imm8,
/*8*/ Grp, Grp, Grp, Grp, ModRM, ModRM, ModRM, ModRM, ModRM, ModRM, ModRM, ModRM, ModRM, ModRM + Mxx, ModRM, Grp,
/*9*/ OneByte, OneByte, OneByte, OneByte, OneByte, OneByte, OneByte, OneByte, OneByte, OneByte, Imm16 + Imm66, OneByte, OneByte, OneByte, OneByte, OneByte,
/*A*/ Addr67, Addr67, Addr67, Addr67, OneByte + StringInstruction, OneByte + StringInstruction, OneByte + StringInstruction, OneByte + StringInstruction, Imm8, Imm66, OneByte + StringInstruction, OneByte + StringInstruction, OneByte + StringInstruction, OneByte + StringInstruction, OneByte + StringInstruction, OneByte + StringInstruction,
/*B*/ Imm8, Imm8, Imm8, Imm8, Imm8, Imm8, Imm8, Imm8, Addr67, Addr67, Addr67, Addr67, Addr67, Addr67, Addr67, Addr67,
/*C*/ Grp, Grp, Imm16, OneByte, ModRM + Mxx, ModRM + Mxx, Grp, Grp, Imm8 + Imm16, OneByte, Imm16, OneByte, OneByte, Imm8, OneByte, OneByte,
/*D*/ Grp, Grp, Grp, Grp, Imm8, Imm8, Reserved, OneByte, Reserved, Reserved, Reserved, Reserved, Reserved, Reserved, Reserved, Reserved,
/*E*/ Imm8, Imm8, Imm8, Imm8, Imm8, Imm8, Imm8, Imm8, Imm66, Imm66, Imm66 + Imm16, Imm8, OneByte, OneByte, OneByte, OneByte,
/*F*/ PreLockF0, OneByte, PreRep, PreRep, OneByte, OneByte, Grp, Grp, OneByte, OneByte, OneByte, OneByte, OneByte, OneByte, Grp, Grp
};
三、打印汇编代码
在正式开始编写前我认为必须讲解一下我对于打印的思路了,网上对于编写反汇编引擎的文章有很多,我也确实借鉴了一些,但我发现一点那就是这些文章都只讲解了如何根据opcode表,Modrm表等找到反汇编的机器码,而并没有去打印对应的汇编代码,这也是我为什么要写这篇文章的原因,我相信有许多像我一样的初学者希望能不仅仅是局限于打印出机器码,而是希望能有汇编代码,虽然这样会大大增加其中的工作量,但请相信我,在看见自己的反汇编引擎能打印出正确的汇编代码时,那种成就感是非常让人愉悦的。
我在仔细研究了intel手册的opcode表后发现,其中每一个机器码对应的汇编代码都是不一样的,即使你咋一看觉得会有很多相识之处,但请记住,我们是希望去打印汇编代码的,所以重点要看每一个机器码对应的汇编代码,这样你就能明白我所说的了。
那么既然是这样就不能轻易地编写了,到这里其实我已经有了两种思路了,一种是直接写很多种判断,将每一个机器码对应的情况都一个个的打印出来,但我并不推荐这种方法,一来这样的判断实在是太多了,而且这样就会使得我们上面填充opcode表变得豪无意义(我可不想做无用功);那么现在就只剩第二种方法了,我们将每种类型分开后再去判断,我相信有人看到这里就会有疑问了:这和第一种有什么不一样吗?诶,别急,往下看就知道了。
我们已经在上面定义了许多表格属性了,那么就要利用起来,并且如果通过查找表格属性去打印就会节省很多的判断,不同的类型就到对应位置去查询,然后我们在将判断的情况统一存入一个地址中,再统一的打印,这样是不是就使得整个编写更有可读性,让思路更清晰了呐?没错,聪明的小伙伴已经发现了,我们在第一步结构体中定义的几个数组就是用在这里的,接下来请往下看。
static const char *sets[229] = {
"al", "cl", "dl", "bl", "ah", "ch", "dh", "bh", //8byte
"ax", "cx", "dx", "bx", "sp", "bp", "si", "di", //16byte
"eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi", //32byte
//段寄存
"[bx+si]",