前言
1.稍微看了看第四章,发现挺多内容是我没有掌握的,在这篇博客里,我尽量把这些内容讲明白
2.由于博客篇幅原因,这篇博客就只记录比较难懂的内容,其他内容请自己看书学习
3.第四章内容实在太多了,我打算分成上下两篇博客:(
本文参考资料:《加密与解密》 《Windows程序设计(第七版)》《Primer c++》Win32API大全 还有一些博客
让我们开始吧----哈↗哈→哈↘哈↗嘻↗嘻→嘻↘嘻↗(自行脑补声音)
32位程序逆向技术
书中说,在编写32位程序时,首先要实现WinMain
函数。WinMain
函数和main
函数一样,是程序的进入点。
因为我从来没有开发过32位程序,所以从来没有手动调用过
WinMain
函数。
以下内容来自《Windows程序设计(第七版)》
补充一下Windows程序开发的知识
这是一个c语言版本的Hello World
程序
#include<stdio.h>
int main()
{
printf("Hello World");
}
这是“同样效果”的Windows程序
#include <windows.h>
int WINAPI WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
MessageBox (NULL, TEXT ("Hello, Windows 98!"), TEXT ("HelloMsg"), 0);
return 0 ;
}
书中的IDE为vc6.0 ,这个IDE现在已经过时了,可以用vs代替
运行结果如下
分析这个windows程序代码
在WinBase.h
中可以找到WinMain
函数的定义
int
#if !defined(_MAC)
#if defined(_M_CEE_PURE)
__clrcall
#else
WINAPI
#endif
#else
CALLBACK
#endif
WinMain (
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nShowCmd
);
介绍WinMain函数参数
- hInstance 为应用程序当前事例的句柄
- hPrelnstance:应用程序的前事例的句柄。对于一个32位的程序,该参数总为
NULL
- lpCmdLine:指向应用程序命令行的空字符串的指针,
- nCmdShow:指明窗口如何显示 (在上面的代码中没有用到)
介绍函数调用
介绍一下WINAPI
:在minwindef.h
文件中,可以发现以下定义
#define WINAPI __stdcall
函数传递参数有3种方式,分别是栈方式、寄存器方式及通过全局变量进行隐含参数传递的方式。__stdcall
是函数的调用约定中,利用栈传递参数的方式之一,用于规定参数压入栈的顺序等等问题。
关于__clrcall,我是有查到一些资料啦,但是以我目前的水平看不懂。。。
关于__stdcall
,可以看看这一篇博客:带你玩转Visual Studio——调用约定__cdecl、__stdcall和__fastcall
总结一下:stdcall
有以下的特点
- 函数参数从右到左入栈(需要知道这点,才能理解一些pwn的漏洞)
- 函数的返回值存在EAX寄存器中。函数返回时会自动清理堆栈(也就是书中说的由子程序来平衡栈)
什么是栈平衡?
当函数被调用时,需要申请栈空间来存放局部变量,函数参数等等,当函数调用结束后,需要释放申请的栈空间,保证栈的内容和函数调用前一致
接下来书中分析test2(Par1,Par2)
的汇编代码,可以同时参考这篇博客进行理解,这篇博客将ebp,esp的操作用图像表现出来:栈帧ebp,esp详解。顺便一提,这篇博客函数参数m,n入栈顺序好像错了
注意:由于栈是向下增长的,所以 sub esp 8 可以在栈中留出一段空间
数据结构
数据结构是计算机存储、组织数据的方式。书中介绍了怎么判断局部变量,全局变量,数组等等
局部变量
学习c语言的时候,我们知道栈中存放函数的参数和返回地址,以及局部变量。其实局部变量不仅能存在栈中,也能存在寄存器中。
首先需要了解栈是怎么对局部变量分配空间的,有以下三种用汇编语言表示的方式:
序号 | 1 | 2 | 3 |
---|---|---|---|
sub esp n | sub esp -n | push reg | |
add esp n | add esp -n | pop reg |
操作1和操作2都直接操作栈顶指针,直接开辟栈的空间。操作3将寄存器弹入栈或者弹出栈,esp寄存器的值会间接改变。
创建局部变量的两种汇编语句,第一种是mov [ebp-xxx] x
,在当前的栈帧里,ebp应该会在最开始被赋值,局部变量进入栈的时间一定是在ebp赋值之后。所以是[ebp-xxx]
而不是[ebp+xxx]
第二种操作是push xxx
, 就是将数字弹入栈中
关于全局变量和数组,书中讲得挺详细的,理解难度应该不大
虚函数
如果不知道虚函数是啥,建议先了解一下,再接着学习下面的知识
可以阅读《Primer C++》第15章:面向对象程序设计。
简单来说:对于某一些函数,派生类希望它覆盖基类的函数,而不是直接继承。基类就会将这些函数声明为虚函数。
按照书中的解释,所有虚函数的地址都会放在一个虚函数表(VTBL)中。其他内容自己看书吧,不难理解。才不是我懒得写呢,哼^_^
分析“纯算法实现逻辑判断”示例程序
书中的代码如下
int main(void)
{
if(FindWindow("计算器",NULL))
return 1;
else
return 5;
}
汇编语句如下
00401000 push 406030 ;/Title="计算器"
00401005 push 0 ;iclass = 0
00401007 call dword ptr[40509c] ;\FindwindowA
0040100D neg eax
0040100F sbb eax, eax
00401011 and al 0FC
00401013 add eax, 5
00401016 retn
这段汇编语句的具体分析,我在网上找到了相应的文章neg eax sbb eax, eax
注意:0FC不是0,它是一个二进制数:1111 1100
如果eax的值为0,那么这段代码结束后,eax的值是5
如果eax的值不为0,那么这段代码结束后,eax的值为1 (0FC+5,并且高位截断)
这段没有看懂,先跳过。。。。。
本文结束,接下来是meme环节
学不明白,崩溃大哭 be like↓
学明白了,露出纯真笑容 be like↓
后记
第四章难度突然上去了,但是仔细研究后,感觉自己对程序的理解又进了一步
这几天在网上冲浪,看到了一些感觉挺厉害的文章
-
拖了这么久的cs61A终于结束了,第四个project真的好难:(
-
顺便照着《qt6 c++开发指南》重现了文件管理系统,
虽然没有看懂代码,但还是copy出结果了