通过修改PE加载DLL

基本概念

除了DLL动态注入技术外,还可以通过手工修改PE文件的方式来加载DLL,这种方式只要应用过一次之后,每当进程开始运行时便会自动加载指定的DLL。

整体思路如下:

1、查看IDT是否有充足的空间,若无则移动IDT至其他位置,若有则直接添加至列表末尾;

2、若无,修改OPTIONAL头IMPORT TABLE的RVA值并增大Size值,删除绑定导入表BOUND IMPORT Table,复制原IAT内容到目标地址并设置INT、NAME、IAT,最后到.rdata节区头修改IAT属性值添加可写属性。


编写测试程序和DLL文件

下面练习的目标是编写简单的文本查看程序SKI12Viewer.exe,直接修改SKI12Viewer.exe文件使其在运行时自动加载myhack3.dll文件。

首先编写SKI12Viewer.exe。

SKI12Viewer.cpp

//SKI12Viewer.cpp

#include "windows.h"
#include "stdio.h"

TCHAR szAppName[] = L"SKI12Viewer" ;
TCHAR szFile[MAX_PATH] = {0,};
TCHAR szMsg[2048] = {0,};

#define MAX_BUF_SIZE (32768)

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"SKI12Viewer (%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);
}

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;
}

注意,由于这次调试一开始选择控制台程序,但程序代码的入口函数为WinMain(),所以在项目中右键>属性>配置属性>链接器>系统,在其中的子系统项中勾选窗口(/SUBSYSTEM:WINDOWS)即可。

打开SKI12Viewer.exe,然后将SKI12Viewer.cpp拖入窗口中可查看源代码:

通过修改PE加载DLL_第1张图片

使用PEView查看SKI12Viewer.exe的IDT(Import Directory Table),可以看到直接导入的DLL文件有KERNEL32.dll、USER32.dll、GDI32.dll、SHELL32.dll、MSVCR100.dll:

通过修改PE加载DLL_第2张图片

接着编写DLL文件。

myhack3.cpp

//myhack3.cpp

#include "stdio.h"
#include "windows.h"
#include "shlobj.h"
#include "tchar.h"
//包含InternetOpen(),InternetOpenUrl(),InternetReadFile()等API
#include "Wininet.h"

#pragma comment(lib, "Wininet.lib")

#define DEF_BUF_SIZE (4096)
#define DEF_URL L"http://127.0.0.1/phpinfo.php"
#define DEF_INDEX_FILE L"phpinfo.html"

HWND g_hWnd = NULL;

#ifdef __cplusplus
extern "C" {
#endif
//出现在IDT中的导出函数dummy()是myhack3.dll向外部提供服务的导出函数,并无任何功能
//仅为了保持形式上的一致性,使DLL文件能够顺利添加到SKI12Viewer.exe进程中
__declspec(dllexport) void dummy(){
    return;
}
#ifdef __cplusplus
}
#endif

//下载目标URL内容
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;

    //获取Internet句柄
	hInternet = InternetOpen(L"SKI12", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
    if( NULL == hInternet ){
        OutputDebugString(L"[-]InternetOpen() failed!");
        return FALSE;
    }

    //打开目标URL
    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;

    //goto语句跳转的地址,即程序终止时跳转至此处执行
	_DownloadURL_EXIT:
		if( pFile ){
			fclose(pFile);
		}

		if( hURL ){
			InternetCloseHandle(hURL);
		}

		if( hInternet ){
			InternetCloseHandle(hInternet);
		}

		return bRet;
}

//获取目标进程PID
BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lParam){
	DWORD dwPID = 0;

    GetWindowThreadProcessId(hWnd, &dwPID);

    if( dwPID == (DWORD)lParam ){
        g_hWnd = hWnd;
        return FALSE;
    }

    return TRUE;
}

//从目标进程PID获取目标进程主窗口句柄
HWND GetWindowHandleFromPID(DWORD dwPID){
	EnumWindows(EnumWindowsProc, dwPID);
	return g_hWnd;
}

//将下载的文件拖入SKI12Viewer.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;
    }

    //向目标进程主窗口传送WM_DROPFILES消息
    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));

	//_tcsrchr:兼容Unicode和ANSI编码,从一个字符串中查找字符
	if( p = _tcsrchr(szPath, L'\\') ){
		//_tcscpy_s:字符串拷贝函数,后缀_s表示使用安全的函数,防止缓冲区不够大而引起错误
		//wcslen:取宽字符字符串中字符长度
		_tcscpy_s(p+1, wcslen(DEF_INDEX_FILE)+1, DEF_INDEX_FILE);

		OutputDebugString(L"[*]DownloadURL() start...");
		//若下载URL成功,则将下载的文件拖入SKI12Viewer.exe进程中
		if( DownloadURL(DEF_URL, szPath) ){
			OutputDebugString(L"[*]DropFlie() start...");
            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;
}

在PE文件中导入某个DLL,实质是在文件代码内调用该DLL提供的导出函数。myhack3.dll至少需要向外提供1个以上的导出函数才能保持形式上的一致,因此需要编写形式完整但无任何功能的dummy()函数。


修改SKI12Viewer.exe实现DLL注入

基本思路为,PE文件中导入的DLL信息以结构体列表的形式存储在IDT中,只需将myhack3.dll添加到列表尾部即可,前提是查看IDT是否有足够的空间。

1、查看IDT是否有足够空间:

使用PEView查看SKI12Viewer.exe的IDT地址(PE文件头的IMAGE_OPTIONAL_HEADER结构体中导入表RVA值即为IDT的RVA):

通过修改PE加载DLL_第3张图片

可以看到,IDT的地址RVA为2334。接着在PEView设置地址视图为RVA(View>Address>RVA),然后到RVA 2334中查看,可以发现其在.rdata节区中:

通过修改PE加载DLL_第4张图片

IDT即IMAGE_IMPORT_DESCRIPTOR(IID)结构体组成的数组,且数组末尾以NULL结构体结束。由于每个导入的DLL文件都对应1个IID结构体(每个IID结构体的大小为14个字节),因此,图中整个IID区域为RVA 2334~23AC(整体大小为14*6=78)。

再将PEView视图改为File Offset,可以看到IDT的文件偏移为1334~13AC区域:

通过修改PE加载DLL_第5张图片

接着使用Win Hex查看该文件偏移:

通过修改PE加载DLL_第6张图片

可以看到,该区域有6个IID结构体,其中最后一个为NULL结构体。由于在IDT尾部存在其他数据,并没有足够的空间来添加myhack3.dll的IID结构体。


2、移动IDT:

移动IDT至其他位置主要有三种:

(1)查找文件中的空白区域;

(2)增加文件最后一个节区的大小;

(3)在文件末尾添加新节区。

下面使用第一种方法移动IDT。首先尝试在.rdata节区尾部查找空白区域:

通过修改PE加载DLL_第7张图片

可以看到,.rdata节区末尾虽然存在NULL-Padding区域,但其大小明显不足以放入IDT。

接着换个节区查看,到.reloc节区末尾查看,可以发现存在一大片NULL-Padding区域:

通过修改PE加载DLL_第8张图片

然而,还要确认该区域是否全是空白可用的区域,因为并不是文件中的所有区域都会被无条件加载到进程的虚拟内存的,只有节区头中明确记录的区域才会被加载。到.reloc节区头查看:

通过修改PE加载DLL_第9张图片

可以看到,.reloc节区在磁盘文件中的大小为400,在内存中的大小为24E。剩余未被使用的区域大小为400 - 24E = 1B2 >修改后 IDT的大小8C,即可以确定该NULL-Padding区域为624E~6400。

那么,从RVA 6250(RAW 2050)开始创建IDT。

通过修改PE加载DLL_第10张图片

基本操作的步骤为,先使用PEView打开SKI12Viewer.exe,查看PE信息,根据该信息使用Win Hex对另外保存的SKI12Viewer_Patch.exe进程修改。

1、修改导入表的RVA值:

IMAGE_OPTIONAL_HEADER的导入表结构成员用来指出IDT的位置(RVA)与大小:

通过修改PE加载DLL_第11张图片

将该导入表的RVA值为2334,将其修改为新IDT的RVA值6250,在Size值的基础上加上14即修改为8C:



2、删除绑定导入表:

BOUNG_IMPORT_TABLE是一种提高DLL加载速度的技术。若想正常导入指定的DLL文件,需要向绑定导入表添加信息。然而绑定导入表是可选项,不是必须存在的,因而可以删除(修改其值为0)以更方便地操作。但是若存在的话,当其中的内容记录错误时,会引发程序运行出错。

可以看到,此处绑定导入表本来已经是删除了的:

通过修改PE加载DLL_第12张图片


3、创建新IDT:

使用Win Hex完全复制原IDT(RAW 1334~13AC),然后覆写到新IDT的位置(RAW 2050):

通过修改PE加载DLL_第13张图片

通过修改PE加载DLL_第14张图片

在新IDT的下面位置挑选一个地方,这里选择RAW 2100地址处(经过PEView可知RVA为6300)设置myhack3.dll的Name、INT和IAT:

通过修改PE加载DLL_第15张图片

地址2100处的6330为RVA地址,其为INT,即指向RAW 2130地址处;同理地址2120为IAT,同样指向RAW 2130地址处;地址2110处保存着Name,即包含导入函数的“myhack3.dll”字符串名称。INT和IAT指向的2130地址,其中前面的0000为导入函数的Ordinal,后面的保存着myhack3.dll的导入函数字符串名称“dummy”。

接着在IDT尾部(RAW 20B4)添加与myhack3.dll对应的IID:



4、修改IAT节区的属性值:

加载PE文件到内存,PE装载器会修改IAT,写入函数的实际地址,因此相关节区一定要有WRITE即可写权限。

使用PEView查看.reloc节区头:

通过修改PE加载DLL_第16张图片

可见并没有可写权限。

使用Win Hex向该Characteristics项添加IMAGE_SCN_MEM_WRITE(80000000)属性,执行bit OR异或操作后最终属性值为C2000040,到RVA 29C中修改:



效果验证

运行SKI12Viewer_Patch.exe,可以发现并没有出现运行错误的信息,打开程序窗口一段时间后便下载了phpinfo.html文件并用SKI12Viewer_Patch.exe进程打开该文件查看源码:

通过修改PE加载DLL_第17张图片

通过修改PE加载DLL_第18张图片

通过修改PE加载DLL_第19张图片

使用PEView查看SKI12Viewer_Patch.exe,可以看到PE文件的一些修改的内容确实修改成功且成功注入了myhack3.dll:

通过修改PE加载DLL_第20张图片

通过修改PE加载DLL_第21张图片

通过修改PE加载DLL_第22张图片


你可能感兴趣的:(逆向,Windows)