【黑客免殺攻防】讀書筆記15 - 源碼免殺、C++殼的編寫


1、源碼免殺

1.1 定位產生特征的源碼

  • 定位文件特征

    • 1、根據MyCCL的特征碼定位工具,定位出有特征的地址
    • 2、根據VS的反匯編窗口,輸入有特征的地址得到特征地址與源碼的關系
    • 3、插入MessageBox,然后定位出特征碼離哪個MessageBox最近,並在附近以更高密度安插MessageBox,最終定位出產生特征碼的源碼位置。
  • 定位行為特征

    • 1、啟發式檢測方法:特征碼定位突破
    • 2、虛擬機檢查方法:通過控制程序僅執行部分代碼,在遠程線程注入前插入MessageBox,起到斷點作用。
    • 3、基於HOOK技術的沙盒檢查方法:同2、方法。

1.2 基於源碼的特征修改

  • 變換編譯器與編譯選項
  • 添加垃圾代碼
  • 等價語法替換原有語句
  • 添加匯編花指令

2、C++殼的編寫

殼的原理與蠕蟲病毒感染的原理一致

2.1 殼的運行流程

  • 給相關程序中添加一個足夠大的區段,並將負責解密解壓的代碼(Stub)與相關配置文件寫入這個新添加的空區段中。
  • 修改宿主程序的入口點信息,使宿主程序在運行時先執行Stub部分的代碼
  • 修改新區段中的配置信息,使Stub部分的代碼跳轉到真正的宿主程序入口點。

圖:加殼前與加殼后的狀態

2.2 設計純C++編寫的殼

設計殼要注意以下幾個重點:

  • Stub程序一定要擁有重定位表,否則無法執行重定位操作。
  • 修改編譯選項編譯出較小體積的Stub程序,並將代碼段和數據段合並在一起,這樣可以將Stub程序中的代碼段與數據段直接復制到宿主程序中。
  • 使用動態獲取API地址的方法來避免直接與PE中的導入表產生糾葛。

2.3 C++殼框架

  • 預處理部分:
    • 1、讀取目標文件並確定文件體積;
    • 2、讀取代碼段的相關信息;
    • 3、將代碼段屬性修改為可讀、可寫、可執行;
    • 4、對代碼段進行簡單的異或加密處理;
  • 植入Stub:
    • 1、讀取保存在資源中的Stub部分,並計算體積;
    • 2、依據此體積添加區段;
    • 3、將Stub復制到新區段,對Stub進行重定位等一系列處理;
    • 4、修改入口點到Stub處,並將原入口點傳遞給Stub,留以備用
  • Stub執行
    • 1、讀取被加密區域的起始及結束偏移;
    • 2、使用異或算法解密相應區域的代碼;
    • 3、彈出對話框提示相關信息;
    • 4、根據保存下來的OEP信息,執行原程序;

圖:殼的執行流程

2.4 配置工程

第1行代碼指定入口點函數指定為StubEntryPoint(),第2、3行代碼將.data、.rdata這兩個區段合並到.text區段中,第4行的意思是修改.text區段的屬性為可讀、可寫、可執行。

Stub項目中的Dllmain.cpp:

#pragma comment(linker, "/entry:\"StubEntryPoint\"") // 指定程序入口函數為StubEntryPoint()
#pragma comment(linker, "/merge:.data=.text")        // 將.data合並到.text
#pragma comment(linker, "/merge:.rdata=.text")       // 將.rdata合並到.text
#pragma comment(linker, "/section:.text,RWE")        // 將.text段的屬性設置為可讀、可寫、可執行

2.5 編寫Stub部分

  • 問題1:Stub部分的正常執行需要一些必要的信息及參數,要根據Stub部分將要完成的功能,准備好運行時用到的具體數據。
// 聲明一個導出的全局變量,用以保存傳遞給Stub部分的參數
typedef struct _GLOBAL_PARAM
{
	BOOL  bShowMessage; // 是否顯示解密信息
	DWORD dwOEP;        // 程序入口點
	PBYTE lpStartVA;    // 起始虛擬地址(被異或加密區)
	PBYTE lpEndVA;      // 結束虛擬地址(被異或加密區)
}GLOBAL_PARAM,*PGLOBAL_PARAM;
extern "C" __declspec(dllexport) GLOBAL_PARAM g_stcParam;

通過以上代碼,定義GLOBAL_PARAM,*PGLOBAL_PARAM的結構體類型,並用GLOBAL_PARAM類型定義一個被導出的全局變量g_stcParam。

  • 問題2:Stub部分以一個DLL格式文件的形式存在,會執行很多引導代碼。為了讓Stub部分能健壯穩定地運行,所以要通過修改編譯選項來自定義一個入口函數,這樣程序在編譯時就會忽略那些引導代碼。
// 指定自定義入口函數StubEntryPoint()
#pragma comment(linker, "/entry:\"StubEntryPoint\"") // 指定程序入口函數為StubEntryPoint()

// 調用殼的主體部分
void start()
{
	// 1. 初始化所有API

	// 2. 解密宿主程序


	// 3. 詢問是否執行解密后的程序


	// 4. 跳轉到OEP
	__asm jmp g_stcParam.dwOEP;
}


//定義一個裸函數,此函數將作為入口函數首先執行
void __declspec(naked) StubEntryPoint()
{
	__asm sub esp,0x50;        // 抬高棧頂,提高兼容性
	start();                   // 執行殼的主體部分
	__asm add esp,0x50;        // 平衡堆棧

	// 主動調用ExitProcess函數退出進程可以解決一些兼容性問題
	if ( g_funExitProcess )
	{
		g_funExitProcess(0);
	}
	__asm retn;
}
  • 問題3:加殼的程序會丟棄掉IAT與導入表信息,因此直接調用API是不可行的,所以要求程序有一套能自動獲取API函數地址的替代方案。

使用動態加載API的方法解決會碰到獲取GetProcAddress()函數地址的問題,GetProcAddress()函數是從系統文件kernel32.dll中導出的,如果能找到kernel32.dll的加載基址,根據它的導出表就一定能找到GetProcAddress()函數的地址。

獲取Kernel32.dll加載基址

獲取Kernel32.dll加載基址的公開方法有3種:

  • 1.通過特征匹配的暴力搜索
  • 2.利用系統的SEH機制找到kernel32.dll並搜索出加載基址
  • 3.通過線程環境塊TEB的信息逐步找到Kernel32.dll的加載基址

其中代碼最少的是最后一種方法。

  • 1)通過段選擇FS在內存中得到當前的線程環境塊TEB的地址;
  • 2)TEB偏移為0x30處是指進程環境塊PEB的指針;
  • 3)PEB偏移為0x0C處是指向PEB_LDR_DATA結構的指針;
  • 4)PEB_LDR_DATA偏移為0x1C處是模擬初始化鏈表的頭指針InInitializationOrderModuleList;
  • 5)InInitializationOrderModuleList中按順序存放着此進程初始化模塊的信息,
    • 在NT 5.X的內核中,第一個節點存放的是ntdll.dll的基址;第二個節點存放的是kernel32.dll的基址;
    • 在NT 6.1內核中,其第二個節點存放的是KernelBase.dll的基址(KernelBase.dll中包含着kernel32.dll中絕大多數常用API的另一份實現,其中就包含GetProcAddress()函數);

圖:獲取Kernel32.dll基址的流程

獲取kernel32.dll基址的代碼:

DWORD GetKernel32Base()
{
	DWORD dwKernel32Addr = 0;
	__asm
	{
		push eax
		mov eax,dword ptr fs:[0x30] // eax = PEB的地址
		mov eax,[eax+0x0C]          // eax = 指向PEB_LDR_DATA結構的指針
		mov eax,[eax+0x1C]          // eax = 模塊初始化鏈表的頭指針InInitializationOrderModuleList
		mov eax,[eax]               // eax = 列表中的第二個條目
		mov eax,[eax+0x08]          // eax = 獲取到的Kernel32.dll基址(Win7下獲取的是KernelBase.dll的基址)
		mov dwKernel32Addr,eax
		pop eax
	}

	return dwKernel32Addr;
}

遍歷導出表

找出GetProcAddress函數地址()

DWORD GetGPAFunAddr()
{
	DWORD dwAddrBase = GetKernel32Base();

	// 1. 獲取DOS頭、NT頭
	PIMAGE_DOS_HEADER pDos_Header;
	PIMAGE_NT_HEADERS pNt_Header;
	pDos_Header = (PIMAGE_DOS_HEADER)dwAddrBase;
	pNt_Header  = (PIMAGE_NT_HEADERS)(dwAddrBase + pDos_Header->e_lfanew);

	// 2. 獲取導出表項
	PIMAGE_DATA_DIRECTORY   pDataDir;
	PIMAGE_EXPORT_DIRECTORY pExport;
	pDataDir = pNt_Header->OptionalHeader.DataDirectory+IMAGE_DIRECTORY_ENTRY_EXPORT;
	pExport  = (PIMAGE_EXPORT_DIRECTORY)(dwAddrBase + pDataDir->VirtualAddress);

	// 3. 獲取導出表詳細信息
	PDWORD pAddrOfFun      = (PDWORD)(pExport->AddressOfFunctions    + dwAddrBase);
	PDWORD pAddrOfNames    = (PDWORD)(pExport->AddressOfNames        + dwAddrBase);
	PWORD  pAddrOfOrdinals = (PWORD) (pExport->AddressOfNameOrdinals + dwAddrBase);

	// 4. 處理以函數名查找函數地址的請求,循環獲取ENT中的函數名,並與傳入值對比對,如能匹配上
	//    則在EAT中以指定序號作為索引,並取出其地址值。
	DWORD dwFunAddr;
	for (DWORD i=0; i<pExport->NumberOfNames; i++)
	{
		PCHAR lpFunName = (PCHAR)(pAddrOfNames[i]+dwAddrBase);
		if ( !strcmp(lpFunName, "GetProcAddress") )
		{
			dwFunAddr = pAddrOfFun[pAddrOfOrdinals[i]] + dwAddrBase;
			break;
		}
		if ( i == pExport->NumberOfNames-1 )
			return 0; 
	}

	return dwFunAddr;
}

2.6 編寫加殼部分

加殼程序要多做兩件事,一是實現獨特的參數寫入方式,另一個就是需要對stub的代碼段進行重定位操作。

1.設計加殼部分

建立項目時將作為加殼的部分獨立成了一個DLL,為了方便調用,需要導出一個或多個函數。

#ifdef A1PACK_BASE_EXPORTS
#define A1PACK_BASE_API __declspec(dllexport)
#else
#define A1PACK_BASE_API __declspec(dllimport)
#endif

// 聲明一個導出的API,共界面程序調用執行加殼操作
A1PACK_BASE_API bool A1Pack_Base(LPWSTR strPath,bool bShowMsg);

通過以上代碼可知,導出了一個名為A1Pack_Base()的函數,此函數的兩個參數分別為需要加殼的文件路徑及加殼后的程序是否彈出提示解密成功的對話框。涉及大量的PE操作,由一個CProcessingPE()類完成。

2.讀取目標文件相關信息

  • 獲取文件信息,並映射進內存中

    • CreateFile:讀取文件
    • GetFileSize:獲取文件大小
    • VirtualAlloc:開辟內存空間
    • ReadFile:將文件寫入開辟的內存空間中
  • 獲取目標文件關鍵PE信息

// 關鍵PE信息
typedef struct _PE_INFO 
{
	DWORD                 dwOEP;          // 入口點
	DWORD                 dwImageBase;    // 映像基址
	PIMAGE_DATA_DIRECTORY pDataDir;       // 數據目錄指針
	IMAGE_DATA_DIRECTORY  stcExport;      // 導出目錄
	PIMAGE_SECTION_HEADER pSectionHeader; // 區段表頭部指針
}PE_INFO,*PPE_INFO;


// 獲取關鍵PE信息的函數
BOOL CProcessingPE::GetPeInfo(LPVOID lpImageData, DWORD dwImageSize, PPE_INFO pPeInfo)
{
	// 1、判斷映像指針是否有效
	if ( m_stcPeInfo.dwOEP )
	{
		CopyMemory(pPeInfo, &m_stcPeInfo, sizeof(PE_INFO));
		return true;
	} 
	else
	{
		if ( !lpImageData )  return false;
		m_dwFileDataAddr = (DWORD)lpImageData;
		m_dwFileDataSize = dwImageSize;
	}

	// 2. 獲取基本信息
	// 2.1 獲取DOS頭、NT頭
	m_pDos_Header = (PIMAGE_DOS_HEADER)lpImageData;
	m_pNt_Header  = (PIMAGE_NT_HEADERS)((DWORD)lpImageData+m_pDos_Header->e_lfanew);
	// 2.2 獲取OEP
	m_stcPeInfo.dwOEP = m_pNt_Header->OptionalHeader.AddressOfEntryPoint;
	// 2.3 獲取映像基址
	m_stcPeInfo.dwImageBase = m_pNt_Header->OptionalHeader.ImageBase;
	// 2.4 獲取關鍵數據目錄表的內容
	PIMAGE_DATA_DIRECTORY lpDataDir = m_pNt_Header->OptionalHeader.DataDirectory;
	m_stcPeInfo.pDataDir = lpDataDir;
	CopyMemory(&m_stcPeInfo.stcExport, lpDataDir+IMAGE_DIRECTORY_ENTRY_EXPORT,    sizeof(IMAGE_DATA_DIRECTORY));
	// 2.5 獲取區段表與其他詳細信息
	m_stcPeInfo.pSectionHeader = IMAGE_FIRST_SECTION(m_pNt_Header);
	
	// 3. 檢查PE文件是否有效
	if ( (m_pDos_Header->e_magic!=IMAGE_DOS_SIGNATURE) || (m_pNt_Header->Signature!=IMAGE_NT_SIGNATURE) )
	{
		// 這不是一個有效的PE文件
		return false;
	}

	// 4. 傳出處理結果
	CopyMemory(pPeInfo, &m_stcPeInfo, sizeof(PE_INFO));

	return true;
}

3.讀取代碼段信息並處理

讀取代碼端信息的第一步是確定哪個區段是代碼段。一般可讀但不可寫的區段為代碼段。書里的例子默認第一個區段為代碼段。

	// 4. 獲取目標文件代碼段的起始結束信息
	//    讀取第一個區段的相關信息,並將其加密(默認第一個區段為代碼段)
	PBYTE lpStart   = (PBYTE)(stcPeInfo.pSectionHeader->PointerToRawData+(DWORD)lpFileImage);
	PBYTE lpEnd     = (PBYTE)((DWORD)lpStart+stcPeInfo.pSectionHeader->SizeOfRawData);
	PBYTE lpStartVA = (PBYTE)(stcPeInfo.pSectionHeader->VirtualAddress+stcPeInfo.dwImageBase);
	PBYTE lpEndVA   = (PBYTE)((DWORD)lpStartVA+stcPeInfo.pSectionHeader->SizeOfRawData);

加密函數:

// 5. 對文件進行預處理(加密、給第一個區段附加上可寫屬性)
void Pretreatment(PBYTE lpCodeStart, PBYTE lpCodeEnd, PE_INFO stcPeInfo)
{
	// 1. 加密指定區域
	while ( lpCodeStart<lpCodeEnd )
	{
		*lpCodeStart ^= 0xA1;
		*lpCodeStart += 0x88;
		lpCodeStart++;
	}

    // 2. 給第一個區段附加上可寫屬性
	PDWORD pChara = &(stcPeInfo.pSectionHeader->Characteristics);
	*pChara = *pChara|IMAGE_SCN_MEM_WRITE;
}

以上代碼除了將代碼段加密外,還給代碼段附加上了可寫屬性,因為stub在植入宿主主程序后需要對其代碼段進行寫操作(解密),如果代碼段是不可寫的,那么將導致程序崩潰。

4.根據Stub體積添加新區段

源碼中使用添加區段的方式來開辟新的空間,首先將前面已經作為資源的Stub部分釋放出來。

	// 1. 在資源中讀取文件內容
	HRSRC   hREC        = NULL; // 資源對象
	HGLOBAL hREC_Handle = NULL; // 資源句柄
	DWORD   dwStubSize  = NULL; // 文件大小
	LPVOID  lpResData   = NULL; // 資源數據指針
	HMODULE hModule     = GetModuleHandle(L"A1Pack_Base.dll");
	if ( !(hREC=FindResource(hModule, MAKEINTRESOURCE(IDR_STUB1), L"STUB")) )  return false;
	if ( !(hREC_Handle=LoadResource(hModule, hREC)) )                          return false;
	// 資源賦值
	if ( !(lpResData=LockResource(hREC_Handle)) )                              return false; 
	// 資源大小
	if ( !(dwStubSize=SizeofResource(hModule, hREC)) )                         return false;

添加區段函數代碼

代碼沒有考慮到附加數據等細節

PVOID CProcessingPE::AddSection(LPCTSTR strName, DWORD dwSize, DWORD dwChara, PIMAGE_SECTION_HEADER pNewSection, PDWORD lpSize)
{
	PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(m_pNt_Header);

	// 1. 獲取基本信息
	DWORD dwDosSize  = m_pDos_Header->e_lfanew;
	DWORD dwPeSize   = sizeof(IMAGE_NT_HEADERS32);
	DWORD dwStnSize  = m_pNt_Header->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER);
	DWORD dwHeadSize = dwDosSize+dwPeSize+dwStnSize;

	// 2. 在區段表中加入新區段的信息
	// 2.1 獲取基本信息
	CHAR  szVarName[7]    = {0};
	DWORD dwFileAlign     = m_pNt_Header->OptionalHeader.FileAlignment;    // 文件粒度
	DWORD dwSectAlign     = m_pNt_Header->OptionalHeader.SectionAlignment; // 區段粒度   
	WORD  dwNumOfsect     = m_pNt_Header->FileHeader.NumberOfSections;     // 區段數目

	// 2.2 獲取最后一個區段的信息
	IMAGE_SECTION_HEADER stcLastSect = {0};
	CopyMemory(&stcLastSect, &pSectionHeader[dwNumOfsect-1], sizeof(IMAGE_SECTION_HEADER));

	// 2.3 根據區段粒度計算相應地址信息
	DWORD dwVStart = 0;                                                        // 虛擬地址起始位置
	DWORD dwFStart = stcLastSect.SizeOfRawData + stcLastSect.PointerToRawData; // 文件地址起始位置

	if ( stcLastSect.Misc.VirtualSize%dwSectAlign )
		dwVStart = (stcLastSect.Misc.VirtualSize / dwSectAlign+1) * dwSectAlign + stcLastSect.VirtualAddress;
	else
		dwVStart = (stcLastSect.Misc.VirtualSize / dwSectAlign  ) * dwSectAlign + stcLastSect.VirtualAddress;

	DWORD dwVirtualSize   = 0; // 區段虛擬大小
	DWORD dwSizeOfRawData = 0; // 區段文件大小
	if ( dwSize%dwSectAlign)
		dwVirtualSize   = (dwSize / dwSectAlign+1) * dwSectAlign;
	else
		dwVirtualSize   = (dwSize / dwSectAlign  ) * dwSectAlign;

	if ( dwSize%dwFileAlign )
		dwSizeOfRawData = (dwSize / dwFileAlign+1) * dwFileAlign;
	else
		dwSizeOfRawData = (dwSize / dwFileAlign  ) * dwFileAlign;

	WideCharToMultiByte(CP_ACP, NULL, strName, -1, szVarName, _countof(szVarName), NULL, FALSE);

	// 2.4 組裝一個新的區段頭
	IMAGE_SECTION_HEADER stcNewSect  = {0};
	CopyMemory(stcNewSect.Name, szVarName, 7);     // 區段名稱
	stcNewSect.Misc.VirtualSize = dwVirtualSize;   // 虛擬大小
	stcNewSect.VirtualAddress   = dwVStart;        // 虛擬地址
	stcNewSect.SizeOfRawData    = dwSizeOfRawData; // 文件大小
	stcNewSect.PointerToRawData = dwFStart;        // 文件地址
	stcNewSect.Characteristics  = dwChara;         // 區段屬性

	// 2.5 寫入指定位置
	CopyMemory( (PVOID)((DWORD)m_dwFileDataAddr+dwHeadSize), &stcNewSect, sizeof(IMAGE_SECTION_HEADER) );

	// 3. 修改區段數目字段NumberOfSections
	m_pNt_Header->FileHeader.NumberOfSections++;

	// 4. 修改PE文件的景象尺寸字段SizeOfImage
	m_pNt_Header->OptionalHeader.SizeOfImage += dwVirtualSize;

	// 5. 返回新區段的詳細信息、大小,以及可直接訪問的地址
	CopyMemory(pNewSection, &stcNewSect, sizeof(IMAGE_SECTION_HEADER));
	*lpSize     = dwSizeOfRawData;
	return (PVOID)(m_dwFileDataAddr+dwFStart);
}

5.對Stub進行重定位處理並寫入配置信息

Stub程序在宿主程序上執行,那么在植入前就必須對其做重定位操作。

新加載的地址 = (新區段RVA - Stub的".Text區段RVA")+宿主程序映像基址

執行重定位操作的代碼:

未考慮修復類型的問題,如果要提高兼容性,應該對3種重定位類型進行區別對待。

void CProcessingPE::FixReloc(DWORD dwLoadImageAddr)
{
	// 1. 獲取映像基址與代碼段指針
	DWORD             dwImageBase;
	PVOID             lpCode;
	dwImageBase = m_pNt_Header->OptionalHeader.ImageBase;
	lpCode      = (PVOID)( (DWORD)m_dwFileDataAddr + RVAToOffset(m_pNt_Header->OptionalHeader.BaseOfCode) );

	// 2. 獲取重定位表在內存中的地址
	PIMAGE_DATA_DIRECTORY  pDataDir;
	PIMAGE_BASE_RELOCATION pReloc;
	pDataDir = m_pNt_Header->OptionalHeader.DataDirectory+IMAGE_DIRECTORY_ENTRY_BASERELOC;
	pReloc   = (PIMAGE_BASE_RELOCATION)((DWORD)m_dwFileDataAddr + RVAToOffset(pDataDir->VirtualAddress));

	// 3. 遍歷重定位表,並對目標代碼進行重定位
	while ( pReloc->SizeOfBlock && pReloc->SizeOfBlock < 0x100000 )
	{
		// 3.1 取得重定位項TypeOffset與其數量
		PWORD  pTypeOffset = (PWORD)((DWORD)pReloc+sizeof(IMAGE_BASE_RELOCATION));
		DWORD  dwCount     = (pReloc->SizeOfBlock-sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);

		// 3.2 循環檢查重定位項
		for ( DWORD i=0; i<dwCount; i++ )
		{
			if ( !*pTypeOffset ) continue;

			// 3.2.1 獲取此重定位項指向的指針
			DWORD  dwPointToRVA  = (*pTypeOffset&0x0FFF)+pReloc->VirtualAddress;
			PDWORD pPtr          = (PDWORD)(RVAToOffset(dwPointToRVA)+(DWORD)m_dwFileDataAddr);
			// 3.2.2 計算重定位增量值
			DWORD dwIncrement = dwLoadImageAddr - dwImageBase;
			// 3.2.3 修復需重定位的地址數據
			*((PDWORD)pPtr) += dwIncrement;
			pTypeOffset++;
		}

		// 3.3 指向下一個重定位塊,開始另一次循環
		pReloc = (PIMAGE_BASE_RELOCATION)((DWORD)pReloc + pReloc->SizeOfBlock);
	}
}
寫入stub配置信息

將關鍵的OEP信息、配置信息寫入Stub的全局變量g_stcParam中,GetExpVarAddr()函數獲取導出的g_stcParam地址,並向其地址中寫入我們已經組織好的結構體。

獲取g_stcParam地址的關鍵代碼:

// 5. 寫入配置參數
// 5.1 獲取Stub的導出變量地址
PVOID lpPatam = objProcPE.GetExpVarAddr(L"g_stcParam");
// 5.2 保存配置信息到Stub中
CopyMemory(lpPatam,&stcParam,sizeof(GLOBAL_PARAM));


// 獲取導出變量地址
PVOID CProcessingPE::GetExpVarAddr(LPCTSTR strVarName)
{
	// 1、獲取導出表地址,並將參數strVarName轉為ASCII形式,方便對比查找
	CHAR szVarName[MAX_PATH] = {0};
	PIMAGE_EXPORT_DIRECTORY lpExport = (PIMAGE_EXPORT_DIRECTORY)(m_dwFileDataAddr + RVAToOffset(m_stcPeInfo.stcExport.VirtualAddress));
	WideCharToMultiByte(CP_ACP, NULL, strVarName, -1, szVarName, _countof(szVarName), NULL, FALSE);

	// 2、循環讀取導出表輸出項的輸出函數,並依次與szVarName做比對,如果相同,則取出相對應的函數地址
	for (DWORD i=0; i<lpExport->NumberOfNames; i++)
	{
		PDWORD pNameAddr  = (PDWORD)(m_dwFileDataAddr+RVAToOffset(lpExport->AddressOfNames+i));
		PCHAR strTempName = (PCHAR)(m_dwFileDataAddr + RVAToOffset(*pNameAddr));
		if ( !strcmp(szVarName, strTempName) )
		{
			PDWORD pFunAddr = (PDWORD)(m_dwFileDataAddr+RVAToOffset(lpExport->AddressOfFunctions+i));
			return (PVOID)(m_dwFileDataAddr + RVAToOffset(*pFunAddr));
		}
	}
	return 0;
}

6.將Stub復制到區段中

講Stub的.text段內容復制到新區段中,並將目標文件的OEP指向Stub的入口處就可以了。因為Stub的代碼段被移動了位置,其RVA已經發生了變化。因此將這些因素考慮進去后。可以得出以下公式:

新OEP = (Stub的OEP - Stub代碼段的RVA) + Stub所在新區段的RVA

2.7 編寫界面部分

界面部分涉及到A1Pack_Base.dll的調用。這里用得很好,記錄一下。

動態庫調用DLL中的函數,但是增加了頭文件。方便用VS調試。

#pragma once
#define A1PACK_BASE_EXPORTS
#include "../A1Pack_Base/A1Pack_Base.h"

#ifdef _DEBUG
#pragma comment(lib, "../Debug/A1Pack_Base.lib")
#else
#pragma comment(lib, "../Release/A1Pack_Base.lib")
#endif

打造免殺殼

  • 導入表加密
  • 代碼混淆與代碼亂序
  • 附加驅動


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM