首页
社区
课程
招聘
[原创]通过修改PE加载DLL
发表于: 2021-4-15 15:27 9540

[原创]通过修改PE加载DLL

2021-4-15 15:27
9540

前面的创建远程线程进行注入等都是直接向"运行中的进程"强制注入DLL。而通过直接修改目标程序的可执行文件,使其在运行时就加载指定的dll文件。这种方法只需要更改一次,之后每当程序运行时就会自动加载指定的dll文件。所以这其实是一种破解方法。

练习文件下载:

一个查看和编辑文本的程序

链接:https://pan.baidu.com/s/1sGpIhZJ0fbrJIf7aMzH57Q
提取码:0syj

其实就是一个LPVOID型的指针

概况:初始化一个应用程序,以使用 WinINet 函数。

为了使网络连接生效,必须用函数InternetOpen函数创建一个HINTERNET根句柄。InternetOpen函数接收的参数为:用户代理信息(访问Internet函数的应用),访问网络的类型,代理主机和地址以及代理的行为动作。

InternetOpen 的第一个参数lpszCallerName指定正在使用网络函数的应用程序。当HTTP协议使用时,这个名字将变成用户代理。

函数声明:

参数

lpszAgent

指向一个结束的字符串,该字符串指定调用WinInet函数的应用程序或实体的名称。使用此名称作为用户代理的HTTP协议

dwAccessType

指定访问类型,参数可以是下列值之一:

Untitled

lpszProxyName

指针指向一个空结束的字符串,该字符串指定的代理服务器的名称,不要使用空字符串;如果dwAccessType未设置为INTERNET_OPEN_TYPE_PROXY,则此参数应该设置为NULL。

lpszProxyBypass

指向一个空结束的字符串,该字符串指定的可选列表的主机名或IP地址。如果dwAccessType未设置为INTERNET_OPEN_TYPE_PROXY的 ,参数省略则为NULL。

dwFlags

参数可以是下列值的组合:

dwFlags

返回值

成功:返回一个有效的句柄,该句柄将由应用程序传递给接下来的WinINet函数。

失败:返回NULL。

通过一个完整的FTP,Gopher或HTTP网址打开一个资源。

函数声明:

参数:

hInternet

当前的 Internet 会话句柄。句柄必须由前期的 InternetOpen 调用返回。

lpszUrl

一个空字符结束的字符串变量的指针,指定读取的网址。只有以ftp:, gopher:, http:, 或者 https: 开头的网址被支持。

lpszHeaders

一个空字符结束的字符串变量的指针,指定发送到HTTP服务器的头信息。欲了解更多信息,请参阅HttpSendRequest函数里lpszHeaders参数的说明。

dwHeadersLength

dwFlags

此参数可为下列值之一。

数值 说明

INTERNET_FLAG_EXISTING_CONNECT

如果使用相同的必须属性创建会话,会尝试利用现有的InternetConnect对象。这只对FTP操作非常有用,因为FTP是唯一在同一会话中执行多种操作的协议。WinINet API 为每个由InternetOpen产生的HINTERNET句柄缓冲一个单独链接句柄。InternetOpenUrl使用此标志的HTTP和FTP连接。

INTERNET_FLAG_HYPERLINK

当决定何时从网络重载时,如果服务器没有返回 Expires time 和 LastModified,那么强制重载。

INTERNET_FLAG_IGNORE_CERT_CN_INVALID

停用检查从服务器对必须的主机名称返回的SSL/PCT-based证书。 WinINet函数使用简单的比较匹配主机名称和通配符的规则检查证书。

INTERNET_FLAG_IGNORE_CERT_DATE_INVALID

停用检查的SSL/PCT-based的证书的适当的有效日期。

INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP

禁用检测这中特殊的重定向。当使用此标志, WinINet 透明允许从HTTPS到HTTP URL的重定向。

INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS

禁用检测这中特殊的重定向。当使用此标志, WinINet 透明的允许的HTTP到HTTPS URL的重定向。

INTERNET_FLAG_KEEP_CONNECTION

如果可能的话,为连接使用保活语义。这个标志要求微软网络( MSN ),NTLM和其他类型的身份验证。

INTERNET_FLAG_NEED_FILE

如果要创建的文件不能被缓存,创建临时文件

INTERNET_FLAG_NO_AUTH

不试图自动验证。

INTERNET_FLAG_NO_AUTO_REDIRECT

不自动处理HttpSendRequest中的重定向

INTERNET_FLAG_NO_CACHE_WRITE

不添加返回实体到缓存。

INTERNET_FLAG_NO_COOKIES

不会自动添加的Cookie头到请求,并且不自动添加返回的cookie到cookie数据库。

INTERNET_FLAG_NO_UI

禁用Cookie的对话框。

INTERNET_FLAG_PASSIVE

使用被动FTP语义。InternetOpenUrl为FTP的文件和目录使用此标志。

INTERNET_FLAG_PRAGMA_NOCACHE

即使代理中存在缓存副本,也强制要求由源服务器返回。

INTERNET_FLAG_RAW_DATA

检索的Gopher目录信息时,传回的数据作为GOPHER_FIND_DATA结构,如果检索的FTP目录信息时,作为一个WIN32_FIND_DATA结构。如果此标志没有指定,或者请求通过CERN代理创建, InternetOpenUrl返回的HTML版本的目录。

INTERNET_FLAG_RELOAD

从原服务器强制下载所要求的文件,对象,或目录列表,而不是从缓存下载。

INTERNET_FLAG_RESYNCHRONIZE

重新加载的HTTP资源,如果资源在最后一次下载后已被修改。所有FTP和Gopher资源将被重载

INTERNET_FLAG_SECURE

使用安全传输语义。这次传输使用安全套字节层/专用通信技术(的SSL / PCT ),这只有在HTTP请求时有意义。

dwContext

一个指向一个应用程序定义的值,将随着返回的句柄,一起传递给回调函数465。

返回值

如果已成功建立到FTP,Gopher,或HTTP URL的连接,返回一个有效的句柄,如果连接失败返回NULL。要检索特定的错误讯息,请GetLastError 。要确定为什么对服务器的访问被拒绝,请调用InternetGetLastResponseInfo

函数介绍: 从一个由InternetOpenUrl,FtpOpenFile, 或HttpOpenRequest函数打开的句柄中读取数据

参数:

Parameters

InternetOpenUrl,FtpOpenFile, 或HttpOpenRequest函数返回的句柄

lpBuffer[out]

缓冲器指针

dwNumberOfBytesToRead[in]

欲读数据的字节量

lpdwNumberOfBytesRead[out]

返回值

成功:返回TRUE,失败,返回FALSE

WideCharToMultiByte是一个函数,该函数可以映射一个unicode字符串到一个多字节字符串,执行转换的代码页、接收转换字符串、允许额外的控制等操作。

函数原型:

CodePage:指定执行转换的代码页,这个参数可以为系统已安装或有效的任何代码页所给定的值。

(可理解为要转化成的字符串的编码方式)

lpWideCharStr:指向将被转换的unicode字符串。

cchWideChar:指定由参数lpWideCharStr指向的缓冲区的字符个数。如果这个值为-1,字符串将被设定为以NULL为结束符的字符串,并且自动计算长度。

lpMultiByteStr:指向接收被转换字符串的缓冲区。

cchMultiByte:指定由参数lpMultiByteStr指向的缓冲区最大值(用字节来计量)。若此值为零,函数返回lpMultiByteStr指向的目标缓冲区所必需的字节数,在这种情况下,lpMultiByteStr参数通常为NULL。

lpDefaultCharpfUsedDefaultChar:只有当WideCharToMultiByte函数遇到一个宽字节字符,而该字符在uCodePage参数标识的代码页中并没有它的表示法时,WideCharToMultiByte函数才使用这两个参数。如果宽字节字符不能被转换,该函数便使用lpDefaultChar参数指向的字符。如果该参数是NULL(这是大多数情况下的参数值),那么该函数使用系统的默认字符。该默认字符通常是个问号。这对于文件名来说是危险的,因为问号是个通配符。pfUsedDefaultChar参数指向一个布尔变量,如果Unicode字符串中至少有一个字符不能转换成等价多字节字符,那么函数就将该变量置为TRUE。如果所有字符均被成功地转换,那么该函数就将该变量置为FALSE。当函数返回以便检查宽字节字符串是否被成功地转换后,可以测试该变量。

注意:指针lpMultiByteStr和lpWideCharStr必须不一样。如果一样,函数将失败,GetLastError将返回ERROR_INVALID_PARAMETER的值。

该函数从堆中分配一定数目的字节数。Win32内存管理器并不提供相互分开的局部和全局堆。提供这个函数只是为了与16位的Windows相兼容。简称全局堆分配.

uFlags

指定如何分配内存,若指定为0,则是默认的GMEM_FIXED.这个值可以是下面其中一个或几个位标识(那些指明不兼容的组合除外)

标识的含意

dwBytes

指定要申请的字节数.若该参数为 0 且参数 uFlags 指定为 GMEM_MOVEABLE 则该函数返回一个内存对象的句柄,该内存对象被标识为discarded(可抛弃的)。

返回值:

若函数调用成功,则返回一个新分配的内存对象的句柄

若函数调用失败,则返回NULL。可调用GetLastError以获得更多错误信息。

锁定内存中指定的内存块,并返回一个地址值,令其指向内存块的起始处。

说明:

锁定内存中指定的内存块,并返回一个地址值,令其指向内存块的起始处。除非用 GlobalUnlock 函数将内存块解锁,否则地址会一直保持有效。Windows为每个内存对象都维持着一个锁定计数。对这个函数的每次调用都应有一个对应的 GlobalUnlock 调用返回值Long,如成功,返回内存块的地址;如出错,或者这是一个已被丢弃的“可丢弃”内存块,则返回零。会设置GetLastError参数表

获取当前进程一个唯一的标识符。

说明:

获取当前进程的标示符(PID)

返回值:

返回一个标示符(PID)

该函数枚举所有屏幕上的顶层窗口,并将窗口句柄传送给应用程序定义的回调函数。回调函数返回FALSE将停止枚举,否则EnumWindows函数继续到所有顶层窗口枚举完为止。

参数:

lpEnumFunc:指向一个应用程序定义的回调函数指针,请参看EnumWindowsProc

lPararm:指定一个传递给回调函数的应用程序定义值。

回调函数原型

BOOL CALLBACK EnumWindowsProc(HWND hwnd,LPARAM lParam);

参数:

hwnd:顶层窗口的句柄

lparam:应用程序定义的一个值(即EnumWindows中lParam)

用于将一条消息放入到消息队列中。消息队列里的消息通过调用GetMessage和PeekMessage取得。

该函数将一个消息放入(寄送)到与指定窗口创建的线程相联系消息队列里,不等待线程处理消息就返回,是异步消息模式。消息队列里的消息通过调用GetMessage和PeekMessage取得。

函数原型:

BOOL WINAPI PostMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam);

参数:

hWnd:其窗口程序接收消息的窗口的句柄。可取有特定含义的两个值:

HWND_BROADCAST:消息被寄送到系统的所有顶层窗口,包括无效或不可见的非自身拥有的窗口、 被覆盖的窗口和弹出式窗口。消息不被寄送到子窗口

NULL:此函数的操作和调用参数dwThread设置为当前线程的标识符PostThreadMessage函数一样

Msg:指定被寄送的消息。

wParam:指定附加的消息特定的信息。

LParam:指定附加的消息特定的信息。

返回值

如果函数调用成功,返回非零,否则函数调用返回值为零

其实我们修改的就是(IDT) IMPORT DIRECTORY TABLE(image_import_descriptor,即IID结构体)组成的数组,只要将我们的dll添加到这个数组的结尾就可以让程序运行时自动加载我们的dll了。

我们从Image_Optional_Header的DataDirectory的第二项IMPORT TABLE得到的就是这个结构体数组的RVA和Size。
图片描述
这里再详细的讲解一下RVA和文件偏移的转化,其实很简单,记住每个节区有个差值是不变的,什么差值呢, 就是每个节区RVA - 对应的文件偏移是不变的,记住这一点,我们找到节区的起始RVA和起始文件偏移,相减就可以得到这个差值,

通过查看image_section_header,发现RVA84CC处于rdata区域

图片描述

然后我们就可以得出差值:0x6000-0x5200 = 0xE00

然后再用我们知道的RVA,即0x84CC - 差值0xE00 = 0x76CC

得到的0x76CC就是文件偏移。

下面再遇见RVA和文件偏移的转化不再过多赘述
图片描述

然后我们使用winhex打开可以发现在我们的IDT之后紧贴着其它数据,我们没有足够的空间来添加一个0x14字节的结构体进去。(?)

图片描述
在这种情况之下,我们只有移动我们的IDT到文件的其它地方

主要有三种方法:①查找文件的空白区域。②增加文件最后一个节区的大小。③在文件末尾添加新的节区。

常用的就是第一种,因为由于内存对齐的原因,一般都会导致节区的最后会有一段Null-padding区域。

查看IMAGE_NT_Headers对应的各节区头的信息
图片描述
查看rdata的,发现这个节区在文件中的大小为2E00,但是Virtual Size只有2C56,相减得到1AA,足够我们放下(0x14 * 6 = 0x78)字节的数据。(只有分析节区头的这个差值我们才能存放我们的代码,如果是为了对齐而在内存中添加的填充区域,我们是不能存放代码的)

所以我们的新的IDT的RVA就可以从(0x6000+0x2C56)之后选取一段0x78(0x14 * 6)字节的大小来存放我们的IDT

我们从0x8C80开始存放我们的IDT(转化为文件偏移为0x7E80)(新IDT: 0x7E80到(0x7E80+0x78))

直接将原先的IDT(76CC~772F)复制到新区域
图片描述

原先的值:

图片描述
根据文件偏移,我们找到那个位置,更改为我们的新的IDT的RVA和Size
图片描述

图片描述

这里可以发现为0,就直接可以不用管了,这是一种提高dll加载速度的技术,是个可选项,如果不存在,无影响,如果存在,又发生了错误,那么就会导致程序运行时发生错误。

首先我们设置OriginalFirstThunk,这是一个INT的RVA,我们使用8D00,处于1AA的那个区域之内都可以,TimeDateStamp和ForwarderChain都直接设置为0即可(8字节),Name是用来存放导入模块的名称字符串的,8D10处存放,FirstThunk是IAT的RVA,我们设置为8D20.

如下:

图片描述
然后我们去设置对应的INT和IAT和Name的值

最后如下:
图片描述

其实就是INT和IAT都是IMAGE_IMPORT_BY_NAME(hint+函数名)的RVA

在动态链接器刚完成映射没有开始重定位和符号解析的时候,IAT中的元素就是这个,但是如果Windows的动态链接器在完成该模块的链接时,元素值会被动态链接器改为该符号的真正地址。(类似于elf的got)。

原因:加载IAT的时候,PE装载器会修改IAT,写入函数的实际地址,所以对应的节区必须有写的属性。

直接修改image_section_header中的characteristic的值

图片描述

加上IMAGE_SCN_MEN_WRITE(80000000)的值

即修改那个值为C0000040

(其实我感觉这种方法不好,这样就直接把整个rdata区域都赋上了write的属性,容易被利用)。

补充:另外一种方法

向DataDirectory中的IAT添加值

图片描述
先将Size增加8字节(变为0x154+0x8 = 0x15C)

然后在对应RVA处就填入对应的IAT的RVA(00008D20)

即可

最后都保存出去运行即可

最后将程序和我们的dll放在同一个文件夹下,运行程序。

Process Explorer查看程序导入的dll

图片描述
成功导入dll

 
 
 
 
 
 
 
HINTERNET InternetOpen(
    _In_ LPCTSTR lpszAgent,
    _In_ DWORD dwAccessType,
    _In_ LPCTSTR lpszProxyName,
    _In_ LPCTSTR lpszProxyBypass,
    _In_ DWORD dwFlags
);
HINTERNET InternetOpen(
    _In_ LPCTSTR lpszAgent,
    _In_ DWORD dwAccessType,
    _In_ LPCTSTR lpszProxyName,
    _In_ LPCTSTR lpszProxyBypass,
    _In_ DWORD dwFlags
);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
HINTERNET InternetOpenUrl(
HINTERNET hInternet,
LPCTSTR lpszUrl,
LPCTSTR lpszHeaders,
DWORD dwHeadersLength,
DWORD dwFlags,
DWORD_PTR dwContext)
HINTERNET InternetOpenUrl(
HINTERNET hInternet,
LPCTSTR lpszUrl,
LPCTSTR lpszHeaders,
DWORD dwHeadersLength,
DWORD dwFlags,
DWORD_PTR dwContext)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
int WideCharToMultiByte(
 
UINT CodePage, //指定执行转换的[代码页]
 
DWORD dwFlags, //允许你进行额外的控制,它会影响使用了读音符号(比如重音)的字符
 
LPCWSTR lpWideCharStr, //指定要转换为宽字节字符串的[缓冲区]
 
int cchWideChar, //指定由参数lpWideCharStr指向的缓冲区的[字符]个数
 
LPSTR lpMultiByteStr, //指向接收被转换字符串的缓冲区
 
int cchMultiByte, //指定由参数lpMultiByteStr指向的[缓冲区]最大值
 
LPCSTR lpDefaultChar, //遇到一个不能转换的宽字符,函数便会使用pDefaultChar参数指向的字符
 
LPBOOL pfUsedDefaultChar //至少有一个字符不能转换为其多字节形式,函数就会把这个变量设为TRUE
 
);
int WideCharToMultiByte(
 
UINT CodePage, //指定执行转换的[代码页]
 
DWORD dwFlags, //允许你进行额外的控制,它会影响使用了读音符号(比如重音)的字符
 
LPCWSTR lpWideCharStr, //指定要转换为宽字节字符串的[缓冲区]
 
int cchWideChar, //指定由参数lpWideCharStr指向的缓冲区的[字符]个数
 
LPSTR lpMultiByteStr, //指向接收被转换字符串的缓冲区
 
int cchMultiByte, //指定由参数lpMultiByteStr指向的[缓冲区]最大值
 
LPCSTR lpDefaultChar, //遇到一个不能转换的宽字符,函数便会使用pDefaultChar参数指向的字符

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2021-4-18 19:14 被SYJ-Re编辑 ,原因:
收藏
免费 4
支持
分享
最新回复 (2)
雪    币: 7379
活跃值: (4086)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2

学习了

最后于 2021-4-15 23:00 被htpidk编辑 ,原因:
2021-4-15 22:54
1
雪    币: 5229
活跃值: (2860)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
好文,mark 了
2021-4-17 00:28
0
游客
登录 | 注册 方可回帖
返回
//