PE file format

2026-07-31

Старые заметки по PE формату

Для работы под виндой необходимо знать PE file format <- очень понятная схема формата Полезные ссылки: * https://ferreirasc.github.io/PE-Export-Address-Table/ * https://ferreirasc.github.io/PE-imports/ * PE Portable Executable File Format | NutCrackersSecurity

(1) PE file format, Windows API

(1.1) PE parser

Header, Imports, Delay Imports, RSRC, Debug, Bound Imports, Load Config, Security Directory, Forensic Flags (писал не я)

#include <errno.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define DIR_COUNT 16U
#define MAX_RECURSION 8U
#define MAX_ITEMS 4096U

#define MZ_SIG 0x5A4DU
#define PE_SIG 0x00004550U
#define PE32_MAGIC 0x10BU
#define PE64_MAGIC 0x20BU

#define ORD32 0x80000000U
#define ORD64 0x8000000000000000ULL

enum {
    DIR_EXPORT = 0,
    DIR_IMPORT = 1,
    DIR_RESOURCE = 2,
    DIR_SECURITY = 4,
    DIR_BASERELOC = 5,
    DIR_DEBUG = 6,
    DIR_LOAD_CONFIG = 10,
    DIR_BOUND_IMPORT = 11,
    DIR_DELAY_IMPORT = 13
};

#pragma pack(push, 1)
typedef struct {
    uint16_t e_magic;
    uint8_t _pad[58];
    int32_t e_lfanew;
} IMAGE_DOS_HEADER;
typedef struct {
    uint16_t Machine;
    uint16_t NumberOfSections;
    uint32_t TimeDateStamp;
    uint32_t PointerToSymbolTable;
    uint32_t NumberOfSymbols;
    uint16_t SizeOfOptionalHeader;
    uint16_t Characteristics;
} IMAGE_FILE_HEADER;
typedef struct {
    uint32_t VirtualAddress;
    uint32_t Size;
} IMAGE_DATA_DIRECTORY;
typedef struct {
    uint16_t Magic;
    uint8_t _pad[90];
    uint32_t NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[DIR_COUNT];
} IMAGE_OPTIONAL_HEADER32;
typedef struct {
    uint16_t Magic;
    uint8_t _pad[106];
    uint32_t NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[DIR_COUNT];
} IMAGE_OPTIONAL_HEADER64;
typedef struct {
    uint8_t Name[8];
    union {
        uint32_t PhysicalAddress;
        uint32_t VirtualSize;
    } Misc;
    uint32_t VirtualAddress;
    uint32_t SizeOfRawData;
    uint32_t PointerToRawData;
    uint32_t PointerToRelocations;
    uint32_t PointerToLinenumbers;
    uint16_t NumberOfRelocations;
    uint16_t NumberOfLinenumbers;
    uint32_t Characteristics;
} IMAGE_SECTION_HEADER;
typedef struct {
    uint32_t OriginalFirstThunk;
    uint32_t TimeDateStamp;
    uint32_t ForwarderChain;
    uint32_t Name;
    uint32_t FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR;
typedef struct {
    uint32_t Attributes;
    uint32_t Name;
    uint32_t ModuleHandle;
    uint32_t DelayImportAddressTable;
    uint32_t DelayImportNameTable;
    uint32_t BoundDelayImportTable;
    uint32_t UnloadDelayImportTable;
    uint32_t TimeDateStamp;
} IMAGE_DELAYLOAD_DESCRIPTOR;
typedef struct {
    uint32_t Characteristics;
    uint32_t TimeDateStamp;
    uint16_t MajorVersion;
    uint16_t MinorVersion;
    uint32_t Name;
    uint32_t Base;
    uint32_t NumberOfFunctions;
    uint32_t NumberOfNames;
    uint32_t AddressOfFunctions;
    uint32_t AddressOfNames;
    uint32_t AddressOfNameOrdinals;
} IMAGE_EXPORT_DIRECTORY;
typedef struct {
    uint32_t Characteristics;
    uint32_t TimeDateStamp;
    uint16_t MajorVersion;
    uint16_t MinorVersion;
    uint32_t Type;
    uint32_t SizeOfData;
    uint32_t AddressOfRawData;
    uint32_t PointerToRawData;
} IMAGE_DEBUG_DIRECTORY;
typedef struct {
    uint32_t TimeDateStamp;
    uint16_t OffsetModuleName;
    uint16_t NumberOfModuleForwarderRefs;
} IMAGE_BOUND_IMPORT_DESCRIPTOR;
typedef struct {
    uint32_t Characteristics;
    uint32_t TimeDateStamp;
    uint16_t MajorVersion;
    uint16_t MinorVersion;
    uint16_t NumberOfNamedEntries;
    uint16_t NumberOfIdEntries;
} IMAGE_RESOURCE_DIRECTORY;
typedef struct {
    union {
        struct {
            uint32_t NameOffset : 31;
            uint32_t NameIsString : 1;
        };
        uint32_t Name;
        uint16_t Id;
    };
    union {
        uint32_t OffsetToData;
        struct {
            uint32_t OffsetToDirectory : 31;
            uint32_t DataIsDirectory : 1;
        };
    };
} IMAGE_RESOURCE_DIRECTORY_ENTRY;
typedef struct {
    uint32_t OffsetToData;
    uint32_t Size;
    uint32_t CodePage;
    uint32_t Reserved;
} IMAGE_RESOURCE_DATA_ENTRY;
#pragma pack(pop)

typedef struct {
    uint8_t *data;
    size_t size;
    bool is64;
    IMAGE_FILE_HEADER fh;
    IMAGE_OPTIONAL_HEADER32 o32;
    IMAGE_OPTIONAL_HEADER64 o64;
    IMAGE_SECTION_HEADER *sec;
    bool json;
} PE;

static bool in_range(size_t off, size_t len, size_t total) { return off <= total && len <= (total - off); }
static bool cp(const PE *pe, size_t off, void *dst, size_t len) {
    if (!in_range(off, len, pe->size)) return false;
    memcpy(dst, pe->data + off, len);
    return true;
}
static const IMAGE_DATA_DIRECTORY *dir(const PE *pe, uint32_t i) {
    uint32_t n = pe->is64 ? pe->o64.NumberOfRvaAndSizes : pe->o32.NumberOfRvaAndSizes;
    if (i >= n || i >= DIR_COUNT) return NULL;
    return pe->is64 ? &pe->o64.DataDirectory[i] : &pe->o32.DataDirectory[i];
}
static bool rva2off(const PE *pe, uint32_t rva, size_t *off) {
    for (uint16_t i = 0; i < pe->fh.NumberOfSections; i++) {
        const IMAGE_SECTION_HEADER *s = &pe->sec[i];
        uint32_t span = s->Misc.VirtualSize ? s->Misc.VirtualSize : s->SizeOfRawData;
        if (span == 0) continue;
        if (rva >= s->VirtualAddress && rva < s->VirtualAddress + span) {
            uint32_t d = rva - s->VirtualAddress;
            if (d >= s->SizeOfRawData) return false;
            *off = (size_t)s->PointerToRawData + d;
            return in_range(*off, 1, pe->size);
        }
    }
    return false;
}
static bool cstr_rva(const PE *pe, uint32_t rva, char *out, size_t n) {
    size_t off;
    if (n == 0 || !rva2off(pe, rva, &off)) return false;
    size_t i;
    for (i = 0; i + 1 < n && off + i < pe->size; i++) {
        out[i] = (char)pe->data[off + i];
        if (out[i] == 0) return true;
    }
    out[n - 1] = 0;
    return false;
}
static void jstr(const char *s) {
    putchar('"');
    for (; *s; s++) {
        if (*s == '"' || *s == '\\') putchar('\\');
        putchar(*s);
    }
    putchar('"');
}

static bool parse(PE *pe) {
    IMAGE_DOS_HEADER dos;
    uint32_t sig = 0;
    size_t nt;
    uint16_t magic;
    if (!cp(pe, 0, &dos, sizeof(dos)) || dos.e_magic != MZ_SIG || dos.e_lfanew < 0) return false;
    nt = (size_t)dos.e_lfanew;
    if (!cp(pe, nt, &sig, sizeof(sig)) || sig != PE_SIG) return false;
    if (!cp(pe, nt + 4, &pe->fh, sizeof(pe->fh))) return false;
    size_t o = nt + 4 + sizeof(pe->fh);
    if (!cp(pe, o, &magic, sizeof(magic))) return false;
    if (magic == PE32_MAGIC) {
        pe->is64 = false;
        if (pe->fh.SizeOfOptionalHeader < sizeof(pe->o32) || !cp(pe, o, &pe->o32, sizeof(pe->o32))) return false;
    } else if (magic == PE64_MAGIC) {
        pe->is64 = true;
        if (pe->fh.SizeOfOptionalHeader < sizeof(pe->o64) || !cp(pe, o, &pe->o64, sizeof(pe->o64))) return false;
    } else {
        return false;
    }
    if (pe->fh.NumberOfSections == 0) return false;
    size_t st = o + pe->fh.SizeOfOptionalHeader;
    size_t need = (size_t)pe->fh.NumberOfSections * sizeof(IMAGE_SECTION_HEADER);
    if (!in_range(st, need, pe->size)) return false;
    pe->sec = (IMAGE_SECTION_HEADER *)calloc(pe->fh.NumberOfSections, sizeof(IMAGE_SECTION_HEADER));
    if (!pe->sec) return false;
    return cp(pe, st, pe->sec, need);
}

static void forensic_flags(const PE *pe) {
    int bad = 0;
    for (uint16_t i = 0; i < pe->fh.NumberOfSections; i++) {
        const IMAGE_SECTION_HEADER *s = &pe->sec[i];
        if (s->SizeOfRawData && !in_range(s->PointerToRawData, s->SizeOfRawData, pe->size)) bad++;
        if ((s->Characteristics & 0x20000000U) && (s->Characteristics & 0x80000000U)) bad++;
    }
    if (!pe->json) {
        printf("\n[Forensic Flags]\n");
        printf("section_anomalies=%d\n", bad);
    } else {
        printf("\"forensic_flags\":{\"section_anomalies\":%d},", bad);
    }
}

static void dump_imports(const PE *pe, uint32_t rva) {
    size_t off;
    if (!rva2off(pe, rva, &off)) return;
    for (uint32_t i = 0; i < MAX_ITEMS; i++) {
        IMAGE_IMPORT_DESCRIPTOR d;
        if (!cp(pe, off + i * sizeof(d), &d, sizeof(d))) break;
        if (!d.Name && !d.FirstThunk && !d.OriginalFirstThunk) break;
        char dll[260] = "<bad>";
        cstr_rva(pe, d.Name, dll, sizeof(dll));
        if (!pe->json) printf("  %s\n", dll);
        else { jstr(dll); printf(i + 1 < MAX_ITEMS ? "," : ""); }
    }
}

static void print_imports_all(const PE *pe) {
    const IMAGE_DATA_DIRECTORY *d = dir(pe, DIR_IMPORT);
    const IMAGE_DATA_DIRECTORY *dd = dir(pe, DIR_DELAY_IMPORT);
    if (!pe->json) {
        printf("\n[Imports]\n");
        if (!d || !d->VirtualAddress) printf("<none>\n"); else dump_imports(pe, d->VirtualAddress);
        printf("\n[Delay Imports]\n");
    } else {
        printf("\"imports\":[");
        if (d && d->VirtualAddress) dump_imports(pe, d->VirtualAddress);
        printf("],\"delay_imports\":[");
    }
    if (dd && dd->VirtualAddress) {
        size_t off;
        if (rva2off(pe, dd->VirtualAddress, &off)) {
            for (uint32_t i = 0; i < MAX_ITEMS; i++) {
                IMAGE_DELAYLOAD_DESCRIPTOR x;
                if (!cp(pe, off + i * sizeof(x), &x, sizeof(x))) break;
                if (!x.Name) break;
                char dll[260] = "<bad>";
                cstr_rva(pe, x.Name, dll, sizeof(dll));
                if (!pe->json) printf("  %s\n", dll); else { jstr(dll); printf(","); }
            }
        }
    }
    if (pe->json) printf("],");
}

static void print_exports(const PE *pe) {
    const IMAGE_DATA_DIRECTORY *d = dir(pe, DIR_EXPORT);
    if (!d || !d->VirtualAddress) return;
    size_t off;
    IMAGE_EXPORT_DIRECTORY ex;
    if (!rva2off(pe, d->VirtualAddress, &off) || !cp(pe, off, &ex, sizeof(ex))) return;
    char name[260] = "<bad>";
    cstr_rva(pe, ex.Name, name, sizeof(name));
    if (!pe->json) {
        printf("\n[Exports]\nDLL: %s names=%u funcs=%u\n", name, ex.NumberOfNames, ex.NumberOfFunctions);
    } else {
        printf("\"exports\":{\"dll\":");
        jstr(name);
        printf(",\"names\":%u,\"functions\":%u},", ex.NumberOfNames, ex.NumberOfFunctions);
    }
}

static void walk_res(const PE *pe, uint32_t root_rva, uint32_t rel, uint32_t depth, uint32_t *cnt) {
    if (depth > MAX_RECURSION || *cnt > MAX_ITEMS) return;
    size_t off;
    if (!rva2off(pe, root_rva + rel, &off)) return;
    IMAGE_RESOURCE_DIRECTORY rd;
    if (!cp(pe, off, &rd, sizeof(rd))) return;
    uint32_t n = (uint32_t)rd.NumberOfNamedEntries + rd.NumberOfIdEntries;
    size_t eoff = off + sizeof(rd);
    for (uint32_t i = 0; i < n; i++) {
        IMAGE_RESOURCE_DIRECTORY_ENTRY e;
        if (!cp(pe, eoff + i * sizeof(e), &e, sizeof(e))) break;
        (*cnt)++;
        if (e.DataIsDirectory) walk_res(pe, root_rva, e.OffsetToDirectory, depth + 1, cnt);
    }
}

static void print_resources(const PE *pe) {
    const IMAGE_DATA_DIRECTORY *d = dir(pe, DIR_RESOURCE);
    uint32_t cnt = 0;
    if (d && d->VirtualAddress) walk_res(pe, d->VirtualAddress, 0, 0, &cnt);
    if (!pe->json) printf("\n[Resources]\nentries=%u\n", cnt);
    else printf("\"resources\":{\"entries\":%u},", cnt);
}

static void print_debug(const PE *pe) {
    const IMAGE_DATA_DIRECTORY *d = dir(pe, DIR_DEBUG);
    uint32_t n = 0;
    if (d && d->VirtualAddress && d->Size >= sizeof(IMAGE_DEBUG_DIRECTORY)) {
        size_t off;
        if (rva2off(pe, d->VirtualAddress, &off)) n = d->Size / sizeof(IMAGE_DEBUG_DIRECTORY);
    }
    if (!pe->json) printf("\n[Debug]\nentries=%u\n", n);
    else printf("\"debug\":{\"entries\":%u},", n);
}

static void print_bound_import(const PE *pe) {
    const IMAGE_DATA_DIRECTORY *d = dir(pe, DIR_BOUND_IMPORT);
    uint32_t n = 0;
    if (d && d->VirtualAddress && d->Size >= sizeof(IMAGE_BOUND_IMPORT_DESCRIPTOR)) n = d->Size / sizeof(IMAGE_BOUND_IMPORT_DESCRIPTOR);
    if (!pe->json) printf("\n[Bound Imports]\nentries=%u\n", n);
    else printf("\"bound_imports\":{\"entries\":%u},", n);
}

static void print_security(const PE *pe) {
    const IMAGE_DATA_DIRECTORY *d = dir(pe, DIR_SECURITY);
    bool ok = false;
    if (d && d->VirtualAddress && d->Size) ok = in_range(d->VirtualAddress, d->Size, pe->size);
    if (!pe->json) printf("\n[Security Directory]\npresent=%s\n", ok ? "true" : "false");
    else printf("\"security\":{\"present\":%s},", ok ? "true" : "false");
}

static void print_load_config(const PE *pe) {
    const IMAGE_DATA_DIRECTORY *d = dir(pe, DIR_LOAD_CONFIG);
    bool present = d && d->VirtualAddress && d->Size >= 4;
    if (!pe->json) printf("\n[Load Config]\npresent=%s size=%u\n", present ? "true" : "false", d ? d->Size : 0);
    else printf("\"load_config\":{\"present\":%s,\"size\":%u},", present ? "true" : "false", d ? d->Size : 0);
}

static void print_sections(const PE *pe) {
    if (!pe->json) {
        printf("\n[Sections]\n");
        for (uint16_t i = 0; i < pe->fh.NumberOfSections; i++) {
            char n[9] = {0};
            memcpy(n, pe->sec[i].Name, 8);
            printf("%-8s RVA=%08X RAW=%08X/%08X CH=%08X\n", n, pe->sec[i].VirtualAddress, pe->sec[i].PointerToRawData,
                   pe->sec[i].SizeOfRawData, pe->sec[i].Characteristics);
        }
    } else {
        printf("\"sections\":%u,", pe->fh.NumberOfSections);
    }
}

static bool load(const char *path, uint8_t **buf, size_t *sz) {
    FILE *f = fopen(path, "rb");
    if (!f) return false;
    if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return false; }
    long s = ftell(f);
    if (s <= 0) { fclose(f); return false; }
    if (fseek(f, 0, SEEK_SET) != 0) { fclose(f); return false; }
    *buf = (uint8_t *)malloc((size_t)s);
    if (!*buf) { fclose(f); return false; }
    if (fread(*buf, 1, (size_t)s, f) != (size_t)s) { fclose(f); free(*buf); return false; }
    fclose(f);
    *sz = (size_t)s;
    return true;
}

int main(int argc, char **argv) {
    if (argc < 2 || argc > 3) {
        fprintf(stderr, "usage: %s [--json] <pe_file>\n", argv[0]);
        return 1;
    }
    const char *path = argv[1];
    PE pe;
    memset(&pe, 0, sizeof(pe));
    if (argc == 3) {
        if (strcmp(argv[1], "--json") != 0) {
            fprintf(stderr, "unknown option: %s\n", argv[1]);
            return 1;
        }
        pe.json = true;
        path = argv[2];
    }
    if (!load(path, &pe.data, &pe.size)) {
        fprintf(stderr, "load failed: %s\n", strerror(errno));
        return 1;
    }
    if (!parse(&pe)) {
        fprintf(stderr, "invalid/corrupted PE\n");
        free(pe.data);
        return 1;
    }

    if (pe.json) printf("{");
    if (!pe.json) {
        printf("[Header]\nMachine=0x%04X Sections=%u Optional=%s\n",
               pe.fh.Machine, pe.fh.NumberOfSections, pe.is64 ? "PE32+" : "PE32");
    } else {
        printf("\"machine\":\"0x%04X\",\"sections_count\":%u,\"format\":", pe.fh.Machine, pe.fh.NumberOfSections);
        jstr(pe.is64 ? "PE32+" : "PE32");
        printf(",");
    }

    print_sections(&pe);
    print_exports(&pe);
    print_imports_all(&pe);
    print_resources(&pe);
    print_debug(&pe);
    print_bound_import(&pe);
    print_load_config(&pe);
    print_security(&pe);
    forensic_flags(&pe);

    if (pe.json) printf("\"ok\":true}\n");
    free(pe.sec);
    free(pe.data);
    return 0;
}

(1.2) RVA/RAW converter

RAW - физическое смещение байтов в файле на диске, RVA ( Relative Virtual Address ) - смещение от image base address в памяти после загрузки. Конвертер нужен, например, для того, чтобы пропатчить конкретные байты в файле после того как что-то обнаружится в дампе по виртуальному адресу.

Функция перебирает заголовки всех секций PE-файла (.text, .rdata, .data). Находит секцию, в которую попадает нужный RVA. Вычисляет относительное смещение (дельта) от начала этой секции в памяти, а затем прибавляет эту дельту к физическому адресу начала этой же секции на диске (PointerToRawData).

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

DWORD Rva2Raw(PIMAGE_NT_HEADERS pNtHead, DWORD rva) {
    PIMAGE_SECTION_HEADER pSecHead = IMAGE_FIRST_SECTION(pNtHead);
    WORD n = pNtHead->FileHeader.NumberOfSections;

    for (WORD i = 0; i < n; i++) {
        DWORD sectionStart = pSecHead[i].VirtualAddress;
        DWORD sectionSize = pSecHead[i].Misc.VirtualSize;

        if ((rva >= sectionStart) && (rva < sectionStart + sectionSize)) {
            DWORD offsetInSection = rva - sectionStart;
            DWORD trueOffset = pSecHead[i].PointerToRawData + offsetInSection;
            return trueOffset;
        }
    }
    return rva;
}

int main(int argc, char* argv[]) {
    if (argc != 2) {
        printf("Usage: %s <filename.dll>\n", argv[0]);
        return 1;
    }

    const char* targetFile = argv[1];

    HANDLE hFile = CreateFileA(targetFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        printf("Cannot open file %s. Error: %lu\n", targetFile, GetLastError());
        return 1;
    }

    HANDLE hMapping = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
    if (!hMapping) {
        printf("CreateFileMapping failed. Error: %lu\n", GetLastError());
        CloseHandle(hFile);
        return 1;
    }

    LPVOID pFileBase = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);
    if (!pFileBase) {
        printf("MapViewOfFile failed. Error: %lu\n", GetLastError());
        CloseHandle(hMapping);
        CloseHandle(hFile);
        return 1;
    }

    PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)pFileBase;
    
    if (pDosHead->e_magic != IMAGE_DOS_SIGNATURE) {
        printf("Invalid DOS signature.\n");
        UnmapViewOfFile(pFileBase);
        CloseHandle(hMapping);
        CloseHandle(hFile);
        return 1;
    }

    PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)((BYTE*)pDosHead + pDosHead->e_lfanew);

    if (pNtHead->Signature != IMAGE_NT_SIGNATURE) {
        printf("Invalid NT signature.\n");
        UnmapViewOfFile(pFileBase);
        CloseHandle(hMapping);
        CloseHandle(hFile);
        return 1;
    }

    WORD nSections = pNtHead->FileHeader.NumberOfSections;
    PIMAGE_SECTION_HEADER pSecHead = IMAGE_FIRST_SECTION(pNtHead);

    printf("Successfully mapped [%s]\n", targetFile);
    printf("Number of sections: [%d]\n", nSections);

    UnmapViewOfFile(pFileBase);
    CloseHandle(hMapping);
    CloseHandle(hFile);

    return 0;
}

(1.3) Узнать все зависимости программы: IAT, INT

Адреса функций резолвятся так: 1. Loader обращается к INT (OriginalFirstThunk), чтобы получить RVA структуры с именем функции и ее Hint 2. По имени или Hint загрузчик находит реальный виртуальный адрес (VA) этой функции в памяти процесса (внутри уже загруженной ОС библиотеки msvcrt.dll) 3. Loader берет этот реальный адрес (x64/x32) и перезаписывает им соответствующую ячейку в вашей таблице IAT (начиная с RVA 0x0000F260 плюс смещение для конкретной функции в массиве).

Во время выполнения скомпилированный код делает косвенный вызов через IAT. Программа читает значение из определенной ячейки IAT, куда загрузчик Windows уже поместил готовый и валидный адрес функции.

INT (Import Name Table / OriginalFirstThunk) - это таблица имен (или ординалов), read only. Loader читает таблицу, чтобы понять какие функции из указанной DLL нужно найти в системе.

IAT (Import Address Table / FirstThunk) - это таблица реальных адресов. Когда Windows находит нужную функцию по имени из INT, она берет ее физический адрес в оперативной памяти и перезаписывает им соответствующую ячейку в IAT.

Идейно все очень просто: по сути мы идем по списку DLL и смотрим какие функции из каждой импортируются.

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

DWORD Rva2Raw(PIMAGE_SECTION_HEADER sections, WORD numSections, DWORD rva) {
    for (WORD i = 0; i < numSections; i++) {
        DWORD start = sections[i].VirtualAddress;
        DWORD size = sections[i].Misc.VirtualSize;
        if (rva >= start && rva < (start + size)) {
            return sections[i].PointerToRawData + (rva - start);
        }
    }
    return rva;
}

int main(int argc, char* argv[]) {
    if (argc != 2) {
        printf("usage: %s <filename>\n", argv[0]);
        return -1;
    }

    const char *home = "C:\\";
    const char *argv1 = argv[1];

    size_t needed_size = strlen(home) + strlen(argv1) + 1;
    char *targetFile = (char*)malloc(needed_size);
    
    if (targetFile == NULL) {
        printf("Memory allocation for targetFile failed\n");
        return -1;
    }
    snprintf(targetFile, needed_size, "%s%s", home, argv1);

    FILE *fp = fopen(targetFile, "rb");
    if (!fp) {
        printf("Cannot open file %s\n", targetFile);
        free(targetFile);
        return 1;
    }

    IMAGE_DOS_HEADER dosHead;
    fread(&dosHead, sizeof(IMAGE_DOS_HEADER), 1, fp);
    if (dosHead.e_magic != IMAGE_DOS_SIGNATURE) {
        printf("Invalid DOS signature.\n");
        fclose(fp);
        free(targetFile);
        return 1;
    }

    fseek(fp, dosHead.e_lfanew, SEEK_SET);
    IMAGE_NT_HEADERS ntHead;
    fread(&ntHead, sizeof(IMAGE_NT_HEADERS), 1, fp);
    if (ntHead.Signature != IMAGE_NT_SIGNATURE) {
        printf("Invalid NT signature.\n");
        fclose(fp);
        free(targetFile);
        return 1;
    }

    WORD numSections = ntHead.FileHeader.NumberOfSections;
    PIMAGE_SECTION_HEADER sections =
        (PIMAGE_SECTION_HEADER)malloc(numSections * sizeof(IMAGE_SECTION_HEADER));
        
    if (!sections) {
        fclose(fp);
        free(targetFile);
        return -1;
    }
    
    fread(sections, sizeof(IMAGE_SECTION_HEADER), numSections, fp);

    DWORD importRva = ntHead.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
    
    if (importRva == 0) {
        printf("No imports found.\n");
        free(sections);
        free(targetFile);
        fclose(fp);
        return 0;
    }

    DWORD importRaw = Rva2Raw(sections, numSections, importRva);
    fseek(fp, importRaw, SEEK_SET);
    printf("Parsing imports directly from disk for: %s\n\n", targetFile);

    IMAGE_IMPORT_DESCRIPTOR importDesc;

    while (1) {
        if (fread(&importDesc, sizeof(IMAGE_IMPORT_DESCRIPTOR), 1, fp) != 1) break;
        if (importDesc.Name == 0) break;

        DWORD nameRaw = Rva2Raw(sections, numSections, importDesc.Name);
        long currentPos = ftell(fp);
        fseek(fp, nameRaw, SEEK_SET);

        char dllName[256] = {0};
        int i = 0;
        do {
            if (fread(&dllName[i], 1, 1, fp) != 1) break;
        } while (dllName[i++] != '\0' && i < 255);

        printf("-> %s\n", dllName);
        printf("   INT RVA: 0x%08lX\n", importDesc.OriginalFirstThunk);
        printf("   IAT RVA: 0x%08lX\n\n", importDesc.FirstThunk);

        DWORD thunkRva = importDesc.OriginalFirstThunk;
        if (thunkRva == 0) {
            thunkRva = importDesc.FirstThunk;
        }

        DWORD thunkRaw = Rva2Raw(sections, numSections, thunkRva);
        fseek(fp, thunkRaw, SEEK_SET);

        IMAGE_THUNK_DATA thunk;

        while (1) {
            if (fread(&thunk, sizeof(IMAGE_THUNK_DATA), 1, fp) != 1) break;

            if (thunk.u1.AddressOfData == 0) break;

            if (IMAGE_SNAP_BY_ORDINAL(thunk.u1.Ordinal)) {
                printf("    [Ordinal] %llu\n", (unsigned long long)IMAGE_ORDINAL(thunk.u1.Ordinal));
            } else {
                long savedThunkPos = ftell(fp);
                
                DWORD funcNameRaw = Rva2Raw(sections, numSections, (DWORD)(thunk.u1.AddressOfData & 0xFFFFFFFF));
                fseek(fp, funcNameRaw, SEEK_SET);

                WORD hint;
                fread(&hint, sizeof(WORD), 1, fp);

                char funcName[256] = {0};
                int k = 0;
                do {
                    if (fread(&funcName[k], 1, 1, fp) != 1) break;
                } while (funcName[k++] != '\0' && k < 255);

                printf("    [Name] %s (Hint: %d)\n", funcName, hint);
                fseek(fp, savedThunkPos, SEEK_SET);
            }
        }
        fseek(fp, currentPos, SEEK_SET);
    }

    free(sections);
    free(targetFile);
    fclose(fp);
    return 0;
}

(1.4) Ищем процессы через Toolhelp32

  1. CreateToolhelp32Snapshot создает снимок текущего состояния системы. В данном случае (с флагом TH32CS_SNAPPROCESS) запрашивается список всех запущенных процессов

  2. Перебор процессов делаем через Process32First и Process32Next. Данные о каждом процессе поочередно помещаются в структуру PROCESSENTRY32. ВАЖНО - неочевидным условием работы этих API является ручная инициализация поля dwSize размером самой структуры перед первым вызовом. Если этого не сделать, функции вернут ошибку, так как API должно знать, какую именно версию структуры мы ожидаем

  3. Имя бинарника хранится в поле szExeFile. Используем независимое от капслока сравнение строк (lstrcmpiA), т.к в windows все имена и команды как раз case-insensitive.

Применение: Task Manager, x64dbg, мониторы ресурсов, т.к в WinAPI большинство действий над процессом (через OpenProcess) требует PID \Rightarrow перевод названия в PID через Toolhelp32 является самым очевидным и простым методом.

В малвари используем для нахождения процесса для injections/перед hollowing (как самые очевидные примеры). Мы также можем использовать Toolhelp32 с Thread32First/Thread32Next для thread hijacking, чтобы захватить существующий поток вместо кола CreateRemoteThread (который привлекает много внимания)

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

DWORD GetProcessByName(const char *lpProcessName) {
    PROCESSENTRY32 ProcList;
    HANDLE hProcList;

    // Инициализация размера структуры обязательна для Toolhelp API
    ProcList.dwSize = sizeof(PROCESSENTRY32);

    hProcList = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hProcList == INVALID_HANDLE_VALUE) {
        return (DWORD)-1;
    }

    if (!Process32First(hProcList, &ProcList)) {
        CloseHandle(hProcList);
        return (DWORD)-1;
    }

    do {
        // lstrcmpiA игнорирует регистр символов
        if (lstrcmpiA(ProcList.szExeFile, lpProcessName) == 0) {
            CloseHandle(hProcList);
            return ProcList.th32ProcessID; 
        }
    } while (Process32Next(hProcList, &ProcList));

    CloseHandle(hProcList);
    return (DWORD)-1;
}

int main(int argc, char *argv[]) {
    if (argc != 3) {
        printf("[HELP] program.exe <dll> <process>\n");
        return -1;
    }

    const char *lpDLLName = argv[1];
    const char *lpProcessName = argv[2];

    const DWORD dwProcessID = GetProcessByName(lpProcessName);
    
    if (dwProcessID == (DWORD)-1) {
        printf("Error: Cannot find target process '%s'.\n", lpProcessName);
        return -1;
    }

    printf("[Process Targeter]\n");
    printf("Process : %s\n", lpProcessName);
    printf("Process ID : %lu\n\n", dwProcessID);

    //---------------------------------- got proc ID & name ---

    return 0;
}