首页
论坛
课程
招聘
[原创]PE加载器的简单实现
2019-1-25 20:44 14140

[原创]PE加载器的简单实现

2019-1-25 20:44
14140

模拟PE加载器加载PE文件,对导入表以及重定位表的操作过程。

 

PE加载器的步骤:

 

1.将PE文件用ReadFile读取数据

char szFileName[] = "1.exe";

//打开文件,设置属性可读可写
HANDLE hFile = CreateFileA(szFileName,
    GENERIC_READ | GENERIC_WRITE,
    FILE_SHARE_READ | FILE_SHARE_WRITE,
    NULL,
    OPEN_EXISTING,
    FILE_ATTRIBUTE_ARCHIVE,
    NULL);

if (INVALID_HANDLE_VALUE == hFile)
{
    printf("文件打开失败\n");
    return 1;
}

//获取文件大小
DWORD dwFileSize = GetFileSize(hFile, NULL);

//申请空间将exe读取到内存中
char *pData = new char[dwFileSize];
if (NULL == pData)
{
    printf("空间申请失败\n");
    return 2;
}

DWORD dwRet = 0;
ReadFile(hFile, pData, dwFileSize, &dwRet, NULL);
CloseHandle(hFile);

2.根据PE结构获取镜像大小,在自己的程序中申请可读可写可执行的内存。

char* chBaseAddress = NULL;

//获取镜像大小
DWORD dwSizeOfImage = GetSizeOfImage(pFileBuff);

//在进程中开辟一块内存空间
chBaseAddress = (char*)VirtualAlloc(NULL,
    dwSizeOfImage,
    MEM_COMMIT | MEM_RESERVE,
    PAGE_EXECUTE_READWRITE);
if (NULL == chBaseAddress)
{
    printf("申请进程空间失败\n");
    return NULL;
}

3.将申请的空间全部填为0

RtlZeroMemory(chBaseAddress, dwSizeOfImage);

4.将用ReadFile读取的数据映射到内存中

 


由上图可知,PE文件在加载到内存中对齐粒度不同,因此在写代码时要注意这个问题

PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pFileBuff;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pFileBuff + pDos->e_lfanew);
PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNt);
//所有头 + 结表头的大小
DWORD dwSizeOfHeaders = pNt->OptionalHeader.SizeOfHeaders;
//获取区段数量
int nNumerOfSections = pNt->FileHeader.NumberOfSections;

// 将前一部分都拷贝过去
RtlCopyMemory(chBaseAddress, pFileBuff, dwSizeOfHeaders);

char* chSrcMem = NULL;
char* chDestMem = NULL;
DWORD dwSizeOfRawData = 0;
for (int i = 0; i < nNumerOfSections; i++)
{
    if ((0 == pSection->VirtualAddress) ||
        (0 == pSection->SizeOfRawData))
    {
        pSection++;
        continue;
    }

    chSrcMem = (char*)((DWORD)pFileBuff + pSection->PointerToRawData);
    chDestMem = (char*)((DWORD)chBaseAddress + pSection->VirtualAddress);
    dwSizeOfRawData = pSection->SizeOfRawData;
    RtlCopyMemory(chDestMem, chSrcMem, dwSizeOfRawData);

    pSection++;
}

return TRUE;

5.修复重定位

 

重定位后的地址 = 需要重定位的地址 - 默认加载基址 + 当前加载基址

PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)chBaseAddress;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(chBaseAddress + pDos->e_lfanew);
PIMAGE_BASE_RELOCATION pLoc = (PIMAGE_BASE_RELOCATION)(chBaseAddress + pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);

//判断是否有重定位表
if ((char*)pLoc == (char*)pDos)
{
    return TRUE;
}

while ((pLoc->VirtualAddress + pLoc->SizeOfBlock) != 0) //开始扫描重定位表
{
    WORD *pLocData = (WORD *)((PBYTE)pLoc + sizeof(IMAGE_BASE_RELOCATION));
    //计算需要修正的重定位项(地址)的数目
    int nNumberOfReloc = (pLoc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);

    for (int i = 0; i < nNumberOfReloc; i++)
    {
        // 每个WORD由两部分组成。高4位指出了重定位的类型,WINNT.H中的一系列IMAGE_REL_BASED_xxx定义了重定位类型的取值。
        // 低12位是相对于VirtualAddress域的偏移,指出了必须进行重定位的位置。

        if ((DWORD)(pLocData[i] & 0x0000F000) == 0x00003000) //这是一个需要修正的地址
        {
            DWORD* pAddress = (DWORD *)((PBYTE)pDos + pLoc->VirtualAddress +                                                                    (pLocData[i] & 0x0FFF));    
            DWORD dwDelta = (DWORD)pDos - pNt->OptionalHeader.ImageBase;
            *pAddress += dwDelta;
        }
    }

    //转移到下一个节进行处理
    pLoc = (PIMAGE_BASE_RELOCATION)((PBYTE)pLoc + pLoc->SizeOfBlock);
}

return TRUE;

6.根据PE结构的导入表,加载所需的dll,并获取导入函数的地址并写入导入表中

PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)chBaseAddress;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(chBaseAddress + pDos->e_lfanew);
PIMAGE_IMPORT_DESCRIPTOR pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pDos +
    pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

// 循环遍历DLL导入表中的DLL及获取导入表中的函数地址
char *lpDllName = NULL;
HMODULE hDll = NULL;
PIMAGE_THUNK_DATA lpImportNameArray = NULL;
PIMAGE_IMPORT_BY_NAME lpImportByName = NULL;
PIMAGE_THUNK_DATA lpImportFuncAddrArray = NULL;
FARPROC lpFuncAddress = NULL;
DWORD i = 0;

while (TRUE)
{
    if (0 == pImportTable->OriginalFirstThunk)
    {
        break;
    }

    // 获取导入表中DLL的名称并加载DLL
    lpDllName = (char *)((DWORD)pDos + pImportTable->Name);
    hDll = GetModuleHandleA(lpDllName);
    if (NULL == hDll)
    {
        hDll = LoadLibraryA(lpDllName);
        if (NULL == hDll)
        {
            pImportTable++;
            continue;
        }
    }

    i = 0;
    // 获取OriginalFirstThunk以及对应的导入函数名称表首地址
    lpImportNameArray = (PIMAGE_THUNK_DATA)((DWORD)pDos + pImportTable->OriginalFirstThunk);
    // 获取FirstThunk以及对应的导入函数地址表首地址
    lpImportFuncAddrArray = (PIMAGE_THUNK_DATA)((DWORD)pDos + pImportTable->FirstThunk);
    while (TRUE)
    {
        if (0 == lpImportNameArray[i].u1.AddressOfData)
        {
            break;
        }

        // 获取IMAGE_IMPORT_BY_NAME结构
        lpImportByName = (PIMAGE_IMPORT_BY_NAME)((DWORD)pDos + lpImportNameArray[i].u1.AddressOfData);

        // 判断导出函数是序号导出还是函数名称导出
        if (0x80000000 & lpImportNameArray[i].u1.Ordinal)
        {
            // 序号导出
            lpFuncAddress = GetProcAddress(hDll, (LPCSTR)                           (lpImportNameArray[i].u1.Ordinal & 0x0000FFFF));
        }
        else
        {
            // 名称导出
            lpFuncAddress = GetProcAddress(hDll, (LPCSTR)lpImportByName->Name);
        }
        lpImportFuncAddrArray[i].u1.Function = (DWORD)lpFuncAddress;
        i++;
    }

    pImportTable++;
}

7.修改PE文件的加载基址

PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)chBaseAddress;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(chBaseAddress + pDos->e_lfanew);
pNt->OptionalHeader.ImageBase = (ULONG32)chBaseAddress;

8.跳转到PE的入口点处执行

PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)chBaseAddress;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(chBaseAddress + pDos->e_lfanew);
char* ExeEntry = (char*)(chBaseAddress + pNt->OptionalHeader.AddressOfEntryPoint);
// 跳转到入口点处执行
__asm
{
    mov eax, ExeEntry
    jmp eax
}

通过上述代码,成功加载并运行程序:

 


[公告] 欢迎大家踊跃尝试高研班11月试题,挑战自己的极限!

最后于 2019-2-27 19:25 被Iam0x17编辑 ,原因:
上传的附件:
收藏
点赞8
打赏
分享
最新回复 (41)
雪    币: 6255
活跃值: 活跃值 (964)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jgs 活跃值 2019-1-25 22:53
2
1
普及文章,收藏备用,谢谢楼主
雪    币: 657
活跃值: 活跃值 (576)
能力值: ( LV5,RANK:73 )
在线值:
发帖
回帖
粉丝
bambooqj 活跃值 2019-1-26 20:13
3
0
科普文章 感谢 
雪    币: 45
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xbra 活跃值 2019-1-26 22:47
4
0
谢谢!很实用
雪    币: 2
活跃值: 活跃值 (1017)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
咖啡_741298 活跃值 2019-1-27 00:18
5
0
64位PE呢??
雪    币: 204
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xingke 活跃值 2019-1-27 08:37
6
1
建议打包个源码文件
雪    币: 13650
活跃值: 活跃值 (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Mr Julius 活跃值 2019-1-27 09:06
7
0
咖啡_741298 64位PE呢??
64位PE叫做PE32+,和PE32除了头部分之外基本相同。
雪    币: 3158
活跃值: 活跃值 (55)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
我是一条咸鱼 活跃值 2019-1-27 09:25
8
0
顶一下
雪    币: 2356
活跃值: 活跃值 (679)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
sinkay 活跃值 2019-1-29 20:01
9
0
mark顶一下
雪    币: 1581
活跃值: 活跃值 (211)
能力值: ( LV13,RANK:460 )
在线值:
发帖
回帖
粉丝
shayi 活跃值 9 2019-1-29 21:00
10
0
支持


最后于 2019-1-29 21:02 被shayi编辑 ,原因:
雪    币: 0
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
张勋_652486 活跃值 2019-1-29 21:11
11
0
学习了
雪    币: 4347
活跃值: 活跃值 (807)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Iceshara 活跃值 2019-1-29 22:16
12
0
目测是科锐的学生~ 
雪    币: 1100
活跃值: 活跃值 (163)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
ttstation 活跃值 2019-1-30 00:12
13
0
有个64位PE 的加载器就完美了!!!
雪    币: 279
活跃值: 活跃值 (501)
能力值: ( LV9,RANK:150 )
在线值:
发帖
回帖
粉丝
Iam0x17 活跃值 1 2019-1-30 09:24
14
0
Iceshara 目测是科锐的学生~
15pb
雪    币: 4347
活跃值: 活跃值 (807)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Iceshara 活跃值 2019-1-30 14:50
15
2
看雪已经成为了科锐和51PB的作业论坛了.....   文章质量下滑严重 
雪    币: 4022
活跃值: 活跃值 (680)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
yimingqpa 活跃值 1 2019-1-30 15:51
16
0
没有异常处理的PE LOAD都是 渣渣。
雪    币: 54
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
兰陵 活跃值 2019-1-30 16:24
17
0
马克
雪    币: 279
活跃值: 活跃值 (501)
能力值: ( LV9,RANK:150 )
在线值:
发帖
回帖
粉丝
Iam0x17 活跃值 1 2019-1-30 17:48
18
0
 
最后于 2019-1-30 17:50 被Iam0x17编辑 ,原因:
雪    币: 279
活跃值: 活跃值 (501)
能力值: ( LV9,RANK:150 )
在线值:
发帖
回帖
粉丝
Iam0x17 活跃值 1 2019-1-30 17:50
19
0
 
最后于 2019-1-30 17:50 被Iam0x17编辑 ,原因:
雪    币: 375
活跃值: 活跃值 (174)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
virsnow 活跃值 2019-2-19 15:18
20
0
支持,学习了,还少一个GetSizeOfImage(pFileBuff)函数,我怎么总是不成功?有源码吗,发一份给,谢谢!
雪    币: 501
活跃值: 活跃值 (170)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
Justgoon 活跃值 1 2019-2-25 13:23
21
0
感谢分享
雪    币: 836
活跃值: 活跃值 (708)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yllen 活跃值 2019-2-25 17:12
22
0
Iceshara 看雪已经成为了科锐和51PB的作业论坛了..... 文章质量下滑严重 [em_38]
雪    币: 3002
活跃值: 活跃值 (795)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
qj111111 活跃值 2019-2-26 10:56
23
0
楼主有源码么,可以发一份不,最近在学这部分内容,照着你的思路自己写的,总是失败,排查bug也查了十几个小时也查不到在哪,和你发的源码一点点比对也找不出来,吐血了.
雪    币: 3002
活跃值: 活跃值 (795)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
qj111111 活跃值 2019-2-27 17:33
24
0
你这人怎么关键性的东西留一手?如果这个程序没有重定位表,这种方法根本就没法实现PE加载器,所有全局变量全是错的
雪    币: 3002
活跃值: 活跃值 (795)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
qj111111 活跃值 2019-2-27 17:45
25
0
而大多数.exe都没有重定位表,怎么去实现这个加载器?
游客
登录 | 注册 方可回帖
返回