Использование только арифметики указателя для обхода загруженной DLL/EXE для формата файла PE

Моей конечной целью было получить список имен DLL из статической таблицы данных импорта.

Я подумал, что могу сделать что-то вроде:
auto data_dirs = p_loaded_image->FileHeader->OptionalHeader.DataDirectory;

А затем каким-то образом перебрать этот список адресов, а затем таким образом получить имена DLL; что-то такое.

Итак, для детских шагов я просто пытался проверить, могу ли я сопоставить значения для p_loaded_image->FileHeader->OptionalHeader.SizeOfStackCommit; с эквивалентом ручного указателя-математики. Кажется, я не могу сделать это без Access Violation исключений, которые, кажется, подтверждают, что я делаю это неправильно.

Что я сделал не так и как конкретно сделать так, чтобы мой математический запрос указателя соответствовал фактически возвращаемому API загруженного изображения для получения того же значения SizeOfStackCommit? Если вы сможете многому меня научить, я надеюсь, что смогу продвинуться дальше текущего этапа моей WIP по поиску имен DLL.

В целях экономии времени, если ваш компилятор поддерживает 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;
}

Последняя документация по формату PE-файла для Windows представлена ​​здесь, в приложении к техническому документу, в котором содержится его .docx: http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx

Обновление 1:

У меня есть мой чистый арифметический обход указателя, работающий прямо до моей конечной цели. Чтобы добраться до этого момента, мне потребовалось удалить два уровня сложности.

  1. Не возитесь с загрузкой изображения с помощью специальных API, так как я пытаюсь получить статическую информацию об импорте; решение для этого состояло в том, чтобы загрузить EXE в вектор символов, чтобы сделать его снимок в памяти.

  2. Забудьте о слишком сложном звучании RVA, если вам необходимо его использовать. Просто используйте Byte Offsets для заголовков PE. В разделах вам нужно использовать RVA. Просто считайте, что адрес элемента 0 для вектора символов является вашим базовым адресом, для которого вычисляются все RVA. docx также сообщает вам, когда использовать смещение по сравнению с фактическим адресом, что полезно знать. Проверьте мой добавленный ответ, где я кратко рассказываю об использовании RVA для получения таблицы импорта.

Моя программа все еще не делает то, что я хочу, но, по крайней мере, у меня есть суть арифметики указателей, соответствующая средствам доступа к указателям, что и было целью этого вопроса.

Я думаю, что мои оставшиеся блокираторы связаны с тем, какие структуры загружать данными и где. Вы можете собрать и запустить мою суть WIP на компьютере с Win10 или обновить значение ph_file должно быть какой-либо другой локально установленной 64-битной программой в вашей ОС, желательно без раздела .idata. Наличие раздела .idata не гарантируется даже при импорте библиотек DLL. Например, в Calculator.exe его нет.

Обновление 2:

Я получил некоторую помощь в отладке и, наконец, заработал. Код POC, так что почти ничего не подчищено и не оптимизировано, но он функционален. Проверено на двоичных файлах x86/win32 и x64. Суть здесь.


person kayleeFrye_onDeck    schedule 03.05.2017    source источник
comment
Когда вы прошли через отладчик по закомментированной строке, проверили ли вы, какие значения были присвоены переменным size_t: coverted_base_address и size_stack_commit_offset, чтобы убедиться, что вы получаете те значения, которые должны были ожидать? То же самое можно сказать и о следующей строке кода.   -  person Francis Cugler    schedule 03.05.2017
comment
Пока вы #include Windows.h, вы можете использовать его для решения своей проблемы. CreateToolhelp32Snapshot() + Module32First/Next(). Акцент на снимке, без него никогда не бывает безопасно.   -  person Hans Passant    schedule 03.05.2017
comment
Святые макароны, @HansPassant! CreateToolhelp32Snapshot звучит отлично! Является ли это программным аварийным дампом или даже лучше/дополнительной информацией? Хотел бы я знать об этом раньше...   -  person kayleeFrye_onDeck    schedule 04.05.2017
comment
@FrancisCugler Я не думаю, что получаю те значения, которые ожидал. Использование converted_base_address и добавление серии смещений не дает адреса значения SizeOfStackCommit. Добавление только size_stack_commit_offset к converted_base_address тоже не помогает, поэтому у меня проблема с математикой указателей. Я выгрузил переменные конца выполнения в виде списка: -нос-знает/b08a188665c3fd3d78d04a0ec6ce89c0. Я добавил еще одну строку для тестирования, DWORD64 * addy = &direct_access_size_stack;   -  person kayleeFrye_onDeck    schedule 04.05.2017
comment
Я добавил переменные auto, чтобы захватить все части p_loaded_image, а также выгрузил их в суть. Это должно помочь пролить свет на возможные проблемы с перекрестными ссылками/смещением: /знает-нос/11dcdc481a8196a931c710b7ad160f9c   -  person kayleeFrye_onDeck    schedule 04.05.2017
comment
@kayleeFrye_onDeck: Вы предполагаете, что FileHeader находится в начале сопоставленного раздела (0x000001560cd90000), но это не так. Туда помещается заглушка DOS, а в этом заголовке находится относительное расположение (0x000001560cd90100) FileHeader.   -  person Ben Voigt    schedule 04.05.2017
comment
И ваша переменная addy ничего не значит, это адрес локальной переменной, содержащей копию ваших данных. Вы хотели &p_loaded_image->FileHeader->OptionalHeader.SizeOfStackCommit   -  person Ben Voigt    schedule 04.05.2017
comment
Я считаю, что ваш windows_optional_header_offset неверен. Это не должно быть БОЛЬШЕ для 32-разрядных, чем для 64-разрядных, и я думаю, что на самом деле это константа, которая не зависит от архитектуры. Глядя на определение структуры, я думаю, что у вас есть переменные 4x2 байта и переменные 3x4 байта, всего windows_optional_header_offset=20.   -  person Ben Voigt    schedule 04.05.2017
comment
Похоже, что смещение для 32-битного больше, чем для 64-битного, но размер для 64-битного удваивается, согласно последней документации: i.imgur.com/OjDfs1q.png   -  person kayleeFrye_onDeck    schedule 04.05.2017
comment
@kayleeFrye_onDeck: это смещение от начала поля IMAGE_OPTIONAL_HEADER до поля ImageBase. Это не имеет ничего общего с поиском начала необязательного заголовка.   -  person Ben Voigt    schedule 04.05.2017
comment
Я не вижу смещение, указанное для самого необязательного заголовка в разделе 3.4 Optional Header (Image Only) - есть ли какой-то способ, не упомянутый на этом снимке экрана, о том, как его получить? i.imgur.com/xzzvnq3.png   -  person kayleeFrye_onDeck    schedule 04.05.2017
comment
Ребята спасибо всем за помощь!!! Как только я переключился на загрузку EXE в векторе вместо того, чтобы загружать его, указывая на диск и анализируя то, что находится на диске с этого адреса, стало намного проще следовать документации для VA и RVA. Прыжок в winnt.h тоже не помешал!   -  person kayleeFrye_onDeck    schedule 23.05.2017


Ответы (2)


Я не верю вам, что указанная строка (вычисление sum_for_size_stack) вызывает нарушение прав доступа. Это просто беззнаковая арифметика, которая не может переполниться или привести к ловушке.

Я действительно считаю, что вы получаете нарушение прав доступа от printf, потому что вы используете спецификатор формата %s с аргументом, который не является указателем на строку ASCII с нулевым завершением. Я понятия не имею, что натолкнуло вас на мысль, что размеры стеков хранятся в виде строк или что хорошей идеей будет передать size_t функции с переменным числом переменных, требующей const char*, но ни то, ни другое неверно.

Обратите внимание на предварительные условия printf. Строка правильного формата для параметра size_t%zx.

person Ben Voigt    schedule 03.05.2017
comment
Спасибо, что поделились своими знаниями и опытом, которые помогли мне понять проблему нарушения прав доступа! IIRC, я преобразовывал size_t таким образом, прежде чем использовать printf, char* converted_ptr = (char*)a_size_t_value;, и забыл повторно добавить эту часть и использовать преобразованное значение вместо size_t :( Я не понял, почему это была проблема пока не указали ^_^ - person kayleeFrye_onDeck; 04.05.2017
comment
@kayleeFrye_onDeck: Ну, это правильно передает аргумент для извлечения в качестве указателя, но printf нужно не только иметь возможность читать const char* из своего списка аргументов, указатель должен указывать на строку ASCII с нулевым завершением, что и у вас есть нет. Распечатайте значение size_t (используя %zx) или значение указателя (используя %p), прежде чем беспокоиться о значении, хранящемся в указанной ячейке памяти. - person Ben Voigt; 04.05.2017
comment
Ах я вижу! Хотя этого могло бы быть достаточно, чтобы получить имена DLL, другие поля в загруженном образе PE явно не все строки ASCII с нулевым завершением! :( Еще раз спасибо! Я последовал вашему совету и обновил свой фактический WIP для нескольких других printf проблем, которые были менее важными. - person kayleeFrye_onDeck; 04.05.2017

Отдавая должное Бену за это, поскольку, хотя это не разблокировало меня, он указал мне правильное направление, когда определил мое непонимание указателей в связи с выделением памяти и инициализацией указателя из адреса, а не из объекта. Чтобы преодолеть это, я провел некоторые исследования и упражнения, чтобы:

  1. Понимать, как указатели C/C++ и работают в памяти, а также в отношении расположения элементов в структурах через смещения байтов.
  2. Используйте правильные указатели для правильной загрузки данных

Выполнение этих двух вещей разблокировало путь для использования чистой арифметики указателей.

Моя проблема в этом отношении заключается в том, что мне нужно было только самое базовое понимание указателей при их использовании в прошлом. Этот процесс требовал фактического понимания распределения и обхода памяти.

В целом, это был отличный опыт повторного обучения!

В определенный момент доступ к данным через массив символов для загрузки файла требует использования 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

person kayleeFrye_onDeck    schedule 22.05.2017