手动加载PE文件

今天手撸一下加载PE文件,并执行加载的PE文件。看完这一节之后相信大家会对PE文件的结构和在内存中的加载顺序有一个比较深刻的理解。
本文中可能对PE文件的基础知识介绍的不是很详细,建议大家先看看PE文件的基础结构,了解了这些基础知识后再看本文会简单许多。废话不多说,下边让我们进入正是环节吧~
主要流程分为这么几步:
1、读取PE文件到内存中;
2、申请用于加载PE文件的内存;
3、复制PE文件的所有节表到内存中;
4、修复IAT表;
5、修复重定位表;
6、转换并执行PE文件的入口函数。

读取PE文件

这块太简单了,就不多说了,直接上代码:

#include <Windows.h>

		HANDLE hFile = CreateFile(fileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
		if (hFile)
		{
			DWORD dwFileSize = GetFileSize(hFile,NULL);
			LPSTR fileData = new CHAR[dwFileSize];
			if(fileData )
			{
				RtlZeroMemory(fileData, dwFileSize);
				DWORD dwReadSize = 0;
				ReadFile(hFile,fileData ,dwFileSize,&dwReadSize,NULL);
			}
			
			CloseHandle(hFile);
			hFile = NULL;
		}

申请内存

申请内存之前我们需要先解析PE文件中的DOS头和NT头(PS:不知道这是啥的同学,强烈建议去翻翻PE基础结构的知识,务必!务必!务必!),从中获取到PE文件加载后的大小。
我们使用VirtualAlloc函数申请内存,获取到申请的内存地址后,需要将PE的DOS头和NT头复制到此块内存中,这也是PE文件加载的基础。

#include <Windows.h>
#include <winternl.h>

BOOL AllocateMemory()
{
	//这里的fileData就是第一步加载的PE文件数据
	//Dos头
	PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)fileData;
	//Nt头
	PIMAGE_NT_HEADERS NtHeaders = (PIMAGE_NT_HEADERS)((ULONG_PTR)fileData + DosHeader->e_lfanew);

	//申请内存,返回的是申请到内存地址
	PVOID ImageBase = VirtualAlloc(NULL, NtHeaders->OptionalHeader.SizeOfImage,MEM_COMMIT|MEM_RESERVE,PAGE_EXECUTE_READWRITE);
	if (!ImageBase)
		return FALSE;

	//dos头、nt头和节表头复制到新申请的内存中
	RtlCopyMemory(ImageBase,DosHeader,NtHeaders->OptionalHeader.SizeOfHeaders);

	return TRUE;
}

复制节表到内存中

要想复制PE文件中的所有节表,我们需要知道两个数据,一个是节表的大小,另一个则是第一个节表的地址。找第一个节表的地址时,我们可以使用微软提供的宏IMAGE_FIRST_SECTION,传参就是我们的NT头指针。当然,我们也可以手动查找,就在NT头的后边。
找到所有节表之后,我们需要循环将节表的数据复制到我们申请的内存中。

#include <Windows.h>
#include <winternl.h>

VOID CopyAllSections()
{
	//第一个节表
	PIMAGE_SECTION_HEADER section_header = IMAGE_FIRST_SECTION(NtHeaders);
	//节表的大小
	DWORD dwSectionsSize = NtHeaders->FileHeader.NumberOfSections;
	for (DWORD i = 0; i < dwSectionsSize; i++)
	{
		RtlCopyMemory((PVOID)((ULONG_PTR)ImageBase+ section_header->VirtualAddress),(PVOID)((ULONG_PTR)DosHeader + section_header->PointerToRawData),
			section_header->SizeOfRawData);
		section_header++;
	}
	
	//DOS头
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)ImageBase;
	//保存申请内存中的NT头
	PIMAGE_NT_HEADERS MemNtHeaders = (PIMAGE_NT_HEADERS)((ULONG_PTR)pDosHeader + pDosHeader->e_lfanew);
	//PE文件的入口地址
	ULONG_PTR EntryPointer = (ULONG_PTR)ImageBase + MemNtHeaders->OptionalHeader.AddressOfEntryPoint;
	return VOID();
}

修复IAT表

好了,我们正式进入修复IAT表的阶段,这是加载PE文件两个重要过程之一。
IAT表也被叫做导入表,它包含了运行这个PE文件需要用到的库(dll),由于我们的基址已经改变了,所以我们需要将用到的库函数地址进行修复,使其正确的指向要调用的函数,不然会导致PE文件无法正常加载。
导入表从上到下可以分为两层,库名-函数名。
简单解析一下就是,一个PE文件包含多个库,每个库都包含多个函数。
1、我们可以从NT头的目录结构中找到导入表的地址。
2、需要注意的是,PE文件加载导入表的时候有两种情况,一种是根据函数序号获取函数地址,一种是根据函数名获取函数地址。而判断究竟使用了哪种方式。主要是根据OriginalFirstThunk字段的最高位判断。

BOOL RepairIAT()
{
	//MemNtHeaders 内存中的NT头数据
	if (MemNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size == 0)
		return TRUE;

#ifdef _WIN64
	union
	{
		struct
		{
			ULONGLONG low : 63;
			ULONGLONG high : 1;
		}BitField;
		ULONGLONG Value;
	}Temp = { 0 };
#else
	union
	{
		struct
		{
			ULONG low : 31;
			ULONG high : 1;
		}BitField;
		ULONG Value;
	}Temp = { 0 };
#endif 

	PIMAGE_IMPORT_DESCRIPTOR pIID = (PIMAGE_IMPORT_DESCRIPTOR)((ULONG_PTR)ImageBase+ MemNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

	while (pIID->Name)
	{
		PIMAGE_THUNK_DATA pITD = (PIMAGE_THUNK_DATA)((ULONG_PTR)ImageBase+ pIID->OriginalFirstThunk);

		//记录真是函数地址的字段
		PULONG_PTR pFuncAddr = (PULONG_PTR)((ULONG_PTR)ImageBase + pIID->FirstThunk);
		while (*pFuncAddr != 0)
		{
			HMODULE hModule = LoadLibraryA((LPSTR)((ULONG_PTR)ImageBase + pIID->Name));
			if (!hModule)
				return FALSE;

			Temp.Value = pITD->u1.AddressOfData;
			//根据函数序号获取函数地址
			if (Temp.BitField.high == 1)
			{
				//修复函数地址
				*pFuncAddr = (ULONG_PTR)GetProcAddress(hModule, (LPCSTR)Temp.BitField.low);
			}
			//根据函数名获取函数地址
			else
			{
				//函数名
				PIMAGE_IMPORT_BY_NAME pFuncName = (PIMAGE_IMPORT_BY_NAME)((ULONG_PTR)ImageBase+ pITD->u1.AddressOfData);
				//修复函数地址
				*pFuncAddr = (ULONG_PTR)GetProcAddress(hModule, pFuncName->Name);
			}

			pITD++;
			pFuncAddr++;
		}

		pIID++;
	}

	return TRUE;
}

修复重定位表

对于可执行文件来说,一般没有重定位表,而对于动态库(dll)文件来说,基本都会有重定位表。重定位表就是对导出函数的地址修正。
要修复重定位表,需要了解它的结构,一般是一个头+一组数据。

BOOL RepairReloc()
{
	//MemNtHeaders 内存中Nt头数据
	if (MemNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size == 0)
	{
		return TRUE;
	}

	//一个重定位的数据是2字节
	typedef struct BASE_RELOCATION_ENTRY 
	{
		USHORT offset : 12;
		USHORT type : 4;
	}BASE_RELOCATION_ENTRY,*PBASE_RELOCATION_ENTRY;

	//NtHeaders是PE文件中的Nt头
	//这里是为了计算我们内存中加载PE文件的基址和PE文件默认基址的一个偏移
	INT_PTR offset = (INT_PTR)((ULONG_PTR)ImageBase - NtHeaders->OptionalHeader.ImageBase);
	PIMAGE_BASE_RELOCATION pIBR = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)ImageBase+m_MemNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
	while (pIBR->VirtualAddress != 0)
	{
		//重定位表的一个数据
		PBASE_RELOCATION_ENTRY pBlock = (PBASE_RELOCATION_ENTRY)((ULONG_PTR)pIBR+sizeof(IMAGE_BASE_RELOCATION));

		//一组重定位数据的个数
		DWORD NumberOfBlocks = (pIBR->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(BASE_RELOCATION_ENTRY);

		for (DWORD i = 0; i < NumberOfBlocks; i++, pBlock++)
		{
			//类型是IMAGE_REL_BASED_ABSOLUTE的不需要进行重定位
			if (pBlock->type != IMAGE_REL_BASED_ABSOLUTE)
			{
				//需要重定位的地址
				PINT_PTR RepairAddr = (PINT_PTR)((ULONG_PTR)ImageBase + pIBR->VirtualAddress + pBlock->offset);
				if (*RepairAddr <= 0)
					return FALSE;
				
				//重定位
				*RepairAddr += offset;
			}
		}

		pIBR = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)pIBR + pIBR->SizeOfBlock);
	}

	return TRUE;
}

转换函数入口

从第二步中我们可以得到函数入口地址,直接执行即可。这是对于可执行文件而言的,如果是动态库文件,我们需要转换一下

VOID CallEntryPoint()
{
	if (IsDllFile())
	{
		typedef BOOL(APIENTRY *_DllMain)(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved);
		//EntryPointer 是第二步获取到的函数地址
		((_DllMain)EntryPointer)((HMODULE)ImageBase, DLL_PROCESS_ATTACH,NULL);

		//如果动态库中有导出函数,则可以使用这个转换一下,获取导出函数地址(MemGetProcAddress)的下边再说
		typedef int(*_fntestdll)(void);
		_fntestdll fntestdll = (_fntestdll)MemGetProcAddress("fntestdll");

		fntestdll();
	}
	else
	{
		((void(*)())(EntryPointer))();
	}
	
	return VOID();
}

下边我们说一下如何获取动态库中导出函数,简单的原理就是从修复完的PE文件内存中解析导出表,然后从导出表中获取相应的函数。
其中我们需要知道导出表的三个地址:函数名地址、导出函数序号地址、导出函数地址
导出函数序号地址记录的是相应函数名的函数序号,再根据函数序号从导出函数地址中获取函数的地址。
原因嘛,导出函数地址是按函数序号从小到大顺序记录的,但是函数名地址中保存地址和导出函数地址顺序是不一样的。一般函数名地址排序方式是按首字母排序的。

PVOID MemGetProcAddress(LPCSTR funcName)
{
	if (MemNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size == 0)
		return NULL;

	PIMAGE_EXPORT_DIRECTORY pIED = (PIMAGE_EXPORT_DIRECTORY)((ULONG_PTR)ImageBase + m_MemNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

	PDWORD pNameAddress = (PDWORD)((ULONG_PTR)ImageBase + pIED->AddressOfNames);
	PDWORD pFuncAddress = (PDWORD)((ULONG_PTR)ImageBase + pIED->AddressOfFunctions);
	PWORD pNameOrdinalsAddress = (PWORD)((ULONG_PTR)ImageBase + pIED->AddressOfNameOrdinals);

	for (DWORD i = 0; i < pIED->NumberOfNames; i++)
	{
		if (strcmp(funcName, (LPCSTR)(ULONG_PTR)ImageBase + *pNameAddress) == 0)
		{
			return (PVOID)((ULONG_PTR)ImageBase + pFuncAddress[pNameOrdinalsAddress[i]]);
		}
		pNameAddress++;
	}

	return PVOID();
}

好了,手动加载PE文件并执行的全部流程就到此为止了,刚开始的时候大家可能感觉理解起来有点费劲,但是熟悉了之后就感觉还好,为了方便大家,下边给大家附上全部代码(PS:使用C++写的)

DealPEFile.h

#pragma once
#include <Windows.h>
#include <winternl.h>

class DealPEFile
{
public:
	DealPEFile(LPCWSTR fileName);
	~DealPEFile();

	VOID LoadMemory();
private:
	//判断是否是PE文件
	BOOL IsValidPE();
	//申请内存
	BOOL AllocateMemory();
	//拷贝所有节表到内存中
	VOID CopyAllSections();
	//修复IAT表
	BOOL RepairIAT();
	//修复重定位表
	BOOL RepairReloc();
	//获取导出函数
	PVOID MemGetProcAddress(LPCSTR funcName);

	//判断是否是Dll文件
	BOOL IsDllFile();
	//执行PE文件的入口函数
	VOID CallEntryPoint(DealPEFile* pDealPEFile);

private:
	//文件数据
	LPSTR m_fileData;
	//PE基地址
	PVOID m_ImageBase;

	//Dos头
	PIMAGE_DOS_HEADER m_DosHeader;
	//Nt头
	PIMAGE_NT_HEADERS m_NtHeaders;

	//内存中Nt头
	PIMAGE_NT_HEADERS m_MemNtHeaders;

	//PE文件入口地址
	ULONG_PTR m_EntryPointer;
};

DealPEFile.cpp

#include "DealPEFile.h"

DealPEFile::DealPEFile(LPCWSTR fileName)
{
	m_fileData = NULL;
	m_DosHeader = NULL;
	m_NtHeaders = NULL;

	if (fileName != NULL && fileName[0] != L'\0')
	{
		HANDLE hFile = CreateFile(fileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
		if (hFile)
		{
			DWORD dwFileSize = GetFileSize(hFile,NULL);
			m_fileData = new CHAR[dwFileSize];
			RtlZeroMemory(m_fileData, dwFileSize);

			DWORD dwReadSize = 0;
			ReadFile(hFile,m_fileData,dwFileSize,&dwReadSize,NULL);

			CloseHandle(hFile);
			hFile = NULL;
		}

		if (m_fileData)
		{
			m_DosHeader = (PIMAGE_DOS_HEADER)m_fileData;
			m_NtHeaders = (PIMAGE_NT_HEADERS)((ULONG_PTR)m_DosHeader + m_DosHeader->e_lfanew);
		}
	}
}

DealPEFile::~DealPEFile()
{
	if (m_fileData)
	{
		delete[] m_fileData;
		m_fileData = NULL;
	}

	m_DosHeader = NULL;
	m_NtHeaders = NULL;
}

VOID DealPEFile::LoadMemory()
{
	if (m_fileData == NULL)
		return;

	//判断是否是ie文件,申请加载PE文件的内存
	if (!IsValidPE() || !AllocateMemory())
		return;

	//复制所有节表到内存中
	CopyAllSections();

	//修复IAT表
	if (!RepairIAT())
		return;

	//修复重定位表
	if (!RepairReloc())
		return;

	//执行PE文件的入口函数
	CallEntryPoint(this);

	return;
}

BOOL DealPEFile::IsValidPE()
{
	if (m_DosHeader->e_magic != IMAGE_DOS_SIGNATURE || m_NtHeaders->Signature != IMAGE_NT_SIGNATURE)
		return FALSE;

#ifdef _WIN64
	if (m_NtHeaders->FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64)
	{
		return FALSE;
	}
#else
	if (m_NtHeaders->FileHeader.Machine != IMAGE_FILE_MACHINE_I386)
	{
		return FALSE;
	}
#endif

	return TRUE;
}

BOOL DealPEFile::AllocateMemory()
{
	m_ImageBase = VirtualAlloc(NULL, m_NtHeaders->OptionalHeader.SizeOfImage,MEM_COMMIT|MEM_RESERVE,PAGE_EXECUTE_READWRITE);
	if (!m_ImageBase)
		return FALSE;

	RtlCopyMemory(m_ImageBase,m_DosHeader,m_NtHeaders->OptionalHeader.SizeOfHeaders);

	return TRUE;
}

//拷贝所有节表到内存中
VOID DealPEFile::CopyAllSections()
{
	PIMAGE_SECTION_HEADER section_header = IMAGE_FIRST_SECTION(m_NtHeaders);
	//节表数量
	DWORD dwSectionsSize = m_NtHeaders->FileHeader.NumberOfSections;
	for (DWORD i = 0; i < dwSectionsSize; i++)
	{
		RtlCopyMemory((PVOID)((ULONG_PTR)m_ImageBase+ section_header->VirtualAddress),(PVOID)((ULONG_PTR)m_DosHeader + section_header->PointerToRawData),
			section_header->SizeOfRawData);
		section_header++;
	}

	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)m_ImageBase;
	m_MemNtHeaders = (PIMAGE_NT_HEADERS)((ULONG_PTR)pDosHeader + pDosHeader->e_lfanew);

	m_EntryPointer = (ULONG_PTR)m_ImageBase + m_MemNtHeaders->OptionalHeader.AddressOfEntryPoint;
	return VOID();
}

//修复IAT表
BOOL DealPEFile::RepairIAT()
{
	if (m_MemNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size == 0)
		return FALSE;

#ifdef _WIN64
	union
	{
		struct
		{
			ULONGLONG low : 63;
			ULONGLONG high : 1;
		}BitField;
		ULONGLONG Value;
	}Temp = { 0 };
#else
	union
	{
		struct
		{
			ULONG low : 31;
			ULONG high : 1;
		}BitField;
		ULONG Value;
	}Temp = { 0 };
#endif 

	//导入表
	PIMAGE_IMPORT_DESCRIPTOR pIID = (PIMAGE_IMPORT_DESCRIPTOR)((ULONG_PTR)m_ImageBase+ m_MemNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

	while (pIID->Name)
	{
		PIMAGE_THUNK_DATA pITD = (PIMAGE_THUNK_DATA)((ULONG_PTR)m_ImageBase+ pIID->OriginalFirstThunk);

		//函数地址
		PULONG_PTR pFuncAddr = (PULONG_PTR)((ULONG_PTR)m_ImageBase + pIID->FirstThunk);
		while (*pFuncAddr != 0)
		{
			HMODULE hModule = LoadLibraryA((LPSTR)((ULONG_PTR)m_ImageBase + pIID->Name));
			if (!hModule)
				return FALSE;

			Temp.Value = pITD->u1.AddressOfData;
			if (Temp.BitField.high == 1)
			{
				*pFuncAddr = (ULONG_PTR)GetProcAddress(hModule, (LPCSTR)Temp.BitField.low);
			}
			else
			{
				PIMAGE_IMPORT_BY_NAME pFuncName = (PIMAGE_IMPORT_BY_NAME)((ULONG_PTR)m_ImageBase+ pITD->u1.AddressOfData);
				*pFuncAddr = (ULONG_PTR)GetProcAddress(hModule, pFuncName->Name);
			}

			pITD++;
			pFuncAddr++;
		}

		pIID++;
	}

	return TRUE;
}

//修复重定位表
BOOL DealPEFile::RepairReloc()
{
	if (m_MemNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size == 0)
	{
		return FALSE;
	}

	typedef struct BASE_RELOCATION_ENTRY 
	{
		USHORT offset : 12;
		USHORT type : 4;
	}BASE_RELOCATION_ENTRY,*PBASE_RELOCATION_ENTRY;

	//偏移
	INT_PTR offset = (INT_PTR)((ULONG_PTR)m_ImageBase - m_NtHeaders->OptionalHeader.ImageBase);
	
	//重定位表
	PIMAGE_BASE_RELOCATION pIBR = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)m_ImageBase+m_MemNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
	while (pIBR->VirtualAddress != 0)
	{
		//重定位块数据
		PBASE_RELOCATION_ENTRY pBlock = (PBASE_RELOCATION_ENTRY)((ULONG_PTR)pIBR+sizeof(IMAGE_BASE_RELOCATION));
		//一块重定位数据的数量
		DWORD NumberOfBlocks = (pIBR->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(BASE_RELOCATION_ENTRY);

		for (DWORD i = 0; i < NumberOfBlocks; i++, pBlock++)
		{
			if (pBlock->type != IMAGE_REL_BASED_ABSOLUTE)
			{
				//修复地址
				PINT_PTR RepairAddr = (PINT_PTR)((ULONG_PTR)m_ImageBase + pIBR->VirtualAddress + pBlock->offset);
				if (*RepairAddr <= 0)
					return FALSE;

				*RepairAddr += offset;
			}
		}

		pIBR = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)pIBR + pIBR->SizeOfBlock);
	}

	return TRUE;
}

//获取导出函数
PVOID DealPEFile::MemGetProcAddress(LPCSTR funcName)
{
	if (m_MemNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size == 0)
		return NULL;

	PIMAGE_EXPORT_DIRECTORY pIED = (PIMAGE_EXPORT_DIRECTORY)((ULONG_PTR)m_ImageBase + m_MemNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

	//函数名地址
	PDWORD pNameAddress = (PDWORD)((ULONG_PTR)m_ImageBase + pIED->AddressOfNames);
	//函数地址
	PDWORD pFuncAddress = (PDWORD)((ULONG_PTR)m_ImageBase + pIED->AddressOfFunctions);
	//函数名序号地址
	PWORD pNameOrdinalsAddress = (PWORD)((ULONG_PTR)m_ImageBase + pIED->AddressOfNameOrdinals);

	for (DWORD i = 0; i < pIED->NumberOfNames; i++)
	{
		if (strcmp(funcName, (LPCSTR)(ULONG_PTR)m_ImageBase + *pNameAddress) == 0)
		{
			return (PVOID)((ULONG_PTR)m_ImageBase + pFuncAddress[pNameOrdinalsAddress[i]]);
		}
		pNameAddress++;
	}

	return PVOID();
}

//判断是否是Dll文件
BOOL DealPEFile::IsDllFile()
{
	return m_MemNtHeaders->FileHeader.Characteristics & IMAGE_FILE_DLL;
}


//执行PE文件的入口函数
VOID DealPEFile::CallEntryPoint(DealPEFile* pDealPEFile)
{
	if (IsDllFile())
	{
		typedef BOOL(APIENTRY *_DllMain)(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved);
		((_DllMain)pDealPEFile->m_EntryPointer)((HMODULE)m_ImageBase, DLL_PROCESS_ATTACH,NULL);

		typedef int(*_fntestdll)(void);
		_fntestdll fntestdll = (_fntestdll)MemGetProcAddress("fntestdll");

		fntestdll();
	}
	else
	{
		((void(*)())(pDealPEFile->m_EntryPointer))();
	}
	
	return VOID();
}

此代码加载的PE文件是我测试使用的,测试的代码也给大家附上
Test.exe

#include <Windows.h>

#pragma comment(linker,"/entry:Test")

int Test()
{
	MessageBox(NULL,L"Test Exe!!!", L"提示", MB_OK);
    return 0;
}

Test.dll

dllmain.cpp

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include <Windows.h>

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
					 )
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		MessageBox(NULL,L"test dll Main",L"提示",MB_OK);
		break;
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}

testdll.h


#ifdef __cplusplus
extern "C" {
#endif
	__declspec(dllexport) int fntestdll(void);
#ifdef __cplusplus
}
#endif

testdll.cpp

#include "testdll.h"
#include <Windows.h>


// 这是导出函数的一个示例。
int fntestdll(void)
{
	MessageBox(NULL,L"fntestdll",L"提示",MB_OK);
    return 42;
}
  • 19
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
发布个大型PE,号称“巨无霸”,切合热衷于“称王称霸”的网络流行心态。 这个PE在在win8_x64的WinRE基础上加入Win8系统部分组件做出来的。所以体积较大,达460M。 的特色简介: 1、很大,有好多“垃圾”。对精简党而言,这么庞大的PE纯属垃圾,但对某群体而言,是宝贝啊。 2、可手动添加硬件驱动,主要是磁盘驱动器的驱动。比如,当启动PE后认不到服务器的RAID,没问题,下载厂家的RAID驱动到U盘,右击inf文件选“安装”就OK了(本论坛好多热门的PE做不到这个)。 3、脱机添加硬件驱动。如果觉得每次手工安装驱动不爽,可以用本PE的DISM组件,向本PE的核心WIM添加驱动,一劳永逸(本论坛好多热门的PE也做不到这个)。 注意:上述两项添加驱动的图解教程可以参考2楼的介绍。需要提醒的是:不要尝试添加声卡、网卡驱动,因为本PE不支持娱乐。 4、极佳的兼容性。本PE支持很多应用程序安装,包括x86/x64的程序。绝大多数“绿色软件”也能完美运行。因为运行库VC较全(论坛其它PE支持不佳的,本PE仍然可以运行)。 5、颇具特色的输入法图标。本PE含微软的中文拼音输入法和极点五笔拼音输入法,对喜欢五笔和拼音的网友均友好支持。输入法托盘图标也很友好,跟正常系统一样(本论坛很多PE都没有这种显示哦)。 6、直接双击微软的setup程序安装Win8_x84系统,支持BIOS/UEFI。论坛好多高手都介绍用第三方“安装器”win8系统,本PE不赞成这样做。只要双击微软win8光盘镜像中的setup图标,轻轻松松安装系统,不用事先做ESP、MSR分区之类操作。 7、启动快。虽然核心很大,加载到内存的时间长很多,但是,启动速度非常快。从那个小“圈圈”转动到桌面显示完成启动的时间,就眨眼工夫。比那些在“正在xxx”字幕下长时间黑屏的爽多了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值