开始学习“老码识途”这本书,希望能有所收获。
目录
计算机环境 | Win7 32位 |
调试环境 | VS2019 32位 |
测试代码
单纯在新建工程上加了全局变量
#include <iostream>
int gi;
int main()
{
gi = 12;
std::cout << "Hello World!\n";
}
VS快捷键:
F9加断点
F5断点调试
Ctrl+F5 非调试运行,断点无效
反汇编
开启位置:调试—窗口—反汇编
可代码处右键勾选“显示代码字节”,去除符号名,得到如下:
反汇编中的结构,以断点这一行为例:mov指令的地址,mov指令的机器码,汇编指令
此时可以检查 gi 的地址,选择调试—窗口—监视,输入 &gi
该地址与反汇编中的地址一致,只是反汇编中加了16进制的h符号;
我们可以拿出move指令的机器码来验证mov指令的相关信息
机器码:C7 05 3C C1 CD 00 0C 00 00 00
赋值的12转成16进制为:0C, 此时可以猜测为机器码的 0C可能是赋值12的意思;
gi变量的地址为:00CDC13C,此时可以拆分 00 CD C1 3C,此时对比可以发现实际
就是机器码中的 3C C1 CD 00,但是是倒过来写的;
而最前面的 C7 05 便是mov的指令,此时可以得到如下结论
C7 05(mov的指令) 3C C1 CD 00(gi变量的地址) 0C 00 00 00(代表值12)
这个机器码从右往左看,最后4个字节倒过来 00 00 00 0c,16进制转成10进制就是12,int类型4个字节,1个字节8位,即2个16进制数;
此时可以发现规律,机器码实际上是倒序的;
实际上这里可以引入一个知识点:
内存中存储整数的规范:
小端机(小端序):低字节存在低地址,高字节存高地址;
大端机(大端序):低字节存在高地址,高字节存低地址;
此时我们可以知道倒序属于小端机,即小端序(Intel x86系列CPU是小端机);
若是大端序,则12的机器码肯定是 00 00 00 0c;
内存观察窗体
位置:调试—窗口—内存
在地址栏中输入地址,其值为16进制,输入后下方显示内存面板区,显示的是内存的值,左边是内存地址,地址从左到右,从上到下增加,显示单位为字节。
可以输入gi的地址
然后单步执行“gi = 12”;
此时出现了 0c,即赋值为12,但我们并不知道是否修改了4个字节还是只修改了1个字节,此时我们可以使用内存窗体的另一个功能——修改内存的值。
重新执行程序,但断点中断时,将指向的4字节全部修改为11,如下:
然后单步执行 gi = 12,看到内存变化为 0c 00 00 00,所以修改了4个字节。
同理,我们可以通过内存窗体来验证 mov指令是否存在反汇编第一列的地址中:
修改赋值语句机器码
我们是否可以通过修改内存中的值,从而达到修改指令的效果?
如:修改为: gi = 894567,计算gi的16进制得到:0xda667;
按照上面所说,我们是否可以把mov指令从
C7 05 3C C1 DA 00 0C 00 00 00,改为:
C7 05 3C C1 DA 00 67 a6 0d 00
这里的对应关系可以这么看:
0x 00 0d a6 67
倒序
0x 67 a6 0d 00
可以在VS中修改,步骤:断点到 gi=12,修改mov的内存值
修改完后,按F10单步执行,查看监视窗口的值变化,发现确实是894567;
直接构建新的赋值语句
通过上面,我们能否可以抛开C、C++代码,自己构造指令来执行?
实际上,我们可以查看当前寄存器的值,此时需要激活寄存器窗体。(位置:调试—窗口—寄存器)
再次断点调试,此时我们可以看到反汇编显示的指令地址是否与EIP寄存器中的值相同。
(EIP寄存器为32位,该寄存器指向哪里,CPU就将该地址作为将执行指令的入口,即使错误的指向了数据区)
按F10单步执行,发现下一条指令的地址也是EIP寄存器中的值。
假定我们构造了一段指令,则需要用jmp语句跳转这段指令,执行完后,还需要跳转回来如下,否则会导致不可预料的行为。
因此在构造的新代码中需要构造jmp指令,操作码为 ff 25;
其中C语言程序嵌入汇编方式
载入汇编两种方式:
1._asm{...},在花括号内填写多条汇编语句;
2._asm一条汇编语句,如 “_asm mov eax,1”
看书中例子:
//写机器指令代码
void* buildCode() {
}
int gi;
void main() {
void* code = buildCode;
_asm {
mov address, offset _lb1 //将标签_lb1即下面的_lb1:后语句的地址设定给变量address
//即printf("02 gi = %d\n", gi);语句的地址
}
gi = 12;
printf("01 gi = %d\n", gi);
_asm jmp code //执行自己构建的代码
gi = 13;
_lb1:
printf("02 gi = %d\n", gi);
getchar();
}
继续分析 buildCode函数,它要构建的汇编指令如下
mov gi,18;
jmp address
这两条指令的机器码如下,应需要分配16个字节:
引用书中的代码和对应关系,可以很好看出写法