《Windows PE》12.4 万能补丁

本节我们将介绍基于嵌入式补丁的万能补丁技术。

本节必须掌握的知识点:

        万能补丁原理

        示例程序

12.4.1 万能补丁原理

上一节中,我们介绍了嵌入补丁,就是把补丁代码嵌入到目标程序的代码段中。如果补丁代码过于庞大,可能需要修改各个节区的大小和地址,带来不必要的麻烦。我们可以使用万能补丁轻松解决这个问题。

万能补丁的原理

我们可以制作两个补丁代码,补丁代码一负责嵌入目标程序的代码段。补丁代码二负责实现具体功能,并将其打包为一个DLL,这样补丁DLL就可以脱离于目标程序,因此补丁DLL的大小和功能不受目标程序的限制。而补丁代码一的功能自然就是实现动态加载补丁DLL。【注意】动态加载DLL的API函数是kernel32.dll中的LoadLibraryx系列函数,所以,补丁代码一需要做的工作就是找到内存中的kernel32.dll的基地址,然后査找其导出表并获取LoadLibraryA的VA地址。

为了不影响目标程序的原有功能,我们可以将补丁代码一嵌入到目标程序代码段的结尾处,并将补丁代码一的最后一条语句“0E9h,0F0h,0FFh,0FFh,0FFh”的跳转地址修改为目标程序的原入口地址。

万能补丁实现过程

我们将万能补丁的实现过程总结如下:

●补丁代码一:

1.通过PEB获取kernel32.dll基地址。

2.获取LoadLibraryA的函数VA。

3.调用LoadLibraryA函数,动态引入动态链接库pa.dll(winResult.dll改一下入口函数)。

4.跳转到入口地址执行,该部分代码也可以使用FF 25无条件跳转指令。

●补丁代码二

1.编写DLL入口函数。当一个进程动态加载外部DLL文件时,除了将DLL内容映射到内存外,还会执行DLL 的入口函数;因此我们可以在DLL入口函数中添加想要实现的功能代码。

2.编写DLL导出函数。

我们可以用示意图表示万能补丁的实现过程,如下所示:

图12-7 万能补丁原理示意图

12.4.2 示例程序

32位PE

实验八十九:32位万能补丁

示例先完成一个汇编代码写的patch1.exe补丁代码一程序,然后将补丁程序字节码.text节区结尾处。补丁代码一动态获取LoadLibrayA函数地址,动态加载由补丁代码二生成的DLL。最后一条JMP指令跳转到目标程序入口地址处。补丁代码二改写第五章写的winResult.dll,在DllMain入口函数内添加一个MessageBox函数调用。

●补丁代码一

;------------------------------------------------------------------------
; FileName:patch1.asm
; 实验89:32位万能补丁码
; 功能:获取LoadLibraryA的函数地址并调用
; (c) bcdaren, 2024
;-----------------------------------------------------------------------*/
    .386
    .model flat,stdcall
    option casemap:none

include    windows.inc
include    user32.inc
includelib user32.lib
include    kernel32.inc
includelib kernel32.lib

;数据段(数据段冗余,仅供对比参考)
    .data

szText  db 'LoadLibrary的函数地址为: %08x',0
szOut   db '%08x',0dh,0ah,0
szBuffer db 256 dup(0)

;代码段
    .code

start:
   mov edi,edi
   call loc0
   db 'LoadLibraryA',0  ;特征函数名
   db 'pach2',0         ;动态链接库pach2.dll
loc0:
   pop edx            ;edx中存放了特征函数名所在地址
   push edx

   push edx

   assume fs:nothing
   mov eax,fs:[30h] ;获取PEB所在地址
   mov eax,[eax+0ch] ;获取PEB_LDR_DATA 结构指针
   mov esi,[eax+1ch] ;获取InInitializationOrderModuleList 链表头
                     ;第一个LDR_MODULE节点InInitializationOrderModuleList成员的指针
   lodsd             ;获取双向链表当前节点后继的指针
   mov ebx,[eax+8]   ;获取kernel32.dll的基地址

loc2:   ;遍历导出表
   mov esi,dword ptr [ebx+3ch] 
   add esi,ebx ;ESI指向PE头
   mov esi,dword ptr [esi+78h]
   add esi,ebx ;ESI指向数据目录中的导出表
   mov edi,dword ptr [esi+20h] ;指向导出表的AddressOfNames
   add edi,ebx ;EDI为AddressOfNames数组起始位置
   mov ecx,dword ptr [esi+14h] ;指向导出表的NumberOfNames

   push esi
   xor eax,eax

loc3:
   push edi
   push ecx
   mov edi,dword ptr [edi]
   add edi,ebx  ;edi指向了第一个函数的字符串名起始
   mov esi,edx  ;esi指向了特征函数名起始
   xor ecx,ecx
   mov cl,0ch   ;特征函数名的长度
   repe cmpsb
   je loc4    ;找到特征函数,转移

   pop ecx
   pop edi
   add edi,4  ;edi移动到下一个函数名所在地址
   inc eax    ;eax为索引
   loop loc3
loc4:
   pop ecx
   pop edi
   pop esi ;ESI指向数据目录中的导出表   
   mov edi,dword ptr [esi+24h] ;指向导出表的Name索引
   add edi,ebx ;EDI为AddressOfNamesOrdinals数组起始位置

   ;计算eax处的值
   sal eax,1   ;eax中存放了指定索引距离数组的偏移
   add edi,eax
   mov ax,word ptr [edi]  ;又是一个索引
   mov edi,dword ptr [esi+1ch]  ;AddressOfFunctions
   add edi,ebx   
   
   sal eax,2
   add edi,eax
   mov eax,dword ptr [edi]
   add eax,ebx

   ;edx指向patch.dll
   ;加载dll,引发对补丁的调用
   pop edx		;db 'LoadLibraryA'地址
   add edx,0dh  ;'pach2'地址
   push edx
   call eax	;LoadLibraryA
   ;跳转
   db 0E9h,0FFh,0FFh,0FFh,0FFh
   end start

  总结

pach1.asm 流程:

1.通过PEB获取kernel32.dll基地址。

2.获取LoadLibraryA的函数VA。

3.调用LoadLibraryA函数,动态引入动态链接库pach2.dll(winResult.dll改一下入口函数)。

4.跳转到入口地址执行,该部分代码也可以使用FF 25无条件跳转指令。

       ●补丁代码二(32位C语言程序补丁DLL)

       winResult.h

/*
; winResult.dll 导出函数:
; 1、AnimateOpen(DWORD)
;	窗口抖动进入效果
; 2、AnimateClose(DWORD)
;	窗口抖动退出效果
; 3、FadeInOpen(DWORD)
;	窗口淡入效果,仅运行在2000/XP以上操作系统
; 4、FadeOutClose(DWORD)
;	窗口淡出效果,仅运行在2000/XP以上操作系统
; ******************************************************************** /
*/
#pragma once
#include <windows.h>

#ifdef _cplusplus //如果C++模式编译

	#ifdef API_EXPORT 
		#define EXPORT   extern "C" __declspec(dllexport)   //C和C++程序都能使用这个DLL
	#else
		#define EXPORT    extern "C" __declspec(dllimport)	//当头文件供调用库的程序使用时
	#endif
#else
	#ifdef API_EXPORT 
		#define EXPORT  __declspec(dllexport) //导出DLL中任何函数、变量、或者类
	#else
		#define EXPORT  __declspec(dllimport) //当头文件供调用库的程序使用时
	#endif
#endif

EXPORT  void AnimateOpen(HWND);
EXPORT  void AnimateClose(HWND);
EXPORT  void FadeInOpen(HWND);
EXPORT  void FadeOutClose(HWND);

	winResult.c
	/*------------------------------------------------------------------------
 FileName:winResult.c
 实验89:一个简单的动态链接库例子,DLL入口函数增加一个对话框
 (c) bcdaren, 2024
-----------------------------------------------------------------------*/
#include <windows.h>
#define API_EXPORT
#include "winResult.h"

#define MAX_XYSTEPS		50
#define DELAY_VALUE		50	//动画效果使用的步长
#define X_STEP_SIZE		10
#define Y_STEP_SIZE		9
#define X_START_SIZE		20
#define Y_START_SIZE		10
#define LMA_ALPHA		2
#define LMA_COLORKEY	1

//入口和退出点
int WINAPI DllMain(HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved)
{
	const TCHAR szHello[] = TEXT("HelloWorldPE");
	switch (fdwReason)
	{
	case DLL_PROCESS_ATTACH:
		MessageBox(NULL,szHello,NULL,MB_OK);
		break;
	default:
		break;
	}
	return TRUE;
}

//私有函数:本函数在dll内部使用
DWORD TopXY(DWORD wDim,DWORD sDim)
{
	return (sDim / 2 - wDim / 2);
}

//窗口抖动进入效果
EXPORT  void AnimateOpen(HWND hWin)
{
	RECT rect;
	DWORD Xsize, Ysize,Xplace,Yplace;
	int sWth,sHth,counts;

	GetWindowRect(hWin, &rect);
	Xsize = X_START_SIZE;
	Ysize = Y_START_SIZE;
	sWth = GetSystemMetrics(SM_CXSCREEN);
	Xplace = TopXY(Xsize, sWth);
	sHth = GetSystemMetrics(SM_CYSCREEN);
	Yplace = TopXY(Ysize, sHth);
	counts = MAX_XYSTEPS;
	while (counts--)
	{
		MoveWindow(hWin, Xplace, Yplace, Xsize, Ysize, FALSE);
		ShowWindow(hWin, SW_SHOWNA);
		Sleep(DELAY_VALUE);
		ShowWindow(hWin, SW_HIDE);
		Xsize += X_STEP_SIZE;
		Ysize += Y_STEP_SIZE;
		Xplace = TopXY(Xsize, sWth);
		Yplace = TopXY(Ysize, sHth);
	}
	Xsize = rect.right - rect.left;
	Ysize = rect.bottom - rect.top;
	Xplace = TopXY(Xsize,sWth);
	Yplace = TopXY(Ysize,sHth);
	MoveWindow(hWin,Xplace,Yplace,Xsize,Ysize,TRUE);
	ShowWindow(hWin, SW_SHOW);
}

//窗口抖动退出效果
EXPORT void AnimateClose(HWND hWin)
{
	RECT rect;
	DWORD Xsize, Ysize, Xplace, Yplace;
	int sWth, sHth, counts;

	ShowWindow(hWin, SW_HIDE);
	GetWindowRect(hWin, &rect);
	Xsize = rect.right - rect.left;
	Ysize = rect.bottom - rect.top;
	sWth = GetSystemMetrics(SM_CXSCREEN);
	Xplace = TopXY(Xsize, sWth);
	sHth = GetSystemMetrics(SM_CYSCREEN);
	Yplace = TopXY(Ysize, sHth);
	counts = MAX_XYSTEPS;
	while (counts--)
	{
		MoveWindow(hWin, Xplace, Yplace, Xsize, Ysize, FALSE);
		ShowWindow(hWin, SW_SHOWNA);
		Sleep(DELAY_VALUE);
		ShowWindow(hWin, SW_HIDE);
		Xsize -= X_STEP_SIZE;
		Ysize -= Y_STEP_SIZE;
		Xplace = TopXY(Xsize, sWth);
		Yplace = TopXY(Ysize, sHth);
	}
}
//窗口淡入效果,仅运行在2000/XP以上操作系统
EXPORT void FadeInOpen(HWND hWin)
{
	const TCHAR User32[] = TEXT("user32.dll");
	const CHAR SLWA[] = "SetLayeredWindowAttributes";//函数名使用ASCII码字符
	FARPROC pSLWA;
	int Value = 90;

	LONG stl = GetWindowLong(hWin, GWL_EXSTYLE);//检索扩展的窗口样式
	// 增加“分层窗口”样式
	stl |= WS_EX_LAYERED;	
	SetWindowLong(hWin,GWL_EXSTYLE,stl);
	//获取SetLayeredWindowAttributes在user32.dll中的地址
	pSLWA = GetProcAddress(GetModuleHandle(User32), (LPCSTR)SLWA);
	//设置分层窗口的不透明度和透明度颜色键
	pSLWA(hWin,0,0,LMA_ALPHA);
	ShowW
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值