《天书夜读:从汇编语言到windows内核编程》一 汇编指令与C语言
1、 Debug模式下,VC++6.0下断点运行,按CTRL+F11可查看汇编代码;另外可以用cl /c /FAs YourCppFile.cpp命令行在同目录生成YourCppFile.asm汇编文件。
2、 Push将32位操作数压入堆栈,esp指向栈顶,故esp减去4(字节=32位,在64位机器上则是8)。记住:esp为栈顶指针,堆栈越高,这个值越小。由于Intel处理器采用小端存储模式,故当PUSH一个寄存器(如EBP)的值0012FFB4时,先存低字节再存高字节,即堆栈从栈顶向下应该是B4FF1200,当然,采用LONG型HEX解析内存时显示的数值将不变,仍是0012FFB4。
3、 CALL的本质相当于PUSH+JMP(压入CALL语句下一条指令地址,然后跳转至CALL指定的函数入口地址),RET的本质相当于POP+JMP(弹出CALL语句下一条指令地址,然后跳转该地址)。
4、 对堆栈的操作指令除了PUSH、POP、CALL、RET以外,还可以是ADD,SUB。在C语言中局部变量是保持在栈里,可以用esp – 4*4的方式在堆栈中分配4个4字节长的整形空间,而不用PUSH4次。
5、 用XOR EAX,EAX来取代MOV EAX,0,这样来实现清零的好处是:速度更快,占用字节更少。
6、 LEA取址运算符,但如下语句:LEA EDI,[EBP - 0CCH]与MOV EDI,EBP - 0CCH在语义上是等同的([X]表示存储器X中的内容),但是后者会导致语法错误,原因是MOV不支持后一个操作数为寄存器减去一个数字的形式
7、 C语言函数调用默认方式(_cdcall):调用方把参数反序(从右到左)压入堆栈,被调用方把堆栈复原(获取参数)。这些参数须对齐到机器字长,32、64位CPU分别对齐到4、8个字节。
8、 右3可知,CALL和RET并不是函数存在的绝对证据,我们可以自行操作堆栈然后使用JMP绝对跳转实现函数调用。由于高级语言对函数调用的规则各不一样,由此产生了调用约定,在windows上有:Pascal方式、WINAPI方式(_stdcall)、C方式(_cdcall)。
9、 Pascal函数调用方式基本用在16位函数库中,现在基本不用。
10、_stdcall函数调用方式规则(Windows API):参数从右到左入栈;被调用的函数在返回前自行清理堆栈(可变参数函数调用除外)。
11、_cdcall函数调用方式规则(C编译器默认):参数从右到左入栈;函数返回后,调用者负责清理堆栈,由此通常生成较大可执行文件
12、不管何种调用方式,返回值都放入EAX寄存器中。
13、_cdcall函数:
int MyFunction(int a,int b)
{
return a+b;
}
汇编后代码为:
int MyFunction(int a,int b)
{
0040D430 push ebp ;1)
0040D431 move ebp,esp ;2)
0040D433 sub esp,40h ;3)
0040D436 push ebx
0040D437 push esi
0040D438 push edi ;4)
0040D439 lea edi,[ebp-40h]
0040D43C mov ecx,10h
0040D441 mov eax,0CCCCCCCCh
0040D446 rep stos dword ptr [edi] ;5)
return a+b;
]
0040D44B add eax,dword ptr [ebp+0Ch] ;6)
}
0040D44E pop edi
0040D44F pop esi
0040D450 pop ebx ;7)
0040D451 mov esp,ebp
0040D453 pop ebp ;8)
0040D454 ret
它所做的事情有:
1) 保存EBP(基址寄存器)。保存之前的ESP值,在返回时恢复,使函数对堆栈能够实现正确操作。
2) 保存ESP到EBP,此时两者相等,都是函数调用以后的栈基址(栈顶)。
3) 在栈中腾出40H的字节区域用来保存局部变量。大小是可变的,由编译器自动分配。
4) 保存ebx,esi,edi到堆栈,函数调用完后恢复。
5) 初始化局部变量区域,循环10H次,每次赋值4个字节(共10H * 4 = 40H字节,与上面分配的字节区域刚好对应)为0CCCCCCCCH,这个值实际上是INT3的机器码,是一个断点中断指令,因局部变量区域不可能被执行,如果执行了则报错。
6) 第一个参数获取位置为ebp + 双倍CPU字长,这里是ebp + 8,后面的依据类型进行偏移。需要说明一点的是,调用者在调用前将参数倒序压入堆栈,所有参数压入以后,在执行CALL指令时,它会自动将CALL指令下面的一条指令地址压入堆栈;此外,进入调用的函数以后,第一件事就是压入EBP到堆栈。由此可以看出,函数当前栈顶(ESP指向位置)与最后一个压入栈的参数(参数列表的第一个参数)相隔了两个CPU字长。这里是4 * 2 =8个字节,故为ebp + 8。
7) 恢复ebx,esi,edi。这里是与入口对应的现场恢复,没什么好说的
8) EBP是被调用者栈顶指针,其内存单元的值是调用者栈顶指针。所以,这里一方面是使ESP指向被调用者的栈顶(也是调用者的栈底),另一方面是恢复调用者的栈基址。
9) RET执行函数返回,此时,ESP自动加上一个CPU字长,指向最后一个被压入的参数位置。
10) 函数的返回值在EAX中,这里不需要额外操作,如果结果不是在EAX,则在返回前一定有MOV操作(或其等同效果的操作)
14、对上面的程序执行如下调用:
int main(void)
{
MyFunction(,);
;
}
其汇编代码为:
int main(void)
{
0040D3F0 push ebp ;a)
0040D3F1 mov ebp,esp
0040D3F3 sub esp,40h ;b)
0040D3F6 push ebx ;c)
0040D3F7 push esi ;d)
0040D3F8 push edi ;e)
0040D3F9 lea edi,[ebp-40h]
0040D3FC mov ecx,10h
0040D401 mov eax,0CCCCCCCCh
0040D406 rep stos dword ptr [edi]
MyFunction(,);
;f)
;g)
(MyFunction) (0040100a) ;h)
return ;
0040D414 xor eax,eax
}
0040D416 pop edi
0040D417 pop esi
0040D418 pop ebx
0040D419 add esp,40h
0040D41C cmp ebp,esp
0040D41E call __chkesp (0040d460)
0040D423 mov esp,ebp
0040D425 pop ebp
0040D426 ret
对以上代码执行时的内存单元数据情况如下表:
|
堆栈地址 | HEX数据 | 描述 | EBP寄存器 | |
| … | … | ||||
| 4) | 0012FED8 | 0012FF80 | MyFunction函数压入edi | 0012FF24 | |
| 4) | 0012FEDC | 01F8BCE8 | MyFunction函数压入esi | 0012FF24 | |
| 4) | 0012FEE0 | 7FFDF000 | MyFunction函数压入ebx | 0012FF24 | |
| 3)、5) |
0012FEE4 ~ 0012FF20 |
CCCCCCCC … CCCCCCCC |
MyFunction函数压入预留局部变量存储区,全部初始化为0xCCCCCCCCH | 0012FF24 | |
| 1)、2) | 0012FF24 | 0012FF80 | MyFunction函数压入ebp | 0012FF24 | |
| h) | 0012FF28 | 0040D411 | mian函数CALL调用,压入返回地址 | 0012FF80 | |
| g) | 0012FF2C | 00000001 | 参数1入栈 | 0012FF80 | |
| d) | 0012FF30 | 00000002 | 参数2入栈 | 0012FF80 | |
| e) | 0012FF34 | 01DD0000 | main函数压入edi | 0012FF80 | |
| d) | 0012FF38 | 01F8BCE8 | main函数压入esi | 0012FF80 | |
| c) | 0012FF3C | 7FFDF000 | main函数压入ebx | 0012FF80 | |
| b) |
0012FF40 ~ 0012FF7C |
CCCCCCCC … CCCCCCCC |
main函数压入预留局部变量存储区,全部初始化为0xCCCCCCCCH | 0012FF80 | |
| a) | 0012FF80 | 0012FFC0 | main函数压入ebp | 0012FF80 |
函数地用过程中的操作由下往上看,注意CALL与RET指令执行时的堆栈变化。其中红色和蓝色为EBP寄存器变化情况,灰绿色为参数压栈。
15、 需要注意的是,以上的汇编代码来自于VC++6.0编译器,在自行写汇编或者嵌入汇编时要稍做改变。比如说CALL指令直接写为call MyFunction
《天书夜读:从汇编语言到windows内核编程》一 汇编指令与C语言的更多相关文章
- 《天书夜读:从汇编语言到windows内核编程》三 练习反汇编C语言程序
1) Debug版本算法反汇编,现有如下3×3矩阵相乘的程序: #define SIZE 3 int MyFunction(int a[SIZE][SIZE],int b[SIZE][SIZE],in ...
- 《天书夜读:从汇编语言到windows内核编程》五 WDM驱动开发环境搭建
(原书)所有内核空间共享,DriverEntery是内核程序入口,在内核程序被加载时,这个函数被调用,加载入的进程为system进程,xp下它的pid是4.内核程序的编写有一定的规则: 不能调用win ...
- 《天书夜读:从汇编语言到windows内核编程》八 文件操作与注册表操作
1)Windows运用程序的文件与注册表操作进入R0层之后,都有对应的内核函数实现.在windows内核中,无论打开的是文件.注册表或者设备,都需要使用InitializeObjectAttribut ...
- 《天书夜读:从汇编语言到windows内核编程》六 驱动、设备、与请求
1)跳入到基础篇的内核编程第7章,驱动入口函数DriverEnter的返回值决定驱动程序是否加载成功,当打算反汇编阅读驱动内核程序时,可寻找该位置. 2)DRIVER_OBJECT下的派遣函数(分发函 ...
- 《天书夜读:从汇编语言到windows内核编程》十一 用C++编写内核程序
---恢复内容开始--- 1) C++的"高级"特性,是它的优点也是它的缺点,微软对于使用C++写内核程序即不推崇也不排斥,使用C++写驱动需注意: a)New等操作符不能直接使用 ...
- 《天书夜读:从汇编语言到windows内核编程》四 windows内核调试环境搭建
1) 基础篇是讲理论的,先跳过去,看不到代码运行的效果要去记代码是一个痛苦的事情.这里先跳入探索篇.其实今天的确也很痛苦,这作者对驱动开发的编译与调试环境介绍得太模糊了,我是各种尝试,对这个环境的搭建 ...
- 《天书夜读:从汇编语言到windows内核编程》十 线程与事件
1)驱动中使用到的线程是系统线程,在system进程中.创建线程API函数:PsCreateSystemThread:结束线程(线程内自行调用)API函数:PsTerminateSystemThrea ...
- 《天书夜读:从汇编语言到windows内核编程》九 时间与定时器
1)使用如下自定义函数获取自系统启动后经历的毫秒数:KeQueryTimeIncrement.KeQueryTickCount void MyGetTickCount(PULONG msec) { L ...
- 《天书夜读:从汇编语言到windows内核编程》七 内核字符串与内存
1)驱动中的字符串使用如下结构: typedef struct _UNICODE_STRING{ USHORT Length; //字符串的长度(字节数) USHORT MaximumLength; ...
- 《天书夜读:从汇编语言到windows内核编程》二 C语言的流程与处理
1) Debug与Release的区别:前者称调试版,后者称发行版.调试版基本不优化,而发行版会经过编译器的极致优化,往往与优化前的高级语言执行流程会大相径庭,但是实现的功能是等价的. 2) 如下fo ...
随机推荐
- vs 或 Sql server2012连接Sql server时出现的问题:已成功与服务器建立连接,但在登陆过程中发生错误
以前连接是正常的,就这两天连不上了.(没有耐心的直接看末尾解决办法) 错误消息如下: 1.尝试读取或写入受保护的内存.这通常指示其他内存已损坏.(System.Data) 2.已成功与服务器建立连接, ...
- Centos7安装Percona5.7
OS: Centos7.0 DB: Percona5.7 1. 通过yum安装 ## 删除之前的mysql数据库, 我用的是centos7.再安装虚拟机的时候,预装了很多软件.所以mysql和mari ...
- 基于FPGA的肤色识别算法实现
大家好,给大家介绍一下,这是基于FPGA的肤色识别算法实现. 我们今天这篇文章有两个内容一是实现基于FPGA的彩色图片转灰度实现,然后在这个基础上实现基于FPGA的肤色检测算法实现. 将彩色图像转化为 ...
- linux 生成随机密码和wordlist常用方法
注:文章内容来自网络收集 关于下面这10个方法,估计很多人也知道了,这里也是为了自己以后用收集一下,不过顺便吐槽下,google第一页,只要是“linux 随机密码”这几个类似的关键字,蹦出来的全特么 ...
- JqueryMobile基础之创建页面
首先简答介绍一下JQueryMobile吧,我觉得用一句话来讲就是可以 "写更少的代码,做更多的事情" : 它可以通过一个灵活及简单的方式来布局网页,且兼容所有移动设备.这也是我自 ...
- PHP设计模式三:原型设计模式
一.什么是原型设计模式 原型设计模式使用一种克隆技术来复制实例化的对象,新对象是通过复制原型实例创建的.原型设计模式的目的是通过使用克隆以减少 实例化对象的开销. 在原型设计模式中,Client类是不 ...
- LeetCode 268. Missing Number (缺失的数字)
Given an array containing n distinct numbers taken from 0, 1, 2, ..., n, find the one that is missin ...
- Asp.net Api中使用OAuth2.0实现“客户端验证”
一.实现继承自OAuthAuthorizationServerProvider的类,实现以"客户端验证"方式传入的相关认证和access_token发放. public class ...
- swift 之 namespace
场景: 项目中类名过长,造成不能根据文件名区分出来,并且如果一个模块的类较多时,很难取一个比较优雅的名字.为了使模块名可读, 我们一般的做法就是添加模块前缀.但是如果模块中还有个子模块,如果还继续按 ...
- Ruby on Rails---Active Admin使用(一)
概述 Active Admin提供了一个友好的后台管理界面,将CRUD等操作可视化,操作极其方便 安装 1. 添加gem gem "devise", :github => ' ...