AddExeImport – 将硬编码的 DLL 依赖项添加到任何 EXE

原文链接:https://www.x86matthew.com/view_post?id=add_exe_import

这个项目松散地基于我几个月前发布的另一个项目(ImportDLLInjection)。ImportDLLInjection 将在挂起状态下启动 EXE,修改内存中的 IAT 表以包含额外的 DLL,然后继续执行。这将导致注入我们的目标 DLL 文件。

在此项目中,我们将使用类似的概念来修改磁盘上的 EXE 文件,以添加额外的 DLL 导入条目。这比修改内存中的 PE 标头稍微复杂一些,因为我们需要在向标头添加额外条目的同时维护 EXE 文件的原始结构。

此方法可能对持久性有用 – 例如,如果系统已经在启动时运行特定进程(abc.exe),我们可以修改此EXE以在后台自动加载DLL。

如上一篇文章所述,IAT 表中的 DLL 条目需要导入至少一个函数。我们将再次使用序数 #1。IAT 表也将重新定位到文件末尾,因为现有标头中不太可能有足够的空间来添加额外的 DLL。

我还首次为此项目添加了 64 位支持。此工具可以编译为 32/64 位,默认情况下,它将同时适用于这两种体系结构。

此工具的工作原理如下:

1.将原始EXE文件加载到内存中。
2. 检查 EXE 是 32 位还是 64 位 – 这很重要,因为 NT 标头的长度不同。
3. 从IMAGE_DIRECTORY_ENTRY_IMPORT数据目录中查找原始导入表位置。
4. 将导入表位置从虚拟地址转换为文件指针 – 我创建了一个函数来自动执行此操作,称为 VirtualAddressToFilePtr
5. 获取原始导入表的副本并将其存储在内存中。
6. 创建此导入表的克隆版本,并为我们的新 DLL 文件添加一个额外的条目。
7. 创建一个空白的 EXE 输出文件。
8. 将原始 EXE 文件的内容写入新的输出文件(直到最后一节的末尾)。
9. 将新的扩展 IAT 表附加到新的 EXE 文件(紧跟在最后一节之后)。
10. 更新IMAGE_DIRECTORY_ENTRY_IMPORT目录以指向新的 IAT 表。
11. 增加上一节的 SizeOfRawData 和 VirtualSize 字段,以确保新的 IAT 表正确加载到内存中。磁盘上的分区数据必须与 pImageNtHeader->OptionalHeader.FileAlignment 的值对齐,因此此处可能需要额外的填充。
12. 检查原始 EXE 中在最后一个标头之后是否存在任何剩余数据。调试符号和嵌入文件通常附加到EXE文件的末尾 – 这些不是EXE格式的一部分,因此默认情况下它们不会加载到内存中。如有必要,pImageNtHeader->FileHeader.PointerToSymbolTable 值将更新为指向正确的位置,尽管这些值不太可能存在于生产软件中。

完整代码如下:

#include <stdio.h>
#include <windows.h>

DWORD LoadFileIntoMemory(char *pPath, BYTE **pFileData, DWORD *pdwFileSize)
{
	HANDLE hFile = NULL;
	DWORD dwFileSize = 0;
	BYTE *pFileDataBuffer = NULL;
	DWORD dwBytesRead = 0;

	// open file
	hFile = CreateFile(pPath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if(hFile == INVALID_HANDLE_VALUE)
	{
		return 1;
	}

	// calculate file size
	dwFileSize = GetFileSize(hFile, NULL);

	// allocate buffer
	pFileDataBuffer = (BYTE*)malloc(dwFileSize);
	if(pFileDataBuffer == NULL)
	{
		return 1;
	}

	// read file contents
	if(ReadFile(hFile, pFileDataBuffer, dwFileSize, &dwBytesRead, NULL) == 0)
	{
		return 1;
	}

	// verify byte count
	if(dwBytesRead != dwFileSize)
	{
		return 1;
	}

	// close file handle
	CloseHandle(hFile);

	// store values
	*pFileData = pFileDataBuffer;
	*pdwFileSize = dwFileSize;

	return 0;
}

DWORD WriteToFile(char *pPath, BYTE *pFileData, DWORD dwOrigFileSize, DWORD dwNewDataFilePosition, BYTE *pNewImportDirectory, DWORD dwNewImportDirectorySize, char *pDllName, BYTE *pImportLookupTable, DWORD dwImportLookupTableSize, DWORD dwPaddingBytes)
{
	HANDLE hFile = NULL;
	DWORD dwBytesWritten = 0;
	BYTE bPaddingByte = 0;

	// create file
	hFile = CreateFile(pPath, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if(hFile == INVALID_HANDLE_VALUE)
	{
		return 1;
	}

	// write original EXE data
	if(WriteFile(hFile, (void *)pFileData, dwNewDataFilePosition, &dwBytesWritten, NULL) == 0)
	{
		return 1;
	}

	// write new import directory
	if(WriteFile(hFile, (void *)pNewImportDirectory, dwNewImportDirectorySize, &dwBytesWritten, NULL) == 0)
	{
		return 1;
	}

	// write DLL name
	if(WriteFile(hFile, (void *)pDllName, (DWORD)(strlen(pDllName) + 1), &dwBytesWritten, NULL) == 0)
	{
		return 1;
	}

	// write import lookup table
	if(WriteFile(hFile, (void *)pImportLookupTable, dwImportLookupTableSize, &dwBytesWritten, NULL) == 0)
	{
		return 1;
	}

	// write import lookup table
	if(WriteFile(hFile, (void*)pImportLookupTable, dwImportLookupTableSize, &dwBytesWritten, NULL) == 0)
	{
		return 1;
	}

	// write section padding
	for(DWORD i = 0; i < dwPaddingBytes; i++)
	{
		if(WriteFile(hFile, (void*)&bPaddingByte, 1, &dwBytesWritten, NULL) == 0)
		{
			return 1;
		}
	}

	// write original appended data (debug symbols, installation data, etc)
	if(WriteFile(hFile, (void *)(pFileData + dwNewDataFilePosition), dwOrigFileSize - dwNewDataFilePosition, &dwBytesWritten, NULL) == 0)
	{
		return 1;
	}

	// close file handle
	CloseHandle(hFile);

	return 0;
}

BYTE *VirtualAddressToFilePtr(BYTE *pFileData, IMAGE_NT_HEADERS32 *pImageNtHeader, DWORD dwVirtualAddress)
{
	IMAGE_SECTION_HEADER *pCurrSectionHeader = NULL;
	BYTE *pFilePtr = NULL;
	DWORD dwSectionDataLength = 0;

	// loop through all sections
	for(DWORD i = 0; i < pImageNtHeader->FileHeader.NumberOfSections; i++)
	{
		// get current section header
		pCurrSectionHeader = (IMAGE_SECTION_HEADER *)((BYTE*)&pImageNtHeader->OptionalHeader + pImageNtHeader->FileHeader.SizeOfOptionalHeader + (i * sizeof(IMAGE_SECTION_HEADER)));
		if(pCurrSectionHeader->SizeOfRawData != 0)
		{
			// calculate section data length (on disk)
			dwSectionDataLength = pCurrSectionHeader->SizeOfRawData;
			if(dwVirtualAddress >= pCurrSectionHeader->VirtualAddress && dwVirtualAddress < (pCurrSectionHeader->VirtualAddress + dwSectionDataLength))
			{
				pFilePtr = pFileData;
				pFilePtr += pCurrSectionHeader->PointerToRawData;
				pFilePtr += (dwVirtualAddress - pCurrSectionHeader->VirtualAddress);

				return pFilePtr;
			}
		}
	}

	return NULL;
}

int main(int argc, char *argv[])
{
	DWORD dwFileSize = 0;
	BYTE *pFileData = NULL;
	IMAGE_DOS_HEADER *pImageDosHeader = NULL;
	IMAGE_NT_HEADERS32 *pImageNtHeader = NULL;
	IMAGE_NT_HEADERS64 *pImageNtHeader64 = NULL;
	IMAGE_DATA_DIRECTORY *pImageDataDirectory = NULL;
	char *pInputFilePath = NULL;
	char *pDllName = NULL;
	char szOutputFilePath[512];
	IMAGE_THUNK_DATA32 ImportLookupTable32[2];
	IMAGE_THUNK_DATA64 ImportLookupTable64[2];
	DWORD dwTotalAddedSize = 0;
	IMAGE_IMPORT_DESCRIPTOR *pImageImportDescriptor = NULL;
	BYTE *pImportBaseAddr = NULL;
	DWORD dwCurrImportBlockOffset = 0;
	IMAGE_SECTION_HEADER *pCurrSectionHeader = NULL;
	IMAGE_SECTION_HEADER *pLastSectionHeader = NULL;
	DWORD dwNewDataVirtualAddress = 0;
	DWORD dwModuleCount = 0;
	DWORD dwNewDataFilePosition = 0;
	IMAGE_IMPORT_DESCRIPTOR NewDllImportDescriptors[2];
	DWORD dwOrigImportSize = 0;
	DWORD dwNewImportDirectorySize = 0;
	BYTE *pNewImportDirectory = NULL;
	BYTE *pCopyImportPtr = NULL;
	DWORD dwFileAlignment = 0;
	DWORD dwPaddingBytes = 0;
	BYTE *pImportLookupTable = NULL;
	DWORD dwImportLookupTableSize = 0;

	printf("AddExeImport - www.x86matthew.com\n\n");

	if(argc != 3)
	{
		printf("Usage: %s [input_exe_path] [add_dll_name]\n\n", argv[0]);

		return 1;
	}

	// get cmd param
	pInputFilePath = argv[1];
	pDllName = argv[2];

	printf("Opening EXE: '%s'...\n", pInputFilePath);

	// load dll into memory
	if(LoadFileIntoMemory(pInputFilePath, &pFileData, &dwFileSize) != 0)
	{
		printf("Error: Failed to load EXE into memory\n");

		return 1;
	}

	// get dos header
	pImageDosHeader = (IMAGE_DOS_HEADER *)pFileData;
	if(pImageDosHeader->e_magic != 0x5A4D)
	{
		printf("Error: Invalid EXE\n");

		free(pFileData);
		return 1;
	}

	// get nt header
	pImageNtHeader = (IMAGE_NT_HEADERS32 *)(pFileData + pImageDosHeader->e_lfanew);
	if(pImageNtHeader->Signature != IMAGE_NT_SIGNATURE)
	{
		printf("Error: Invalid EXE\n");

		free(pFileData);
		return 1;
	}

	// check exe type
	if(pImageNtHeader->FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64)
	{
		// 64-bit
		printf("64-bit EXE detected\n");
		pImageNtHeader64 = (IMAGE_NT_HEADERS64 *)pImageNtHeader;
		pImageDataDirectory = pImageNtHeader64->OptionalHeader.DataDirectory;
	}
	else if(pImageNtHeader->FileHeader.Machine == IMAGE_FILE_MACHINE_I386)
	{
		// 32-bit
		printf("32-bit EXE detected\n");
		pImageNtHeader64 = NULL;
		pImageDataDirectory = pImageNtHeader->OptionalHeader.DataDirectory;
	}
	else
	{
		printf("Error: Invalid EXE\n");

		free(pFileData);
		return 1;
	}

	// find import table
	pImportBaseAddr = VirtualAddressToFilePtr(pFileData, pImageNtHeader, pImageDataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
	if(pImportBaseAddr == NULL)
	{
		printf("Error: Invalid EXE\n");

		free(pFileData);
		return 1;
	}

	// find last section in file (this should be the last entry in the list but this is not necessarily the case)
	for(DWORD i = 0; i < pImageNtHeader->FileHeader.NumberOfSections; i++)
	{
		// get current section header
		pCurrSectionHeader = (IMAGE_SECTION_HEADER*)((BYTE*)&pImageNtHeader->OptionalHeader + pImageNtHeader->FileHeader.SizeOfOptionalHeader + (i * sizeof(IMAGE_SECTION_HEADER)));

		if(pLastSectionHeader == NULL)
		{
			// set initial value
			pLastSectionHeader = pCurrSectionHeader;
		}
		else
		{
			// check if this section is the last entry so far
			if(pCurrSectionHeader->PointerToRawData > pLastSectionHeader->PointerToRawData)
			{
				// store current value
				pLastSectionHeader = pCurrSectionHeader;
			}
		}
	}

	// ensure the last section was found
	if(pLastSectionHeader == NULL)
	{
		printf("Error: Invalid EXE\n");

		free(pFileData);
		return 1;
	}

	// store positions of the end of the current EXE contents (virtual address + file position)
	dwNewDataVirtualAddress = pLastSectionHeader->VirtualAddress + pLastSectionHeader->SizeOfRawData;
	dwNewDataFilePosition = pLastSectionHeader->PointerToRawData + pLastSectionHeader->SizeOfRawData;

	// check if the exe already contains imports
	if(pImageDataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size != 0)
	{
		// calculate number of existing imported modules
		for(;;)
		{
			pImageImportDescriptor = (IMAGE_IMPORT_DESCRIPTOR*)(pImportBaseAddr + dwCurrImportBlockOffset);
			if(pImageImportDescriptor->Name == 0)
			{
				// finished
				break;
			}

			// increase counter
			dwModuleCount++;

			// update import block offset
			dwCurrImportBlockOffset += sizeof(IMAGE_IMPORT_DESCRIPTOR);
		}
	}

	printf("Adding '%s' to import table...\n", pDllName);

	// allocate memory for new (enlarged) import table
	dwOrigImportSize = dwModuleCount * sizeof(IMAGE_IMPORT_DESCRIPTOR);
	dwNewImportDirectorySize = dwOrigImportSize + sizeof(NewDllImportDescriptors);
	pNewImportDirectory = (BYTE*)malloc(dwNewImportDirectorySize);
	if(pNewImportDirectory == NULL)
	{
		printf("Error: Failed to allocate memory\n");

		free(pFileData);
		return 1;
	}

	// set import descriptor values for new dll
	NewDllImportDescriptors[0].Name = dwNewDataVirtualAddress + dwNewImportDirectorySize;
	NewDllImportDescriptors[0].OriginalFirstThunk = NewDllImportDescriptors[0].Name + (DWORD)strlen(pDllName) + 1;
	NewDllImportDescriptors[0].FirstThunk = NewDllImportDescriptors[0].OriginalFirstThunk;
	if(pImageNtHeader64 == NULL)
	{
		// 32-bit
		NewDllImportDescriptors[0].FirstThunk += sizeof(ImportLookupTable32);
	}
	else
	{
		// 64-bit
		NewDllImportDescriptors[0].FirstThunk += sizeof(ImportLookupTable64);
	}
	NewDllImportDescriptors[0].TimeDateStamp = 0;
	NewDllImportDescriptors[0].ForwarderChain = 0;

	// end of import descriptor chain
	NewDllImportDescriptors[1].OriginalFirstThunk = 0;
	NewDllImportDescriptors[1].TimeDateStamp = 0;
	NewDllImportDescriptors[1].ForwarderChain = 0;
	NewDllImportDescriptors[1].Name = 0;
	NewDllImportDescriptors[1].FirstThunk = 0;

	// copy original imports to the buffer
	pCopyImportPtr = pNewImportDirectory;
	if(dwModuleCount != 0)
	{
		memcpy(pNewImportDirectory, pImportBaseAddr, dwOrigImportSize);
		pCopyImportPtr += dwOrigImportSize;
	}

	// append the new imported module to the end of the list
	memcpy((void*)pCopyImportPtr, (void*)&NewDllImportDescriptors, sizeof(NewDllImportDescriptors));

	// initialise import lookup table for the new DLL (1 import - ordinal #1) - 32-bit
	ImportLookupTable32[0].u1.Ordinal = 0x80000001;
	ImportLookupTable32[1].u1.Ordinal = 0;

	// initialise import lookup table for the new DLL (1 import - ordinal #1) - 64-bit
	ImportLookupTable64[0].u1.Ordinal = 0x8000000000000001;
	ImportLookupTable64[1].u1.Ordinal = 0;

	// update IAT directory position
	pImageDataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress = dwNewDataVirtualAddress;
	pImageDataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size = dwNewImportDirectorySize;

	// calculate total length of additional data to append
	dwTotalAddedSize = dwNewImportDirectorySize;
	dwTotalAddedSize += (DWORD)strlen(pDllName) + 1;
	if(pImageNtHeader64 == NULL)
	{
		// 32-bit
		dwTotalAddedSize += (sizeof(ImportLookupTable32) * 2);
	}
	else
	{
		// 64-bit
		dwTotalAddedSize += (sizeof(ImportLookupTable64) * 2);
	}

	// get file alignment value
	if(pImageNtHeader64 == NULL)
	{
		// 32-bit
		dwFileAlignment = pImageNtHeader->OptionalHeader.FileAlignment;
	}
	else
	{
		// 64-bit
		dwFileAlignment = pImageNtHeader64->OptionalHeader.FileAlignment;
	}

	// calculate number of bytes to pad (section data in file must be aligned)
	dwPaddingBytes = dwFileAlignment - (dwTotalAddedSize % dwFileAlignment);
	if(dwPaddingBytes == dwFileAlignment)
	{
		dwPaddingBytes = 0;
	}
	dwTotalAddedSize += dwPaddingBytes;

	// the last section must have read/write permissions at minimum to allow the loader to store the resolved IAT value
	pLastSectionHeader->Characteristics |= IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE;
	pLastSectionHeader->SizeOfRawData += dwTotalAddedSize;
	pLastSectionHeader->Misc.VirtualSize += dwTotalAddedSize;
	if(pImageNtHeader64 == NULL)
	{
		// 32-bit
		pImageNtHeader->OptionalHeader.SizeOfImage += dwTotalAddedSize;
	}
	else
	{
		// 64-bit
		pImageNtHeader64->OptionalHeader.SizeOfImage += dwTotalAddedSize;
	}

	// check if debug symbols are currently stored at the end of the exe
	if(pImageNtHeader->FileHeader.PointerToSymbolTable == dwNewDataFilePosition)
	{
		// adjust debug symbol ptr
		pImageNtHeader->FileHeader.PointerToSymbolTable += dwTotalAddedSize;
	}

	// get import lookup table values
	if(pImageNtHeader64 == NULL)
	{
		// 32-bit
		pImportLookupTable = (BYTE*)&ImportLookupTable32[0];
		dwImportLookupTableSize = sizeof(ImportLookupTable32);
	}
	else
	{
		// 64-bit
		pImportLookupTable = (BYTE*)&ImportLookupTable64[0];
		dwImportLookupTableSize = sizeof(ImportLookupTable64);
	}

	// write new exe to file
	memset(szOutputFilePath, 0, sizeof(szOutputFilePath));
	_snprintf_s(szOutputFilePath, sizeof(szOutputFilePath) - 1, "%s_modified.exe", pInputFilePath);
	printf("Writing new file to '%s'...\n", szOutputFilePath);
	if(WriteToFile(szOutputFilePath, pFileData, dwFileSize, dwNewDataFilePosition, pNewImportDirectory, dwNewImportDirectorySize, pDllName, pImportLookupTable, dwImportLookupTableSize, dwPaddingBytes) != 0)
	{
		printf("Error: Failed to write new EXE\n");

		free(pNewImportDirectory);
		free(pFileData);
		return 1;
	}

	printf("Finished\n");

	// free memory
	free(pNewImportDirectory);
	free(pFileData);

	return 0;
}

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注