IAT 隐藏和混淆

温故知新

闲聊几句

最近看到一些IAT的文章,回顾了一下项目的代码,发现之前确实有留下一些想法

d2b5ca33bd20241010170705

  1. 利用 LoadLibrary() 加载 DLL 获取句柄
  2. 利用 GetProcAddress() 加载 DLL 对应的 API
  3. 通过 API 的地址去调用定义的函数

但因为Go底层本身会调用一些 API,会导致导入表导入一些不应该存在的 API,在此基础上结合这个想法和之前文章的说明,写了一个新的思路

自定义 GetProcAddress()

由于测试Go的时间比较长,所以这次使用C++来实现

先不探讨寻找函数基址的原理,简单写一下应用的方法

先定义需要替换的函数

FARPROC GetProcAddressReplacement(IN HMODULE hModule, IN LPCSTR lpApiName) {

	// 避免每次使用 hModule 时进行强制转换
	PBYTE pBase = (PBYTE) hModule;

	// 获取 DOS 头并进行签名检查
	PIMAGE_DOS_HEADER pImgDosHdr = (PIMAGE_DOS_HEADER) pBase;
	if (pImgDosHdr->e_magic != IMAGE_DOS_SIGNATURE)
		return NULL;

	// 获取 NT 头并进行签名检查
	PIMAGE_NT_HEADERS pImgNtHdrs = (PIMAGE_NT_HEADERS) ( pBase + pImgDosHdr->e_lfanew );
	if (pImgNtHdrs->Signature != IMAGE_NT_SIGNATURE)
		return NULL;

	// 获取可选头
	IMAGE_OPTIONAL_HEADER ImgOptHdr = pImgNtHdrs->OptionalHeader;

	// 获取映像导出表
	PIMAGE_EXPORT_DIRECTORY pImgExportDir = (PIMAGE_EXPORT_DIRECTORY) ( pBase + ImgOptHdr.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress );

	// 获取函数名数组指针
	PDWORD FunctionNameArray = (PDWORD) ( pBase + pImgExportDir->AddressOfNames );

	// 获取函数地址数组指针
	PDWORD FunctionAddressArray = (PDWORD) ( pBase + pImgExportDir->AddressOfFunctions );

	// 获取函数序号数组指针
	PWORD  FunctionOrdinalArray = (PWORD) ( pBase + pImgExportDir->AddressOfNameOrdinals );


	// 遍历所有导出的函数
	for (DWORD i = 0; i < pImgExportDir->NumberOfFunctions; i++) {

		// 获取函数名
		CHAR* pFunctionName = (CHAR*) ( pBase + FunctionNameArray[i] );

		// 通过其序号获取函数地址
		PVOID pFunctionAddress = (PVOID) ( pBase + FunctionAddressArray[FunctionOrdinalArray[i]] );

		// 查找指定的函数
		if (strcmp(lpApiName, pFunctionName) == 0) {
			//printf("[ %0.4d ] FOUND API -\ NAME: %s -\t ADDRESS: 0x%p  -\t ORDINAL: %d\n", i, pFunctionName, pFunctionAddress, FunctionOrdinalArray[i]);
			printf("[*] FOUND API - NAME: %s \n", pFunctionName);
			printf("[*] FOUND API - ADDRESS: 0x%p \n", pFunctionAddress);
			printf("[*] FOUND API - ORDINAL: %d \n", FunctionOrdinalArray[i]);
			return FARPROC(pFunctionAddress);
		}
	}

	return NULL;
}

调用替换的自定义函数

// 加载一个 DLL 文件,例如 User32.dll
	HMODULE hModule = LoadLibraryA("user32.dll");
	if (hModule == NULL) {
		printf("Failed to load library.\n");
		return 1;
	}

// 调用 GetProcAddressReplacement 函数,查找 MessageBoxA 函数的地址
	FARPROC pFunc = GetProcAddressReplacement(hModule, "MessageBoxA");
	if (pFunc != NULL) {
		printf("Address of MessageBoxA: 0x%p\n", pFunc);
	} else {
		printf("Function not found.\n");
	}

	// 释放 DLL
	FreeLibrary(hModule);
	return 0;
}

或者是直接定义函数指针类型进行调用

FARPROC pFunc = GetProcAddressReplacement(hModule, "MessageBoxA");
	if (pFunc != NULL) {
		// 定义函数指针类型
		typedef int ( WINAPI* MessageBoxA_t )( HWND, LPCSTR, LPCSTR, UINT );

		// 将函数地址转换为相应的函数指针类型
		MessageBoxA_t MessageBoxAReplacement = (MessageBoxA_t) pFunc;

		// 调用 MessageBoxA
		MessageBoxAReplacement(NULL, "Hello, World!", "Test", MB_OK);
	}
	else {
		printf("Function not found.\n");
	}

亦或者是写成一个 自定义 API,且使用字符串数组的方式规避静态的扫描

typedef int ( WINAPI* MessageBoxA_t )( HWND, LPCSTR, LPCSTR, UINT );

void XMessageBox(HMODULE hModule, LPCSTR message, LPCSTR title) {
	// 查找 MessageBoxA 函数地址
	char MessageBoxA[] = { 'M','e','s','s','a','g','e','B','o','x','A','\0' };
	FARPROC pFunc = GetProcAddress(hModule, MessageBoxA);
	if (pFunc != NULL) {
		MessageBoxA_t MessageBoxA = (MessageBoxA_t) pFunc;

		// 调用 MessageBoxA
		MessageBoxA(NULL, message, title, MB_OK);
	}
	else {
		printf("Function not found.\n");
	}
}

再检查一下导入表

d2b5ca33bd20241010172114

将这个写法写成一个C++库就可以批量调用Windows APIs 而不会出现在导入表中。但是可能会被查杀 LoadLibrary() ,所以还是要考虑能不能隐藏掉

其实有看过这篇文章,但是我忘记了在哪了 : ( 

总结

由于Go编译的原因,导入表会出现很多敏感的API

在IAT这块,可能C++更有优势,但Go其实也不输C++

d2b5ca33bd20241010172623

 

© 版权声明
THE END
喜欢就支持一下吧
点赞44 分享
评论 抢沙发

请登录后发表评论

    请登录后查看评论内容