windows注入续讲

本文内容引述至《windows核心编程》

DLL注入

Dll注入除了使用前文《windows注入》中所推荐的三种方法

  1. 修改注册表
  2. Hook
  3. RemoteThread
    还有其他的注入方式

木马注入DLL

木马注入常见的黑客手法,把预知常见的进程必然载入的DLL库同名替换。
Dll库除了名字要相同,还有原来DLL中导出的所有符号,而且随着替换的Dll版本发生变化,更改了导入表甚至是导入地址,那么这样的适配工作就会很麻烦。

将DLL作为调试器注入

系统载入一个被调试程序的时候,会在被调试程序的地址空间准备完毕后,但在调试程序的主线程尚未开始执行任何代码之前,自动通知调试器。这时候,调试器可以将一些代码注入到被调试程序的地址空间中。让调试程序的主线程执行代码。
这样的方式要求我们对被调试线程的CONTEXT结构进程操作,也意味着我们必须编写与CPU相关代码。

注入代码

使用CreateProcess注入代码

如果注入代码的进程是由我们的进程生成的,那么就可以用这种方法。这种方法允许我们修改子进程的状态,又不影响它的执行。

  1. 让进程生成一个被挂起的子进程;
  2. 从.exe模块的头文件中取得主进程的起始内存地址;
  3. 将位于该内存地址处的机器指令保存起来;
  4. 强制将一些手工编写的机器指令写入到该内存地址处,指令应该调用LoadLibrary来载入一个DLL;
  5. 让子进程的主线程恢复运行,执行编写的指令;
  6. 把保存起来的原始指令恢复到起始地址处;
  7. 让进程从起始地址继续执行

这个方法在6,7步时有难度,因为必须修改正在执行的代码。
好处:首先,在应用开始前得到地址空间;其次,非常容易对应用程序和注入的Dll进行调试;最后,方法同样适用于控制台应程序和GUI应用程序。

API拦截例子

简单地注入DLL并不能为我们提供足够的信息。我们常常想要知道某个进程中的线程具体是怎么调用各种函数的,还相对windows函数进行修改。

通过覆盖代码拦截API

覆盖代码实现API的拦截是比较简单可控的方式

  1. 在内存中对要拦截的函数(比如Kernel32.dll中的ExitProcess)进行定位,从而得到它的内存地址;
  2. 把这个函数其实的几个字节保存到内存中;
  3. 用CPU的一条JUMP指令来覆盖这个函数起始的几个字节,这条JUMP指令用来跳转到我们的替代函数的内存地址。
  4. 当线程调用被拦截函数的时候,跳转指令实际上会调转到我们的替代函数中;
  5. 为了撤销对函数的拦截,需要把2中保存到字节放回被拦截函数其实的地址中;
  6. 继续调用被拦截函数,让该函数执行它的正常处理;
  7. 当原来的函数返回时,我们再次执行第2步和第3步,这样我们的替换函数将来还会被调用到

缺点
由于系统升级,x86、x64、IA-64以及其他CPU的JUMP指令各不相同,必须手动编写机器指令。

修改模块的导入段拦截API

知识背景

一个模块的导入段包含一组DLL,为了让模块能够运行,这些DLL是必须的。此外,导入段还包含一个符号表,其中列出了该模块从各DLL中导入的符号。当该模块调用一个导入函数的时候,线程实际上会先从模块的导入表中得到相应的导入函数地址,然后再跳转到那个地址。

具体实现

因此,为了拦截一个特定的函数,可以通过修改它在模块的导入段的地址。

void CAPIHook::ReplaceIATEntryInOneMod(PCSTR pszCalleeModName, PROC pfnCurrent, PROC pfnNew, HMODULE hmodCaller) {
	ULONG ulSize;
	PIMAGE_IMPORT_DESCRIPTOR pImportDesc = NULL;
	__try {
		pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(
			hmodeCaller, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &ulSize);
	}
	__except {

	}

	if (pImportDesc == NULL)
		return;
	// 找到导入描述符,包含对被调用者功能的引用
	for (; pImageDes->Name; pImportDesc++) {
		PSTR pszModName = (PSTR)((PBYTE)hmodCaller + pImportDesc->Name);
		if (lstrcmpiA(pszModName, pszCalleeModName) == 0) {
			// 获取导入函数表IAT
			PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)
				((PBYTE)hmodCaller + pImportDesc->FirstThunk);
			// 替换函数
			for (; pThunk->u1.Function; pThunk++) {
				// 获取函数地址
				PROC* ppfn = (PROC*)&pThunk->u1.Function;
				// 目标函数
				BOOL bFound = (*ppfn == pfnCurrent);
				if (bFound) {
					if (!WriteProcessMemory(GetCurrentProcess(), ppfn, &pfnNew, sizeof(pfnNew), NULL) && (ERROR_NOACCESS == GetLastError())) {
						DWORD dwOldProtect;
						if (VirtualProtect(ppfn, sizeof(pfnNew), PAGE_WRITECOPY, &dwOldProtect)) {
							WriteProcessMemory(GetCurrentProcess(), ppfn, &pfnNew, sizeof(pfnNew), NULL);
							VirtualProtect(ppfn, sizeof(pfnNew), dwOldProtect, &dwOldProtect);
						}
					}
					return;
				}

			}
		}
	}
}

对于上述替换函数的调用,这里取例如下
一个名为Database.exe的模块,调用了Kernel32.dll中的ExitProcess函数,但是我们希望在调用DbExtend.dll模块中的MyExitProcess函数。

PROC pfnOrig = GetProcAddress(GetModulehandle("Kernel32"),"ExitProcess");
HMODULE hmodCaller = GetModuleHandle("Database.exe");
ReplaceIATEntryInOneMod(
	"Kernel32.dll",	// 包含目标函数的模块
	pfnOrig,		// 调用函数地址
	MyExitProcess,  // 替换函数地址
	hmodCaller);	// 调用地址

ReplaceIATEntryInOneMod函数,第一件事就是调用ImageIDrectoryEntryToData并传入IMAGE_DIRECTORY_ENTRY_IMPORT,其目的是为了对hmodCaller的导入段进行定位,如果返回为NULL,说明没有导入段。函数ImageIDrectoryEntryToData是由ImageHlp.dll提供的。
如果有导入段,函数返回导入段地址,实际上是个PIMAGE_IMPORT_DESCRIPTOR类型的指针,然后从导入段中遍历查找的符号,导入段都是ANSI格式的。
如果定位到目标符号引用,获得一个PIMAGE_THUNK_DATA 结构组成的数组,其中包含于导入符号有关的信息。有时候编译器可能会生成多个导入段,所以遍历没有退出。
获取导入段数组后,遍历查找一个与符号当前地址相匹配的地址。
如果找到了地址,函数调用WriteProcessMemory将地址修改为替代函数的地址。如果发生错误,使用VirutalProtect修改页面保护属性,修改完指针后,用VirtualProtect恢复页面保护属性。

这里ReplaceIATEntryInOneMod函数修改的函数调用来至于同一个模块,但是地址空间中的另一个DLL可能也会调用ExitProcess,如果Database.exe之外的模块试图调用ExitProcess,那么它会成功调用到kernel32中的ExitProcess函数。

存在的问题

考虑LoadLibrary,GetProcAddress函数对ReplaceIATEntryInOneMod的影响
问题1
LoadLibrary函数如果在ReplaceIATEntryInOneMod之后被调用,或者windows会首先载入这些静态链接的DLL,而不给我们机会去更新他们的导入地址表中与ExitProcess中有关的部分。
解决办法
ReplaceIATEntryInOneMod变成ReplaceIATEntryInOneMods,这样新的隐式载入的模块也能得到更新。
问题2
调用函数直接通过GetProcAddress取得调用函数的地址

typedef int (WINAPI *PFNEXITPORCESS)(UINT uExitCode);
PFNEXITPROCESS pfnExitProcess = (PFNEXITPORCESS)GetProcAddress(GetModuleHandle("Kernel32"), "ExitProcess");
pfnExitProcess(0);

解决办法
GetProcAddress函数拦截,并返回相应的替换函数地址。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值