Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1176646
  • 博文数量: 573
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 66
  • 用 户 组: 普通用户
  • 注册时间: 2016-06-28 16:21
文章分类

全部博文(573)

文章存档

2018年(3)

2016年(48)

2015年(522)

分类: 嵌入式

2015-12-09 18:57:42

前言:
以前在学校编译原理老师总会提到的一句话,在编译器的后端由汇编代码转换为机器码很容易,容易是容易,但是,how??
在编写shellcode的时候,有时候我们需要特定限制的机器码,比如在制造缓冲区溢出的时候strcpy不能出现0这个字节的机器码,这个时候我们需要精挑细选我们的汇编指令让它的机器码不出现0,但是,汇编指令和机器码是怎么转换的??
在破解软件的时候,找到了关键的crack点,需要修改机器码,我们或许可以通过编译器编译一个我们需要的汇编指令以得到机器码,但是我们发现我们选择的汇编指令或长或短总是不尽人意,这就需要我们自己掌握汇编和机器码的转换密码。
偶然在看雪论坛上看到以前的一篇精华贴讲述了这个问题,

但是作为小白没太看明白,帖子也不再提供回复功能,所以只有自己动手,丰衣足食了。

正文:
讲述x86指令格式的官方文档intel公司的指令手册,我们有理由相信所有的编译器最终将汇编代码转换为机器码的时候都会参考这个手册。
Intel Manual:下载地址:

使用vs2008默认编译选项编译一个计算1加到100的小程序
工程和可执行程序
源码:
int _tmain(int argc, _TCHAR* argv[]) {
  INT i = 0, sum = 0;
  for (i; i <= 100; i++) {
    sum += i;
  }
  printf("%d\n", sum);
  return 0;
}
使用ida打开debug/tmp.exe
名称:  1.jpg
查看次数: 0
文件大小:  7.5 KB
名称:  2.jpg
查看次数: 1
文件大小:  7.0 KB
名称:  3.jpg
查看次数: 0
文件大小:  6.6 KB

我们分析给i赋初值的汇编代码,它的二进制机器码为
C7  45  f8  00  00  00  00      mov  [ebp + i], 0
分析汇编机器码之前我们先了解一下intel 指令格式。打开Intel Manual第二章31页,我们可以看见指令格式如下:
点击图片以查看大图
图片名称:	4.jpg
查看次数:	19
文件大小:	41.8 KB
文件 ID :	91830
图1
Instruction prefixes: 指令前缀,可选项,每个前缀一个字节,可选0个前缀到4个不等。详细信息参考intel manual 2.2节
Opcode: 操作码,这是唯一不可省略的项,1到2个字节,在某些情况下会有额外的三个位作为补充opcode,这三个位是ModR/M中的Reg/Opcode,稍后会讲述什么情况下reg/opcode作为opcode的补充操作码
ModR/M :一共有三个域,mod,reg/opcode, r/m, reg/opcode 在特定情况下作为opcode的补充操作码,特定情况下作为第二个操作数寄存器,(这里的特定情况容稍后解释)。
Mod域和R/M域总共5个位,定义了32种寻址方式。可选项。
SIB:定义ModR/M的寻址方式的补充寻址方式,可选项,什么时候选后面再说。
Displacement:偏移,可选,0,1,2,4个字节
Immediate: 立即数,可选,0,1,2,4个字节。

好,有了这些准备知识,我们查看手册3.2节指令格式,mov指令格式 442页

图2
第一列是opcode机器码,第二列是汇编指令,第三列是描述
解释:
imm是立即数的意思,而imm8就是指8个比特大小的立即数,
r:寄存器,如r16就代表ax、cx等,r32就代表eax、ebx等
m:内存地址,如[01]、[123]、[0FFFF]等
r/m:寄存器或内存
ib:代表OpCode后面跟着一个byte型数值
iw:代表OpCode后面跟着一个word型数值
id:代表OpCode后面跟着一个dword型数值
/digit:代表此OpCode存在ModR/M结构,且ModR/M结构的reg/opcode域为opcode:opcode的补充操作码
/r:代表此OpCode存在ModR/M结构,且ModR/M结构的reg/opcode域为reg,表示第二个操作码寄存器

1.  找到指令参考,匹配指令的模式
我们的例子:mov  [ebp + i], 0,
是将一个立即数0移动到一个[ebp + i]这样一个内存里,属于指令的最后一种情况
Mov  r/m32,  imm32
这里的0如何判断是32位立即数而不是8位或者16位呢?我们观察[ebp+i]是32位的,而指令格式中并没有出现mov r/m32, imm8或者mov r/m32, imm16,所以可以看出为了适配指令可能的情况,编译器将0认为是32位立即数。
2.  计算opcode
计算这个词可能用的不恰当,因为我们不需要任何计算的步骤,所需要的只是去查表。我们看到匹配上的指令模式的第一列为c7 /0
回顾图1,我们可以得知该指令没有前缀,opcode为c7,由于有/0,所以有ModR/M选项,不确定是否有SIB和displacement,Immediate为4个字节的0。
3.  分析ModR/M
还是只有查看intel manual手册,在2.4节对ModR/M 和 SIB bytes有详细的描述,请参考。笔者着重介绍计算方法,参考36页的表格,如下

图3
对于这个图,intel手册有详细说明,笔者也介绍下,Mod两个字节和R/M三个字节总共5个字节,总共编码32种寻址方式,effective address那一列列出了32种方式,mod为00标识的为寄存器间接寻址,01标识的寄存器相对寻址,且偏移为8位,10标识的寄存器相对寻址,且偏移为32位,11为寄存器直接寻址。
mov  [ebp + i], 0 的寻址方式为寄存器相对寻址,且i为-8,所以8位偏移即可,
所以选择mod为01,R/M为101这行;
另外mov  [ebp + i]查出来的指令为c7 /0, 开始说过了/0 表示reg/opcode域表示为扩展opcode,在图中
名称:  7.jpg
查看次数: 0
文件大小:  21.9 KB
 /digit  digit为0,所以opcode为0,它为10进制表示。所以在行列交叉的位置,我们查表得出了ModR/M的值为45
也可以通过计算得到:
mod:01
reg/opcode:000
R/M:101
01 000 101 = 0x45 和查表数据吻合。
(顺便再次说明如果通过指令查出来89 /r ,那么reg/opcode这个域为reg,选择哪一个就根据第二个操作码寄存器来选择具体REG等于多少
比如:名称:  8.jpg
查看次数: 0
文件大小:  4.3 KB的机器码为89 45 f8, 指令为89 /r, 且第二个操作码寄存器为eax,所以reg = 0,查表得到ModR/M为45)

4.  分析displacement
在图2中我们可以发现 名称:  9.jpg
查看次数: 0
文件大小:  13.5 KB,这个表示寄存器相对寻址,disp8或者disp32表示偏移量为8位或者32位,同时请看note的第2条和第3条
名称:  10.jpg
查看次数: 1
文件大小:  13.0 KB
 表明图一中displacement为1个字节或者4个字节,这里的i等于-8,
名称:  11.jpg
查看次数: 1
文件大小:  7.5 KB
所以deplacement为一个字节f8,
至此:我们的mov [ebp+i], 0  就全部分析完了。
没有前缀
opcode 为c7
ModR/M 为45 
没有SIB字节
Displacement 为f8
Immediate为 00 00 00 00 ,
整个指令为:c7 45 f8 00 00 00 00 ,哈哈,成功了!!!

5.  SIB字节
我们的示例中没有出现SIB字节,那么它什么时候出现呢??
我们注意图3的note1,
名称:  12.jpg
查看次数: 1
文件大小:  4.7 KB
       当出现这种寻址方式的时候需要用到SIB字节来对ModR/M进行补充。在2.4节中对SIB进行了描述,当基址加变址寻址(base-plus-index)和比例寻址(scale-plus-index)的时候需要用到SIB.
示例:(以下部分摘抄看雪帖子)

01048E  ADD DWORD PTR DS:[ESI+ECX*4], EAX
    我们重新回顾一下所学知识,首先我们分析它的指令格式如下:
引用:
01 /r    ADD r/m32,r32  Add r32 to r/m32
    根据“/r”我们可以得知这是一个有“ModR/M”结构的OpCode,因此查表得出其“ModR/M”信息 
ModR/M 为 04h
    根据“Effective Address”的“[--][--]”可知此OpCode还存在“SIB”结构,于是继续查位于Intel指令手册第37页的表格。
    这里我们要着重分解目的操作数“[ESI+ECX*4]”里的内容,我们可以将其分为两部分,既索引与倍率因子(或叫做比率因子)。
    索引指的是基址,本例中就是ESI了,而倍率因子在本例中则是“ECX*4”,我们先从横排取得倍率因子信息如下:
引用:
Scaled Index  SS  Index
[ECX*4]       10  001

    而后由竖排取得索引信息如下:

r32  ESI
Base=  6
Base=  110

    将其组合起来就是:

SS  Index  Base
10  001    110  = 10001110 = 8Eh

结束语
学习完了x86汇编指令opcode以后我们可以解决前言所提及的问题了,也可以揣测编译器工程师在后端将汇编代码转换为机器码的时候也是捧着这样一本书册仔细研读,做很多的笔记和备注,也知道了为什么大神们会喜爱写xor eax, eax,而不使用mov eax, 0 也明白了为何有的指令长有的指令短,为何汇编代码改变了一点点编译出来的机器码差别很大等等问题。感谢  作者 A1Pass*转载请注明来自看雪论坛@PEdiy.com 
阅读(3110) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~