说明:
TextView.exe
是一个非常简单的文本查看程序,只要用鼠标将要查看的文本文件拖动(Drop)到其中,即可通过它查看文本文件的内容。如下
使用 PEView 工具查看TextView.exe
可执行文件的 IDT
(Import directory Table
,导入目录表)。
从上图可以看到,TextView.exe
中直接导入的 DLL 文件为 KERNEL32.dll
、USER32.dll
、 GDI32.dll
、 SHELL32.dll
。
这一节用来分析 myhack3.dll
的源代码。
DLLMain()
函数的功能非常简单,创建线程运行指定的线程过程(ThreadProc) 中调用DownloadURL()
与DropFile()
函数。
DownloadURL()
函数会下载参数szURL
中指定的网页文件,并将其保存到szFile
目录。
DropFile()
函数将下载的index.html
文件拖到TextView_Patch.exe
进程并显示其内容。
dummy()
导出函数,但是没有任何功能,仅仅保持dll文件的形式上完整。
在PE文件中导入某个 DLL ,实质就是在文件代码内调用该 DLL 提供的导出函数。PE 文件头中记录着 DLL 名称、函数名称等信息。因此,myhack3.dll
至少需要向外提供一个以上的导出函数才能保持形式上的完整性。
PE文件中导入的 dll 信息以结构体列表的形式存储在 IDT 中。重要将 myhack3.dll 添加到列表尾部就可以了。 当然,此前需要确认一下 IDT 中有无足够的空间。
首先,使用 PEView 查看 TextView.exe 的 IDT 地址(PE 文件头的 IMAGE_OPTIONAL_HEADER 结构体中导入表 RVA 值即为 IDT 的RVA值)。
从下图可以看出,IDT 的地址(RVA)为 84CC 。 接下来在 PEView 中直接查看 IDT (在 PEView
工具栏中设置地址视图选项为 RVA )。
从上图可以看出,TextView.exe 的 IDT 存在与 .rdata
节区。 IDT 是由 IMAGE_IMPORT_DESCRIPTOR
(以下简称 IID) 结构体组成的数组,且数组的末尾以 NULL 结构体结束。由于每个导入的 DLL 文件都对应 1 个 IID 结构体(每个 IID 结构体的大小为 0*1
4字节),所以下图中的整个 IID 区域为RVA:84CC ~ 852F(整体大小为:0*
14*
5 = 0*
64字节)
IID 结构体的定义
在 PEView 工具栏将视图改为 File Offset,可以看到 IDT 的文件偏移为 76CC,如下图所示
使用 HxD 使用工具打开 TextView文件,找到 76CC 地址,如下图所示
IDT 的文件偏移为 76CC ~ 772F ,整个大小为64字节,共有5个 IID 结构体,其中最后一个为 NULL
结构体。从图中可以看出 IDT 尾部存在其它数据,没有足够的空间来添加 myhack3.dll 的 IID 结构体。
在这种情况下,我们首先要把整个 IDT 转移到其他更广阔的位置。然后在添加新的 IID。确定移动的目标位置时,可以使用下面三种方式:
首先第一种,即查找文件中的空白区域(程序运行时未使用的区域)。正如下图所示,.rdata
节区尾部恰好存在大片空白区域(一般来说,节区或文件末尾都存在空白区域,PE 文件中这种空白区域称为 Null-Padding 区域)。
下面,就需要把原来的 IDT 移动到该 Null-Padding 区域(RVA:8C60 ~ 8DFF)中合适的位置即可。在此之前需要确认一下该区域(RVA:8C60 ~ 8DFF)是否是空白空用区域(Null-Padding区域)。
需要注意的是,并不是文件中所有区域都会被无条件加载到进程的虚拟内存,只有节区头中明确记录的区域才会被加载。使用 PEView 工具查看 TextView.exe 文件的 .rdata
节区头,如下所示:
节区头中存储着对应节区的位置,大小,属性等信息。整理.rdata
节区头中信息,如下表所示
从节区头中信息可以看出,.rdata
节区在磁盘文件与内存中的大小是不同的。
.rdata
节区在磁盘文件中的大小为 2E00
,而文件执行后被加载到内存时,程序实际使用的数据大小(映射大小)仅为 2C56 ,剩余未被使用的区域大小为 1AA (2E00 - 2C56)。在这段空白区域创建 IDT 是不会有什么问题的。
空白区域
RVA:8C56 - 8E00 --> RAW : 7E56 - 8000
故下图的 Null-Padding 区域是可以使用的,接下来我们在 RVA:8C80(RAW:7E80)位置创建 IDT。
先把 TextView.exe 复制到工作文件夹,重命名为TextView_Patch.exe
。下面使用TextView_Patch.exe
文件进行练习打补丁。基本操作步骤是:先使用 PEView 打开 TextView.exe 原文件,查看各种 PE 信息,然后使用 HxD 打开 TextView_Patch 文件进行修改。
IMAGE_OPTIONAL_HEADERD
的导入表结构体成员用来指出 IDT 的位置(RVA)与大小,如下所示
TextView.exe 文件中,导入表的 RVA 值为 84CC 。接下来,将导入表的 RVA 值更改为新 IDT 的 RVA 值 8C80,在 Size 原值64字节的基础上再加 14字节(IID 结构体的大小),修改为78字节,如下所示。
从现在开始,导入表位于 RVA: 8C80(RAW : 7E80)地址出。
BOUND IMPORT TABLE
(绑定导出表)是一种提高 DLL 加载速度的技术,如下
若向正常导入 myhack3.dll ,需要向绑定导入表添加信息。但是幸运的是,该绑定导入表是个可选选项,不是必须存在的,所以可以删除(修改其值为 0 即可)以获取更大便利。当然绑定导入表完全不存在也没有关系,但若存在,且其内信息记录错误,则会在程序运行时引发错误。本示例 TextView.exe 文件中,绑定导入表各项的值均为 0 ,不需要再修改。修改文件时,一定要注意检查绑定导入表中的数据。
先使用 Hex Editor 完全复制原 IDT(RAW:76CC ~ 772F),然后覆写(Past write)到 IDT 的新位置(RAW :7E80 ),如下图所示
然后在新的IDT 尾部(RAW:7ED0)添加与 myhack3.dll 对应的 IID (后面会单独讲解各个成员的数据)
在准确位置(RAW:7ED0)写入相关数据,如下所示
前面添加的 IID 结构体成员拥有指向其他数据结构(INT、Name、IAT)的RVA值。因此,必须准确设置这些数据结构才能保证 TextView_Patch.exe 文件的正常运行。由前面的设置可知 INT、Name、IAT的 RVA/RAW 的值整理如下
这些地址(RVA:8D00,8D10,8D20)就位于新建的IDT(RVA:8C80)下方。当然也可以选择其他符合条件的区域。在 HxD 编辑器中转到 7F00 地址处,输入相应值,如下图所示
为了更好地理解以上内容,使用 PEView 打开 TextView_Patch.exe 文件查看同一区域,查看时使用 RVA 视图方式,如下所示
8CD0地址处存在着 myhack3.dll 的结构体,其中3各主要成员(RVA of INT 、RVA of Name、RVA of IAT)的值分别时实际 INT 、Name、IAT的指针。
简单来说,INT(Import Name Table,导入名称表)是 RVA 数组,数组的各个元素都是一个 RVA 地址,该地址由导入函数的 Ordinal(2个字节)+ Func Name String 结构体构成,数组的末尾为 NULL。 上图中 INT 有一个元素,其值为 8D30,该地址是要导入的函数的 Ordinal(2字节)与函数的名称字符串(“dummy”)。
Name 是包含导入函数的 DLL 文件名称字符串,在 8D10 地址处可以看到myhack3.dll
字符串。
IAT 也是RVA 数组,各元素既可以拥有与 INT 相同的值,也可以拥有其他不用的值(若 INT 中的数据准确,IAT 也可以拥有其他不同值)。反正实际运行时,PE 装载器会将虚拟内存中的 IAT 替换为实际函数的地址
加载 PE 文件到内存时,PE装载器会修改 IAT ,写入函数的实际地址,所以相关节区一定要拥有 WRITE(可写)属性。只有这样,PE 装载器才能正常进行写入操作。使用 PEView查看.rdata
节区头,如下所示
向原属性(ChAracteristics)40000040 添加 IMAGE_SCN_MEM_WRITE(80000000)属性值,执行 bit OR 运算,最终属性值变为 C0000040 , 如下图所示
首先使用 PEView 工具打开修改后的 TextView_Patch.exe 文件,查看其 IDT ,如下所示
向 IDT 导入 myhack3.dll 的 IID 结构体已设置正常。如下所示, myhack3.dll 的 dummy() 函数被添加到 INT。
从文件结构来说修改成功,接下来运行文件看看程序是否能正常运行。将TextView_Patch.exe和myhack3.dll 放入相同文件夹,然后运行 TextView_Patch.exe 文件,结果如下所示:
可以看见 TextView_Patch.exe 成功加载了 myhack.dll 文件并下载了指定网站的 index.html 文件,并在 TextView_Patch.exe 中显示。
/
/
dllmain.cpp : 定义 DLL 应用程序的入口点。
HWND g_hWnd
=
NULL;
extern
"C"
{
/
/
导出函数,但是没有任何功能,仅仅保持dll文件的形式上完整。
__declspec(dllexport) void dummy()
{
return
;
}
}
/
/
DownloadURL 下载 szURL 中指定网站的文件,并将其保存在 szFile 目录。
BOOL
DownloadURL(LPCTSTR szURL, LPCTSTR szFile)
{
BOOL
bRet
=
FALSE;
HINTERNET hInternet
=
NULL, hURL
=
NULL;
BYTE pBuf[DEF_BUF_SIZE]
=
{
0
, };
DWORD dwBytesRead
=
0
;
FILE
*
pFile
=
NULL;
errno_t err
=
0
;
hInternet
=
InternetOpen(L
"ReverseCore"
,
INTERNET_OPEN_TYPE_PRECONFIG,
NULL,
NULL,
0
);
if
(NULL
=
=
hInternet)
{
OutputDebugString(L
"InternetOpen() failed!"
);
return
FALSE;
}
hURL
=
InternetOpenUrl(hInternet,
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"
))
{
OutputDebugString(L
"fopen() failed!"
);
goto _DownloadURL_EXIT;
}
while
(InternetReadFile(hURL, pBuf, DEF_BUF_SIZE, &dwBytesRead))
{
if
(!dwBytesRead)
break
;
fwrite(pBuf, dwBytesRead,
1
, pFile);
}
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)
{
DWORD dwPID
=
0
;
GetWindowThreadProcessId(hWnd, &dwPID);
if
(dwPID
=
=
(DWORD)lParam)
{
g_hWnd
=
hWnd;
return
FALSE;
}
return
TRUE;
}
HWND GetWindowHandleFromPID(DWORD dwPID)
{
EnumWindows(EnumWindowsProc, dwPID);
return
g_hWnd;
}
/
/
DropFile 函数将下载的 index.html 文件 拖到 TextView_Path.exe进程并显示其内容。
BOOL
DropFile(LPCTSTR wcsFile)
{
HWND hWnd
=
NULL;
DWORD dwBufSize
=
0
;
BYTE
*
pBuf
=
NULL;
DROPFILES
*
pDrop
=
NULL;
char szFile[MAX_PATH]
=
{
0
, };
HANDLE hMem
=
0
;
WideCharToMultiByte(CP_ACP,
0
, wcsFile,
-
1
,
szFile, MAX_PATH, NULL, NULL);
dwBufSize
=
sizeof(DROPFILES)
+
strlen(szFile)
+
1
;
if
(!(hMem
=
GlobalAlloc(GMEM_ZEROINIT, dwBufSize)))
{
OutputDebugString(L
"GlobalAlloc() failed!!!"
);
return
FALSE;
}
pBuf
=
(LPBYTE)GlobalLock(hMem);
pDrop
=
(DROPFILES
*
)pBuf;
pDrop
-
>pFiles
=
sizeof(DROPFILES);
strcpy_s((char
*
)(pBuf
+
sizeof(DROPFILES)), strlen(szFile)
+
1
, szFile);
GlobalUnlock(hMem);
if
(!(hWnd
=
GetWindowHandleFromPID(GetCurrentProcessId())))
{
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
APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
CloseHandle(CreateThread(NULL,
0
, ThreadProc, NULL,
0
, NULL));
break
;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break
;
}
return
TRUE;
}
/
/
dllmain.cpp : 定义 DLL 应用程序的入口点。
HWND g_hWnd
=
NULL;
extern
"C"
{
/
/
导出函数,但是没有任何功能,仅仅保持dll文件的形式上完整。
__declspec(dllexport) void dummy()
{
return
;
}
}
/
/
DownloadURL 下载 szURL 中指定网站的文件,并将其保存在 szFile 目录。
BOOL
DownloadURL(LPCTSTR szURL, LPCTSTR szFile)
{
BOOL
bRet
=
FALSE;
HINTERNET hInternet
=
NULL, hURL
=
NULL;
BYTE pBuf[DEF_BUF_SIZE]
=
{
0
, };
DWORD dwBytesRead
=
0
;
FILE
*
pFile
=
NULL;
errno_t err
=
0
;
hInternet
=
InternetOpen(L
"ReverseCore"
,
INTERNET_OPEN_TYPE_PRECONFIG,
NULL,
NULL,
0
);
if
(NULL
=
=
hInternet)
{
OutputDebugString(L
"InternetOpen() failed!"
);
return
FALSE;
}
hURL
=
InternetOpenUrl(hInternet,
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"
))
{
OutputDebugString(L
"fopen() failed!"
);
goto _DownloadURL_EXIT;
}
while
(InternetReadFile(hURL, pBuf, DEF_BUF_SIZE, &dwBytesRead))
{
if
(!dwBytesRead)
break
;
fwrite(pBuf, dwBytesRead,
1
, pFile);
}
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)
{
DWORD dwPID
=
0
;
GetWindowThreadProcessId(hWnd, &dwPID);
if
(dwPID
=
=
(DWORD)lParam)
{
g_hWnd
=
hWnd;
return
FALSE;
}
return
TRUE;
}
HWND GetWindowHandleFromPID(DWORD dwPID)
{
EnumWindows(EnumWindowsProc, dwPID);
return
g_hWnd;
}
/
/
DropFile 函数将下载的 index.html 文件 拖到 TextView_Path.exe进程并显示其内容。
BOOL
DropFile(LPCTSTR wcsFile)
{
HWND hWnd
=
NULL;
DWORD dwBufSize
=
0
;
BYTE
*
pBuf
=
NULL;
DROPFILES
*
pDrop
=
NULL;
char szFile[MAX_PATH]
=
{
0
, };
HANDLE hMem
=
0
;
WideCharToMultiByte(CP_ACP,
0
, wcsFile,
-
1
,
szFile, MAX_PATH, NULL, NULL);
dwBufSize
=
sizeof(DROPFILES)
+
strlen(szFile)
+
1
;
if
(!(hMem
=
GlobalAlloc(GMEM_ZEROINIT, dwBufSize)))
{
OutputDebugString(L
"GlobalAlloc() failed!!!"
);
return
FALSE;
}
pBuf
=
(LPBYTE)GlobalLock(hMem);
pDrop
=
(DROPFILES
*
)pBuf;
pDrop
-
>pFiles
=
sizeof(DROPFILES);
strcpy_s((char
*
)(pBuf
+
sizeof(DROPFILES)), strlen(szFile)
+
1
, szFile);
GlobalUnlock(hMem);
if
(!(hWnd
=
GetWindowHandleFromPID(GetCurrentProcessId())))
{
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
APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
CloseHandle(CreateThread(NULL,
0
, ThreadProc, NULL,
0
, NULL));
break
;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break
;
}
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
APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
CloseHandle(CreateThread(NULL,
0
, ThreadProc, NULL,
0
, NULL));
break
;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break
;
}
return
TRUE;
}
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)