PE基础4-练习-导入表-到处表-重定位表-重写写成新文件

PE基础练习

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

struct TypeOffset
{
    WORD Offset : 12;
    WORD Type : 4;
};

// 保存文件大小
DWORD FileSize = 0;

// 保存文件的基址
DWORD FileBase;

// 保存DOS头
PIMAGE_DOS_HEADER DosHeader = nullptr;

PIMAGE_NT_HEADERS NtHeader = nullptr;


DWORD RVAtoFOA(DWORD rva)
{
    // 1. 获取区段表
    auto SectionTables = IMAGE_FIRST_SECTION(NtHeader);

    // 2. 获取区段数量
    WORD Count = NtHeader->FileHeader.NumberOfSections;

    // 3. 遍历区段
    for (int i = 0; i < Count; ++i)
    {
        // 4. 判断是否存在于区段中
        if (rva >= SectionTables[i].VirtualAddress &&
            rva < (SectionTables[i].VirtualAddress + SectionTables[i].SizeOfRawData))
        {
            // 5. 找到之后计算位置并返回值
            return rva - SectionTables[i].VirtualAddress + SectionTables[i].PointerToRawData;
        }
    }

    // 6. 没有找到返回 -1
    return -1;
}


VOID OpenPeFile(LPCSTR FileName)
{
    // 1. 打开文件
    HANDLE Handle = CreateFileA(FileName, GENERIC_READ, NULL,
        NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    // 2. 获取文件大小
    FileSize = GetFileSize(Handle, NULL);

    // 3. 读取文件数据
    DWORD OperSize = 0;
    FileBase = (DWORD)new BYTE[FileSize];
    ReadFile(Handle, (LPVOID)FileBase, FileSize, &OperSize, NULL);

    // 4. 判断是不是一个有效的 PE 文件
    // 4.1 获取DOS头并判断是不是一个有效的DOS文件
    DosHeader = (PIMAGE_DOS_HEADER)FileBase;
    if (DosHeader->e_magic != IMAGE_DOS_SIGNATURE)
    {
        MessageBox(NULL, "不是一个有效的DOS文件", "提示", MB_OK);
        system("pause"); exit(0);
    }

    // 4.2 获取 NT 头并判断是不是一个有效的PE文件
    NtHeader = (PIMAGE_NT_HEADERS)(FileBase + DosHeader->e_lfanew);
    if (NtHeader->Signature != IMAGE_NT_SIGNATURE)
    {
        MessageBox(NULL, "不是一个有效的PE文件", "提示", MB_OK);
        system("pause"); exit(0);
    }

    // 4.3 判断是不是一个32位文件
    if (NtHeader->OptionalHeader.Magic != 0x010B)
    {
        MessageBox(NULL, "不是一个有效的32位文件", "提示", MB_OK);
        system("pause"); exit(0);
    }

    CloseHandle(Handle);
}

// 遍历重定位表
VOID FixReloc()
{
    DWORD base = NtHeader->OptionalHeader.ImageBase;

    // 1. 获取重定位表的 rva
    DWORD RelocRVA = NtHeader->OptionalHeader.DataDirectory[5].VirtualAddress;

    // 2. 获取重定位表
    auto Reloc = (PIMAGE_BASE_RELOCATION)(FileBase + RVAtoFOA(RelocRVA));

    // 3. 遍历重定位表中的重定位块,以0结尾
    while (Reloc->SizeOfBlock != 0)
    {
        // 3.1 输出分页基址
        printf("PAGE_BASE: %08X\n", Reloc->VirtualAddress);
    
        // 3.2 找到重定位项
        auto Offset = (TypeOffset*)(Reloc + 1);
        
        // 3.3 计算重定位项的个数
        // Reloc->SizeOfBlock 保存的是整个重定位块的大小 结构体 + 重定位项数组
        // Reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION) 得到数组大小
        // 上面的结果 \ 2 = 重定位项的个数,原因是重定位项的大小为 两个字节
        DWORD Size = (Reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2;

        // 3.4 遍历所有的重定位项
        for (int i = 0; i < Size; ++i)
        {
            // 获取重定位类型,只关心为3的类型
            DWORD Type = Offset[i].Type;

            // 获取重定位的偏移值
            DWORD pianyi = Offset[i].Offset;

            // 获取要重定位的地址所在的RVA: offset+virtualaddress
            DWORD rva = pianyi + Reloc->VirtualAddress;

            // 获取要重定位的地址所在的FOA
            DWORD foa = RVAtoFOA(rva);

            // 获取要重定位的地址所在的fa
            DWORD fa = foa + FileBase;

            // 获取要重定位的地址
            DWORD addr = *(DWORD*)fa;

            // 计算重定位后的数据: addr - oldbase + newbase
            DWORD new_addr = addr - base + 0x1500000;

            // 将重定位后的数据写回缓冲区(文件)
            if (Offset[i].Type == 3)
                *(DWORD*)fa = new_addr;

            // printf("\tT(%d)%08X->%08X: %08X[%08X]\n", Type, rva, foa, addr, new_addr);
        }

        // 找到下一个重定位块
        Reloc = (PIMAGE_BASE_RELOCATION)
            ((DWORD)Reloc + Reloc->SizeOfBlock);
    }

    NtHeader->OptionalHeader.ImageBase = 0x1500000;

    // 1. 打开文件
    HANDLE Handle = CreateFileA("newfile.exe", GENERIC_WRITE, NULL,
        NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);


    // 3. 写入文件数据
    DWORD OperSize = 0;
    WriteFile(Handle, (LPVOID)FileBase, FileSize, &OperSize, NULL);

    CloseHandle(Handle);
}

VOID Import()
{
    // 1. 从数据目录表的下标为 1的项找到rva
    DWORD rav = NtHeader->OptionalHeader.DataDirectory[1].VirtualAddress;

    // 2. 找到导入表结构体
    auto ImportTable = (PIMAGE_IMPORT_DESCRIPTOR)(RVAtoFOA(rav) + FileBase);

    // 3. 遍历导入表数组,数组以全 0 结尾
    while (ImportTable->Name)
    {
        // 4. 输出对应DLL的名字
        CHAR* DllName = (CHAR*)(RVAtoFOA(ImportTable->Name) + FileBase);
        printf("DLLName: %s\n", DllName);

        // 5. 找到 iat
        auto Iat = (PIMAGE_THUNK_DATA)(RVAtoFOA(ImportTable->FirstThunk) + FileBase);

        // 6. 遍历 iat ,全 0 结尾
        while (Iat->u1.Ordinal != 0)
        {
            // 7. 判断是否有名字
            if (Iat->u1.AddressOfData & 0x80000000)
            {
                // 序号导入,直接输出
                printf("\t[%hd]: None\n", LOWORD(Iat->u1.AddressOfData));
            }
            else
            {
                // 找到名字结构体
                auto Name = (PIMAGE_IMPORT_BY_NAME)(RVAtoFOA(Iat->u1.AddressOfData) + FileBase);
                printf("\t[%hd]: %s\n", Name->Hint, Name->Name);
            }
            ++Iat;
        }

        // 指向下一个结构
        ImportTable++;
    }

}

VOID Export()
{
    // 1. 从数据目录表的下标为 0 的项找到rva
    DWORD rav = NtHeader->OptionalHeader.DataDirectory[0].VirtualAddress;

    // 2. 找到导入表结构体
    auto ExportTable = (PIMAGE_EXPORT_DIRECTORY)(RVAtoFOA(rav) + FileBase);

    // 3. 获取有名字的个数和函数总个数
    DWORD NameCount = ExportTable->NumberOfNames;
    DWORD FunctionCount = ExportTable->NumberOfFunctions;

    // 4. 获取三张表,序号表是WORD
    DWORD* 地址表 = (DWORD*)(RVAtoFOA(ExportTable->AddressOfFunctions) + FileBase);
    DWORD* 名称表 = (DWORD*)(RVAtoFOA(ExportTable->AddressOfNames) + FileBase);
    WORD* 序号表 = (WORD*)(RVAtoFOA(ExportTable->AddressOfNameOrdinals) + FileBase);
    
    // 5. 遍历地址表
    for (int i = 0; i < FunctionCount; ++i)
    {
        bool HaveName = FALSE;

        // 6. 判断是否有名字,有名字的话,下标会存在序号表中
        for (int j = 0; j < NameCount; ++j)
        {
            // 有名字
            if (i == 序号表[j])
            {
                HaveName = TRUE;
                // 对应序号表下标的名称表内保存的是名字
                CHAR* Name = (CHAR*)(RVAtoFOA(名称表[j]) + FileBase);

                printf("[%hd]: %p %s\n", i + ExportTable->Base, 地址表[i], Name);
                break;
            }
        }

        // 如果全部找完还没有名字
        if (HaveName == FALSE)
        {
            printf("[%hd]: %p None\n", i + ExportTable->Base, 地址表[i]);
        }

    }
}

int main()
{
    OpenPeFile("test.dll");

    // FixReloc();
    // Import();
    // Export();

    return 0;
}

猜你喜欢

转载自www.cnblogs.com/ltyandy/p/11099355.html