PE学习



直接写一遍PE结构吧  看看记得住不?




MS-DOS
 {
MZ  
`````
e_lfanew
}  64字节
dos-stub 112字节  不定
NT header 
{
Signature  4
IMAGE_FILE_HEADER
{
machine
NumberOfSections
timedatastamp
PointerToSymbolTable
NumberOfSymbols
sizeofOptionalHeader
characteristics
}
OptionalHeader32
{
magic
MajorLinker
MinorLinker
SizeofCode
SizeOfInitializeData
SizeOfUnInirializeData
AddressOfEntryPoint
BaseOfCpde
BaseOfData
ImageBase
SectionAlignment
FileAlignment
Maior操作系统主
操作系统次
用户自定义主
用户次版本
子系统主
子系统次
Win32预留
SizeOfImage 映像装入内存
SizeOfHeader   DOS头+PE头+区块表
校验和
子系统
DllCharactrstics
SizeOfStackReserve 线程保留
委派堆栈··
进程保留
委派堆保留内存
LoaderFlag 调试有关
数据目录项数
DataDirectory 16个
{
IMAGE_DATA_DIRECTORY
{
VirtualAddress  数据块起始RVA
SIZE 数据块长度
}
出 入 资 异       安 重 调 版 全     线 配 绑 导 延 R 预
}
}
}




IMAGE_SECTION_HEADER   数量由   NT_HEADER -> FILE_HEADER->第二元素 NumbersOfSection 指定
{     两排半  
name
union{  padder,size  }
VitualAddress
SizeOfRawData
PointerToRawData

4个无用信息{  OBJ使用 重定位偏移 + 行号表偏移 + OBJ使用 重定位数目 + 行号数目 }
Characteristics  代码/数据/可读可写 标志    60 00 00 20可读可运行    C0 00 00 20 可读可写  40 00 00 40 可读
}
在后面+一个空的 IMAGE_SECTION_HEADER
最后补齐  200H 倍数


手动添加节区并且以节区为起始位置的步骤:

1)节区大小为    两行半,先添加名称  半排

2)添加节大小,一般0x1000 字节,也就是对齐后长度,注意添加时字节顺序

3)添加节相对虚拟地址,=  上一个节区的相对虚拟地址 + 上一个节对齐后长度(0x1000)

4)添加节在磁盘上的大小,通常为对齐大小0x1000

5)  添加节区在磁盘的偏移值 = 上一个节区的磁盘的偏移值 + 上一个节对齐后长度(0x1000)

6)添加4个无用信息,12字节的都可以是 0 

7)最后4个字节添加  节区的节区属性

8)修改PE文件的节区数量,在OPT header 结构 后面 7 8字节上

9) 修改映像大小  sizeOfImage, OPT header  结构中

10)添加数据 0x1000  在这个区域修改代码即为区段的代码

11)修改PE起始点为 节区代码段(10步骤添加的代码)

代码学习··········································


文件偏移  =  虚拟地址VA  - ImageBase - (所在块 IMAGE_SECTION_HEADER -> 内存偏移 -  所在块文件偏移)




NT_HEADER->OPTIONAL_HEADER->BaseOfCode                       ->代码段



   数据目录中的各种表




NT_HEADER->OPTIONAL_HEADER->BaseOfData                          -> 数据段


基址重定位  主要用于DLL  因为DLL经常加载到不是预设基址的地址上

typedef struct _IMAGE_BASE_RELOCATION

{

DWORD VirtualAddress; //需重定位数据的起始RVA

DWORD SizeofBlock;    //本结构 8字节 + TypeOffset 总大小

//WORD TypeOffset[1];//不属于 本结构    是需要进行重定位的具体偏移

}

TypeOffset 又定义为  

struct{

WORD Offset:12;   //后12位

WORD Type:4; //前4位

}TypeOffset;



一个重定位区块     的      重定位项的数目  = (SizeOfBlock - IMAGE_SIZEOF_RELOCATION )/ 2    //IMAGE_SIZEOF_RELOCATION也就是8

举个例子:

TypeOffset  = 0x3006

Type  =   0x3

Offset  = 0x006

 VirtualAddress = 0x1000;

重定位的RVA = VirtualAddress + 0x006 = 0x1006


由此  推出需要重定位的数据RVA 为  0x1006

示例代码如下:

typedef struct {
	PIMAGE_NT_HEADERS headers;
	unsigned char *codeBase;
	HMODULE *modules;
	int numModules;
	int initialized;
} MEMORYMODULE, *PMEMORYMODULE;

#define GET_HEADER_DICTIONARY(module, idx) &(module)->headers->OptionalHeader::DataDirectory[idx]

void PerformBaseRelocation(PMEMORYMODULE module, DWORD delta) 
//第二参数为 你把DLL导入到的内存基址 - DLL 文件的 ImageBase
{
	DWORD i;
	unsigned char *codeBase = module->codeBase;

	PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY(module, IMAGE_DIRECTORY_ENTRY_BASERELOC);
	if (directory->Size > 0)
	{
		PIMAGE_BASE_RELOCATION relocation = (PIMAGE_BASE_RELOCATION)(codeBase + directory->VirtualAddress);
		for (; relocation->VirtualAddress > 0; )
		{
			unsigned char *dest = (unsigned char *)(codeBase + relocation->VirtualAddress);
			unsigned short *relInfo = (unsigned short *)((unsigned char *)relocation + IMAGE_SIZEOF_BASE_RELOCATION);
			for (i=0; i<((relocation->SizeOfBlock - IMAGE_SIZEOF_BASE_RELOCATION) / 2); i++, relInfo++)
			{//164
				DWORD *patchAddrHL;
				int type, offset;

				// the upper 4 bits define the type of relocation
				type = *relInfo >> 12;   //4bit
				// the lower 12 bits define the offset
				offset = *relInfo & 0xfff;//12bit
				
				switch (type)
				{
				case IMAGE_REL_BASED_ABSOLUTE:
					// skip relocation
					break;

				case IMAGE_REL_BASED_HIGHLOW:
					// change complete 32 bit address
					patchAddrHL = (DWORD *)(dest + offset);
					*patchAddrHL += delta;
					break;

				default:
					//printf("Unknown relocation: %d
", type);
					break;
				}
			}

			// advance to next relocation block 有很多个重定位表~~~
			relocation = (PIMAGE_BASE_RELOCATION)(((DWORD)relocation) + relocation->SizeOfBlock);
		}
	}
}




EG:

push ebp

mov ebp,esp

push 0xfffffffe

push 0x10002210


假如0x1006 + 0x10000000 对应着 0x10002210  

如果没有加载 首选加载地址  而是加载了0x72AB0000 处,很明显要要对其进行重定位了。

首先计算出他们的差值 为    0x62AB0000    再加上原地址0x10002210 就是重定位后的地址


重定位后的地址  = (加载基址 - ImageBase) + 重定位前的地址

上面的例子为 0x72AB0000 - 0x10000000 + 0x10002210 = 0x72AB2210 

重定位后第四排为

push 0x72AB2210 


TLS 变量

保存在一个.tls区段中

TLS  —— 变量 ——静态模式 主要是指  __declspec (thread) 声明的TLS变量     不能使用在DLL中

   ——动态模式   主要TlsAlloc TlsFree TlsSrtValue TlsGetValue

 ——  回调函数

当我们声明一个tls变量后,系统会自动生成.TLS节

__declspec (thread) int tlsnum = 1;

系统会保证对于每个线程的唯一性。

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

__declspec (thread) int g_num = 0x11111111;
__declspec (thread) char g_str[] = "TLS g_num = 0x%p   ````
";


void NTAPI t_tlscallback_A(PVOID DLLHandle, DWORD Reason, PVOID red)
{
	if (DLL_THREAD_DETACH == Reason)
	{
		printf("t_tlscallback_A -> threadDetach ! 
");
	}
	return ;
}
void NTAPI t_tlscallback_B(PVOID DLLHandle, DWORD Reason, PVOID red)
{
	if (DLL_THREAD_DETACH == Reason)
	{
		printf("t_tlscallback_B -> threadDetach ! 
");
	}
	return ;
}


#pragma data_seg(".CRT$XLB")
PIMAGE_TLS_CALLBACK p_thread_callback[] = {
	t_tlscallback_A,t_tlscallback_B,NULL
};
#pragma data_seg()


DWORD WINAPI ThreadProc(
	__in  LPVOID lpParameter
	)
{
	printf("ThreadProc first printf
");
	printf(g_str,g_num);
	g_num = 0x22222222;
	printf("ThreadProc second printf
");
	printf(g_str,g_num);
	return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
	printf("_tmain ``````````````````````````
");
	CreateThread(NULL,0,ThreadProc,NULL,0,0);
	Sleep(1000);
	printf("
");
	CreateThread(NULL,0,ThreadProc,NULL,0,0);
	system("pause");
	
	return 0;
}
/*
_tmain ``````````````````````````
ThreadProc first printf
TLS g_num = 0x11111111   ````
ThreadProc second printf
TLS g_num = 0x22222222   ````
t_tlscallback_A -> threadDetach !
t_tlscallback_B -> threadDetach !


ThreadProc first printf
TLS g_num = 0x11111111   ````
ThreadProc second printf
TLS g_num = 0x22222222   ````
t_tlscallback_A -> threadDetach !
t_tlscallback_B -> threadDetach !
*/

另一个作用  就是在程序main函数运行前 进行操作

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


#pragma comment(linker,"/include:__tls_used")//这句很重要


void NTAPI t_tlscallback_A(PVOID DLLHandle, DWORD Reason, PVOID red)
{
	if (DLL_PROCESS_ATTACH == Reason)
	{
		MessageBox(NULL,_T("hi,this is tls callback"),_T("title"),MB_OK); 
	}
	PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)GetModuleHandle(NULL);


	PIMAGE_NT_HEADERS32 pNt = (PIMAGE_NT_HEADERS32)((DWORD)pDos+(DWORD)pDos->e_lfanew);


	BYTE* OEP = (BYTE*)(pNt->OptionalHeader.AddressOfEntryPoint + (DWORD)pDos);
	
	for (unsigned int i=0;i<200;i++)
	{
		if (OEP[i] == 0xcc)
		{
			MessageBox(NULL,_T("检测到CC断点"),_T("检测到CC断点"),MB_OK);
		}
		 
	}
	return ;
}


#pragma data_seg(".CRT$XLB")
PIMAGE_TLS_CALLBACK p_thread_callback[] = {
	t_tlscallback_A,NULL
};
#pragma data_seg()

int _tmain(int argc, _TCHAR* argv[])
{
	printf("MAIN STARTN");
	system("pause");
	return 0;
}
















原文地址:https://www.cnblogs.com/zcc1414/p/3982445.html