首页
论坛
课程
招聘
[原创]通过修改PE加载DLL
2021-4-15 15:27 6703

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

2021-4-15 15:27
6703

通过修改PE加载DLL

一:概述

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

 

练习文件下载:

 

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

 

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

二:DLL源码分析

(一):源码中使用到的新的函数和定义解析

1. HINTERNET

 

其实就是一个LPVOID型的指针

2. InternetOpen

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

 

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

 

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

 

函数声明:

1
2
3
4
5
6
7
HINTERNET InternetOpen(
    _In_ LPCTSTR lpszAgent,
    _In_ DWORD dwAccessType,
    _In_ LPCTSTR lpszProxyName,
    _In_ LPCTSTR lpszProxyBypass,
    _In_ DWORD dwFlags
);

参数

 

lpszAgent

 

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

 

dwAccessType

 

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

 

Untitled

 

lpszProxyName

 

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

 

lpszProxyBypass

 

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

 

dwFlags

 

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

 

dwFlags

 

返回值

 

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

 

失败:返回NULL。

3. InternetOpenUrl

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

 

函数声明:

1
2
3
4
5
6
7
HINTERNET InternetOpenUrl(
HINTERNET hInternet,
LPCTSTR lpszUrl,
LPCTSTR lpszHeaders,
DWORD dwHeadersLength,
DWORD dwFlags,
DWORD_PTR dwContext)

参数:

 

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

4. InternetReadFile

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

 

参数:

 

Parameters

  • hFile[in]
  • InternetOpenUrl,FtpOpenFile, 或HttpOpenRequest函数返回的句柄

  • lpBuffer[out]

  • 缓冲器指针

  • dwNumberOfBytesToRead[in]

  • 欲读数据的字节量

  • lpdwNumberOfBytesRead[out]

  • 接收读取字节量的变量。该函数在做任何工作或错误检查之前都设置该值为零

返回值

 

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

5. WideCharToMultiByte

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

 

函数原型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int WideCharToMultiByte(
 
UINT CodePage, //指定执行转换的[代码页]
 
DWORD dwFlags, //允许你进行额外的控制,它会影响使用了读音符号(比如重音)的字符
 
LPCWSTR lpWideCharStr, //指定要转换为宽字节字符串的[缓冲区]
 
int cchWideChar, //指定由参数lpWideCharStr指向的缓冲区的[字符]个数
 
LPSTR lpMultiByteStr, //指向接收被转换字符串的缓冲区
 
int cchMultiByte, //指定由参数lpMultiByteStr指向的[缓冲区]最大值
 
LPCSTR lpDefaultChar, //遇到一个不能转换的宽字符,函数便会使用pDefaultChar参数指向的字符
 
LPBOOL pfUsedDefaultChar //至少有一个字符不能转换为其多字节形式,函数就会把这个变量设为TRUE
 
);

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的值。

6. GlobalAlloc

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

1
2
3
4
HGLOBALGlobalAlloc(
UINTuFlags, // 分配属性(方式)
DWORDdwBytes // 分配的字节数
);

uFlags

 

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

 

标识的含意

 

 

dwBytes

 

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

 

返回值:

 

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

 

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

7. Globallock

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

 

说明:

 

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

8. GetCurrentProcessId

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

 

说明:

 

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

 

返回值:

 

返回一个标示符(PID)

9. EnumWindows

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

 

参数:

 

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

 

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

 

回调函数原型

 

BOOL CALLBACK EnumWindowsProc(HWND hwnd,LPARAM lParam);

 

参数:

 

hwnd:顶层窗口的句柄

 

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

10. PostMessage

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

 

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

 

函数原型:

 

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

 

参数:

 

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

 

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

 

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

 

Msg:指定被寄送的消息。

 

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

 

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

 

返回值

 

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

(二):源码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#include "stdio.h"
#include "windows.h"
#include "shlobj.h"
#include "Wininet.h"
#include "tchar.h"
 
#pragma comment(lib, "Wininet.lib")
 
#define DEF_BUF_SIZE            (4096)
#define DEF_URL                 L"http://www.google.com/index.html"
#define DEF_INDEX_FILE          L"index.html"
 
HWND g_hWnd = NULL;
 
#ifdef __cplusplus
extern "C" {
#endif
__declspec(dllexport) void dummy()         //设置这个函数只是为了后面填充IID的时候满足形式上的完整性
{
    return;
}
#ifdef __cplusplus
}
#endif
 
BOOL DownloadURL(LPCTSTR szURL, LPCTSTR szFile)
{
    BOOL            bRet = FALSE;                                          //判断函数是否正确被执行
    HINTERNET        hInternet = NULL, hURL = NULL;     //InternetOpen和InternetOpenUrl的返回值
    BYTE            pBuf[DEF_BUF_SIZE] = { 0, };                 //缓冲区
    DWORD           dwBytesRead = 0;                            //实际读取了多少字节量
    FILE            *pFile = NULL;                                        //文件指针
    errno_t         err = 0;                                                //errno_t是_tfopen_s的返回值类型
 
    hInternet = InternetOpen(L"ReverseCore",
        INTERNET_OPEN_TYPE_PRECONFIG,
        NULL,                                                                    //使用InternetOpen函数创建一个HINTERNET根句柄,从而使网络连接生效
        NULL,
        0);
    if (NULL == hInternet)                                                //判断是否返回了正确的句柄
    {
        OutputDebugString(L"InternetOpen() failed!");
        return FALSE;
    }
 
    hURL = InternetOpenUrl(hInternet,                        //通过一个HTTP网址打开资源
        szURL,
        NULL,
        0,
        INTERNET_FLAG_RELOAD,
        0);
    if (NULL == hURL)                                                    //判断是否返回了正确的句柄
    {
        OutputDebugString(L"InternetOpenUrl() failed!");
        goto _DownloadURL_EXIT;
    }
    if (err = _tfopen_s(&pFile, szFile, L"wt"))               //_tfopen_s打开文件成功返回的值是0
    {
        OutputDebugString(L"fopen() failed!");
        goto _DownloadURL_EXIT;
    }
 
    while (InternetReadFile(hURL, pBuf, DEF_BUF_SIZE, &dwBytesRead))    //读取hURL指定的资源到pBuf中,DEF_BUF_SIZE是准备读的字节,&dwBytesReads是真正读取的字节
    {
        if (!dwBytesRead)
            break;
 
        fwrite(pBuf, dwBytesRead, 1, pFile); //从pBuf写入到pFile中,内容的单位是dwBytesRead,共1个内容
    }
 
    bRet = TRUE;
 
_DownloadURL_EXIT:       //退出代码
    if (pFile)
        fclose(pFile);
 
    if (hURL)
        InternetCloseHandle(hURL);
 
    if (hInternet)
        InternetCloseHandle(hInternet);
 
    return bRet;
}
 
BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lParam) //第一个参数默认是屏幕上顶层窗口的句柄,第二个参数是EnumWindows函数传入的dwPID
{
    DWORD dwPID = 0;
 
    GetWindowThreadProcessId(hWnd, &dwPID);        //通过GetWindowThreadProcessId来通过窗口句柄来获取窗口对应进程的PID
 
    if (dwPID == (DWORD)lParam)          //判断该窗口是否是我们想要找的PID
    {
        g_hWnd = hWnd;     //是真的话,则将该窗口句柄赋给全局变量g_hWnd
        return FALSE;
    }
 
    return TRUE;
}
 
HWND GetWindowHandleFromPID(DWORD dwPID)       //定义一个函数**根据进程PID获取进程对应的窗口句柄
{**
    EnumWindows(EnumWindowsProc, dwPID);                //EnumWindows函数枚举所有屏幕上的顶层窗口
 
    return g_hWnd;
}
 
BOOL DropFile(LPCTSTR wcsFile)
{
    HWND            hWnd = NULL;                                        //窗口句柄
    DWORD           dwBufSize = 0;                                    //存储DROPFILES和szFile的缓冲区
    BYTE            *pBuf = NULL;                                            //指向缓冲区的指针
    DROPFILES        *pDrop = NULL;                                    //定义一个DROPFILES结构体指针
    char            szFile[MAX_PATH] = { 0, };                        //存储文件名
    HANDLE          hMem = 0;                                            //获取的内存的句柄(首地址)
 
    WideCharToMultiByte(CP_ACP, 0, wcsFile, -1,
        szFile, MAX_PATH, NULL, NULL);                              //将我们输入的字符串从宽字节转换成ANSI编码,CP_ACP指定代码页为ANSI
 
    dwBufSize = sizeof(DROPFILES) + strlen(szFile) + 1;   //计算缓冲区的大小(DROPFILES结构体加上路径需要的空间)
 
    if (!(hMem = GlobalAlloc(GMEM_ZEROINIT, dwBufSize))) //从堆中分配dwBufSize的内存并初始化为0
    {
        OutputDebugString(L"GlobalAlloc() failed!!!");
        return FALSE;
    }
 
    pBuf = (LPBYTE)GlobalLock(hMem);                                //让pBuf指向分配的内存
 
    pDrop = (DROPFILES*)pBuf;                                            //缓冲区的句柄就是结构体开始的地址,让pDrop指向DROPFILES结构体存储的地方
    pDrop->pFiles = sizeof(DROPFILES);                                //到要拖拽的文件名字符串存储区的偏移
    strcpy_s((char*)(pBuf + sizeof(DROPFILES)), strlen(szFile) + 1, szFile);   //给存储区赋值
 
    GlobalUnlock(hMem);
 
    if (!(hWnd = GetWindowHandleFromPID(GetCurrentProcessId()))) //获取当前进程的PID并传入GetWindowHandleFromPID获取对应的窗口句柄
    {
        **OutputDebugString(L"GetWndHandleFromPID() failed!!!");
        return FALSE;
    }
 
    PostMessage(hWnd, WM_DROPFILES, (WPARAM)pBuf, NULL); 
 
    return TRUE;
}
 
DWORD WINAPI ThreadProc(LPVOID lParam)
{
    TCHAR szPath[MAX_PATH] = { 0, };
    TCHAR *p = NULL;
 
    OutputDebugString(L"ThreadProc() start...");
 
    GetModuleFileName(NULL, szPath, sizeof(szPath));
 
    if (p = _tcsrchr(szPath, L'\\'))
    {
        _tcscpy_s(p + 1, wcslen(DEF_INDEX_FILE) + 1, DEF_INDEX_FILE);
 
        OutputDebugString(L"DownloadURL()");
        if (DownloadURL(DEF_URL, szPath))
        {
            OutputDebugString(L"DropFlie()");
            DropFile(szPath);
        }
    }
 
    OutputDebugString(L"ThreadProc() end...");
 
    return 0;
}
 
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch (fdwReason)
    {
    case DLL_PROCESS_ATTACH:
        CloseHandle(CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL));     //CreateThread创建线程执行ThreadProc
        break;
    }
 
    return TRUE;
}

(三)修改程序的PE,让程序一开始便加载我们的dll

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

1. 查看IDT是否有足够空间

我们从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到文件的其它地方

2. 移动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)复制到新区域
图片描述

3. 修改DataDirectory的第二项IMPORT TABLE的值

原先的值:

 

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

4. 删除绑定导入表

图片描述

 

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

5. 填写我们新增的IMAGE_IMPORT_DESCRIPTOR

1
2
3
4
5
6
7
8
9
10
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD Characteristics;
        DWORD OriginalFirstThunk;            //INT(Import Name Table) address (RVA)
    };
    DWORD TimeDateStamp;
    DWORD ForwarderChain;
    DWORD Name;                                //library name string address (RVA)
    DWORD FirstThunk;                        //IAT(Import Address Table) address (RVA)
}IMAGE_IMPORT_DESCRIPTOR;

首先我们设置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)。

6. 设置IAT节区的属性值

原因:加载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


Windows开发不完全指南

最后于 2021-4-18 19:14 被SYJ-Re编辑 ,原因:
收藏
点赞4
打赏
分享
最新回复 (2)
雪    币: 4739
活跃值: 活跃值 (1429)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
htpidk 活跃值 2021-4-15 22:54
2
1

学习了

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