PE结构指南:c语言武器开发基础

admin 2023年12月23日01:40:15评论22 views字数 17172阅读57分14秒阅读模式

关注并星标🌟 一起学安全❤️

作者:coleak  

首发于公号:渗透测试安全攻防 

字数:16529

声明:仅供学习参考,请勿用作违法用途

目录

  • c语言基础

    • 一级指针

    • 二(多)级指针

    • 函数指针

    • 内联汇编

    • 数组指针

    • 指针数组

    • 字符转数字

    • 保留位数

    • 句柄类型

  • DLL寻找之LoadLibrary

  • 玩转PE结构

    • 基础知识

    • 解析PE

    • 添加节

    • IMAGE_DATA_DIRECTORY

    • 地址转换

    • 导出表

    • GetProcAddress自实现

    • 导入表

    • 重定位表


c语言基础

一级指针

#include<stdio.h>
int main() {
char n[] = "abcdefghijklmn";
char* N = &n;
*(N+1) = 'p';
printf("%xn",&n);
printf("%pn", N);
printf("%pn", &N);
printf("%pn", &n[1]);
printf("%pn", ( & n[0] + sizeof(char)));
printf("%cn", *( & n[0] + sizeof(char)));
printf("%cn", n[0]);
printf("%cn", *N);
return 0;
}

d8fdf8 00D8FDF8 00D8FDEC 00D8FDF9 00D8FDF9 p a a

d8fdf8:n的地址

00D8FDF8:N指向的地址

00D8FDEC:N的地址

00D8FDF9:n[1]的地址(char类型一个字节)

00D8FDF9:n[0]的地址+一个char大小的地址,即n[1]

p:*( & n[0] + sizeof(char)),取出(n[0]地址+一个char大小的地址)地址的值,这里如果不用&n[0]而是用&n则不能得到n[1],这里&n+1的+1默认加的是sizeof(n)

验证如下

#include<stdio.h>
int main() {
char n[] = "abcde";
printf("%pn", &n);
printf("%pn", &n[0]);
printf("%pn", &n[1]);
printf("%pn", (&n[0] + sizeof(char)));
printf("%pn", ((& n) + sizeof(char)));
printf("%pn", ((& n) + 1));
printf("%dn",sizeof(n));
printf("%pn", (&n + sizeof(n)));
printf("%cn", *(&n[0] + sizeof(char)));
printf("%cn", *(&n + sizeof(char)));
printf("%cn", n[1]);
return 0;
}

010FFC70 010FFC70 010FFC71 010FFC71 010FFC76 010FFC76 6 010FFC94(010FFC70+024),即+6*6 b v b

二(多)级指针

#include<stdio.h>
int main() {
int a = 100;
int* p1 = &a;
int** p2 = &p1;
return 0;
}

PE结构指南:c语言武器开发基础


#include <stdio.h>

int main() {
int a = 100;
int* p1 = &a;
int** p2 = &p1;
int*** p3 = &p2;

printf("%d, %d, %d, %dn", a, *p1, **p2, ***p3);
printf("&p2 = %#X, p3 = %#Xn", &p2, p3);
printf("&p1 = %#X, p2 = %#X, *p3 = %#Xn", &p1, p2, *p3);
printf(" &a = %#X, p1 = %#X, *p2 = %#X, **p3 = %#Xn", &a, p1, *p2, **p3);
return 0;
}

100, 100, 100, 100 &p2 = 0XB8F70C, p3 = 0XB8F70C &p1 = 0XB8F718, p2 = 0XB8F718, *p3 = 0XB8F718 &a = 0XB8F724, p1 = 0XB8F724, *p2 = 0XB8F724, **p3 = 0XB8F724

函数指针

#include <stdio.h>
int maxValue(int a, int b) {
return a > b ? a : b;
}
int main() {
int (*p)(int, int); //定义一个与maxValue兼容的指针
p = maxValue;
printf("%d", p(20, 45)); //通过指针调用
}

函数指针,即指向函数的指针

内联汇编

#include<stdio.h>
int add(int a, int b) {
int c = a;
int d = b;
int ret = c + d;
return ret;
}
int main()
{
char* p;
int k = 0;
p = &add;
__asm {
push 4
push 7
call p
add esp,8
mov k,eax
}
printf("%dn", k);
__asm {
sub esp, 4
mov dword ptr[esp], 8
push 7
call p
add esp, 8
mov k, eax
}
printf("%d", k);
system("pause");
return 0;
}

11 15

数组指针

指向数组的指针

#include <stdio.h>

int main()
{
char(*p)[6]; //定义了指向含有4个元素的一维数组的指针
char a[3][4] = { 'a','b','c','d','e','f','g','h','i','j','k','l'};
p = a; //将二维数组的首地址赋值给p,也可是a[0]或&a[0][0]
//p++; //表示p跨过行a[0][],指向了行a[1][]
printf("%c",(*p)[4]);
return 0;
}

e

PE结构指南:c语言武器开发基础


指针数组

存放指针的数组

#include <stdio.h>

const int MAX = 4;

int main()
{
const char* names[4] = {
"Zara Ali",
"Hina Ali",
"Nuha Ali",
"Sara Ali",
};
int a = 10, b = 20, c = 22, d = 99;
int* age[4] = {&a,&b,&c,&d};
int i = 0;
for (i = 0; i < MAX; i++)
{
printf("Value of names[%d] = %s,age=%dn", i, names[i],*age[i]);
}
return 0;
}

Value of names[0] = Zara Ali,age=10 Value of names[1] = Hina Ali,age=20 Value of names[2] = Nuha Ali,age=22 Value of names[3] = Sara Ali,age=99

PE结构指南:c语言武器开发基础


字符转数字

#include <stdio.h>
int main()
{
char a = '7';
int b = a - '0';
printf("%d",b);
return 0;
}

保留位数

#include<stdio.h>
int main()
{
float b = 9.9;
printf("%3fn", b);
printf("%30fn", b);
printf("%3.fn", b);
printf("%.3fn",b);
return 0;

}

#
#include<stdio.h>
int main()
{
int a = 10;
int b = 3;
printf("%30.7f", float (a) / b);
}

9.900000 9.900000 10 9.900

3.3333333

句柄类型

HWND是线程相关的,你可以通过HWND找到该窗口所属进程和线程

Handle 是代表系统的内核对象,如文件句柄,线程句柄,进程句柄。
系统对内核对象以链表的形式进行管理,载入到内存中的每一个内核对象都有一个线性地址,同时相对系统来说,在串列中有一个索引位置,这个索引位置就是内核对象的handle。

HINSTANCE的本质是模块基地址,他仅仅在同一进程中才有意义,跨进程的HINSTANCE是没有意义

HMODULE 是代表应用程序载入的模块,win32系统下通常是被载入模块的线性地址。

HINSTANCE 在win32下与HMODULE是相同的东西(只有在16位windows上,二者有所不同).

DLL寻找之LoadLibrary

Process Monitor

  • Include

Operation is CreateFile

Operation is LoadImage

Operation is QueryOpen

Path contains .dll

processname is kk.exe

Path begins with C:testKKcapture

  • Exclude

Result is SUCCESS

我们通过运⾏xxx.exe白文件对比,寻找是否存在LoadLibrary函数,如果存在,我们可以直接构造一个恶意黑DLL。反之,我们就需要劫持不存在的DLL。
如果文件允许LoadLibrary函数动态解析库的路径,那么该文件也会在当前目录中查找库DLL,我们通过将"白加黑"复制到具有写入权限的目录即可
劫持DllMain中的控制流时,没有必要枚举和满足所有需要的导出,即可能存在 DLL 没有任何导出并且只能通过 DllMain 入口点被劫持的情况

玩转PE结构

常用工具

cff explorer,x32/64dbg

基础知识

结构图

PE结构指南:c语言武器开发基础


PE结构指南:c语言武器开发基础


解析PE

基础知识

LPVOID 指向任意类型的指针

fseek(pFile, 0, SEEK_END);文件指针到尾部
FileSize = ftell(pFile);获取指针到开头的大小
fseek(pFile, 0, SEEK_SET);文件指针到头部

malloc(size_t size) 分配所需的内存空间,并返回一个指向它的指针。

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
ptr -- 这是指向带有最小尺寸 size*nmemb 字节的内存块的指针。
size -- 这是要读取的每个元素的大小,以字节为单位。
nmemb -- 这是元素的个数,每个元素的大小为 size 字节。
stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流。

#define IMAGE_DOS_SIGNATURE 0x5A4D // MZ
#define IMAGE_NT_SIGNATURE 0x00004550 // PE00
#define IMAGE_SIZEOF_FILE_HEADER 20 :这里指的是文件头为20字节大小

VA
= Image Base + RVA
RAW - PointerToRawData = RVA - ImageBase
RAW = RVA - ImageBase + PointerToRawData
SizeOfOptionalHeader:32位则E0H,64位则F0H
Magic :32位则10B64位则20B
更多细节都在图上

完整代码

// addsection.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include<Windows.h>

void myreadfile(char path[], int* filelen, char** exebuf)
{
FILE* file = NULL;
fopen_s(&file, path, "rb");
fseek(file, 0, SEEK_END);
*filelen = ftell(file);
fseek(file, 0, SEEK_SET);
char* buf = (char*)VirtualAlloc(NULL, *filelen, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
fread(buf, 1, *filelen, file);
*exebuf = buf;
}
int main()
{
char exepath[] = "D:\c_project\c2\Debug\Project1.exe";
char* exebuf = NULL;
int exefilelen = 0;
myreadfile(exepath, &exefilelen, &exebuf);
PIMAGE_DOS_HEADER pIMAGE_DOS_HEADER = (PIMAGE_DOS_HEADER)exebuf;
printf("PIMAGE_DOS_HEADER->e_magic:%xn", pIMAGE_DOS_HEADER->e_magic);
printf("PIMAGE_DOS_HEADER->e_lfanew:%xn", pIMAGE_DOS_HEADER->e_lfanew);

PIMAGE_NT_HEADERS pIMAGE_NT_HEADERS = (PIMAGE_NT_HEADERS)(exebuf + pIMAGE_DOS_HEADER->e_lfanew);
printf("PIMAGE_NT_HEADERS->Signature:%xn", pIMAGE_NT_HEADERS->Signature);

PIMAGE_FILE_HEADER pIMAGE_FILE_HEADER = (PIMAGE_FILE_HEADER)&pIMAGE_NT_HEADERS->FileHeader;

PIMAGE_SECTION_HEADER pPIMAGE_SECTION_HEADER = (PIMAGE_SECTION_HEADER)(pIMAGE_NT_HEADERS + 1);

pPIMAGE_SECTION_HEADER = pPIMAGE_SECTION_HEADER + pIMAGE_FILE_HEADER->NumberOfSections - 1;
printf("pPIMAGE_SECTION_HEADER->Name:%sn", pPIMAGE_SECTION_HEADER->Name);
printf("pPIMAGE_SECTION_HEADER->Misc.VirtualSize:%xn", pPIMAGE_SECTION_HEADER->Misc.VirtualSize);
printf("pPIMAGE_SECTION_HEADER->VirtualAddress:%xn", pPIMAGE_SECTION_HEADER->VirtualAddress);
printf("pPIMAGE_SECTION_HEADER->SizeOfRawData:%xn", pPIMAGE_SECTION_HEADER->SizeOfRawData);
printf("pPIMAGE_SECTION_HEADER->PointerToRawData:%xn", pPIMAGE_SECTION_HEADER->PointerToRawData);
printf("pPIMAGE_SECTION_HEADER->Characteristics:%xn", pPIMAGE_SECTION_HEADER->Characteristics);
}

添加节

// 关键代码

#include <iostream>
#include<Windows.h>

DWORD ArithmeticFileAlignment(DWORD FileSize)
{
DWORD myFileSize = 0;
if (FileSize % FileAlignment != 0)
{
myFileSize = ((FileSize / FileAlignment) + 1) * FileAlignment;
}
else
{
myFileSize = FileSize;
}
return myFileSize;
}

DWORD ArithmeticSectionAlignment(DWORD VirtualSize)
{
DWORD myVirtualSize = 0;
if (VirtualSize % SectionAlignment != 0)
{
myVirtualSize = ((VirtualSize / SectionAlignment) + 1) * SectionAlignment;
}
else
{
myVirtualSize = VirtualSize;
}
return myVirtualSize;
}

int main()
{
DWORD exeAlignmentVirtualSize = ArithmeticSectionAlignment(pPIMAGE_SECTION_HEADER->Misc.VirtualSize);

DWORD FileAlignmentVirtualSize = ArithmeticSectionAlignment(addfilelen);

DWORD FileAlignmentFileSize = ArithmeticFileAlignment(addfilelen);

PIMAGE_SECTION_HEADER pmyPIMAGE_SECTION_HEADER = pPIMAGE_SECTION_HEADER + 1;
memcpy(pmyPIMAGE_SECTION_HEADER->Name, "mysec", strlen("mysec"));
pmyPIMAGE_SECTION_HEADER->Misc.VirtualSize = FileAlignmentVirtualSize;
pmyPIMAGE_SECTION_HEADER->VirtualAddress = exeAlignmentVirtualSize + pPIMAGE_SECTION_HEADER->VirtualAddress;

pmyPIMAGE_SECTION_HEADER->SizeOfRawData = FileAlignmentFileSize;
pmyPIMAGE_SECTION_HEADER->PointerToRawData = pPIMAGE_SECTION_HEADER->PointerToRawData + pPIMAGE_SECTION_HEADER->SizeOfRawData;

pmyPIMAGE_SECTION_HEADER->PointerToLinenumbers = 0;
pmyPIMAGE_SECTION_HEADER->NumberOfRelocations = 0;
pmyPIMAGE_SECTION_HEADER->PointerToRelocations = 0;
pmyPIMAGE_SECTION_HEADER->NumberOfLinenumbers = 0;

pmyPIMAGE_SECTION_HEADER->Characteristics = 0x60000020;

pIMAGE_NT_HEADERS->OptionalHeader.SizeOfImage += FileAlignmentVirtualSize;

char* execopydata = (char*)VirtualAlloc(NULL, exefilelen + FileAlignmentFileSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(execopydata, exebuf, exefilelen);
memcpy(execopydata + exefilelen, addfilebuf, addfilelen);
FILE* savefile = NULL;
fopen_s(&savefile, "11.exe", "wb");
fwrite(execopydata, 1, exefilelen + FileAlignmentFileSize, savefile);
fclose(savefile);
}

修改address of entry point指向新增节也可以执行shellcode,但是原文件功能失效,可以固定程序入口地址(非随机化),然后写死执行完shellcode后jmp到正常功能即可使原功能生效

IMAGE_DATA_DIRECTORY

IMAGE_DATA_DIRECTORY数据目录列表,它由16个相同的IMAGE_DATA_DIRECTORY结构组成,这16个数据目录结构定义很简单仅仅指出了某种数据的位置和长度,定义如下:

typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; // 数据起始RVA
DWORD Size; // 数据块的长度
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

地址转换

内存加载映像图

PE结构指南:c语言武器开发基础


VA = Image Base + RVA
FOA=RVA-VirtualAddress+PointerToRawData 数据的文件偏移=数据RVA - 节RVA + 节的文件偏移

由于内存中第一个节无法映射到文件,因此需要对RVA进行判断是否在.textbss节之后,可以通过获取到的节的SizeOfRawData进行判断,解析第一个节的信息如下,即SizeOfRawData:0

pPIMAGE_SECTION_HEADER->Name:.textbss
pPIMAGE_SECTION_HEADER->Misc.VirtualSize:10000
pPIMAGE_SECTION_HEADER->VirtualAddress:1000
pPIMAGE_SECTION_HEADER->SizeOfRawData:0
pPIMAGE_SECTION_HEADER->PointerToRawData:0
pPIMAGE_SECTION_HEADER->Characteristics:e00000a0

同时若在0填充的块间隙中,也无法映射到文件,即RVA-VirtualAddress>SizeOfRawData

RVA->FOA

BOOL RVAtoFAfun(DWORD RVA, DWORD imagebase, PIMAGE_SECTION_HEADER pSection, int nNumofSection, DWORD* pFA)
{

int FA = 0;
for (int i = 0; i < nNumofSection; i++)
{
DWORD VirtualSizeAlignment = ArithmeticSectionAlignment(pSection[i].Misc.VirtualSize);
if (RVA >= pSection[i].VirtualAddress && RVA < pSection[i].VirtualAddress + VirtualSizeAlignment)
{
if (pSection[i].SizeOfRawData == 0)
{
FA = 0;
break;
}
if (RVA - pSection[i].VirtualAddress <= pSection[i].SizeOfRawData)
FA = RVA - pSection[i].VirtualAddress + pSection[i].PointerToRawData;
else
FA = 0;//数据在节块的空隙中
}
}
if (FA == 0)
{
return FALSE;
}

*pFA = FA;
return TRUE;
};

导出表

导出函数

#dllmain.cpp

#include "pch.h"
#include<stdio.h>

int fun(int a) {
return a + 10;
}

#Source.def
EXPORTS
fun @8

main.cpp
#include <iostream>
#include<Windows.h>
typedef int (*p)(int);
int main() {
HMODULE dl = LoadLibraryA("D:\c_project\c2\Debug\Dll1.dll");
//p p1=(p) GetProcAddress(dl, (LPCSTR)8);
p p1 = (p)GetProcAddress(dl, "fun");
printf("%d", p1(2));
return 0;
}

导出表结构

typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions; // RVA from base of image
DWORD AddressOfNames; // RVA from base of image
DWORD AddressOfNameOrdinals; // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

GetProcAddress自实现

PE结构指南:c语言武器开发基础


原生地址获取

#include<stdio.h>
#include<windows.h>
typedef int(*msg)(int a);
int main()
{
HMODULE hDll = LoadLibrary(L"Dll1.dll");
void *p=GetProcAddress(hDll,"fun");
msg func = (msg)GetProcAddress(hDll, "fun");
printf("%xn", func);
printf("%x", p);
//运行msg函数
printf("%d",func(3));
return 0;
}

70de1177 70de1177

13

自定义获取

dbg调试发现将fun名字和ffff进行cmp,大于ffff则是str查找,否则为序号查找

PE结构指南:c语言武器开发基础


#include<stdio.h>
#include<windows.h>
#include<string.h>

FARPROC
WINAPI
myGetProcAddress(
_In_ HMODULE hModule,
_In_ LPCSTR lpProcName
)
{
PIMAGE_DOS_HEADER pIMAGE_DOS_HEADER = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS pIMAGE_NT_HEADER = (PIMAGE_NT_HEADERS)((DWORD)hModule + pIMAGE_DOS_HEADER->e_lfanew);
PIMAGE_EXPORT_DIRECTORY pIMAGE_EXPORT_DIRECTORYRVA =(PIMAGE_EXPORT_DIRECTORY) (pIMAGE_NT_HEADER->OptionalHeader.DataDirectory[0].VirtualAddress);
PIMAGE_EXPORT_DIRECTORY pIMAGE_EXPORT_DIRECTORY =(PIMAGE_EXPORT_DIRECTORY) ((DWORD)hModule + (DWORD)pIMAGE_EXPORT_DIRECTORYRVA);
printf("%sn", (DWORD)(pIMAGE_EXPORT_DIRECTORY->Name)+ (DWORD)hModule);
DWORD ordRVA = pIMAGE_EXPORT_DIRECTORY->AddressOfNameOrdinals;
WORD* ordVA = (WORD*)(ordRVA + (DWORD)hModule);
DWORD nameRVA = pIMAGE_EXPORT_DIRECTORY->AddressOfNames;
DWORD* nameVA =(DWORD*) (nameRVA + (DWORD)hModule);
DWORD funRVA = pIMAGE_EXPORT_DIRECTORY->AddressOfFunctions;
DWORD* funVA = (DWORD*)(funRVA + (DWORD)hModule);

//获取到导出的函数名字,并遍历名字进行查找
for (int i = 0; i < pIMAGE_EXPORT_DIRECTORY->NumberOfNames; i++)
{
char* funname = (char*)((nameVA[i]) + (DWORD)hModule);
if (strcmp(funname, lpProcName) == 0) {
printf("find it!,ord:%dn", pIMAGE_EXPORT_DIRECTORY->Base + ordVA[i]);
printf("%xn", (DWORD)hModule+funVA[ordVA[i]]);
return NULL;
}
}
printf("no this function");
return NULL;

}

FARPROC
WINAPI
MyOrdinalGetProcAddress(
_In_ HMODULE hModule,
_In_ LPCSTR lpProcName
)

{
if ((DWORD)lpProcName >= 0xffff)
{
myGetProcAddress(hModule, lpProcName);
}
else {
PIMAGE_DOS_HEADER pIMAGE_DOS_HEADER = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS pIMAGE_NT_HEADER = (PIMAGE_NT_HEADERS)((DWORD)hModule + pIMAGE_DOS_HEADER->e_lfanew);
PIMAGE_EXPORT_DIRECTORY pIMAGE_EXPORT_DIRECTORYRVA = (PIMAGE_EXPORT_DIRECTORY)(pIMAGE_NT_HEADER->OptionalHeader.DataDirectory[0].VirtualAddress);
PIMAGE_EXPORT_DIRECTORY pIMAGE_EXPORT_DIRECTORY = (PIMAGE_EXPORT_DIRECTORY)((DWORD)hModule + (DWORD)pIMAGE_EXPORT_DIRECTORYRVA);
printf("%sn", (DWORD)(pIMAGE_EXPORT_DIRECTORY->Name) + (DWORD)hModule);
DWORD ordRVA = pIMAGE_EXPORT_DIRECTORY->AddressOfNameOrdinals;
WORD* ordVA = (WORD*)(ordRVA + (DWORD)hModule);
DWORD nameRVA = pIMAGE_EXPORT_DIRECTORY->AddressOfNames;
DWORD* nameVA = (DWORD*)(nameRVA + (DWORD)hModule);
DWORD funRVA = pIMAGE_EXPORT_DIRECTORY->AddressOfFunctions;
DWORD* funVA = (DWORD*)(funRVA + (DWORD)hModule);
DWORD base = pIMAGE_EXPORT_DIRECTORY->Base;
int ord = (DWORD)lpProcName - base;
printf("%xn", (funVA[ord])+(DWORD)hModule);
}
}

int main()
{
HMODULE hDll = LoadLibrary(L"Dll1.dll");
void *p= MyOrdinalGetProcAddress(hDll,"fun");
void* p2 = GetProcAddress(hDll, "fun");
printf("%xn", p2);
printf("fun2:n");
printf("n");
void* p3 = GetProcAddress(hDll, "fun2");
void* p4 = MyOrdinalGetProcAddress(hDll, (LPCSTR)12);
printf("%xn", p3);
return 0;
}

Dll1.dll find it!,ord:9 7ba31159 7ba31159 fun2:

Dll1.dll 7ba312d0 7ba312d0

导入表

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk;
} DUMMYUNIONNAME;
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;
DWORD FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

typedef struct _IMAGE_THUNK_DATA32
{

union {
DWORD ForwarderString; // 转发字符串的RAV
DWORD Function; // 被导入函数的地址
DWORD Ordinal; // 被导入函数的序号
DWORD AddressOfData; // 指向输入名称表 PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;


typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
CHAR Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

名词

OriginalFirstThunk指向INT
FirstThunk指向IAT
其中将OriginalFirstThunk全部置零不影响程序的运行

载入前结构


PE结构指南:c语言武器开发基础


加载后的结构

PE结构指南:c语言武器开发基础


解析导入表

PIMAGE_IMPORT_DESCRIPTOR pIMAGE_IMPORT_DESCRIPTOR = (PIMAGE_IMPORT_DESCRIPTOR)
(pIMAGE_NT_HEADERS->OptionalHeader.DataDirectory[1].VirtualAddress + (DWORD)hModule);
for (size_t i = 0; pIMAGE_IMPORT_DESCRIPTOR[i].Name != 0; i++)
{
char* dllName = (char*)(pIMAGE_IMPORT_DESCRIPTOR[i].Name + (DWORD)hModule);
printf("%sn", dllName);

PIMAGE_THUNK_DATA pIMAGE_THUNK_DATA = (PIMAGE_THUNK_DATA)(pIMAGE_IMPORT_DESCRIPTOR[i].OriginalFirstThunk + (DWORD)hModule);
DWORD* pfunaddress = (DWORD*)(pIMAGE_IMPORT_DESCRIPTOR[i].FirstThunk + (DWORD)hModule);

for (size_t j = 0; pIMAGE_THUNK_DATA[j].u1.ForwarderString != 0; j++)
{
PIMAGE_IMPORT_BY_NAME FunName = (PIMAGE_IMPORT_BY_NAME)(
(pIMAGE_THUNK_DATA[j].u1.ForwarderString) + (DWORD)hModule);

printf("%s:%xn", FunName->Name, pfunaddress[j]);

}
printf("nn");
}

重定位表

PE文件重定位表中保存的仅仅只是一大堆需要修正的代码的地址,修正算法可以描述为,将直接寻址指令中的地址加上模块实际装入地址与模块建议装入地址之差。为了进行运算需要3个数据,首先是需要修正机器码地址,其次是模块建议装入地址,最后是模块的实际装入地址。在这3个数据中,模块的建议装入地址已经在PE文件头中定义了,而模块的实际装入地址时Windows装载器在装载文件时确定的,事实上PE文件重定位表中保存的仅仅只是,一大堆需要修正的代码的地址

IMAGE_BASE_RELOCATION

重定位表会被单独存放在.reloc命名的节中,重定位表的位置和大小可以从数据目录中的第6个IMAGE_DATA_DIRECTORY结构中获取到,该表的组织方式时以0x1000页为一块,每一块负责一页,从PE文件头获取到重定位表地址后,就可以顺序读取到所有表结构,每个重定位块以一个IMAGE_BASE_RELOCATION结构开头,后面跟着在本页中使用的所有重定位项,每个重定位项占用16字节,最后一个节点是一个使用0填充的_IMAGE_BASE_RELOCATION标志表的结束

typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;
DWORD SizeOfBlock;
// WORD TypeOffset[1];
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
items=(SizeOfBlock-8)/2

枚举重定位块

#include<Windows.h>
#include<iostream>
using namespace std;

DWORD RvaToFoa(_In_ DWORD rva, _In_ PIMAGE_SECTION_HEADER p, _In_ PIMAGE_FILE_HEADER f)
{
for (int i = 0; i < f->NumberOfSections; i++)
{
// FOA = 数据的RVA + 区段的RVA - 区段的FOA
if (rva >= p->VirtualAddress && rva < (p->VirtualAddress + p->Misc.VirtualSize))
{
return rva - p->VirtualAddress + p->PointerToRawData;
}
p++;
}
return 0;
}

void ImageNtHeader(_In_z_ const char* path)
{
// 获取文件对象
HANDLE hfile = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
// 获取文件大小
DWORD fSize = GetFileSize(hfile, NULL);
char* pBuff = new char[fSize];
DWORD dwReadSize = 0;
// 读文件
BOOL bSuccess = ReadFile(hfile, pBuff, fSize, &dwReadSize, NULL);
if (bSuccess)
{
typedef struct _TYPE {
WORD Offset : 12; //大小 2bit 重定位的偏移
WORD Tyoe : 4;
}TYPE, * PTYPE;
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBuff;
PIMAGE_NT_HEADERS32 pNtHeader{ 0 };
pNtHeader = (PIMAGE_NT_HEADERS32)(pDosHeader->e_lfanew + pBuff);
PIMAGE_FILE_HEADER pFileHeader = (PIMAGE_FILE_HEADER)&pNtHeader->FileHeader;
PIMAGE_OPTIONAL_HEADER32 pOptionalHeader = (PIMAGE_OPTIONAL_HEADER32)&pNtHeader->OptionalHeader;
PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pNtHeader);
// PIMAGE_DATA_DIRECTORY dataDirectory = (PIMAGE_DATA_DIRECTORY)&pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
PIMAGE_BASE_RELOCATION pRel = (PIMAGE_BASE_RELOCATION)(pBuff + RvaToFoa(pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress, pSectionHeader, pFileHeader));
DWORD dwCount = 0;
while (*(PLONGLONG)pRel)
{
printf("[%d] VirtualAddress-> [0x%08x] SizeOfBlock-> [0x%08x] rn", dwCount++, pRel->VirtualAddress, pRel->SizeOfBlock);

//需要修复重定位项个数
DWORD dwRelEntry = (pRel->SizeOfBlock - 8) / 2;

//指向重定位项
//PWORD p1 = (PWORD)pRel;
//PWORD pRelData = p1 + 8;
PWORD pRelData = (PWORD)pRel + 4;

for (size_t i = 0; i < dwRelEntry; i++)
{
//判断高4位
//32位高4位0011
//64位高4位1010
if ((pRelData[i] & 0x3000) == 0x3000)
{
//低12位 + VirtualAddress为真正需要修复数据的RVA
DWORD dwData = pRelData[i] & 0x0FFF;
DWORD dwDataRVA = (DWORD)dwData + (DWORD)pRel->VirtualAddress;
printf("[%d] DATA[0x%04x] RVA[0x%08x]rn", i, dwData|0x3000, dwDataRVA); }
}
//指向下一个重定位结构
pRel = (PIMAGE_BASE_RELOCATION)((PUCHAR)pRel + pRel->SizeOfBlock);
}

}
else (cout.write("打开文件失败", 20));
CloseHandle(hfile);
delete[] pBuff;
}

void main()
{
ImageNtHeader(R"(D:c_projectdlltestDebugDll1.dll)");
}

需要重定位的文件偏移量计算方法

PE结构指南:c语言武器开发基础



文章首发于:渗透测试安全攻防

原文始发于微信公众号(渗透测试安全攻防):PE结构指南:c语言武器开发基础

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年12月23日01:40:15
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   PE结构指南:c语言武器开发基础http://cn-sec.com/archives/2330511.html

发表评论

匿名网友 填写信息