直接写一遍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; }