为什么我们需要了解x86机器码
先来一句灵魂拷问:现在的编程语言都做得像吃饭睡觉一样简单了,为什么我们还要花功夫了解底层的x86机器代码?先别着急,我们先看看今天的内容。
下一次你可能会发现自己在调试汇编代码(对于我们中的某些人来说,可能唯一可用的调试方式就是汇编代码了),下面我列举了一些常见的机器代码以及它们”狡猾”的地方,供大家参考。
90
这是一个单字节的无操作(NOP)操作码。如果你希望给现有的代码打一些补丁,则可以无脑地使用这个操作码来替换现有代码。当然了,如果需要还原之前的版本,则你必须将旧版本的指令码替换回去。
CC
这是一个单字节的INT 3 操作码,它用来触发中断到调试器。
74/75
它们是JZ和JNZ的操作码。如果你希望反转一段代码流程,可以使用它们来进行互相替换。其他类似的组合有:72/73(JB/JNB),76/77(JBE/JA),7C/7D(JL/JGE)和7E/7F(JLE/JG)。你并不需要记住这些值,只需要明白,将这些位反转就会导致代码执行流的反转,如果需要撤销反转,则只需要还原这个位反转就可以了。
EB
这个是一个非条件性的短地址跳转(Unconditional Short Jump)指令。如果你希望将一个条件性跳转转换为一个非条件性跳转,则可以将74(假设)修改为EB。如果需要撤销,则你必须得记得之前的操作码字节。
00
换句话说,如果你想将一个条件性短地址跳转转换为一个直接跳转(Never-taken Jump),则你可以将指令的第二个字节修改为0。举个例子,”74 1C”就修改为”74 00″。这个跳转依然有效,只不过它仅仅是跳转到下一条指令,因此它没有任何有实际意义的效果。如果想撤销它,则需要记得修改之前的跳转偏移值。
B8/E8
这些操作码主要用于”MOV EAX, immed32″和”CALL”指令。我经常用它们来去除函数调用指令。如果你想跳过一些函数跳转,除了使用上面所说的将它们替换为90之外,还可以将E8修改为B8。如果想撤销它,则可以将B8修改为E8。
特别需要指出的是,这个方法值对于不包含有栈参数的函数,否则,如果你这样做的话,函数的栈就会遭到破坏。更一般地,你可以使用 83 C4 XX 90 90 (ADD ESP, XX; NOP; NOP) 其中 XX 是你需要弹出(pop)的字节数。 就我个人而言,我不记得这些指令的机器代码,所以我倾向于重写 CALL 指令,以便它在函数末尾调用”RETD”。
我更喜欢这些单字节补丁,而不是用90进行批量擦除,因为将代码恢复到之前的版本更加简单。
总结
了解事物的本质,有助于我们做出更加符合远期目标的决定。
当你的程序出现了预想不到的行为,别害怕,慢慢来:通过研究代码(底层汇编,甚至机器码),最终一定可以找出那只捣乱的老鼠。
在拓扑梅尔智慧办公平台(Topomel Box)的开发过程中,我也碰到过无数的Bug,首选方式,还是通过断点来一行一行的调试代码来定位问题。因为我对汇编语言了解不多,汇编这块应用就比较少,但也有一些涉猎,例如程序的反调试这块。
我相信,一切事物都有原因。
最后
Raymond Chen的《The Old New Thing》是我非常喜欢的博客之一,里面有很多关于Windows的小知识,对于广大Windows平台开发者来说,确实十分有帮助。
本文来自:《Advantages of knowing your x86 machine code》
最近我写了个东西
正如你们所知道的,拓扑梅尔智慧办公平台(Topomel Box)是一款绿色软件,主要面向经常使用电脑的朋友。它提供了各种提升办公效率的小功能,同时操作上尽可能地简单方便。
我想:你值得拥有。
相关推荐
- 为什么有些结构体中会定义一个字节的数组
- Posted on 09月23日
- 外壳对象拖放第四部分:添加一个漂亮的拖动小图标
- Posted on 01月19日
- VS Code C++ 开发:入门和 IntelliSense 配置
- Posted on 01月25日
- Visual Studio 对 C++ 头文件和模块的支持
- Posted on 04月26日
评论已关闭。