仅使用指针算法遍历加载的DLL / EXE以获取PE文件格式

时间:2017-05-03 05:46:32

标签: c++ windows pointer-arithmetic portable-executable

我的最终目标是从静态导入数据表中获取DLL名称列表。

我以为我可以做类似的事情 auto data_dirs = p_loaded_image->FileHeader->OptionalHeader.DataDirectory;

然后以某种方式遍历该地址列表,然后以这种方式获取DLL名称;类似的东西。

因此,对于婴儿步骤,我只是想验证我可以将p_loaded_image->FileHeader->OptionalHeader.SizeOfStackCommit;的值与手动指针 - 数学等价物相匹配。如果没有Access Violation例外,我似乎无法做到这一点,这似乎证明我做错了。

我做错了什么,以及具体如何让我的指针数学查询与实际加载的图片的API返回相匹配,以获得相同的SizeOfStackCommit值?如果你可以教我这么多,我希望从目前我的DLL名称查找WIP中取得进展。

为节省时间,如果您的编译器支持std::experimental::filesystem,您可以从// Skip to here的注释开始,以避免所有控制台和文件验证样板,否则您需要将其存根将其更改为更适合旧版C ++规范的内容。

#include "Windows.h"
#include "Imagehlp.h"
#include "tchar.h"
#include "stdio.h"
#include "stdlib.h"

#include <string>
#include <vector>
#include <experimental\filesystem>

// All hard-coded values taken directly from latest PE/COFF .docx Documentation from MS:
// => http://go.microsoft.com/fwlink/p/?linkid=84140

const int MAGIC_32_NUM = 0x10b;
const int MAGIC_64_NUM = 0x20b;

// These two require C++17 || If needed, replace with older valid file-verification.
namespace fs = std::experimental::filesystem;
bool verify_loaded_file(std::string);

int _tmain(int argc, _TCHAR* argv[])
{
    std::string image_to_load;
    if (argc == 2) {
        image_to_load = argv[1];
    }
    else {
        printf("A valid path to a loadable image needs to be your only command-line parameter for %s", argv[0]);
        return -1;
    }

    bool validFile = verify_loaded_file(image_to_load);

    if (!validFile) {
        printf("A valid file path of a DLL or EXE needs to be your only command-line parameter for %s", argv[0]);
        return -1;
    }

    auto filesystem_image                   = fs::absolute(fs::path(image_to_load));
    std::string image_directory             = filesystem_image.parent_path().string();
    std::string image_name                  = filesystem_image.stem().string();
    std::string image_name_and_extension    = image_name + filesystem_image.extension().string();
    bool is64bit, is32bit                   = false;

    // To use MapAndLoad, you need to manually include Imagehlp.lib in your project.
    // The Imagehlp.h header alone does not suffice.
    LOADED_IMAGE loaded_image = { 0 };
    LOADED_IMAGE * p_loaded_image = &loaded_image;
    bool image_loaded = MapAndLoad(image_name_and_extension.c_str(), image_directory.c_str(), p_loaded_image, FALSE, TRUE);
    int error_check = GetLastError();

    if (!image_loaded) {
        printf("Something went wrong when trying to load %s0 with error code %s1", image_to_load.c_str(), error_check);
        UnMapAndLoad(p_loaded_image);
        return -1;
    }   

    int magic_number = loaded_image.FileHeader->OptionalHeader.Magic;

    if      (magic_number == MAGIC_32_NUM) { is32bit = true; }
    else if (magic_number == MAGIC_64_NUM) { is64bit = true; }
    else {
        printf("The magic number from the optional header wasn't detected as 32-bit or 64-bit\n");
        printf("Check Windows System Error Code: %s\n", magic_number);
        UnMapAndLoad(p_loaded_image);
        return -1;
    }

    // Skip to here
    UCHAR * module_base_address = p_loaded_image->MappedAddress;
    size_t coverted_base_address = size_t(module_base_address);

    size_t windows_optional_header_offset;
    if (is64bit) { windows_optional_header_offset = size_t(24); }
    else { windows_optional_header_offset = size_t(28); }

    size_t data_directory_optional_header_offset;
    if (is64bit) { data_directory_optional_header_offset = size_t(112); }
    else { data_directory_optional_header_offset = size_t(96); }

    size_t size_stack_commit_offset;
    if (is64bit) { size_stack_commit_offset = size_t(80); }
    else { size_stack_commit_offset = size_t(76); }

    // The commented out line below breaks with Access Violations, as does the line following it:
    // auto sum_for_size_stack = size_t(coverted_base_address + size_stack_commit_offset);
    auto sum_for_size_stack = size_t(coverted_base_address + 
                                    windows_optional_header_offset + 
                                    data_directory_optional_header_offset + 
                                    size_stack_commit_offset);

    auto direct_access_size_stack = p_loaded_image->FileHeader->OptionalHeader.SizeOfStackCommit;
    DWORD64 * addy = &direct_access_size_stack;

    printf("Direct: %s\n", direct_access_size_stack);
    printf("Pointer-Math: %s\n", sum_for_size_stack);

    UnMapAndLoad(p_loaded_image);
    return 0;
}

//

bool verify_loaded_file(std::string file_to_verify)
{
    if (fs::exists(file_to_verify))
    {
        size_t extension_query = file_to_verify.find(".dll", 0);
        if (extension_query == std::string::npos)
        {
            extension_query = file_to_verify.find(".DLL", 0);
            if (extension_query == std::string::npos)
            {
                extension_query = file_to_verify.find(".exe", 0);
                if (extension_query == std::string::npos)
                {
                    extension_query = file_to_verify.find(".EXE", 0);
                }
                else { return true; }

                if (extension_query != std::string::npos) { return true; }
            }
            else { return true; }
        }
        else { return true; }   
    }
    return false;
}

Windows的PE文件格式在白皮书附件中有最新文档,其中包含.docxhttp://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx

更新1:

我的纯指针算术遍历工作直到我的终点目标之前。达到这一点需要我删除两层复杂性。

  1. 不要使用特殊的API加载图片,因为我试图获取静态导入信息;解决方案是将EXE加载到char矢量中以在内存中快照它。

  2. 忘记过于复杂的声音RVA垃圾,除非你需要使用它。只需使用Byte Offsets作为PE的标题部分。 部分是您需要使用RVA的地方。只需将char向量的元素0的地址视为您的基地址,计算所有RVA。 docx还告诉您何时使用偏移量与实际地址,这很好。检查我添加的答案,我简要地谈谈使用RVA获取导入表。

  3. 我的程序仍然没有做我想要的,但至少我有指针算术匹配指针访问器的要点,这是这个问题的目标。

    我认为我剩下的阻止程序与要加载数据的结构有关,以及在哪里。您可以在Win10框上构建和运行my WIP gist,或者将ph_file的值更新为您的操作系统上的其他本地安装的64位程序,最好是没有.idata部分的程序。即使导入DLL,.idata部分也不会保证存在。作为一个例子,Calculator.exe没有它。

    更新2:

    我得到了一些调试帮助,最后得到了这个工作。代码是POC,所以没有任何东西被清理或优化,但它是有用的。针对x86 / win32和x64二进制文件进行了测试。 Gist here

2 个答案:

答案 0 :(得分:4)

我不相信您所指示的行(sum_for_size_stack的计算)会导致访问冲突。它只是无符号算术,不会溢出或导致陷阱值。

我确实认为您从printf获得了访问冲突,因为您使用的%s格式说明符的参数不是指向NUL终止的ASCII字符串的指针。我不知道是什么让你认为堆栈大小存储为字符串,或者将size_t传递给需要const char*的可变参数函数是个好主意,但两者都不是真的。

注意printf的先决条件。 size_t参数的正确格式字符串为%zx

答案 1 :(得分:0)

给予Ben这个奖励,因为即使它没有解锁我,他指出了我正确的方向,当他发现我对与内存分配相关的指针和从地址而不是对象的指针初始化缺乏理解。为了克服这个问题,我做了一些学习和练习,以便:

  1. 了解C / C ++指针结构在内存中的工作方式,以及通过字节偏移在结构中的成员位置
  2. 使用正确的指针正确加载数据
  3. 执行这两项操作,取消阻止纯指针算术用途的路径。

    我在这方面的问题是,在过去使用指针时我只需要对指针的最基本的理解。这个过程需要实际理解内存分配和遍历。

    总的来说,这是一次很棒的重新学习经历!

    在某一时刻,通过char数组访问以加载文件的数据需要使用RVA。为了实现这一点,您需要获得具有所需数据的正确IMAGE_SECTION_HEADER结构。您可以使用该结构来计算RVA,如下所示:导入表:

    if  (queried_section_header->PointerToRawData >= import_table_data_dir->VirtualAddress &&
        (queried_section_header->PointerToRawData < (import_table_data_dir->VirtualAddress + queried_section_header->SizeOfRawData)))
    {
        DWORD import_table_offset = queried_section_header->PointerToRawData - import_table_data_dir->VirtualAddress + queried_section_header->PointerToRawData;
    }
    

    我没有亲自使用本指南来理解指针,但一眼看上去很有希望:http://home.netcom.com/~tjensen/ptr/pointers.htm

    如果它过期,此快照可能仍在:https://web.archive.org/web/20161208002919/http://home.netcom.com/~tjensen/ptr/pointers.htm