MASM中VirtualProtect函数的分析

倾旋
倾旋
技术分享|2021-9-3|最后更新: 2023-6-22|
type
status
date
slug
summary
tags
category
icon
password
URL

0x00 调用约定

 
__stdcall关键字用于约定调用Win32 API函数的参数入栈顺序,它的入栈顺序是由右向左,一般C/C++语言代码中没有声明调用约定的话,默认就是__stdcall调用约定。
C++中如果要声明函数的调用约定,可以通过以下格式:

0x01 栈与调用约定的关系

提到栈,这已经是计算机知识体系里面老生常谈的技术了,并且互联网上已经有大量的文章去讲解栈的工作机制。
下面说说我对栈的理解:
  • 函数调用离不开栈
  • 栈用于完整保留调用前CPU的状态值,堆用于保留临时变量,实现了在函数体内部共享内存
  • 栈溢出一般是由于局部变量填满了栈的空间,没有及时释放,导致溢出,比如递归stack overflow
  • 栈遵循先入后出的原则
  • …..
写一段汇编:
栈遵循先入后出的原则,栈顶ESP是低地址,栈底EBP是高地址。

0x02 使用汇编调用Win32 API

环境:Visual Studio 2019 (MSVC工具集版本14.26以下)
MSVC工具集版本14.26以下才能够编译MASM正常调用Win32 API的代码,这里我使用的是14.21.27702
notion image

安装低版本MSVC工具集版本

打开Visual Studio Installer,点击修改
notion image
红框内的工具集版本都支持正常编译。

MASM INVOKE

32位模式中,可以用Microsoft的INVOKE、PROTO 和扩展 PROC 伪指令新建多模块程序。与更加传统的CALL和EXTERN相比,它们的主要优势在于:能够将INVOKE传递的参数列表与PROC声明的相应列表进行匹配。
INVOKE方便了我们将参数与官方文档的API的参数顺序进行对应,例如在MASM汇编中调用MessageBox函数:
如果要转为纯汇编的方式,就要手动压栈了:

0x03 自定义函数分析调用过程

实现功能:将某块内存设置为可执行属性
涉及Windows API:
部分汇编代码:
AddExecutePage是我自己在项目中定义个一个过程,其中寄存器的状态值已经给出,经过调试我发现INVOKE调用VirtualProtect的参数传递顺序和Windows API文档存在差异,就是dwSizelpAddress的顺序不一样。
按常理来讲,最先入栈的数据是函数最右边的参数,入栈顺序是:
但事实情况是:
调试一下看看情况
notion image
此时EAX = 0x00170000指向了要改变属性的内存地址,但第一个push eax并不是开始给VirtualProtect传递参数,而是从call往上数四个push指令,这些push的数据才是VirtualProtect的参数。
lea edx,dword ptr ss:[ebp-4]代表把ebp-4的地址复制给edx,EBP=0014F8300014F830-4=0014F82C,那么edx=0014F82C,下一句push edx作为VirtualProtect的第一个参数lpflOldProtect传递进去。
注:lpflOldProtect的数据类型是PDWORD,也就是DWORD的指针,传指针而非传递值。
notion image
第二个push 10,代表了flNewProtect,0x10代表了内存属性常量PAGE_EXECUTE
notion image
第三个push eax,代表了lpAddress,指向要改变的内存地址。
第四个push ebx,代表了dwSize,ebx的值是000000C1,说明要改变内存属性的内存大小是0xC1个字节。
其中,第三个push和第四个push的顺序按照函数声明的格式是先传递大小,后传递内存地址的,经过分析,我发现必须先传递内存地址后传递大小才能正常执行。
继续跟入后,发现会通过VirtualProtect调用VirtualProtectEx。
notion image
按照函数声明,从右向左对应入栈值:
  • lpflOldProtect -> 0x14F820
  • flNewProtect -> 0x10
  • dwSize -> 0x170000
  • lpAddress -> 0xC1
  • hProcess -> FFFFFFFF
这个时候发现VirtualProtectEx的顺序也有问题,理想情况下应该是:
  • lpflOldProtect -> 0x14F820
  • flNewProtect -> 0x10
  • dwSize -> 0xC1
  • lpAddress -> 0x170000
  • hProcess -> FFFFFFFF

0x04 结论

测试通过Windows 10、Windows 7以后,我最终的解决办法还是要把dwSizelpAddress的入栈顺序进行调换,发现程序可以正常运行。
©2021-2024 倾旋. All rights reserved.