通过修改PE加载DLL
TextView是一个文本查看工具,将文本文件拖入即可显示。
代码如下,注意按照32位编译,否则PEView不能分析64位IDT。
#include "windows.h"
#include "stdio.h"
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
TCHAR szAppName[] = L"TextView";
TCHAR szFile[MAX_PATH] = { 0, };
TCHAR szMsg[2048] = { 0, };
#define MAX_BUF_SIZE (32768)
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASSEX wndclass;
wndclass.cbSize = sizeof(wndclass);
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szAppName;
wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
RegisterClassEx(&wndclass);
hwnd = CreateWindow(
szAppName, szAppName,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
static HWND hwndEdit;
HFONT hFont;
switch (iMsg)
{
case WM_CREATE:
hwndEdit = CreateWindow(L"Edit", NULL,
WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL |
WS_BORDER | ES_LEFT | ES_MULTILINE |
ES_AUTOHSCROLL | ES_AUTOVSCROLL,
0, 0, 0, 0,
hwnd, (HMENU)1,
((LPCREATESTRUCT)lParam)->hInstance, NULL);
hFont = CreateFont(16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, L"Courier New");
SendMessage(hwndEdit, WM_SETFONT, (WPARAM)hFont, (LPARAM)FALSE);
DragAcceptFiles(hwnd, TRUE);
return 0;
case WM_DROPFILES:
if (DragQueryFile((HDROP)wParam, 0, szFile, MAX_PATH))
{
HANDLE hFile = CreateFile(szFile, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
wsprintf(szMsg, L"file(\"%s\") open error!!! [%d]\n", szFile, GetLastError());
MessageBox(hwndEdit, szMsg, szAppName, MB_OK);
return 0;
}
DWORD dwBytesRead = 0;
char *pBuf = new char[MAX_BUF_SIZE];
ZeroMemory(pBuf, MAX_BUF_SIZE);
ReadFile(hFile, pBuf, MAX_BUF_SIZE, &dwBytesRead, NULL);
SetWindowTextA(hwndEdit, pBuf);
wsprintf(szMsg, L"TextView (%s)", szFile);
SetWindowText(hwnd, szMsg);
delete[]pBuf;
CloseHandle(hFile);
}
return 0;
case WM_SETFOCUS:
SetFocus(hwndEdit);
return 0;
case WM_SIZE:
MoveWindow(hwndEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, iMsg, wParam, lParam);
}
PEView中可以看到.rdata的IDT中有4个dll:kernel32.dll, user32.dll, gdi32.dll, shell32.dll
下面是myhack3.cpp,用来生成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.baidu.com/index.html"
#define DEF_INDEX_FILE L"index.html"
HWND g_hWnd = NULL;
#ifdef __cplusplus
extern "C" {
#endif
// dummy export function in IDT
__declspec(dllexport) void dummy()
{
return;
}
#ifdef __cplusplus
}
#endif
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;
}
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 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;
}
DownLoadURL()
下载szURL网页文件并保存到szFile目录。大概流程是利用wininet.dll
的InternetOpen(), InternetOpenUrl(), InternetReadFile(),
也可以用urlmon.dll
的URLDownloadToFile()
。
DropFile()
将网页文件拖到TextView_Patch.exe(载入myhack3.dll),所以要通过pid获取TextView的句柄,再传送WM_DROPFILES
消息到消息队列。
dummy()
的存在是为了形式上的完整,使myhack3.dll
顺利添加到TextView的导入表。因为在PE文件中导入一个dll,实质就是调用该dll的导出函数。
我们使用PE Viewer和Hex Editor来修改TextView的导入表。
修改导入表
dll以struct _IMAGE_IMPORT_DESCRIPTOR
结构体形式存在于exe的导入表中,只要将myhack3.dll添加到列表尾部就可以了。
查看IDT空间是否足够
PEView中查看TextView的IMAGE_OPTIONAL_HEADER64
,找到导入表的_IMAGE_DATA_DIRECTORY
:
pFile | RVA | Size |
---|---|---|
0x170 | 0x384c | 0xdc |
我这里显示RVA为0x384c,位于.rdata,大小为0xdc。所以导入表RVA范围为0x384c - 0x3928
。
转到RVA为0x384c的地方,然后将视图改为pFile(file offset),显示文件偏移为0x1e4c,所以文件偏移范围为0x1e4c - 0x1f27
。
pFile / RVA | data |
---|---|
0x1e4c / 0x384c | 0x3934 |
用hexeditor找到这块范围,然后发现没有空间添加myhack3.dll的结构体。
移动IDT
为了添加IID,可以把IDT转移到以下空旷的位置:
- 文件的空白区域;
- 增加文件最后一个节区的大小;
- 文件末尾添加新节区。
法1:
- 修改导入表RVA和Size
- 删除绑定导入表
- 复制IDT到新位置
- 设置INT, name, IAT
- 修改IAT节区的属性
一般节区或文件末尾都会有空白的Null-Padding区域,比如.rdata,hexeditor中可以看到大片00。查看.rdata节区的IMAGE_SECTION_HEADER
:
data | description |
---|---|
0x1046 | virtual size |
0x3000 | RVA |
0x1200 | size of raw data |
0x1600 | pointer to raw data |
我这里显示virtual size
为0x1046,size of raw data
为0x1200,差值0x1BA,有这么块区域未使用,在这里可以创建IDT。
00未必是Null-Padding区域,也不是所有Null-Padding区域会加载到内存。
目标RVA可以取0x3000 + 0x1046 + 0x2a == 0x4070
,对应文件偏移0x1600 + 0x1046 + 0x2a == 0x2670
。
在hexeditor中,把导入表(文件偏移0x170)的_IMAGE_DATA_DIRECTORY.VirtualAddress
从0x384c改为0x4070,大小改为0xdc + 0x14 == 0xf0
00000170 | 70 40 00 f0 00 00 00
PEView的_IMAGE_OPTIONAL_HEADER64
部分可以看到bound import table
,这是种提高dll加载速度的技术,是个可选项,我这里显示全0,不需要再修改。如果使用但信息错误,程序运行时就会出错。 修改其它文件时要注意检查这一项。
在hexeditor中把文件偏移0x1e4c - 0x1f27
复制到0x2670 - 0x274b
,然后在最后0x14字节的空IID结构体(IDT结尾)写入myhack3.dll数据。
INT, name, IAT的地址:
RVA | RAW | |
---|---|---|
INT | 00x2770 -0x1600 + 0x3000 == 0x4170 | 0x2770 |
name | 0x4180 | 0x2780 |
IAT | 0x4190 | 0x2790 |
0x2770就在IDT下方,观察方便。
00002730 f8 3e 00 00 c4 30 00 00 70 41 00 00 00 00 00 00
00002740 00 00 00 00 80 41 00 00 90 41 00 00 00 00 00 00
...
00002770 a0 41 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00002780 6d 79 68 61 63 6b 33 2e 64 6c 6c 00 00 00 00 00
00002790 a0 41 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000027a0 00 00 64 75 6d 6d 79 00 00 00 00 00 00 00 00 00
0x2770为IID.OriginalFirstThunk
,即INT的RVA;
0x2780为IID.name
;
0x2790为IID.FirstThunk
,即IAT的RVA;
0x27a0处为IMAGE_IMPORT_BY_NAME
结构体,包括导入函数的ordinal和函数名。
加载PE时,PE装载器会修改IAT,写入函数的实际地址,所以相关节区要拥有可写属性IMAGE_SCN_MEM_WRITE
,其值为8,与IMAGE_SECTION_HEADER.Characteristics
异或即可。
其实,.rdata本来就没有可写属性,但我们修改的PE必须有,否则程序无法运行,因为IMAGE_OPTIONAL_HEADER64
的数据目录表里也有IAT,RVA为0x3000,Size为0x13c,在该范围内则不需要可写属性,显然我添加的IID不在此范围内。
修改完成后,PEView中IDT可以看到myhack3.dll,ProcessExplorer也可以看到,并且目录下有了百度网页。
起初我没有将myhack3.dll放在同目录,会弹框提示找不到myhack3.dll,无法运行。