传感器|为什么我们需要了解x86机器码

传感器|为什么我们需要了解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 。
【传感器|为什么我们需要了解x86机器码】特别需要指出的是 , 这个方法值对于不包含有栈参数的函数 , 否则 , 如果你这样做的话 , 函数的栈就会遭到破坏 。 更一般地 , 你可以使用 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》