一些PE文件操作的函數集合DLL庫

把前面一些程序中用到的PE相關函數集合在一個DLL中方便使用

#ifndef PE32_K_H
#define PE32_K_H

#ifdef __cplusplus
#define EXPORT extern "C" __declspec (dllexport)
#else
#define EXPORT __declspec (dllexport)
#endif

//指定
#define ADD_LAST_SECTION 1	//添加代碼到最後一個區段	
#define ADD_NEW_SECTION  2  //添加代碼到一個新建的區段
#define ADD_PE_HEADER	 3  //添加代碼到PE頭部



//重置,重置後可繼續用文件初始化此DLL
EXPORT BOOL _stdcall Reset(TCHAR szFileName[]);


//使用函數前必須調用此函數初始化
//參數---szFileName:文件名
EXPORT BOOL _stdcall InitPE32(TCHAR szFileName[]);


//取得原文件數據指針,只讀屬性
EXPORT CONST PBYTE _stdcall GetFileBuffer();


//取得文件名
EXPORT CONST PTCHAR _stdcall GetFileName();


//添加代碼到目標文件
//參數---szNewFile:添加代碼後新文件的名字	lpCOdeStart:指向代碼起始處	
//		 dwCodeSize:添加代碼的大小			dwTypeOfAdd:添加方法類型
EXPORT BOOL _stdcall AddCode(TCHAR szNewFile[], PBYTE lpCodeStart, DWORD dwCodeSize, DWORD dwTypeOfAdd);


//獲得目標文件的CRC32校驗值
EXPORT DWORD _stdcall GetCRC32(TCHAR szFileName[] );


//檢測是否爲PE文件
//參數---szFileName:文件名
EXPORT BOOL _stdcall IsPeFile(TCHAR szFileName[]);


//將RVA偏移轉換成文件偏移,失敗返回-1
EXPORT DWORD _stdcall RvaToOffset (IMAGE_DOS_HEADER *lpFileHead, DWORD dwRva);


//將RVA偏移轉成文件指針偏移,失敗返回NULL
EXPORT PBYTE _stdcall RvaToPointer(IMAGE_DOS_HEADER *lpFileHead,DWORD dwRva);


//將虛擬地址轉成文件指針偏移,失敗返回NULL
EXPORT PBYTE _stdcall VirtualAddressToPointer(IMAGE_DOS_HEADER *lpFileHead,DWORD dwVirtualAddress);


//獲得RVA偏移處的節區名稱,失敗返回NULL
EXPORT CONST PBYTE _stdcall GetRvaSection (IMAGE_DOS_HEADER *lpFileHead, DWORD dwRva);


//獲得指定RVA所處節區的節表頭,失敗返回NULL
EXPORT PIMAGE_SECTION_HEADER _stdcall GetSectionOfRva (IMAGE_DOS_HEADER *lpFileHead, char* secName);


//文件偏移轉換成RVA,失敗返回-1
EXPORT DWORD _stdcall OffsetToRVA(IMAGE_DOS_HEADER *lpFileHead, DWORD dwOffset);


//文件偏移轉換成內存指針,失敗返回NULL
EXPORT PBYTE _stdcall OffsetToPointer(IMAGE_DOS_HEADER *lpFileHead, DWORD dwOffset);


//提取圖標數據,參數指定文件名
EXPORT BOOL _stdcall GetIcon (TCHAR	szIconFileName[]);

#endif


 

#include <windows.h>
#include "PE32_K.H"


TCHAR		g_szFileName[MAX_PATH] = { 0 };
HINSTANCE	g_hInstDll	= NULL;		//DLL模塊句柄
PBYTE		g_lpBuffer	= NULL;		//存儲文件數據
DWORD		g_dwFileSize= 0;		//文件數據大小
PBYTE		g_lpNewFile = NULL;		//存儲添加代碼後的數據
BOOL		PE	= FALSE;  //指示是否是PE文件


void _stdcall DllClean();

///////////////////////////////////////////////////////////////////////////////

BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD fdwReason, PVOID fImpLoad) {

   switch (fdwReason) {

      case DLL_PROCESS_ATTACH:
         // DLL is attaching to the address space of the current process.
         g_hInstDll = hInstDll;
         break;

      case DLL_THREAD_ATTACH:
         // A new thread is being created in the current process.
         break;

      case DLL_THREAD_DETACH:
         // A thread is exiting cleanly.
         break;

      case DLL_PROCESS_DETACH:
         // The calling process is detaching the DLL from its address space.
         DllClean();
		 break;
   }
   return(TRUE);
}

///////////////////////////////////////////////////////////////////////////////



///////////////////////////////////////將RVA偏移轉換成文件偏移,失敗返回-1////////////////////////////////////////////////

DWORD _stdcall RvaToOffset (IMAGE_DOS_HEADER *lpFileHead, DWORD dwRva)
{
	::IMAGE_NT_HEADERS		*lpPEHead;
	::IMAGE_SECTION_HEADER	*lpSectionHead;
	DWORD i;
	lpPEHead		= (IMAGE_NT_HEADERS*)( (BYTE*)lpFileHead + lpFileHead->e_lfanew);
	i	= lpPEHead->FileHeader.NumberOfSections;
	lpSectionHead	= (IMAGE_SECTION_HEADER*)(++lpPEHead); 
	for ( ; i > 0 ; i--, lpSectionHead++)
	{
		if ( (dwRva >= lpSectionHead->VirtualAddress) && (dwRva < (lpSectionHead->VirtualAddress + lpSectionHead->SizeOfRawData) ) )
		{
			dwRva	= dwRva - lpSectionHead->VirtualAddress + lpSectionHead->PointerToRawData;
			return dwRva;
		}
	}
	return -1;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



////////////////////////////////////將RVA偏移轉成文件指針偏移,失敗返回NULL////////////////////////////////////////////////

PBYTE _stdcall RvaToPointer(IMAGE_DOS_HEADER *lpFileHead,DWORD dwRva)
{
	DWORD	Offset	= RvaToOffset(lpFileHead, dwRva);
	if(Offset == -1)
		return NULL;
	return	(PBYTE)(lpFileHead) + Offset;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



////////////////////////////////////將虛擬地址轉成文件指針偏移,失敗返回NULL////////////////////////////////////////////////

PBYTE _stdcall VirtualAddressToPointer(IMAGE_DOS_HEADER *lpFileHead,DWORD dwVirtualAddress)
{
	::IMAGE_NT_HEADERS		*lpPEHead;
	lpPEHead		= (IMAGE_NT_HEADERS*)( (BYTE*)lpFileHead + lpFileHead->e_lfanew);
	
	return (PBYTE)RvaToPointer(lpFileHead, dwVirtualAddress - lpPEHead->OptionalHeader.ImageBase);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



////////////////////////////////////////////////獲得RVA偏移處的節區名稱////////////////////////////////////////////////

CONST PBYTE _stdcall GetRvaSection (IMAGE_DOS_HEADER *lpFileHead, DWORD dwRva)
{
	IMAGE_NT_HEADERS		*lpPEHead;
	IMAGE_SECTION_HEADER	*lpSectionHead;
	DWORD i;
	lpPEHead		= (IMAGE_NT_HEADERS*)( (BYTE*)lpFileHead + lpFileHead->e_lfanew);
	i	= lpPEHead->FileHeader.NumberOfSections;
	lpSectionHead	= (IMAGE_SECTION_HEADER*)(++lpPEHead); 
	for ( ; i > 0 ; i--, lpSectionHead++)
	{
		if ( (dwRva >= lpSectionHead->VirtualAddress) && (dwRva < (lpSectionHead->VirtualAddress + lpSectionHead->SizeOfRawData) ) )
		{
			return (PBYTE)lpSectionHead;
		}
	}
	return NULL;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



///////////////////////////////////////獲得指定RVA所處節區的節表頭,失敗返回NULL/////////////////////////////////////////

PIMAGE_SECTION_HEADER _stdcall GetSectionOfRva (IMAGE_DOS_HEADER *lpFileHead, char* secName)
{
	::PIMAGE_NT_HEADERS lpNtHead		= (PIMAGE_NT_HEADERS)( (BYTE*)lpFileHead + lpFileHead->e_lfanew);
	DWORD dwSec = lpNtHead->FileHeader.NumberOfSections;
	IMAGE_SECTION_HEADER* lpSection	= (PIMAGE_SECTION_HEADER) (lpNtHead + 1);
	
	for (DWORD i=0; i < dwSec; i++)
	{
		if(!strncmp((char*)lpSection->Name, secName, IMAGE_SIZEOF_SHORT_NAME) )
			return lpSection;
		lpSection++;
	}
	return NULL;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



////////////////////////////////////////////文件偏移轉換成RVA///////////////////////////////////////////////////////////

DWORD _stdcall OffsetToRVA(IMAGE_DOS_HEADER *lpFileHead, DWORD dwOffset)
{
	::IMAGE_NT_HEADERS		*lpPEHead;
	::IMAGE_SECTION_HEADER	*lpSectionHead;
	DWORD i;
	lpPEHead		= (IMAGE_NT_HEADERS*)( (BYTE*)lpFileHead + lpFileHead->e_lfanew);
	i	= lpPEHead->FileHeader.NumberOfSections;
	lpSectionHead	= (IMAGE_SECTION_HEADER*)(++lpPEHead); 

	for ( ; i > 0; i--, lpSectionHead++)
	{
		if ( (dwOffset >= lpSectionHead->PointerToRawData) && (dwOffset < (lpSectionHead->PointerToRawData + lpSectionHead->SizeOfRawData) ) )
		{
			dwOffset	= dwOffset - lpSectionHead->PointerToRawData + lpSectionHead->VirtualAddress;
			return dwOffset;
		}
	}
	return -1;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



////////////////////////////////////////////文件偏移轉換成內存指針///////////////////////////////////////////////////////////

PBYTE _stdcall OffsetToPointer(IMAGE_DOS_HEADER *lpFileHead, DWORD dwOffset)
{
	DWORD	RVA	= OffsetToRVA(lpFileHead, dwOffset);
	if( RVA == -1)
		return NULL;
	return	(PBYTE)(lpFileHead) + RVA;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



/////////////////////////////////////////////////按指定大小對齊//////////////////////////////////////////////////////////

DWORD _stdcall Align(DWORD dwSize, DWORD dwAlignment)
{
	return (dwSize + dwAlignment - 1) /dwAlignment * dwAlignment;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



///////////////////////////////////////////////獲得區塊有效數據部分大小/////////////////////////////////////////////////

DWORD _stdcall GetValidSize(PBYTE lpMemory, PIMAGE_SECTION_HEADER lpSection)
{
	PBYTE	lpData;
	DWORD	dwSize=0;

	lpData	= (PBYTE)( lpMemory + lpSection->PointerToRawData + lpSection->SizeOfRawData - 1);
	while (*lpData == 0)
	{
		lpData--;
		dwSize++;
	}
	dwSize -= 8;  //減去8個字節防止是字符串或某結構的結尾
	if (dwSize > 0)
		return lpSection->SizeOfRawData - dwSize;
	else
		return lpSection->SizeOfRawData;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



//檢測是否是PE文件
BOOL _stdcall IsPeFile(TCHAR szFileName[])
{
	HANDLE	hFile;
	WORD	wMagic;
	DWORD   dwRead,dw;
	if (INVALID_HANDLE_VALUE != ( hFile = CreateFile (szFileName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, \
																		NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL) ) )
	{
		ReadFile(hFile , &wMagic, 2, &dwRead, NULL);
		if ( wMagic == 0x5A4D)
		{
			SetFilePointer(hFile, 0x3C, 0, FILE_BEGIN);
			ReadFile(hFile , &dw, 4, &dwRead, NULL);
			SetFilePointer(hFile, dw, 0, FILE_BEGIN);
			ReadFile(hFile , &wMagic, 2, &dwRead, NULL);
			if (wMagic == 0x4550)
				return TRUE;
		}
	}
	return FALSE;
}



//獲得文件數據指針
CONST PBYTE _stdcall GetFileBuffer()
{
	return g_lpBuffer;
}


//獲得文件名
EXPORT CONST PTCHAR _stdcall GetFileName()
{
	return g_szFileName;
}


//DLL卸載時清理
void _stdcall DllClean()
{
	if (g_lpBuffer)
		VirtualFree(g_lpBuffer, g_dwFileSize, MEM_RELEASE);
}


//重置DLL,然後重新初始化
BOOL _stdcall Reset(TCHAR szFileName[])
{
	if (g_lpBuffer)
		VirtualFree(g_lpBuffer, g_dwFileSize, MEM_RELEASE);
	memset(g_szFileName, 0, sizeof(g_szFileName) ) ;
	g_lpBuffer	 = NULL;	
	g_dwFileSize = 0;		
	g_lpNewFile  = NULL;	
	PE	= FALSE;  
	return InitPE32(szFileName);
}


//初始化,將文件數據copy到分配的緩衝區中
BOOL _stdcall InitPE32(TCHAR szFileName[])
{
	DWORD	dwFileSize;	//文件大小
	PBYTE	lpMemory;	//內存映射指針
	HANDLE	hFile, hMap;
	if (!IsPeFile(szFileName) )
		return FALSE;
	
	if (INVALID_HANDLE_VALUE != ( hFile = CreateFile (szFileName, GENERIC_READ , FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL) ) )
	{
		dwFileSize	= GetFileSize (hFile, NULL);
		if (dwFileSize)
		{
			hMap	= CreateFileMapping (hFile, NULL, PAGE_READONLY, 0, 0, NULL);
			if (hMap)
			{
				lpMemory	= (BYTE *)MapViewOfFile (hMap, FILE_MAP_READ, 0, 0, 0);
				if (lpMemory)
				{
					lstrcpy(g_szFileName, szFileName);
					g_lpBuffer	= (PBYTE)VirtualAlloc(NULL, dwFileSize, MEM_COMMIT, PAGE_READWRITE);
					if (g_lpBuffer)
					{
						PE	= TRUE;
						g_dwFileSize	= dwFileSize;
						memcpy(g_lpBuffer, lpMemory, dwFileSize);
						UnmapViewOfFile(lpMemory);
						CloseHandle(hMap);
						CloseHandle(hFile);
						return TRUE;
					}
				}
			}
		}
	}

	UnmapViewOfFile(lpMemory);
	CloseHandle(hMap);
	CloseHandle(hFile);
	return FALSE;
}


//計算CRC32值,DLL內部函數
//參數---ptr:數據指針	dwSize:數據大小
static DWORD _stdcall CalCRC32(BYTE* ptr,DWORD dwSize)
{
	DWORD crcTable[256],crcTmp1;
	
	//動態生成CRC-32表
	for (int i=0; i<256; i++)
	 {
		crcTmp1 = i;
		for (int j=8; j>0; j--)
		 {
			if (crcTmp1&1) crcTmp1 = (crcTmp1 >> 1) ^ 0xEDB88320L;
			 else crcTmp1 >>= 1;
		}

		 crcTable[i] = crcTmp1;
	 }
	//計算CRC32值
	DWORD crcTmp2= 0xFFFFFFFF;
	while(dwSize--)
	{
		crcTmp2 = ((crcTmp2>>8) & 0x00FFFFFF) ^ crcTable[ (crcTmp2^(*ptr)) & 0xFF ];
		ptr++;
	}
		
	return (crcTmp2^0xFFFFFFFF);
}


//獲得指定文件的CRC32值,錯誤返回0
DWORD _stdcall GetCRC32(TCHAR szFileName[] )
{
	if (!g_lpBuffer)
		return FALSE;
	//如果需要的是本文件的CRC32則直接調用
	if (!lstrcmp(szFileName, g_szFileName) )
		return CalCRC32(g_lpBuffer + ((PIMAGE_DOS_HEADER)g_lpBuffer)->e_lfanew, g_dwFileSize);
	else
	{
		PIMAGE_DOS_HEADER	    pDosHeader=NULL;
		PIMAGE_NT_HEADERS       pNtHeader=NULL;
		PIMAGE_SECTION_HEADER   pSecHeader=NULL;

		DWORD fileSize, CRC32, NumberOfBytesRW;
 		PBYTE  pBuffer ; 

		//打開文件
		HANDLE hFile = CreateFile( szFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL);
		if ( hFile == INVALID_HANDLE_VALUE ) 
			 return FALSE;

		//獲得文件長度 :
		fileSize = GetFileSize(hFile,NULL);
		if (fileSize == 0xFFFFFFFF) 
			return FALSE;

		pBuffer = new BYTE[fileSize];     // 申請內存
		if (!pBuffer)	return FALSE;
		ReadFile(hFile,pBuffer, fileSize, &NumberOfBytesRW, NULL);//讀取文件內容
		CloseHandle(hFile);  //關閉文件

		pDosHeader	= (PIMAGE_DOS_HEADER)pBuffer;
		fileSize	= fileSize - pDosHeader->e_lfanew;
		CRC32	= CalCRC32(pBuffer + pDosHeader->e_lfanew, fileSize);
		delete	pBuffer;
		return CRC32;
	}
}



//添加代碼到最後一個區塊,DLL內部函數
//參數---lpCOdeStart:指向代碼起始處	dwCodeSize:添加代碼的大小
static BOOL _stdcall AddToLastSection(TCHAR szNewFile[], PBYTE lpCodeStart, DWORD dwCodeSize)
{
	PIMAGE_NT_HEADERS		lpNtHeaders;
	PIMAGE_SECTION_HEADER	lpSectionHeader;
	PIMAGE_SECTION_HEADER	lpLastSectionHeader;
	DWORD					dwNewFileSize;		//最終文件大小
	DWORD					dwFileAlignSize;	//原文件對齊後大小
	DWORD					dwLastSectionAlignSize; //最後區段內存對齊後大小
	DWORD					dwFileAlign;		//文件對齊粒度
	DWORD					dwSectionAlign;		//內存對齊粒度
	//PBYTE					g_lpNewFile;		//最終文件緩存
	DWORD					dwSectionNum;

	lpNtHeaders		= (PIMAGE_NT_HEADERS)( g_lpBuffer + ((PIMAGE_DOS_HEADER)g_lpBuffer)->e_lfanew );
	lpSectionHeader	= (PIMAGE_SECTION_HEADER)(lpNtHeaders + 1);
	
	dwSectionNum = lpNtHeaders->FileHeader.NumberOfSections ;
	lpLastSectionHeader	= lpSectionHeader + dwSectionNum - 1;
	
	dwFileAlign		= lpNtHeaders->OptionalHeader.FileAlignment;
	dwSectionAlign	= lpNtHeaders->OptionalHeader.SectionAlignment;
	dwFileAlignSize	= Align(g_dwFileSize, dwFileAlign);	//求原文件對齊大小

	dwNewFileSize	= Align(dwFileAlignSize + dwCodeSize, dwFileAlign); //獲得最終文件對齊後大小
	dwLastSectionAlignSize	= Align(lpLastSectionHeader->Misc.VirtualSize + dwCodeSize, dwSectionAlign); //獲得內存中最後區段大小

	g_lpNewFile		= (PBYTE)VirtualAlloc (NULL, dwNewFileSize, MEM_COMMIT, PAGE_READWRITE);
	if ( !g_lpNewFile )  //分配內存失敗
		return FALSE;

	//複製原文件數據
	memset(g_lpNewFile, 0, dwNewFileSize);
	memcpy(g_lpNewFile, g_lpBuffer, g_dwFileSize);

	//複製指定代碼
	try {
		memcpy(g_lpNewFile + dwFileAlignSize, lpCodeStart, dwCodeSize); 
	}
	catch(...)
	{
		return FALSE;
	}

	//修正PE頭數據
	PIMAGE_NT_HEADERS		lpNewNtHeaders;
	PIMAGE_SECTION_HEADER	lpNewSectionHeader;
	PIMAGE_SECTION_HEADER	lpNewLastSection;
	DWORD*					lpNewEntry;			//指向新入口處
	DWORD					OldEntry;
	
	lpNewNtHeaders		= (PIMAGE_NT_HEADERS)( g_lpNewFile + ((PIMAGE_DOS_HEADER)g_lpNewFile)->e_lfanew );
	lpNewSectionHeader	= (PIMAGE_SECTION_HEADER)(lpNewNtHeaders + 1);
	lpNewLastSection	= lpNewSectionHeader + dwSectionNum - 1;

 	
	//給最後區段添加讀寫執行屬性
	lpNewLastSection->Characteristics  |= 0xC0000020;
	
	//修正最後一個區段的偏移量
	lpNewLastSection->SizeOfRawData		= dwNewFileSize - lpNewLastSection->PointerToRawData;  
	lpNewLastSection->Misc.VirtualSize	= Align( GetValidSize(g_lpNewFile, lpNewLastSection), dwSectionAlign);//Align(lpNewLastSection->Misc.VirtualSize + dwCodeSize, dwSectionAlign) ;
	
	//修正鏡像大小
	lpNewNtHeaders->OptionalHeader.SizeOfImage	= Align(lpNewLastSection->VirtualAddress + lpNewLastSection->Misc.VirtualSize, dwSectionAlign);

	//修正入口地址
	OldEntry	= lpNewNtHeaders->OptionalHeader.AddressOfEntryPoint;
	lpNewNtHeaders->OptionalHeader.AddressOfEntryPoint	= OffsetToRVA( (IMAGE_DOS_HEADER *)g_lpNewFile, dwFileAlignSize) ;
	
	//修正補丁代碼跳回OEP的參數
	lpNewEntry			= (DWORD*)(g_lpNewFile + dwFileAlignSize + dwCodeSize - 5);
	*lpNewEntry			= OldEntry - (lpNewNtHeaders->OptionalHeader.AddressOfEntryPoint + dwCodeSize - 1); 

	//補丁完畢,寫回文件
	HANDLE  hNewFile;
	DWORD	dwRead;
	if (INVALID_HANDLE_VALUE == ( hNewFile = CreateFile (szNewFile, GENERIC_READ | GENERIC_WRITE , FILE_SHARE_READ , NULL, CREATE_NEW, FILE_ATTRIBUTE_ARCHIVE, NULL) ) )
	{
		VirtualFree(g_lpNewFile, dwNewFileSize, MEM_RELEASE);
		g_lpNewFile	= NULL;
		return FALSE;
	}

	WriteFile(hNewFile, g_lpNewFile, dwNewFileSize, &dwRead, NULL);
	CloseHandle(hNewFile);

	//釋放內存
	VirtualFree(g_lpNewFile, dwNewFileSize, MEM_RELEASE);
	g_lpNewFile	= NULL;
	return TRUE;
}


//添加代碼到一個新區塊,DLL內部函數
//參數---lpCOdeStart:指向代碼起始處	dwCodeSize:添加代碼的大小
static BOOL _stdcall AddToNewSection(TCHAR szNewFile[], PBYTE lpCodeStart, DWORD dwCodeSize)
{
	PIMAGE_NT_HEADERS		lpNtHeaders;
	PIMAGE_SECTION_HEADER	lpSectionHeader;
	PIMAGE_SECTION_HEADER	lpLastSectionHeader;
	DWORD					dwNewFileSize;		//最終文件大小
	DWORD					dwPatchSectionSize; //內存中補丁區塊的大小
	DWORD					dwPatchFileSize;	//補丁區段文件中大小
	DWORD					dwFileAlign;		//文件對齊粒度
	DWORD					dwSectionAlign;		//內存對齊粒度
	//PBYTE					g_lpNewFile;			//最終文件緩存
	DWORD					dwSectionNum;
	DWORD					dwNewHeaderSize;	//新頭部大小
	DWORD					dwOldHeaderSize;	//老頭部大小
	DWORD					dwSectionSize;		//內存中所有區塊總大小
	BOOL					bChange = FALSE;	//指示新節表的加入是否影響到文件頭大小,如有影響,則需修正各區段偏移

	lpNtHeaders			= (PIMAGE_NT_HEADERS)(g_lpBuffer + ((PIMAGE_DOS_HEADER)g_lpBuffer)->e_lfanew);
	lpSectionHeader		= (PIMAGE_SECTION_HEADER)(lpNtHeaders + 1);
	dwSectionNum		= lpNtHeaders->FileHeader.NumberOfSections;
	lpLastSectionHeader	= lpSectionHeader + dwSectionNum - 1;
	dwFileAlign			= lpNtHeaders->OptionalHeader.FileAlignment;
	dwSectionAlign		= lpNtHeaders->OptionalHeader.SectionAlignment;
	dwOldHeaderSize		= lpSectionHeader->PointerToRawData;

	dwSectionSize		= Align(lpLastSectionHeader->VirtualAddress + lpLastSectionHeader->Misc.VirtualSize - Align(dwOldHeaderSize, dwSectionAlign), dwSectionAlign);  //內存中區段總大小

	//獲得補丁相關數據
	dwPatchSectionSize	= Align(dwCodeSize, dwSectionAlign);	//內存中新區段對齊大小
	dwPatchFileSize		= Align(dwCodeSize, dwFileAlign);		//文件中新區段對齊大小
	
	//頭部是否能增加一個節表
	DWORD	dwValidSize;	//頭部當前有效大小
	dwValidSize			= sizeof(IMAGE_NT_HEADERS) + sizeof(IMAGE_SECTION_HEADER)*(dwSectionNum + 1) + ((PIMAGE_DOS_HEADER)g_lpBuffer)->e_lfanew ;
	if ( Align(dwValidSize + sizeof(IMAGE_SECTION_HEADER), dwFileAlign) > 0x1000 )  
		return FALSE;

	//是否要增加頭部大小
	dwNewHeaderSize		= Align(dwValidSize + sizeof(IMAGE_SECTION_HEADER), dwFileAlign); //新頭部大小
	if (dwNewHeaderSize > Align(dwValidSize, dwFileAlign) )
		bChange	= TRUE;   //記錄頭部增加,後面需要修改所有節表偏移

	dwNewFileSize		= Align (Align(g_dwFileSize, dwFileAlign) + dwPatchFileSize, dwFileAlign);
	g_lpNewFile			= (PBYTE)VirtualAlloc (NULL, dwNewFileSize, MEM_COMMIT, PAGE_READWRITE);

	if ( !g_lpNewFile )  //分配內存失敗
		return FALSE;

	//複製原文件數據
	memset(g_lpNewFile, 0, dwNewFileSize);
	memcpy(g_lpNewFile, g_lpBuffer, dwValidSize);	//頭部數據複製

	//區段數據複製
	DWORD	dwSize		= lpLastSectionHeader->PointerToRawData + lpLastSectionHeader->SizeOfRawData - dwOldHeaderSize; //文件中所有區段總大小
	memcpy(g_lpNewFile + dwNewHeaderSize, g_lpBuffer + lpSectionHeader->PointerToRawData, dwSize);


	//補丁數據複製
	try {
		memcpy(g_lpNewFile + dwNewHeaderSize + dwSize, lpCodeStart, dwCodeSize);
	}
	catch(...)
	{
		return FALSE;
	}


	//開始修正PE頭
	PIMAGE_NT_HEADERS		lpNewNtHeaders;
	PIMAGE_SECTION_HEADER	lpNewSectionHeader;
	PIMAGE_SECTION_HEADER	lpNewLastSection;
	DWORD*					lpNewEntry;			//指向新入口處
	DWORD					OldEntry;

	lpNewNtHeaders		= (PIMAGE_NT_HEADERS)( g_lpNewFile + ((PIMAGE_DOS_HEADER)g_lpNewFile)->e_lfanew);
	lpNewSectionHeader	= (PIMAGE_SECTION_HEADER) (lpNewNtHeaders + 1);
	lpNewLastSection	= lpNewSectionHeader + dwSectionNum; //此處即指向要創建的新區段
	
	lpNewNtHeaders->FileHeader.NumberOfSections	   += 1; //添加一個區段
	lpNewNtHeaders->OptionalHeader.SizeOfHeaders	= dwNewHeaderSize;
	lpNewNtHeaders->OptionalHeader.SizeOfImage		= dwNewHeaderSize + dwSectionSize + dwPatchSectionSize; //鏡像總大小
	
	//根據需要修正節表偏移
	if (bChange)
	{
		PIMAGE_SECTION_HEADER	lpTempSection = lpNewSectionHeader;
		DWORD	dwOffset	= dwNewHeaderSize - dwOldHeaderSize;
		for (DWORD i=0; i < dwSectionNum; i++, lpTempSection++ )
		{
			lpTempSection->PointerToRawData += dwOffset;
		}
	}

	//建立一個新節表
	lpNewLastSection->Characteristics	= 0xC0000020; //讀寫執行屬性添加
	lpNewLastSection->VirtualAddress	= Align(dwNewHeaderSize, dwSectionAlign) + dwSectionSize;
	lpNewLastSection->Misc.VirtualSize	= dwPatchSectionSize;
	lpNewLastSection->PointerToRawData	= dwNewHeaderSize + dwSize;
	lpNewLastSection->SizeOfRawData		= dwPatchFileSize;
	lstrcpyA( (LPSTR)(lpNewLastSection->Name), ".crk");


	//修正入口地址
	OldEntry		= lpNewNtHeaders->OptionalHeader.AddressOfEntryPoint;
	lpNewNtHeaders->OptionalHeader.AddressOfEntryPoint	= Align(dwNewHeaderSize, dwSectionAlign) + dwSectionSize ;
	
	//修正補丁代碼跳回OEP的參數
	lpNewEntry			= (DWORD*)(g_lpNewFile + dwNewHeaderSize + dwSize + dwCodeSize - 5);
	*lpNewEntry			= OldEntry - (lpNewNtHeaders->OptionalHeader.AddressOfEntryPoint + dwCodeSize - 1); 

	//補丁完畢,寫回文件
	HANDLE  hNewFile;
	DWORD	dwRead;
	if (INVALID_HANDLE_VALUE == ( hNewFile = CreateFile (szNewFile, GENERIC_READ | GENERIC_WRITE , FILE_SHARE_READ , NULL, CREATE_NEW, FILE_ATTRIBUTE_ARCHIVE, NULL) ) )
	{
		VirtualFree(g_lpNewFile, dwNewFileSize, MEM_RELEASE);
		g_lpNewFile	= NULL;
		return FALSE;
	}

	WriteFile(hNewFile, g_lpNewFile, dwNewFileSize, &dwRead, NULL);
	CloseHandle(hNewFile);

	//釋放內存
	VirtualFree(g_lpNewFile, dwNewFileSize, MEM_RELEASE);
	g_lpNewFile	= NULL;
	return TRUE;
}


//添加代碼到PE頭部,DLL內部函數
//參數---lpCOdeStart:指向代碼起始處	dwCodeSize:添加代碼的大小
static BOOL _stdcall AddToHeaderSection(TCHAR szNewFile[], PBYTE lpCodeStart, DWORD dwCodeSize)
{
	PIMAGE_NT_HEADERS		lpNtHeaders;
	PIMAGE_SECTION_HEADER	lpSectionHeader;
	PIMAGE_SECTION_HEADER	lpLastSectionHeader;
	DWORD					dwNewFileSize;		//最終文件大小
	DWORD					dwFileAlign;		//文件對齊粒度
	DWORD					dwSectionAlign;		//內存對齊粒度
	//PBYTE					g_lpNewFile;			//最終文件緩存
	DWORD					dwSectionNum;
	DWORD					dwPatchOffset;		//補丁代碼複製到文件的偏移,也即老頭部有效數據的大小
	DWORD					dwNewHeaderSize;	//新頭部大小
	DWORD					dwOldHeaderSize;	//老頭部大小
	DWORD					dwSectionSize;		//所有區塊總大小


	lpNtHeaders		= (PIMAGE_NT_HEADERS)( g_lpBuffer + ((PIMAGE_DOS_HEADER)g_lpBuffer)->e_lfanew );
	lpSectionHeader	= (PIMAGE_SECTION_HEADER)(lpNtHeaders + 1);
	dwSectionNum	= lpNtHeaders->FileHeader.NumberOfSections;
	lpLastSectionHeader	= lpSectionHeader + dwSectionNum - 1;      //獲得最後一個區塊的節表
	dwFileAlign		= lpNtHeaders->OptionalHeader.FileAlignment;
	dwSectionAlign	= lpNtHeaders->OptionalHeader.SectionAlignment;
	dwOldHeaderSize	= lpSectionHeader->PointerToRawData;					
	
	//dwPatchOffset	= sizeof(IMAGE_NT_HEADERS) + sizeof(IMAGE_SECTION_HEADER)*(dwSectionNum + 1) + ((PIMAGE_DOS_HEADER)g_lpBuffer)->e_lfanew;  //老PE頭有效數據大小,附加代碼從此放置
	//原本是使用上面這種方式求有效大小,但考慮到如果PE已有補丁情況下的兼容,所有直接從頭最後面往前搜索以取得大小
	
	PBYTE					lpTemp = g_lpBuffer + lpSectionHeader->PointerToRawData - 1;
	DWORD					dwSize = 0;
	while(*lpTemp == 0)  { lpTemp--; dwSize++; }	//獲得可填充數據的大小
	dwSize		   -= sizeof(IMAGE_SECTION_HEADER);	//因爲可能有個全零的節表,所以減去 
	dwPatchOffset	= lpSectionHeader->PointerToRawData - dwSize ; //老PE頭有效數據大小,附加代碼從此放置


	dwNewHeaderSize	= Align(dwPatchOffset + dwCodeSize, dwFileAlign);  //獲得新頭部對齊後大小
	if (dwNewHeaderSize > 0x1000)
	{
		MessageBox (GetActiveWindow() , TEXT("PE頭大小不夠,請選擇其他補丁方式"), NULL, MB_OK);
		return FALSE;
	}

	//最後區段偏移加上大小再減去老PE頭大小就等於原文件所有區段的文件大小,再加上新頭部大小並對齊就是我們需要的新文件大小
	dwSectionSize	= Align(lpLastSectionHeader->PointerToRawData + lpLastSectionHeader->SizeOfRawData - dwOldHeaderSize, dwFileAlign);
	dwNewFileSize	= Align(dwNewHeaderSize + dwSectionSize, dwFileAlign);  

	g_lpNewFile		= (PBYTE)VirtualAlloc(NULL, dwNewFileSize, MEM_COMMIT, PAGE_READWRITE);
	if (!g_lpNewFile) //分配內存失敗
		return FALSE;

	//複製原文件數據
	memset(g_lpNewFile, 0, dwNewFileSize);
	memcpy(g_lpNewFile, g_lpBuffer, dwPatchOffset);		//複製原始PE頭
	memcpy(g_lpNewFile + dwNewHeaderSize, g_lpBuffer + dwOldHeaderSize, dwSectionSize);		//複製區塊數據 


	//複製補丁代碼
	memcpy(g_lpNewFile + dwPatchOffset, lpCodeStart, dwCodeSize);

	//開始對頭部數據進行修復
	PIMAGE_NT_HEADERS		lpNewNtHeaders;
	PIMAGE_SECTION_HEADER	lpNewSectionHeader;
	lpNewNtHeaders		= (PIMAGE_NT_HEADERS)(g_lpNewFile + ((PIMAGE_DOS_HEADER)g_lpNewFile)->e_lfanew);
	lpNewSectionHeader	= (PIMAGE_SECTION_HEADER)(lpNewNtHeaders + 1);

	//由於文件頭部大小增加,節表所有的文件偏移都需要修復,RVA則不變
	DWORD	dwOffset;
	PIMAGE_SECTION_HEADER	lpTempSectionHeader = lpNewSectionHeader;
	dwOffset	= dwNewHeaderSize - dwOldHeaderSize; //計算出需要加上的文件偏移
	for (DWORD i=0; i < dwSectionNum; i++, lpTempSectionHeader++)
	{
		lpTempSectionHeader->PointerToRawData += dwOffset;
	}


	//修正頭部大小
	lpNewNtHeaders->OptionalHeader.SizeOfHeaders	= dwNewHeaderSize;

	//修正入口
	DWORD	dwOldEntry;
	dwOldEntry	= lpNewNtHeaders->OptionalHeader.AddressOfEntryPoint;
	lpNewNtHeaders->OptionalHeader.AddressOfEntryPoint	= dwPatchOffset;

	//修正補丁代碼的最後一個跳轉參數
	DWORD	*lpNewEntry;
	lpNewEntry	= (DWORD*)(g_lpNewFile + dwPatchOffset + dwCodeSize - 5);   //指向要修正的參數
	*lpNewEntry	= dwOldEntry - (dwPatchOffset + dwCodeSize - 1); 

	//補丁完畢,寫回文件
	HANDLE  hNewFile;
	DWORD	dwRead;
	if (INVALID_HANDLE_VALUE == ( hNewFile = CreateFile (szNewFile, GENERIC_READ | GENERIC_WRITE , FILE_SHARE_READ , NULL, CREATE_NEW, FILE_ATTRIBUTE_ARCHIVE, NULL) ) )
	{	
		VirtualFree(g_lpNewFile, dwNewFileSize, MEM_RELEASE);
		g_lpNewFile	= NULL;
		return FALSE;
	}

	WriteFile(hNewFile, g_lpNewFile, dwNewFileSize, &dwRead, NULL);
	CloseHandle(hNewFile);

	//釋放內存
	VirtualFree(g_lpNewFile, dwNewFileSize, MEM_RELEASE);
	g_lpNewFile	= NULL;
	return TRUE;
}


BOOL _stdcall AddCode(TCHAR szNewFileName[], PBYTE lpCodeStart, DWORD dwCodeSize, DWORD dwTypeOfAdd)
{
	if ( (!g_lpBuffer) && !PE )
		return FALSE;
	try
	{
		//使用指定方法添加代碼
		switch(dwTypeOfAdd)
		{
			case ADD_LAST_SECTION:
				if (!AddToLastSection(szNewFileName, lpCodeStart, dwCodeSize) )
					return FALSE;
				break;
	
			case ADD_NEW_SECTION:
				if (!AddToNewSection(szNewFileName, lpCodeStart, dwCodeSize) )
					return FALSE;
				break;
	
			case ADD_PE_HEADER:
				if (!AddToHeaderSection(szNewFileName, lpCodeStart, dwCodeSize) )
					return FALSE;
				break;
	
			default:
				return FALSE;
		}
	}
	catch(...)
	{
		return FALSE;
	}

	return TRUE;
}




//////////////////////////////////////////////////提取圖標數據///////////////////////////////////////////////////////

HANDLE	hIconFile;

typedef struct _ICON
{
	BYTE	bWidth;
	BYTE	bHeight;
	BYTE	bColorCount;
	BYTE	bReserved;
	WORD	wPlanes;
	WORD	wBitCount;
	DWORD	dwBytesInRes;
	DWORD	dwImageOffset;
}ICON_DIR_ENTRY;

typedef struct _ION
{
	BYTE	bWidth;
	BYTE	bHeight;
	BYTE	bColorCount;
	BYTE	bReserved;
	WORD	wPlanes;
	WORD	wBitCount;
	DWORD	dwBytesInRes;
	WORD	Order;
}PE_ICON_DIR_ENTRY;

typedef struct ICON
{
	WORD	idReserved;
	WORD	idType;
	WORD	idCount;  
}ICON_DIR;

void _stdcall WriteIconData(TCHAR szIconFileName[],PIMAGE_RESOURCE_DIRECTORY lpResource,DWORD dwOrder)
{
	//存儲三層目錄的參數
	PIMAGE_RESOURCE_DIRECTORY	lpResDir1;
	PIMAGE_RESOURCE_DIRECTORY_ENTRY	lpResEntry1;
	DWORD	dwResNum1;

	PIMAGE_RESOURCE_DIRECTORY	lpResDir2;
	PIMAGE_RESOURCE_DIRECTORY_ENTRY	lpResEntry2;
	DWORD	dwResNum2;

	PIMAGE_RESOURCE_DIRECTORY	lpResDir3;
	PIMAGE_RESOURCE_DIRECTORY_ENTRY	lpResEntry3;

	PIMAGE_RESOURCE_DATA_ENTRY	lpDataEntry;
	BYTE	*lpResourceData;//指向真正的資源數據
	DWORD	dwResSize, temp;
	DWORD	dwNum=0;

	lpResDir1	=	lpResource;
	dwResNum1	=	lpResDir1->NumberOfIdEntries + lpResDir1->NumberOfNamedEntries;
	lpResEntry1	=	(PIMAGE_RESOURCE_DIRECTORY_ENTRY)(++lpResDir1);
	

	//在目錄中尋找圖標組數據
	for (DWORD i=0; i < dwResNum1; i++)
	{
			//如果是圖標資源
			if ( lpResEntry1->Id == 0x3)
			{
				//第二層,下面每次循環都是一個圖標組併產生一個圖標文件
				lpResDir2	= (PIMAGE_RESOURCE_DIRECTORY)( (DWORD)lpResource + lpResEntry1->OffsetToDirectory);
				dwResNum2	= lpResDir2->NumberOfIdEntries + lpResDir2->NumberOfNamedEntries;
				lpResEntry2	= (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(++lpResDir2);
				
				for (DWORD j=0; j < dwResNum2; j++)
				{
					dwNum++; //圖標ID
					if (dwNum == dwOrder)   // 是要尋找的那個圖標ID
					{
					//第三層,假設圖標組只有一個項目
						lpResDir3		= (PIMAGE_RESOURCE_DIRECTORY)( (DWORD)lpResource + lpResEntry2->OffsetToDirectory);
						lpResEntry3		= (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(++lpResDir3);
						lpDataEntry		= (PIMAGE_RESOURCE_DATA_ENTRY)( (DWORD)lpResource + lpResEntry3->OffsetToData);
						dwResSize		= lpDataEntry->Size;
						lpResourceData	= g_lpBuffer + RvaToOffset( (IMAGE_DOS_HEADER*)g_lpBuffer, lpDataEntry->OffsetToData);
					
						//開始寫圖標數據
						WriteFile(hIconFile, lpResourceData, dwResSize, &temp, NULL);
					}

					lpResEntry2++;
				}
				
			}

		lpResEntry1++;
	}
}

void _stdcall GetIconHeader(TCHAR szIconFileName[],BYTE *lpResourceData,DWORD dwSize, DWORD dwNum)
{
	BYTE	*lpData;
	DWORD	temp;
	DWORD	dwIconNum;	//圖標數
	ICON_DIR_ENTRY *lpIconEntry;
	DWORD	dwHeadSize;

	if ( INVALID_HANDLE_VALUE == (hIconFile = CreateFile( szIconFileName, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,0)) )
	{
		return ;
	}
	
	//寫入圖標文件頭
	DWORD	prev=0;
	ICON_DIR_ENTRY icon;
	lpData		= lpResourceData;
	dwIconNum	= ((ICON_DIR*)lpData)->idCount; 
	WriteFile(hIconFile, lpData, 6, &temp, NULL);
	lpData += 6;
	lpIconEntry = (ICON_DIR_ENTRY*)lpData;
	for (DWORD i=0; i < dwIconNum; i++)
	{
		memcpy(&icon, lpIconEntry, sizeof(ICON_DIR_ENTRY) );
		icon.dwImageOffset	= prev + dwIconNum * sizeof(ICON_DIR_ENTRY) + 6;
		WriteFile(hIconFile, &icon, sizeof(ICON_DIR_ENTRY), &temp, NULL);
		prev	+= icon.dwBytesInRes;
		lpIconEntry = (ICON_DIR_ENTRY*)((DWORD)lpIconEntry + sizeof(PE_ICON_DIR_ENTRY) );
	}

	PE_ICON_DIR_ENTRY	*lpPeIcon;
	dwHeadSize	= (BYTE*)lpIconEntry - lpResourceData;

	lpPeIcon	= (PE_ICON_DIR_ENTRY*)(lpResourceData + 6);
	PIMAGE_DATA_DIRECTORY		lpDataDir = (PIMAGE_DATA_DIRECTORY)( &((PIMAGE_NT_HEADERS)(g_lpBuffer + ((PIMAGE_DOS_HEADER)g_lpBuffer)->e_lfanew))->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE]);
	PIMAGE_RESOURCE_DIRECTORY	lpResource= (PIMAGE_RESOURCE_DIRECTORY)(g_lpBuffer + RvaToOffset( (IMAGE_DOS_HEADER*)g_lpBuffer, lpDataDir->VirtualAddress) );
	//寫入圖標數據
	for (DWORD i=0; i < dwIconNum; i++)
	{
		WriteIconData(szIconFileName, lpResource, lpPeIcon->Order);
		lpPeIcon++;
	}

	CloseHandle(hIconFile);
}

//提取圖標數據,參數指定圖標名
BOOL _stdcall GetIcon (TCHAR	szIconFileName[])
{
	if ( (!g_lpBuffer) && !PE )
		return FALSE;
	PIMAGE_DATA_DIRECTORY		lpDataDir;
	PIMAGE_NT_HEADERS			lpPeHead = (PIMAGE_NT_HEADERS)(g_lpBuffer + ((PIMAGE_DOS_HEADER)g_lpBuffer)->e_lfanew);
	BYTE						*lpResource;

	//存儲三層目錄的參數
	PIMAGE_RESOURCE_DIRECTORY	lpResDir1;
	PIMAGE_RESOURCE_DIRECTORY_ENTRY	lpResEntry1;
	DWORD	dwResNum1;

	PIMAGE_RESOURCE_DIRECTORY	lpResDir2;
	PIMAGE_RESOURCE_DIRECTORY_ENTRY	lpResEntry2;
	DWORD	dwResNum2;

	PIMAGE_RESOURCE_DIRECTORY	lpResDir3;
	PIMAGE_RESOURCE_DIRECTORY_ENTRY	lpResEntry3;

	PIMAGE_RESOURCE_DATA_ENTRY	lpDataEntry;
	BYTE	*lpResourceData;//指向真正的資源數據
	DWORD	dwResSize;
	DWORD	dwNum=0;//圖標組編號

	lpDataDir	=	(PIMAGE_DATA_DIRECTORY)( &lpPeHead->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE]);
	lpResource	=   ( g_lpBuffer + RvaToOffset( (IMAGE_DOS_HEADER*)g_lpBuffer, lpDataDir->VirtualAddress) );
	lpResDir1	=	(PIMAGE_RESOURCE_DIRECTORY)lpResource;
	dwResNum1	=	lpResDir1->NumberOfIdEntries + lpResDir1->NumberOfNamedEntries;
	lpResEntry1	=	(PIMAGE_RESOURCE_DIRECTORY_ENTRY)(++lpResDir1);
	

	//在目錄中尋找圖標組數據
	for (DWORD i=0; i < dwResNum1; i++)
	{
		//如果是ID資源
		if ( !lpResEntry1->NameIsString )
		{
			//如果是圖標組資源
			if ( lpResEntry1->Id == 0xe)
			{
				//第二層,下面每次循環都是一個圖標組併產生一個圖標文件
				lpResDir2	= (PIMAGE_RESOURCE_DIRECTORY)(lpResource + lpResEntry1->OffsetToDirectory);
				dwResNum2	= lpResDir2->NumberOfIdEntries + lpResDir2->NumberOfNamedEntries;
				lpResEntry2	= (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(++lpResDir2);
				for (DWORD j=0; j < dwResNum2; j++)
				{
					//第三層,假設圖標組只有一個項目
					lpResDir3		= (PIMAGE_RESOURCE_DIRECTORY)(lpResource + lpResEntry2->OffsetToDirectory);
					lpResEntry3		= (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(++lpResDir3);
					lpDataEntry		= (PIMAGE_RESOURCE_DATA_ENTRY)(lpResource + lpResEntry3->OffsetToData);
					dwResSize		= lpDataEntry->Size;
					lpResourceData	= g_lpBuffer + RvaToOffset( (IMAGE_DOS_HEADER*)g_lpBuffer, lpDataEntry->OffsetToData);
					
					//開始處理圖標數據
					GetIconHeader(szIconFileName,lpResourceData, dwResSize,++dwNum);
				
					lpResEntry2++;
				}
			}
		}

		lpResEntry1++;
	}
	return TRUE;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


 

 

 

發佈了59 篇原創文章 · 獲贊 1 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章