练习文件
- 直接修改TextView.exe文件,使其在运行时自动加载myhack3.dll文件。
- TextView.exe是一个非常简单的文本查看程序,只要用鼠标将要查看的文本文件(myhack3.cpp)拖动到其中,即可通过它查看文本文件的内容。

- 使用PEView工具查看TextView.exe可执行文件的IDT(import Directory Table,导入目录表),TexeView.exe中直接导入的DLL文件为KERNEL32.dll、USER32.dll、GDI32.dll、SHELL32.dll.

- TextView_patched.exe是修改TextView.exe文件的IDT后得到的文件,即在IDT中添加了导入myhack3.dll的部分,运行时会自动导入myhack3.dll文件,使用PEView工具查看TextView_patched.exe的IDT。

- 运行程序,程序会自动加载myhack3.dll,尝试连接Google网站,下载网站的index.html,并将其放到TextView_Patched.exe程序。


源代码 - myhack3.cpp
- 分析myhack3.dll的源代码。
#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"
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));
break;
}
return TRUE;
}
- DllMain()函数的功能非常简单,创建线程运行指定的线程过程,在线程过程(ThreadProc)中调用DownLoadURL()与DropFIle()函数,下载指定网页并将其拖放到文本查看程序。
- DownloadURL()
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;
}
- DownloadURL()函数会下载参数szURL中指定的网页文件,并将其保存到szFile目录。
- 上述示例中DownloadURL()函数使用internetOpen()、InternetOpenUrl()、InternetReadFile() API对URLDownloadToFile() API的简单实现。
- DropFile()
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;
}
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;
}
- DropFile()函数的主要功能是,使用PID获取窗口句柄,在调用postMessage(WM_DROPFILES)API将消息放入消息队列。
- dummy()
__declspec(dllexport) void dummy()
{
return;
}
#ifdef __cplusplus
}
#endif
- dummy()函数是myhack3.dll文件项外部提供服务的导出函数,它没有任何功能,为了保持形式上的完整性,是myhack3.dll能够顺利添加到TextView.exe文件的导入表。
- 在PE文件中导入某个DLL,实质就是在文件伪造内调用该DLL提供的导出函数,PE文件头记录着DLL名称、函数名称等信息,因此,myhack3.dll至少要提供1个以上的导出函数才能保持形式上的完整性。
修改TextView.exe文件的准备工作
- PE文件中导入的DLL信息以结构体列表形式存储在IDT中,只要将myhack3.dll添加到列表末尾就可以了,当然,此前要确认一下IDT中有无足够空间。
- 使用PEView查看TextView.exe的IDT地址,PE文件头的IMAGE_OPTIONAL_HEADER结构体中导入表的RVA值即为IDT的RVA,IDT的地址为84CC,接下来,在PEView中直接查看IDT。

- TextView.exe的IDT存在于.rdata节区,IDT是由IMAGE_IMPORT_DESCRIPTOR(简称IID)结构体组成的数组,且数组末尾以NULL结构体结束,由于每个导入的DLL文件都对应一个IID结构体,每个IID结构图大小为14个字节.
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk;
};
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;
DWORD FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR;
- 使用PEView工具栏将视图改为File Offset,可以看到IDT的文件偏移为76CC.


- IDT的文件偏移为76CC~772F,整个大小为64(十六进制)字节,共有5个IID结构体,其中最后一个为NULL结构体,从图中可以看出IDT尾部存在其他数据,没有足够空间来添加myhack3.dll的IID结构体。
- 先把整个IDT转移到其他更广阔的位置,然后再添加新的IID,确定移动的目标位置时,可以使用下main三种方式:
- 查找文件中的空白区域
- 增加文件最后一个节区的大小。
- 在文件末尾添加新节区。
- .rdata节区尾部恰好存在大片空白区域,一般来说,节区或文件末尾都存在空白区域,PE文件这种空白区域称为Null-Padding区域。

- 把原IDT移动到该Null-Padding区域(RVA:8C60~8DFF)中合适位置就行了,在此之前,先要确认一下该区域是否全是空白可用区域(Null-padding区域),并不是文件中的所有区域都会被无条件加载到进程的虚拟内存,只有节区头中明确记录的区域才会被加载。


- .rdata节区在磁盘文件中的大小为2E00,而文件执行后被加载到内存时,程序实际使用的数据大小为2C56,剩余未被使用的区域大小为1AA,在这段空白区域创建IDT是不会由什么问题的。
- 我们要在RVA:8C80(RAW:7E80)位置创建IDT。
修改TexTView.exe
- 将TextView.exe复制一份,重命名为TextView_Patch.exe。
- IMAGE_OPTIONAL_HEADER的导入表结构成员用来指出IDT的位置与大小。

- TextView.exe文件中,导入表的RVA的值为84CC,接下来,将导入表的RVA值更改为新IDT的RVA值8C80,在Size原值64字节的基础上增加14个字节,修改为78个字节。

- 删除绑定导入表

- BOUND IMPORT TABLE是一种提高DLL加载速度的技术。
- 若想正常导入myhack3.dll,需要项绑定导入表添加信息,但幸运的是,该绑定导入表是个可选项,不是必须存在的,所以可删除(修改其值为0)以获取更大便利。绑定导入表完全不存在也没关系,但若存在,且其内信息记录错误,则会在程序运行时引发错误。
- 本实例TextView.exe,绑定导入表各项的值均为0,不需要在修改。
- 创建新的IDT
- 复写原来的数据到新的位置RAW:7E80,如图所示:

- RAW:7ED0添加与myhack3.dll对应的IID:

- 设置Name、INT、IAT
- 前面添加的IID结构体成员拥有指向其他数据结构(INT、Name、IAT)的RVA的值,因此,必须准确设置这些数据结构才能保证TextView_Patch.exe文件正常运行。

- 这些地址(RVA:8D00,8D10,8D20)就位于新创建的IDT(RVA:8C80)下方,我们输入相应的值:

- 8CD0地址处存在myhack3.dll的IID结构体,其中3个主要成员(RVA of INT、RVA of Name、RVA of IAT)的值分别是实际INT、Name、IAT的指针。
- INT是RVA数组,数组的各个元素都是一个RVA地址,该地址由导入函数的Ordinal(2个字节)+Func Name String结构体组成,数组的末尾为NULL。
- Name包含导入函数文件名称字符串,在8D10地址处可以看到“myhack3.dll"字符串。
- IAT也是RVA数组,各元素既可以拥有与INT相同的值,也可以拥有其他不同值,反正实际运行时,PE装载器会将虚拟内存中的IAT替换为实际函数的地址。
- 修改IAT节区的属性值
- 加载PE文件到内存时,PE装载器会修改IAT,写入函数的实际地址,所以相关节区一定要拥有WRITE属性,只要这样,PE装载器才能正常写入操作。
- 使用PEView查看.rdata节区头:

- 向原属性值40000040添加IMAGE_SCN_MEM_WRITE(80000000)属性值,执行bit OR运算,最终属性值变为C0000040.

检测验证
- 使用PEView打开修改后的TextView_Patch.exe文件,查看IDT。

- 向IDT导入myhack3.dll的IID结构已设置正常,myhack3.dll的dummy()函数被添加到INT。

- 运行程序
