跳至主要內容

DLL挟持模块的生成


背景

上次介绍了DLL挟持原理、分析、应用(/resource/dll-hijack) 很多网友反馈说希望提供快速生成挟持模块的工具,这里详细介绍一些挟持模块的生成过程。

原理

程序对一个模块有静态依赖和动态依赖两种。

静态依赖

编译的时候,会把依赖的模块信息写入到PE结构的导入表中。加载的时候,会根据导入表,加载对应的模块,然后从该模块的导出表检索函数,修复PE映像的导入函数调用地址,这样调用函数的时候就会跳转模块里面执行。

动态依赖

程序对模块不会有直接依赖的关系,而是执行过程中,通过LoadLibrary +
GetProcAddress的方式加载模块,然后获取需要调用的函数去执行。

挟持模块

无论是哪种依赖关系,制作一个挟持模块去替换原始的模块,如果要保证程序运行正常,挟持模块需要提供程序所依赖的全部函数,通常挟持模块会导出被挟持模块的全部导出函数。

怎么查看一个模块的导出函数呢?可以通过dumpbin查看,也可以通过其他的PE工具。

下面的通过dumpbin查看version.dll的导入函数的命令。

dumpbin /exports C:\windows\system32\version.dll

Dump of file C:\windows\system32\version.dll

File Type: DLL

  Section contains the following exports for VERSION.dll

    00000000 characteristics
    90A1B58A time date stamp
        0.00 version
           1 ordinal base
          17 number of functions
          17 number of names

    ordinal hint RVA      name

          1    0 00001EE0 GetFileVersionInfoA
          2    1 000023E0 GetFileVersionInfoByHandle
          3    2 00001F00 GetFileVersionInfoExA
          4    3 00001070 GetFileVersionInfoExW
          5    4 00001010 GetFileVersionInfoSizeA
          6    5 00001F20 GetFileVersionInfoSizeExA
          7    6 00001090 GetFileVersionInfoSizeExW
          8    7 000010B0 GetFileVersionInfoSizeW
          9    8 000010D0 GetFileVersionInfoW
         10    9 00001F40 VerFindFileA
         11    A 000025A0 VerFindFileW
         12    B 00001F60 VerInstallFileA
         13    C 00003390 VerInstallFileW
         14    D          VerLanguageNameA (forwarded to KERNEL32.VerLanguageNameA)
         15    E          VerLanguageNameW (forwarded to KERNEL32.VerLanguageNameW)
         16    F 00001030 VerQueryValueA
         17   10 00001050 VerQueryValueW

知道所有的导出函数了,那么下一步就是实现这些函数。大部分场景,我们并不关心这些函数具体的功能,也没有必要完整实现这些函数,只需要把这些函数映射到原来模块的函数就好了。

方式一:使用导出表重定向的方式(比如上面的:VerLanguageNameA (forwarded to KERNEL32.VerLanguageNameA) )

VC提供#pragma的编译指令,可以手动导出一个指向其他模块的函数。

比如:导出GetFileVersionInfoA实际是指定到original_version.dll里面的GetFileVersionInfoA

#pragma comment(linker,"/export:GetFileVersionInfoA=original_version.GetFileVersionInfoA")

这种方式在替换模块的时候,存在一个问题,就是引入了一个新的模块依赖(原始模块),需要从原始模块拷贝一份重命名后放在一起才能正常加载。这种方式操作会比较麻烦,因为没法提前把原始模块打包在一起(每个系统可能不一样),而是需要在目标机器上手动拷贝一份。

方式二:通过动态依赖原始模块,然后GetProcAddress + JMP的方式。

// 初始化的时候,获取到全部需要的原始函数
// original_GetFileVersionInfoA = GetProcAddress(original_version, "GetFileVersionInfoA");

__declspec(naked) GetFileVersionInfoA
{
	__asm jmp original_GetFileVersionInfoA;
}

但是在x64编译器上,不支持直接内联汇编的方式。

为了通用,直接去掉了手动编写函数,而是使用编辑字节码的方式。

#ifdef _M_IX86

struct JMPStub
{
    //
    // jmp address
    //
	BYTE JMP;
	ULONG Address;
};

#else

struct JMPStub
{
    //
    // mov rax, address
    // jmp rax
    //
	USHORT MOVRax;
	ULONGLONG Address;
	USHORT JMPRax;
};

#endif

完整代码

#pragma pack(push, 1)
//******************************************************************************
#ifdef _M_IX86
//******************************************************************************
struct JMPStub
{
	BYTE JMP;
	ULONG Address;

	bool Hook(HMODULE module, const char* Name)
	{
		PVOID addr = GetProcAddress(module, Name);

		if (addr == NULL)
			return false;

		JMP = 0xE9;
		Address = (ULONG)addr - (ULONG)this - 5;

		return true;
	}
};
//******************************************************************************
#else
//******************************************************************************
struct JMPStub
{
	USHORT MOVRax;
	ULONGLONG Address;
	USHORT JMPRax;

	bool Hook(HMODULE module, const char* Name)
	{
		PVOID addr = GetProcAddress(module, Name);

		if (addr == NULL)
			return false;

		MOVRax = 0xB848;
		Address = (ULONGLONG)addr;
		JMPRax = 0xE0FF;

		return true;
	}
};
//******************************************************************************
#endif
//******************************************************************************
#pragma pack(pop)
//******************************************************************************
#pragma data_seg(".jmp")
__declspec(selectany) HMODULE g_jmp_module = NULL;
#pragma data_seg()
#pragma comment(linker, "/SECTION:.jmp,RWE")
//******************************************************************************
class JMPSetModule
{
public:
	JMPSetModule(LPCTSTR Name)
	{
        //
        // 默认替换系统的镜像,如果需要其他的可以自行修改
        //
		TCHAR path[MAX_PATH] = {};
		GetWindowsDirectory(path, MAX_PATH);
		PathAppend(path, Name);
		g_jmp_module = LoadLibrary(path);
	}
};
//******************************************************************************
class JMPSetHook
{
public:
	JMPSetHook(JMPStub& stub, const char* Name)
	{
		stub.Hook(g_jmp_module, Name);
	}
};
//******************************************************************************
#define CONCAT_RAW_(a, b) a##b
#define CONCAT_(a, b)	  CONCAT_RAW_(a, b)
//******************************************************************************
#define BEGIN_EXPORT_MAP(module) JMPSetModule CONCAT_(module_, __COUNTER__)(_T(module));
#define EXPORT_MAP(name)                                                                 \
	namespace JMPStubPrivate                                                             \
	{                                                                                    \
		extern "C" __declspec(dllexport) __declspec(allocate(".jmp")) JMPStub name = {}; \
		JMPSetHook sethook_##name(name, #name);                                          \
	};
#define END_EXPORT_MAP()
//******************************************************************************

最终使用效果

添加下面的代码,编译后就可以自动生成一个替换version.dll的挟持模块。

BEGIN_EXPORT_MAP("system32\\version.dll")
	EXPORT_MAP(GetFileVersionInfoA)
	EXPORT_MAP(GetFileVersionInfoW)
	EXPORT_MAP(GetFileVersionInfoByHandle)
	EXPORT_MAP(GetFileVersionInfoExA)
	EXPORT_MAP(GetFileVersionInfoExW)
	EXPORT_MAP(GetFileVersionInfoSizeA)
	EXPORT_MAP(GetFileVersionInfoSizeW)
	EXPORT_MAP(GetFileVersionInfoSizeExA)
	EXPORT_MAP(GetFileVersionInfoSizeExW)
	EXPORT_MAP(VerFindFileA)
	EXPORT_MAP(VerFindFileW)
	EXPORT_MAP(VerInstallFileA)
	EXPORT_MAP(VerInstallFileW)
	EXPORT_MAP(VerLanguageNameA)
	EXPORT_MAP(VerLanguageNameW)
	EXPORT_MAP(VerQueryValueA)
	EXPORT_MAP(VerQueryValueW)
END_EXPORT_MAP()