直接写一遍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\n", 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 ````\r\n";
void NTAPI t_tlscallback_A(PVOID DLLHandle, DWORD Reason, PVOID red)
{
if (DLL_THREAD_DETACH == Reason)
{
printf("t_tlscallback_A -> threadDetach ! \r\n");
}
return ;
}
void NTAPI t_tlscallback_B(PVOID DLLHandle, DWORD Reason, PVOID red)
{
if (DLL_THREAD_DETACH == Reason)
{
printf("t_tlscallback_B -> threadDetach ! \r\n");
}
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\n");
printf(g_str,g_num);
g_num = 0x22222222;
printf("ThreadProc second printf\n");
printf(g_str,g_num);
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
printf("_tmain ``````````````````````````\n");
CreateThread(NULL,0,ThreadProc,NULL,0,0);
Sleep(1000);
printf("\r\n");
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 START\N");
system("pause");
return 0;
}