PE系列学习笔记八 实战之HOOK程序添加弹窗

前面学习了PE的结构后,尝试结合先前所学,修改PE文件来实现给程序添加弹窗的功能

PS:这篇笔记并**没有怎么涉及PE的知识点**,重点放在了**HOOK、反汇编和硬编码**上,对PE不是很了解也可以看看,涉及PE知识点的内容放在了后面的笔记:[PE学习笔记九 实战之HOOK程序添加弹窗续](https://www.dslt.tech/article-437-1.html),可以放心食用( ̄︶ ̄)↗ 

## PE实战之给程序添加弹窗

## 修改流程

要给程序添加弹窗,首先就是要了解其修改的流程

首先要修改的便是程序原本的入口地址,将其修改为弹窗代码所在的地址

弹窗代码所在的地址,要在PE文件中找到一片区域,该区域需要 满足 可执行、可读、可写的权限,然后在这片区域写入弹窗代码,弹窗代码的最后要**跳转回**原本的入口地址

* * *

该修改流程是一种十分经典的HOOK思想,即程序按照原本的流程执行着,你把它原本执行的代码修改了,修改去干我们想要做的事情,做完我们想要的事情后再把它放回去继续执行原本的代码

* * *

## 图解HOOK修改流程


被HOOK的地方为B

正常流程 A→B→C

HOOK流程 A→被HOOK的B→自己的代码→复原B中被修改的部分→跳转回B原本要接着执行的地方→C

* * *

## 图解给程序添加弹窗


* * *

## 弹窗代码

既然要给程序添加弹窗,自然需要知道如何通过代码显示一个简单的弹窗

下面给出一个简单的弹窗代码

```
#include <Windows.h>
int main() {
//调用MessageBoxA函数
//显示一个 没有所有者窗口的、内容为lyl610abc的、标题为tips的、只包含一个按钮:OK的 窗口
MessageBoxA(0, "lyl610abc", "tips", 0);
return 0;
}
```

* * *

## 运行结果


* * *

## MessageBoxA

下面为介绍MessageBoxA的使用文档,**熟悉MessageBoxA的可以跳过**,直接跳到后面的**查看反汇编**即可

### 函数原型

```
int MessageBoxA(
HWND hWnd,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType
);
```

* * *

### 参数


### uType

对话框的内容和行为。该参数可以是下列标志组中的标志的**组合**

要指示消息框中显示的按钮,请指定以下值之一:


* * *

要在消息框中显示图标,请指定以下值之一:


* * *

要指定默认按钮,请指定以下值之一:


* * *

若要指示对话框的模式,请指定以下值之一:


* * *

要指定其他选项,请使用下列一个或多个值:


### 返回值

返回值类型:int

* 如果消息框有一个取消按钮,如果ESC键被按下或取消按钮被选中,该函数将返回IDCANCEL值。如果消息框没有取消按钮,按ESC将没有效果-除非MB_OK按钮存在。如果出现MB_OK按钮,按“ESC”键,返回值为“IDOK”。
* 如果函数失败,返回值为0。要获取扩展的错误信息,请调用 GetLastError
* 如果函数成功,返回值是下面的菜单项值之一:


* * *

通过将uType参数设置为对应的标志值,可以在消息框中使用以下系统图标


* * *

## 查看反汇编

```
9: MessageBoxA(0,"lyl610abc","tips",0);
00401028 8B F4 mov esi,esp
0040102A 6A 00 push 0
0040102C 68 28 20 42 00 push offset string "tips" (00422028)
00401031 68 1C 20 42 00 push offset string "lyl610abc" (0042201c)
00401036 6A 00 push 0
00401038 FF 15 AC A2 42 00 call dword ptr [__imp__MessageBoxA@16 (0042a2ac)]
0040103E 3B F4 cmp esi,esp
00401040 E8 2B 00 00 00 call __chkesp (00401070)
```

* * *

这里截取出关键的代码

```
0040102A 6A 00 push 0
0040102C 68 28 20 42 00 push offset string "tips" (00422028)
00401031 68 1C 20 42 00 push offset string "lyl610abc" (0042201c)
00401036 6A 00 push 0
00401038 FF 15 AC A2 42 00 call dword ptr [__imp__MessageBoxA@16 (0042a2ac)]
```

* * *

其余代码说明

```
00401028 8B F4 mov esi,esp //保存执行前的esp(栈顶)到esi
0040103E 3B F4 cmp esi,esp //比较esi和执行完call后的esp(栈顶)
00401040 E8 2B 00 00 00 call __chkesp (00401070) //调用检测esp的函数
```

用来检测,调用完函数后堆栈是否保持平衡,是C语言自动生成的,这里无需关注

* * *

## 解析反汇编

```
0040102A 6A 00 push 0
0040102C 68 28 20 42 00 push offset string "tips" (00422028)
00401031 68 1C 20 42 00 push offset string "lyl610abc" (0042201c)
00401036 6A 00 push 0
```

前四行代码是依次压入四个参数(从右到左压入),这和调用协定相关,在<u style="text-decoration: none; border-bottom: 1px dashed grey;">[逆向基础笔记九 C语言内联汇编和调用协定](https://link.zhihu.com/?target=https%3A//www.dslt.tech/article-135-1.html)</u>已说过,这里不再赘述

```
00401038 FF 15 AC A2 42 00 call dword ptr [__imp__MessageBoxA@16 (0042a2ac)]
```

调用call这里,可以看到这是一个间接调用,形式为call [地址],所以这里要查看一下其实际地址里存储的内容是什么,也就是其实际call的地址

通过内存窗口可以得到,其实际call的地址为77D507EA


* * *

所以此时的代码相当于

```
00401038 call 77D507EA
```

* * *

这里为什么是一个间接call,77D507EA代表什么?

这里**采用间接call的原因是引用了导入表**,关于导入表和导出表的内容之后的笔记会再做说明

这里的77D507EA**代表的是MessageBoxA函数的地址**

每个电脑的MessageBoxA函数的地址**不一定相同**,它取决于系统的user32.dll中导出表中给出的地址(这里调用的MessageBoxA是由user32.dll提供的)

可以用IDA来验证这一点:

找到系统中的user32.dll(在32位的xp中在C:\WINDOWS\system32下,在64位系统中则有2个,一个在C:\Windows\SysWOW64下(32位的dll),一个也在C:\WINDOWS\system32下(64位的dll),**具体使用哪个取决于程序是32位还是64位**)


* * *

将其用IDA打开,找到导出表,然后在导出表中搜索得到MessageBoxA


可以看到它的地址和前面得到的一致,为77D507EA,验证来源完毕

**注意此时的系统是XP32位**,在其它高版本的系统中这里的Address**并不一定能和前面的一致**,这里主要是为了说明**函数的来源与导入表和导出表有关**,也说明了**为何不同系统的MessageBoxA的地址不一定相同**,为后面的学习作个铺垫

* * *

**准确得到函数地址的方法为**:

如果想要得到MessageBoxA函数的(其它函数同理)地址,首先要找到一个有调用这个函数所属的模块的程序(MessageBoxA所属的模块为user32.dll),使用OD附加来查找:

一般来说只要是图形化的程序都会调用user32.dll这个模块,于是这里就随便拿一个exe程序就行,这里以dbgview.exe为例

使用OD打开dbgview.exe,然后单击图中所指的e,或者使用快捷键Alt+E


* * *

在弹出来的窗口中找到user32.dll,双击


* * *

在弹出来的反汇编界面按快捷键 Ctrl+N


* * *

然后在新窗口找到MessageBoxA对应的地址即可


此时得到的地址就是准确的MessageBoxA地址了

* * *

## 自写反汇编测试

### 测试代码

既然得到了MessageBoxA的函数地址,就无需间接调用了,可以直接调用它,于是自写代码如下:

```
#include<stdio.h>
#include <windows.h>
int MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
int ret;
//这里的地址填前面通过OD得到的MessageBoxA地址,每个系统不一定相同
//先前是在xp系统中测试的,那时的MessageBoxA为77D507EA
//此时换到WIN10 X64 测试,修改MessageBoxA的值
int addr = 0x76a3ee90;
__asm {
push hWnd
push lpCaption
push lpText
push uType
call addr
mov ret, eax
}
return ret;
}

int main(int argc, char* argv[])
{
MyMessageBoxA(0, "lyl610abc", "tips", 0);
return 0;
}
```

* * *

### 测试结果


依然能够正常弹窗,测试成功

* * *

## 反汇编转为硬编码(字节码)

通过先前的学习可以知道在计算机中,无论是执行的代码还是数据都是以二进制来存储的,为了方便查看,进制查看工具将内容以十六进制的方式展示

所以为了给程序添加弹窗,显然就不是直接将反汇编写入PE文件中,而是**要将反汇编对应的硬编码(字节码)写入到PE文件中**

如何将反汇编转化为硬编码?这个是**反汇编引擎**所做的事,在OD或VC中的反汇编引擎已经将反汇编对应的硬编码(字节码)给出了

有关反汇编引擎的内容也属于硬编码相关的知识,**看以后要不要开这个坑**

将前面写的反汇编再稍作修改,将参数写死,并且去掉返回值的接收

### 修改反汇编代码

```
#include<stdio.h>
#include <windows.h>
void MyMessageBoxA() {
//获取MessageBoxA的地址
int addr = (int)&MessageBoxA;
printf("addr:%X\n", addr);
//"lyl610abc"对应的ASCII编码 6c 79 6c 36 31 30 61 62 63
unsigned char bytes[] = { 0x6c,0x79,0x6c,0x36,0x31,0x30,0x61,0x62,0x63,0x00 };
//"tips"对应的ASCII编码
unsigned char bytes2[] = { 0x74,0x69,0x70,0x73 };
//申请内存,该内存的属性为可执行 可读可写
LPVOID _lpText = VirtualAlloc(NULL, sizeof(byte), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
//申请内存,该内存的属性为可执行 可读可写
LPVOID _lpCaption = VirtualAlloc(NULL, sizeof(byte), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
//将硬编码写入申请的有权限的内存中
WriteProcessMemory(INVALID_HANDLE_VALUE, (LPVOID)_lpText, (BYTE*)bytes, sizeof(bytes), 0);
//将硬编码写入申请的有权限的内存中
WriteProcessMemory(INVALID_HANDLE_VALUE, (LPVOID)_lpCaption, (BYTE*)bytes2, sizeof(bytes2), 0);
__asm {
push 0
push _lpCaption
push _lpText
push 0
call addr
}
}

int main(int argc, char* argv[])
{
MyMessageBoxA();
return 0;
}
```

* * *

### OD模拟代码操作

**先总结一下上面修改后的反汇编代码做了什么**

1. 获得MessageBoxA的函数地址
2. 将字符串对应的ASCII码写入到**可读可写**的一片内存中
3. 压入参数
4. 调用MessageBoxA函数
5. 调用完后跳转回原本要执行的代码

* * *

**然后在OD中复现操作**

用OD随便载入一个**包含user32.dll模块**的程序,我这里直接拿前面的代码编译后的程序来作演示


* * *

按照前面总结的步骤来

**1.MessageBoxA在OD中的地址是已知的无需获取**

* * *

**2.将字符串对应的ASCII码写入到可读可写的一片内存中**

首先是选取一块可读可写的内存,很显然,接下来要执行的代码一定是可读可写可执行的,于是这里就无须申请内存了,直接将下面的代码覆盖即可

解决了选取内存的问题,接下来就是将字符串对应的ASCII码写入内存即可,但这要放在压入参数之后,原因后面会说明,现在先看压入参数

* * *

**3.压入参数**

第一个参数为0,直接将其对应的反汇编修改为push 0即可


* * *

第二个参数为_lpCaption,要写入的字符串为"tips",但此时字符串还没写入内存,也就是并不确定字符串的内存地址,但可以确定一个大致的范围,要存放字符串的地方就在下方不远处,于是这里可以暂时先填写离这里不远的随便一个内存地址**用来占位,等之后确定了字符串的内存地址后再回来修改**

这里就随便选取下面的地址00401156

于是将反汇编修改为push 0x00401156


* * *

第三个参数为_lpText,要写入的字符串为"lyl610abc",和前面的_lpCaption一样,先填入push xxxx占位,之后再回来修改

于是依旧将其反汇编修改为push 0x00401156


* * *

第四个参数为0,直接将其对应的反汇编修改为push 0即可


* * *

**4.调用MessageBoxA函数**

直接修改反汇编为call MessageBoxA即可

* * *

**5.调用完后跳转回原本要执行的代码**

这里是为了**模拟之后执行完弹窗代码后再跳回程序入口**

这里随便模拟一个**长跳转**即可,修改反汇编代码为jmp 000412D0


* * *

6.填充字符串到内存中

下面的代码因为前面的绝对跳转是不会执行的,因此可以用来充当数据区,这也是**要将字符串的填充放在后面的原因**

先填充_lpText,也就是"lyl610abc"

选中跳转下的那一行,右键→二进制→编辑


* * *

选中ASCII,并将要写入内存的字符串 "lyl610abc"填入


* * *

填入后再选中Hex那一行,在后面添加00(字符串以'\0’结尾)


* * *

添加完00后得到


然后确定即可

* * *

修改完得到


* * *

这是第三个参数_lpText就可以修正了,将前面的压入第三个参数的反汇编修改为 push 0x00401148


* * *

同样的方法填充第二个参数_lpCaption,也就是"tips"

这里先记录一下其地址为0x00401152,然后开始修改


* * *

修改完后得到:


* * *

最后再修正先前push的用来占位的地址即可


* * *

修改完成,这里记录一下修改的十六进制代码为

```
6A 00 68 52 11 40 00 68 48 11 40 00 6A 00 E8 A7 F6 94 77 E9 88 01 C4 FF 6C 79 6C 36 31 30 61 62
63 00 74 69 70 73 00
```

* * *

修改完成后不断按F8单步步过,直call user32.MessageBoxA这


可以看到,对应的参数是没有问题的

* * *

最后再按F8单步步过即可看到结果:


能够按照代码弹框,完成( •̀ ω •́ )✧

* * *

## 说明

限于篇幅原因,这次的笔记主要讲解了**弹窗代码的编写流程**,之后的笔记则是将上面的硬编码稍作修改然后写入PE文件中再修改PE入口点来实现最终目的

终于来到**实战环节**了,前面关于知识点介绍的笔记看的人数一言难尽,希望通过实战能够勾起大家的兴趣,一起学习共同进步O(∩_∩)O

本文 **OD修改的程序下载链接** 都在下方链接中,欢迎下载并交流沟通

[https://www.dslt.tech/article-436-1.html](https://www.dslt.tech/article-436-1.html)

版权声明:本文由lyl610abc 原创,欢迎分享本文,转载请保留出处

posted @ 2021-04-24 09:25  大神论坛  阅读(346)  评论(0编辑  收藏  举报