diff --git a/.gitignore b/.gitignore index c90b71a..b60c644 100644 --- a/.gitignore +++ b/.gitignore @@ -93,6 +93,8 @@ venv.bak/ /mapping/ida/entry.py **/genlib/*.exp **/genlib/*.lib +**/genlib/*.def +**/genlib/*.map **/delinked/*.obj **/delinked/*.obj.map /tools/PEview.exe diff --git a/CMakeLists.txt b/CMakeLists.txt index 4efd4e4..7e1dd46 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,8 +65,3 @@ if(CONFIGURE_DATE) endif () add_subdirectory(src) -install(TARGETS dkii_flame RUNTIME DESTINATION ".") -install(FILES $ DESTINATION ".") -get_target_property(_target_map_file dkii_flame TARGET_MAP_FILE) -install(FILES ${_target_map_file} DESTINATION ".") - diff --git a/how_it_works.md b/how_it_works.md deleted file mode 100644 index 0a563c7..0000000 --- a/how_it_works.md +++ /dev/null @@ -1,75 +0,0 @@ - -### Introduction -Based on the reverse, I decided that the compiler `msvc` was used in the build of Dungeon Keeper 2 and I tailor all the rebuilding processes specifically for it. -Since the official build of Dungeon Keeper 2, the basic principles of the standard compilation have not changed. - -The process of compiling `.cpp` source files into the final `.exe` file has an intermediate step. -First, all `.cpp` files, each in its own process, are compiled single-threaded into `.obj` files by the `cl.exe` compiler. -For each `.dll` file that can be linked to, there is a separate `.lib` file. -After compilation, the linker `link.exe` takes all the compiled `.obj` files and additionally linked `.lib` files and merges them all into a `.exe` file if all links between `.obj` files have been satisfied. - -In the case of standard compilation, `.obj` files are in `COFF` format and contain machine code and global(+static) variables, -which does not changes during linking, with the exception of editing connections between functions and data. - -In modern compilation there are more advanced scenarios where `LTO`(Link Time Optimization) is used. -In this scenario, `.obj` files store `IR`(Intermediate Representation) bytecode instead of compiled machine code. -This way, more efficient optimizations can be applied when linking. but this is not our case. - -`.lib` files can be thought of as archives containing many `.obj` files. At the same time, `.obj` files can also describe the dependence on the exported symbols of any `.dll` file. -Many functions and data can be declared in `.cpp` files. Accordingly, one `.obj` file can contain many functions and data units. - -The `.exe` file stores the compiled machine code and data. -If you collect information about the relationships between functions and data, you can try to return them back to `.obj` files. -And then recompile with the changes. - -### Terms used in the project -For each function and data unit, a byte array and `IMAGE_SECTION_HEADER` which describes it are created in the `.obj` file. -In my code, I call such a unit of code or data `Chunk`(tools/delinker/chunk/Chunk.h). -`Chunk` is an array of bytes(Chunk::data) with information about whether this data can be read, written and executed(Chunk::chars). -`Chunk` also stores information about connections between chunks(Chunk::refs, Chunk::xrefs). - -Known functions and data are called `Global`(mapping/sgmap/Global.h). -Each `Global` represents data mapping with detailed information about the data type. -So detailed that `cpp` headers can be generated from them. - -Mapping of `Global` data is obtained through reverse engineering in `Ida PRO`(mapping/ida/sgmap_ida.py). -Mapping of connections between `Chunk` objects is also achieved through reverse engineering in `Ida PRO`(mapping/ida/references_ida). -Alas, I have not yet found a way to synchronize reverse work in `git` between different researchers. -There was an option to synchronize via `.idc` dump. Unfortunately, the `.idc` dump preserves the types, but does not preserve the tree structure of the types. - -### How it works -In `DKII.EXE` version 1.7.0 there are sections: -``` -# access: R-read, W-write, X-execute -- -00401000-00652AA2 R-X .text -00653000-0066BE1A R-X cseg -0066C000-0068D53C R-- .rdata -0068E000-006CD190 RW- .data -006CD190-007A6DD0 RW- .data uninitialized -007A7000-007A7730 RW- grpoly_d -007A8000-007ACACC RW- uva_data -007AD000-007AE658 RW- idct_dat -007AF000-007AFA00 RW- tqia_dat -007B0000-007B1004 RW- dseg -007B2000-007B2400 RW- lbmpeg_d -007B3000-007B5F30 R-- .rsrc -``` -I place each section in `Chunk` and use previously collected connections between chunks from mapping(mapping/references.map). -This is already enough to generate `.obj` files through the builder(tools/delinker/CoffBuilder.cpp). -But when linking, such an exe file will not work. -To work, you need to cut out the imports from `.rdata` and declare them again so that the linker links them with existing `.dll` in the system. -You also need to somehow call the entry point of the program and in theory the exe will work. -At this stage we will have 12 `.obj` files and each will be assembled from one `Chunk` object. -But this is not enough to replace one function or one global variable. - -The next step is to collect `Global` objects from mapping(mapping/DKII_EXE_v170.sgmap) and using these objects I split `Chunk` objects into parts. -This is how we got sliced `Chunk` objects, each with its own name, with the cutting detail that reverse researchers were able to achieve. - -To carry out substitution of functions and data, it is necessary to exclude the desired `Chunk` objects from the delinking process using mapping(src/replace_globals.map). -So we will have missing functions and data during linking and we will get a linking error. -After that, we will write them manually with the correct naming and add our .cpp files to the linking. - -This way we get a project with partially decompiled source code that can be compiled back into a working `.exe`. -Changing written source files does not require special skills, unlike point binary patches. -This is the convenience that I could not achieve in the `Ember` project. diff --git a/how_it_works_ru.md b/how_it_works_ru.md deleted file mode 100644 index 673271d..0000000 --- a/how_it_works_ru.md +++ /dev/null @@ -1,76 +0,0 @@ - -### Предисловие -Исходя из реверса я решил что в сборке Dungeon Keeper 2 использовался компилятор `msvc` и все процессы пересборки затачиваю именно под него. -Со времен официальной сборки Dungeon Keeper 2 основные принципы стандартной компиляции не менялись. - -Процесс компиляции `.cpp` исходных файлов в конечный `.exe` файл имеет промежуточный этап. -Сначала все `.cpp` файлы каждый в своем процессе однопоточно компилируется в `.obj` файлы компилятором `cl.exe`. -Для каждого `.dll` файла, с которым можно образовать связь, есть отдельный `.lib` файл. -После компиляции, линкер `link.exe` берет все скомпилированные `.obj` файлы и дополнительно подключенные `.lib` файлы и объединяет их всех в `.exe` файл если, все ссылки между `.obj` файлами были удовлетворены. - -В случае стандартной компиляции, `.obj` файлы имеют формат `COFF` и содержат машинный код и глобальные(+статические) переменные, -который во время линковки не подвергается изменениям за исключением правки связей между функциями и данными. - -В cовременной компиляции есть более продвинутые сценарии где используется `LTO`(Link Time Optimization). -В этом сценарии в `.obj` файлах хранится `IR`(Intermediate Representation) байткод вместо скомпилированного машинного кода. -Таким образом при линковке можно применять более эффективные оптимизации. но это не наш случай. - -`.lib` файлы можно считать архивами, содержащими множество `.obj` файлов. При этом `.obj` файлы могут так же описывать зависимость от экспортированных символов какого либо `.dll` файла. -В `.cpp` файлах может быть объявлено множество функций и данных. Соответственно в одном `.obj` файле может находится множество функций и единиц данных. - -В `.exe` файле хранится скомпилированный машинный код и данные. -Если собрать информацию о связях между функциями и данными, то можно попытаться вернуть их обратно в `.obj` файлы. -А затем перекомпилировать с изменениями. - -### Используемые термины в проекте -Для каждой функции и единице данных в `.obj` файле создается массив байт и `IMAGE_SECTION_HEADER`, что его описывает. -У себя в коде такую единицу кода или данных я называю `Chunk`(tools/delinker/chunk/Chunk.h). -`Chunk` представляет собой массив байтов(Chunk::data) с информацией о том можно ли эти данные читать, писать и исполнять(Chunk::chars). -Так же `Chunk` хранит информацию о связях между чанками(Chunk::refs, Chunk::xrefs). - -Известные функции и данные называются `Global`(mapping/sgmap/Global.h). -Каждый `Global` представляет собой разметку данных с детальной информацией о типе даных. -Настолько детальной что из них можно генерировать `cpp` заголовки. - -Маппинг `Global` данных достается через реверс-инжиниринг в `Ida PRO`(mapping/ida/sgmap_ida.py). -Маппинг связей между `Chunk` объектами так же достается через реверс-инжиниринг в `Ida PRO`(mapping/ida/references_ida). -Увы, я пока не нашел способа синхронизировать в `git` между различными исследователями работу по реверсу. -Был вариант синхронизации через дамп `.idc`. К сожалению дамп `.idc` сохраняет типы, но не сохраняет древовидную структуру типов. - -### Принцип работы -В `DKII.EXE` версии 1.7.0 находятся секции: -``` -# access: R-read, W-write, X-execute -- -00401000-00652AA2 R-X .text -00653000-0066BE1A R-X cseg -0066C000-0068D53C R-- .rdata -0068E000-006CD190 RW- .data -006CD190-007A6DD0 RW- .data uninitialized -007A7000-007A7730 RW- grpoly_d -007A8000-007ACACC RW- uva_data -007AD000-007AE658 RW- idct_dat -007AF000-007AFA00 RW- tqia_dat -007B0000-007B1004 RW- dseg -007B2000-007B2400 RW- lbmpeg_d -007B3000-007B5F30 R-- .rsrc -``` -Каждую секцию я помещаю в `Chunk` и применяю собранные ранее связи между чанками из меппинга(mapping/references.map). -Этого уже достаточно, чтобы сформировать `.obj` файлы через сборщик(tools/delinker/CoffBuilder.cpp). -Но при линковке такой exe файл не заработает. -Для работы нужно вырезать импорты из `.rdata` и объявить их заново так, чтобы линкер связал их с существующими `.dll` в системе. -Так же нужно каким то образом вызвать точку вхождения программы и в теории exe заработает. -На этом этапе у нас получится 12 `.obj` файлов и каждый будет собран из одного `Chunk` объекта. -Но этого не достаточно, чтобы произвести подмену одной функции или одной глобальной переменной. - -Следующим шагом я собираю `Global` объекты из маппинга(mapping/DKII_EXE_v170.sgmap) и используя эти объекты нарезаю `Chunk` объекты на части. -Так у нас получились нарезанные `Chunk` объекты каждый со своим именем с детализацией нарезки которой реверс-исследователи смогли добиться. - -Для проведения подмены функций и данных необходимо исключить желаемые `Chunk` объекты из процесса делинковки при помощи маппинга(src/replace_globals.map). -Так у нас появятся недостающие функции и данные во время линковки и мы получим ошибку линковки. -После этого напишем их вручную с правильным именованием и добавим наши .cpp файлы к линковке. - -Так мы получаем проект, с частично декомпилированным исходным кодом способный обратно компилировать его в рабочую `.exe`. -Изменение написанные исходных файлов не требует особых навыков, в отличие от точечных бинарных патчей. -Это и есть то удобство, которого я не мог достичь в проекте `Ember`. - diff --git a/libs/dkii_exe/CMakeLists.txt b/libs/dkii_exe/CMakeLists.txt index d8cd587..1233095 100644 --- a/libs/dkii_exe/CMakeLists.txt +++ b/libs/dkii_exe/CMakeLists.txt @@ -1,5 +1,6 @@ -add_subdirectory(delinked) +#add_subdirectory(delinked) +add_subdirectory(genlib) set(API_HEADERS diff --git a/libs/dkii_exe/genlib/CMakeLists.txt b/libs/dkii_exe/genlib/CMakeLists.txt new file mode 100644 index 0000000..5689976 --- /dev/null +++ b/libs/dkii_exe/genlib/CMakeLists.txt @@ -0,0 +1,39 @@ +find_program(LIB_PROGRAM lib) +find_program(DUMPBIN_PROGRAM dumpbin) + +set(GENLIB_LIB dkii.lib) + +set(ABS_GENLIB_LIB ${GENLIB_LIB}) +list(TRANSFORM ABS_GENLIB_LIB PREPEND "${CMAKE_CURRENT_LIST_DIR}/") + +#get_target_property(GENAPI_SRCS genlib_dkii SOURCES) +#get_target_property(SOURCE_DIR genlib_dkii SOURCE_DIR) +#set(ABS_GENAPI_SRCS ${GENAPI_SRCS}) +#list(TRANSFORM ABS_GENAPI_SRCS PREPEND "${SOURCE_DIR}/") +add_custom_command( + OUTPUT ${ABS_GENLIB_LIB} ${CMAKE_CURRENT_LIST_DIR}/dkii_symbols.map +# COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${CMAKE_HOME_DIRECTORY}/mapping/ida +# ${Python3_EXECUTABLE} ${CMAKE_HOME_DIRECTORY}/tools/genlib_dkii/main.py + COMMAND $ + -sgmap_file ${CMAKE_HOME_DIRECTORY}/mapping/DKII_EXE_v170.sgmap + -def_file ${CMAKE_CURRENT_LIST_DIR}/dkii.def + -replace_globals ${CMAKE_HOME_DIRECTORY}/src/replace_globals.map + -exp_file ${CMAKE_CURRENT_LIST_DIR}/dkii_exp.def + -map_file ${CMAKE_CURRENT_LIST_DIR}/dkii_symbols.map + COMMAND cmd /C ${CMAKE_HOME_DIRECTORY}/tools/genlib/gen_lib.bat + ${LIB_PROGRAM} ${CMAKE_HOME_DIRECTORY}/libs/dkii_exe/genlib/dkii.def ${ABS_GENLIB_LIB} + DEPENDS +# ${ABS_GENAPI_SRCS} + ${CMAKE_HOME_DIRECTORY}/mapping/DKII_EXE_v170.sgmap + ${CMAKE_HOME_DIRECTORY}/src/replace_globals.map + COMMENT "genlib DKII.EXE" +) + + +set(TARGET dkii_genlib) +add_library(${TARGET} INTERFACE + ${GENLIB_LIB} # make target depend on lib file building + ) +target_link_libraries(${TARGET} INTERFACE ${GENLIB_LIB}) # make target share lib dependency with others +target_link_directories(${TARGET} INTERFACE ${CMAKE_CURRENT_LIST_DIR}) # make other targets able to find lib file + diff --git a/readme.md b/readme.md index 1c40427..0382e5b 100644 --- a/readme.md +++ b/readme.md @@ -1,17 +1,19 @@ Flame is a new approach to modifying compiled code Dungeon Keeper 2 -Difference from the previous implementation(https://github.com/DiaLight/Ember): -* Ember converts `DKII.EXE` to `dk2.dll` and performs memory dot patches on it after the code has been loaded into memory. -* Flame parses `DKII.EXE` into `msvc` compatible `.obj` files. These files are replaced with other `.obj` files. Then they are compiled back into `DKII-Flame.EXE` +Flame recompiles some functions of `DKII.EXE` into a separate `.exe` file. +Then it merges this file with the original `.exe` file, replacing the references to +the original functions with the references to recompiled functions. +Recompiled functions are supplemented with switchable changes that fix some game bugs -For a more detailed description of how Flame works, read `how_it_works.md` +[Earlier](https://github.com/DiaLight/Flame/tree/46e5b0c1df93060bd01a83bb6d14d064e9c8c3dc "Full relinking approach"), this project implemented an approach to fully relinking `DKII.EXE`, +which contains false positive references that caused new bugs. The latest build can be taken from the github actions How to install: - copy DKII-Flame-1.7.0-*.exe from github actions to game directory (no need rename to DKII-DX.exe or DKII.exe. exe name does not matter) -- copy ddraw.dll from https://github.com/narzoul/DDrawCompat/releases/tag/v0.5.3 to game directory -- copy dinput.dll from https://github.com/elishacloud/dinputto8/releases/tag/v1.0.54.0 to game directory +- (optional, but recommended) copy ddraw.dll from https://github.com/narzoul/DDrawCompat/releases/tag/v0.5.3 to game directory +- (optional, but recommended) copy dinput.dll from https://github.com/elishacloud/dinputto8/releases/tag/v1.0.54.0 to game directory - run DKII-Flame-1.7.0-*.exe Additional ddraw.dll and dinput.dll are fixing some graphical bugs and i think improve general stability. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 285095a..3bf5d84 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(TARGET dkii_flame) +set(TARGET flame) add_executable(${TARGET} main.cpp @@ -23,7 +23,7 @@ add_executable(${TARGET} patches/micro_patches.cpp patches/game_version_patch.cpp - ${DKII_RESOURCES_FILE} +# ${DKII_RESOURCES_FILE} ) target_include_directories(${TARGET} PRIVATE ${CMAKE_CURRENT_LIST_DIR}) target_compile_definitions(${TARGET} PRIVATE @@ -31,7 +31,7 @@ target_compile_definitions(${TARGET} PRIVATE DIRECT3D_VERSION=0x0600 ) target_link_libraries(${TARGET} PRIVATE - dkii_delinked dkii_exe_api + dkii_genlib dkii_exe_api # gog_patch_dll # dk2 specific libs @@ -60,11 +60,47 @@ target_compile_options(${TARGET} PRIVATE ) target_link_options(${TARGET} PRIVATE /OPT:NOREF) target_link_options(${TARGET} PRIVATE /MAP) # generate msvc mapping file for exe +target_link_options(${TARGET} PRIVATE /DEF:${CMAKE_HOME_DIRECTORY}/libs/dkii_exe/genlib/dkii_exp.def) set_property(TARGET ${TARGET} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") -set(OUTPUT_NAME "DKII-Flame${OUTPUT_SUFFIX}") +set(OUTPUT_NAME "Flame${OUTPUT_SUFFIX}") set_target_properties(${TARGET} PROPERTIES OUTPUT_NAME - "${OUTPUT_NAME}" + "${OUTPUT_NAME}_code" ) -set_target_properties(${TARGET} PROPERTIES TARGET_MAP_FILE "${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_NAME}.map") +set_target_properties(${TARGET} PROPERTIES TARGET_MAP_FILE "${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_NAME}_code.map") + +# === Merge Flame.exe with DKII.EXE === + +set(MERGED_EXE "DKII-${OUTPUT_NAME}.exe") +set(ABS_MERGED_EXE ${MERGED_EXE}) +list(TRANSFORM ABS_MERGED_EXE PREPEND "${CMAKE_CURRENT_BINARY_DIR}/") + +get_target_property(MSVC_MAP_FILE flame TARGET_MAP_FILE) +get_target_property(MERGE_SRCS merge_dkii SOURCES) +get_target_property(SOURCE_DIR merge_dkii SOURCE_DIR) +set(ABS_MERGE_SRCS ${MERGE_SRCS}) +list(TRANSFORM ABS_MERGE_SRCS PREPEND "${SOURCE_DIR}/") +add_custom_command( + OUTPUT ${ABS_MERGED_EXE} + COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${CMAKE_HOME_DIRECTORY}/mapping/ida + ${Python3_EXECUTABLE} ${CMAKE_HOME_DIRECTORY}/tools/merge_dkii/main.py + -flame_map_file ${MSVC_MAP_FILE} + -dkii_map_file ${CMAKE_HOME_DIRECTORY}/libs/dkii_exe/genlib/dkii_symbols.map + -references_file ${CMAKE_HOME_DIRECTORY}/mapping/references.map + -dkii_exe ${CMAKE_HOME_DIRECTORY}/libs/dkii_exe/DKII.EXE + -flame_exe $ + -version "${VER_PRODUCT_NUMBER}.${VER_PRODUCT_VERSION}${VER_FILE_SUFFIX}" + -output_exe ${ABS_MERGED_EXE} + DEPENDS ${ABS_MERGE_SRCS} + ${CMAKE_HOME_DIRECTORY}/libs/dkii_exe/genlib/dkii_symbols.map + ${CMAKE_HOME_DIRECTORY}/mapping/references.map + ${CMAKE_HOME_DIRECTORY}/libs/dkii_exe/DKII.EXE + flame + COMMENT "Merge Flame.dll with DKII.EXE" +) + +add_custom_target(dkii_flame ALL DEPENDS ${ABS_MERGED_EXE}) + +install(FILES ${ABS_MERGED_EXE} DESTINATION ".") +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/DKII-${OUTPUT_NAME}.map" DESTINATION ".") diff --git a/src/patches/game_version_patch.cpp b/src/patches/game_version_patch.cpp index f8b311b..b09e3fe 100644 --- a/src/patches/game_version_patch.cpp +++ b/src/patches/game_version_patch.cpp @@ -8,54 +8,10 @@ bool game_version_patch::enabled = true; -bool resolveFileVersion(char *out) { - bool status = false; - CHAR szVersionFile[MAX_PATH]; - GetModuleFileNameA(NULL, szVersionFile, sizeof(szVersionFile)); - - DWORD verHandle = 0; - DWORD verSize = GetFileVersionInfoSizeA( szVersionFile, &verHandle); - if (verSize != NULL) { - LPSTR verData = new char[verSize]; - if (GetFileVersionInfoA( szVersionFile, verHandle, verSize, verData)) { - UINT uiSize; - BYTE* lpb; - if( VerQueryValueA( - verData, "\\VarFileInfo\\Translation", - (void**)&lpb, &uiSize)) { - WORD* lpw = (WORD *) lpb; - - char strQuery[256]; - snprintf(strQuery, 256, "\\StringFileInfo\\%04x%04x\\FileVersion", lpw[0], lpw[1]); - if(VerQueryValue( - verData, const_cast( (LPCSTR)strQuery ), - (void**)&lpb, &uiSize) && uiSize > 0) { - LPCSTR version = (LPCSTR)lpb; - printf("%s\n", version); - if(LPCSTR pos = strstr(version, "build")) { - char ver[64]; - char build[64]; - strncpy(ver, version, pos - version - 1); - ver[pos - version - 1] = '\0'; - strcpy(build, pos); - sprintf(out, " V%s\n %s", ver, build); - status = true; - } - } - } - } - delete[] verData; - } - return status; -} - -namespace { - char versionCache[64] = {0}; -} +extern "C" char Flame_version[64] = {'\0', '1'}; char *game_version_patch::getFileVersion() { if(!enabled) return nullptr; - if(versionCache[0] == '\0') if(!resolveFileVersion(versionCache)) enabled = false; - if(versionCache[0] == '\0') return nullptr; - return versionCache; + if(Flame_version[0] == '\0') return nullptr; + return Flame_version; } diff --git a/src/replace_globals.map b/src/replace_globals.map index b6978ff..90c50b2 100644 --- a/src/replace_globals.map +++ b/src/replace_globals.map @@ -14,8 +14,6 @@ # MyDxMouse.h 005BC760 int *__cdecl MyDxMouse_create(int *, MyDxMouse **); /* auto */ 005DDA90 void handleData(int); // ------------------- /* auto */ -005DDB88 extern void *jpt_5DDAC1[5]; // ------------- /* auto */ -005DDBAC extern void *jpt_5DDB05[4]; // ------------- /* auto */ # MyDxKeyboard.h 005DE260 int processKeyboardData(int); // ----------- /* auto */ @@ -23,8 +21,6 @@ # CWindowTest.h 00556650 LRESULT CWindowTest_proc(HWND, uint32_t, WPARAM, LPARAM); /* auto */ -00556704 extern void *jpt_5566AF[6]; // ------------- /* auto */ -0055671C extern uint8_t idt_5566A9[212]; // --------- /* auto */ 005B5070 LRESULT BullfrogWindow_proc(HWND, uint32_t, WPARAM, LPARAM); /* auto */ 005B2E70 int __cdecl getCustomDefWindowProcA(); // -- /* auto */ diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 841c4c4..325d6ff 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -2,6 +2,8 @@ add_subdirectory(msvc_mangler) add_subdirectory(delinker) add_subdirectory(genapi) +add_subdirectory(genlib_dkii) +add_subdirectory(merge_dkii) if(DEV_DK2_DIR) add_subdirectory(devhelper) endif() diff --git a/tools/genapi/gen_globals_h.py b/tools/genapi/gen_globals_h.py index 220b81c..18e590e 100644 --- a/tools/genapi/gen_globals_h.py +++ b/tools/genapi/gen_globals_h.py @@ -39,6 +39,7 @@ def format_h_head(): yield from map(format_autogen_line, format_h_head()) def format_h_body(): + yield f'#define _imp __declspec( dllimport ) extern' yield f"namespace dk2 {{" for glob in filter(filter_global_var, globals): glob_type = glob.type @@ -60,8 +61,9 @@ def format_h_body(): # name = f"(&{name})" # else: # name = f"&{name}" - yield f"/*{glob.va:08X}*/ extern {format_type(glob_type, name)};" + yield f"/*{glob.va:08X}*/ _imp {format_type(glob_type, name)};" yield f"}} // namespace dk2" + yield f"#undef _imp" yield from map(format_autogen_line, format_h_body()) def format_h_tail(): diff --git a/tools/genapi/gen_struct_h.py b/tools/genapi/gen_struct_h.py index 80d4a10..7f28773 100644 --- a/tools/genapi/gen_struct_h.py +++ b/tools/genapi/gen_struct_h.py @@ -59,7 +59,7 @@ def format_h_struct_head(): if struct.vtable is not None: vtable_glob = vtable_map.get(struct.vtable.id, None) if vtable_glob is not None: - yield f"/*{vtable_glob.va:08X}*/ static void *vftable[];" + yield f"/*{vtable_glob.va:08X}*/ __declspec( dllimport ) static void *vftable[];" yield f"/*---*/ inline void *getVtbl() const {{ return *(void **) this; }}" yield empty_line offs = struct.calc_fields_offs() diff --git a/tools/genlib_dkii/CMakeLists.txt b/tools/genlib_dkii/CMakeLists.txt new file mode 100644 index 0000000..e1f4eb8 --- /dev/null +++ b/tools/genlib_dkii/CMakeLists.txt @@ -0,0 +1,11 @@ +set(TARGET genlib_dkii) + +#add_custom_target(${TARGET} +# SOURCES +# main.py +# ) + +add_executable(${TARGET} + main.cpp + ) +target_link_libraries(${TARGET} PRIVATE sgmap references msvc_mangler) diff --git a/tools/genlib_dkii/main.cpp b/tools/genlib_dkii/main.cpp new file mode 100644 index 0000000..656b233 --- /dev/null +++ b/tools/genlib_dkii/main.cpp @@ -0,0 +1,162 @@ +// +// Created by DiaLight on 01.09.2024. +// +#include +#include +#include +#include +#include +#include +#include +#include +#include "SGMap.h" +#include "msvc_mangler.h" + +#define fmtHex32(val) std::hex << std::setw(8) << std::setfill('0') << std::uppercase << val << std::dec +#define fmtHex16(val) std::hex << std::setw(4) << std::setfill('0') << std::uppercase << val << std::dec + + +char *getCmdOption(char **begin, char **end, const std::string &option) { + char **it = std::find(begin, end, option); + if (it != end && ++it != end) return *it; + return nullptr; +} + +bool hasCmdOption(char **begin, char **end, const std::string &option) { + return std::find(begin, end, option) != end; +} + +bool getGlobalsToReplace(const std::string &path, std::set &globalsToReplace) { + std::ifstream is(path); + std::string line; + while(is) { + if (std::getline(is, line)) { + if(line.starts_with("#")) continue; + if(line.empty()) continue; + + std::stringstream iss(line); + std::string rva; + if ( std::getline(iss, rva, ' ')) { + globalsToReplace.insert(std::stoul(rva, nullptr, 16)); + } else { + std::cout << "failed" << std::endl; + is.setstate(std::ios::failbit); + } + } + } + if(!is.eof()) { + printf("[-] failed to collect globals to replace. %s\n", path.c_str()); + return false; + } + return true; +} + +void show_help() { + printf("genlib_dkii\n"); + printf(" -sgmap_file \n"); + printf(" -def_file \n"); + printf(" -replace_globals \n"); + printf(" -exp_file \n"); + printf(" -map_file \n"); +} + +int main(int argc, char * argv[]) { + if (hasCmdOption(argv, argv + argc, "-h")) { + show_help(); + return 0; + } + + char *sgmap_file = getCmdOption(argv, argv + argc, "-sgmap_file"); + if (sgmap_file == nullptr) { + show_help(); + return -1; + } + + char *def_file = getCmdOption(argv, argv + argc, "-def_file"); + if (def_file == nullptr) { + show_help(); + return -1; + } + + char *replace_globals = getCmdOption(argv, argv + argc, "-replace_globals"); + if (replace_globals == nullptr) { + show_help(); + return -1; + } + + char *exp_file = getCmdOption(argv, argv + argc, "-exp_file"); + if (exp_file == nullptr) { + show_help(); + return -1; + } + + char *map_file = getCmdOption(argv, argv + argc, "-map_file"); + if (map_file == nullptr) { + show_help(); + return -1; + } + + DWORD startMs = GetTickCount(); + + printf("Dungeon Keeper 2 genlib\n"); +// printf("sgmap_file %s\n", sgmap_file); +// printf("def_file %s\n", def_file); +// printf("replace_globals %s\n", replace_globals); +// printf("exp_file %s\n", exp_file); +// printf("map_file %s\n", map_file); + + SGMapArena sgArena; + std::vector structs; + std::vector globals; + + std::set globalsToReplace; + { + { + std::ifstream is(sgmap_file); + if (!SGMap_deserialize(is, structs, globals, sgArena)) return -1; + if (!SGMap_link(structs, globals)) return -1; + } + if (!getGlobalsToReplace(replace_globals, globalsToReplace)) return -1; + } + + { + std::ofstream def_os(def_file); + if (!def_os.is_open()) { + printf("[-] write def mapping failed %s\n", exp_file); + return -1; + } + std::ofstream exp_os(exp_file); + if (!exp_os.is_open()) { + printf("[-] write exp mapping failed %s\n", exp_file); + return -1; + } + std::ofstream map_os(map_file); + if (!map_os.is_open()) { + printf("[-] write symbol mapping failed %s\n", map_file); + return -1; + } + def_os << "LIBRARY DKII" << std::endl; + def_os << "EXPORTS" << std::endl; +// exp_os << "LIBRARY DKII-Flame" << std::endl; + exp_os << "EXPORTS" << std::endl; + for (auto *global: globals | std::views::reverse) { + bool isReplace = globalsToReplace.contains(global->va); + auto name = msvcMangleName(global); + std::ofstream &os = isReplace ? exp_os : def_os; + os << " " << name; + if(global->type->kind != TK_Function) { + os << " DATA"; + } + os << std::endl; + map_os << fmtHex32(global->va) << " " << name; + if(isReplace) map_os << " REPLACE"; + map_os << std::endl; + } + def_os << std::endl; + exp_os << std::endl; + map_os << std::endl; + } + + printf("finished in %lu ms\n", GetTickCount() - startMs); + return 0; +} diff --git a/tools/merge_dkii/CMakeLists.txt b/tools/merge_dkii/CMakeLists.txt new file mode 100644 index 0000000..7b875a4 --- /dev/null +++ b/tools/merge_dkii/CMakeLists.txt @@ -0,0 +1,6 @@ +set(TARGET merge_dkii) + +add_custom_target(${TARGET} + SOURCES + main.py + ) diff --git a/tools/merge_dkii/ida_apply_map.py b/tools/merge_dkii/ida_apply_map.py new file mode 100644 index 0000000..7e26642 --- /dev/null +++ b/tools/merge_dkii/ida_apply_map.py @@ -0,0 +1,22 @@ +import idc +import idaapi +import idautils +import pathlib +import os.path + + +def apply_map(): + exe_file = pathlib.Path(idaapi.get_input_file_path()) + map_file = exe_file.parent / f'{os.path.splitext(exe_file.name)[0]}.map' + if map_file.exists(): + with open(map_file, 'r') as f: + lines = f.readlines() + for line in lines: + line = line.rstrip() + va, name = line.split(' ', 1) + va = int(va, 16) + idaapi.set_name(va, name, idaapi.SN_NOCHECK) + + +def get_teb(): + return idaapi.dbg_get_thread_sreg_base(idaapi.get_current_thread(), idautils.cpu.fs) diff --git a/tools/merge_dkii/main.py b/tools/merge_dkii/main.py new file mode 100644 index 0000000..81cccbe --- /dev/null +++ b/tools/merge_dkii/main.py @@ -0,0 +1,497 @@ +import argparse +import io +import os.path +import pathlib +import re +import ctypes +from ctypes import wintypes +import pe_types +import my_pe + + +class ReplaceRefInfo: + + def __init__(self, name: str, target_va: int, new_va: int): + self.name = name + self.target_va = target_va + self.target_xrefs: list[tuple[int, int, str]] = [] + self.new_va = new_va + + def __repr__(self): + return f'RFI({self.target_va:08X}->{self.new_va:08X}, xrefs.len={len(self.target_xrefs)}, "{self.name}")' + + +def read_symbols(dkii_map_file: pathlib.Path) -> tuple[dict[str, int], dict[str, int]]: + with open(dkii_map_file, 'r') as f: + dkii_map_lines = f.readlines() + dkii_map = {} + to_replace = {} + for line in dkii_map_lines: + line = line.rstrip() + if not line: + continue + split = line.split(' ') + va = int(split[0], 16) + name = split[1] + is_replace = len(split) > 2 and split[2] == 'REPLACE' + if is_replace: + to_replace[name] = va + name += '_replaced' + dkii_map[name] = va + return dkii_map, to_replace + + +def collect_replace_info( + flame_map_file: pathlib.Path, + to_replace: dict[str, int], + replace_refs: list[ReplaceRefInfo], + image_base: int +): + with open(flame_map_file, 'r') as f: + map_lines = f.readlines() + + flame_map = {} + msvc_map_line = re.compile(' \\d{4}:[\\da-f]{8}\\s+(\\S+)\\s+([\\da-f]{8}) .{3} (\\S+)') + for line in map_lines: + line = line.rstrip() + m = msvc_map_line.match(line) + if m is None: + continue + flame_va = int(m.group(2), 16) + name = m.group(1) + obj_file = m.group(3) + if flame_va == 0: + continue + flame_rva = flame_va - image_base + if flame_rva == 0: + continue + # print(f'{flame_va:08X} {name}') + if name in flame_map: + if (not name.startswith('??__') + and not name.startswith('__guard') + and not name.startswith('$') + and not name.endswith('VLCtable@@A')): + print(f'duplicate {flame_va:08X} {flame_map[name]:08X} {name}') + raise Exception() + if not obj_file.endswith('.cpp.obj'): + name = obj_file + ':' + name + flame_map[name] = flame_va + dkii_va = to_replace.get(name, None) + if dkii_va is None: + continue + del to_replace[name] + replace_refs.append(ReplaceRefInfo(name, dkii_va, flame_va)) + if len(to_replace): + print("not every replace function was implemented. missing functions:") + for name, va in to_replace.items(): + print(f'{va:08X} {name}') + raise Exception() + return flame_map + + +def collect_xrefs(references_file: pathlib.Path) -> dict[int, list[tuple[int, int, str]]]: + with open(references_file, 'r') as f: + references_lines = f.readlines() + dkii_xrefs = {} + for line in references_lines: + line = line.rstrip() + if not line or line.startswith('#'): + continue + src_va, value, dst_va, kind = line.split(' ') + src_va, value, dst_va, kind = int(src_va, 16), int(value, 16), int(dst_va, 16), kind + dkii_xrefs.setdefault(dst_va, []).append((src_va, value, kind)) + return dkii_xrefs + + +class ImportTableBuild: + + def __init__(self, dll_name_rva, dll_name): + self.dll_name_rva = dll_name_rva + self.dll_name = dll_name + self.funs = [] + self.thunks = None + + +def append_dll_sections_into_exe(dkii_data: bytes, flame_data: bytes) -> my_pe.MyPe: + dkii_pe = my_pe.MyPe(dkii_data) + flame_pe = my_pe.MyPe(flame_data) + print(f'flame virtual space: {dkii_pe.nt.OptionalHeader.ImageBase:08X}-{dkii_pe.nt.OptionalHeader.ImageBase + dkii_pe.nt.OptionalHeader.SizeOfImage:08X}') + print(f'dkii virtual space: {flame_pe.nt.OptionalHeader.ImageBase:08X}-{flame_pe.nt.OptionalHeader.ImageBase + flame_pe.nt.OptionalHeader.SizeOfImage:08X}') + # validate some headers + last = list(dkii_pe.sections)[-1] + sections_virt_end = my_pe.align_up(last.VirtualAddress + last.VirtualSize, dkii_pe.nt.OptionalHeader.SectionAlignment) + assert dkii_pe.nt.OptionalHeader.SizeOfImage == sections_virt_end + sections_file_end = my_pe.align_up(last.PointerToRawData + last.SizeOfRawData, dkii_pe.nt.OptionalHeader.FileAlignment) + assert dkii_pe.size == sections_file_end + # print(f'{sections_file_end:08X} {len(dkii_data):08X}') + + # assert sections has same alignment + assert dkii_pe.nt.OptionalHeader.SectionAlignment == flame_pe.nt.OptionalHeader.SectionAlignment + assert dkii_pe.nt.OptionalHeader.FileAlignment == flame_pe.nt.OptionalHeader.FileAlignment + delta_virt = sections_virt_end - flame_pe['.text'].VirtualAddress + delta_file = sections_file_end - flame_pe['.text'].PointerToRawData + flame_data_start = flame_pe.base + flame_pe['.text'].PointerToRawData + flame_data_size = (flame_pe['.data'].PointerToRawData + flame_pe['.data'].SizeOfRawData) - flame_pe['.text'].PointerToRawData + + free_sections_left = (dkii_pe.nt.OptionalHeader.SizeOfHeaders - (dkii_pe.sections_end - dkii_pe.base)) / ctypes.sizeof(pe_types.IMAGE_SECTION_HEADER) + assert free_sections_left >= 4.0 + sections: list[pe_types.IMAGE_SECTION_HEADER] = ctypes.pointer(pe_types.IMAGE_SECTION_HEADER.from_address(dkii_pe.sections_end)) + Name_ty = (ctypes.c_ubyte*8) + + # add flame section headers to dkii and remap them + def convert_sec(sec: pe_types.IMAGE_SECTION_HEADER, src: pe_types.IMAGE_SECTION_HEADER, name: bytes): + ctypes.pointer(sec)[0] = src + sec.Name = Name_ty(*name) + sec.VirtualAddress += delta_virt + sec.PointerToRawData += delta_file + convert_sec(sections[0], flame_pe['.text'], b'.flame_t') + convert_sec(sections[1], flame_pe['.rdata'], b'.flame_r') + convert_sec(sections[2], flame_pe['.data'], b'.flame_d') + dkii_pe.nt.FileHeader.NumberOfSections += 3 + + sections: list[pe_types.IMAGE_SECTION_HEADER] = ctypes.pointer(pe_types.IMAGE_SECTION_HEADER.from_address(dkii_pe.sections_end)) + sections[0].Name = Name_ty(*b'.imports') + last_sec = sections[-1] + sections[0].PointerToRawData = my_pe.align_up(last_sec.PointerToRawData + last_sec.SizeOfRawData, dkii_pe.nt.OptionalHeader.FileAlignment) + sections[0].VirtualAddress = my_pe.align_up(last_sec.VirtualAddress + last_sec.VirtualSize, dkii_pe.nt.OptionalHeader.SectionAlignment) + sections[0].Characteristics = pe_types.IMAGE_SCN_MEM_READ | pe_types.IMAGE_SCN_CNT_INITIALIZED_DATA + dkii_pe.nt.FileHeader.NumberOfSections += 1 + + dkii_pe.nt.OptionalHeader.SizeOfCode += flame_pe.nt.OptionalHeader.SizeOfCode + dkii_pe.nt.OptionalHeader.SizeOfInitializedData += flame_pe.nt.OptionalHeader.SizeOfInitializedData + dkii_pe.nt.OptionalHeader.SizeOfUninitializedData += flame_pe.nt.OptionalHeader.SizeOfUninitializedData + dkii_pe.nt.OptionalHeader.AddressOfEntryPoint = flame_pe.nt.OptionalHeader.AddressOfEntryPoint + delta_virt + + # relocate flame references + for ty, rva in flame_pe.relocs(): + assert ty is pe_types.IMAGE_REL_BASED.HIGHLOW + offs = flame_pe.rva2raw(rva) + src_val = wintypes.DWORD.from_address(flame_pe.base + offs) + dst_va = src_val.value + dst_rva = dst_va - flame_pe.nt.OptionalHeader.ImageBase + dst_rva += delta_virt + src_val.value = dst_rva + dkii_pe.nt.OptionalHeader.ImageBase + # print(f'{rva:08X}->{dst_rva:08X} {ty.name}') + + # ctypes.pointer(ctypes.c_char.from_address(flame_pe.base + flame_pe.rva2raw(names[i]))) + + # for name, rva in flame_pe.exports(): + # print(f'{rva + delta_virt:08X} {name}') + + # merge imports + imports = {} + for imp in flame_pe.imports(): + if imp.name == 'DKII.dll': + continue + # print(imp.name) + for rva, ord, name, thunk_rva in imp.thunks(): + # print(f' {rva:08X} {ord} {name} {imp.desc.Name + delta_virt:08X} {thunk_rva + delta_virt:08X}') + imports[f'{imp.name.lower()}:{name if name is not None else ord}'] = (imp.desc.Name + delta_virt, imp.name, name, ord, thunk_rva + delta_virt) + for imp in dkii_pe.imports(): + # print(imp.name) + for rva, ord, name, thunk_rva in imp.thunks(): + # print(f' {rva:08X} {ord} {name}') + imports[f'{imp.name.lower()}:{name if name is not None else ord}'] = (imp.desc.Name, imp.name, name, ord, thunk_rva) + by_lib: dict[str, ImportTableBuild] = {} + for dll_name_rva, dll_name, fun_name, ord, thunk_rva in imports.values(): + by_lib.setdefault( + dll_name.lower(), ImportTableBuild(dll_name_rva, dll_name) + ).funs.append((fun_name, ord, thunk_rva)) + descriptors = (pe_types.IMAGE_IMPORT_DESCRIPTOR * (len(by_lib) + 1))() + iat_size = 0 + all_thunks = [] + for i, itb in enumerate(by_lib.values()): + desc: pe_types.IMAGE_IMPORT_DESCRIPTOR = descriptors[i] + desc.Name = itb.dll_name_rva + # print(dll_name) + itb.thunks = (wintypes.DWORD * (len(itb.funs) + 1))() + all_thunks.append(itb.thunks) + for j, (fun_name, ord, thunk_rva) in enumerate(itb.funs): + if thunk_rva is not None: + itb.thunks[j] = thunk_rva + else: + itb.thunks[j] = ord | pe_types.IMAGE_ORDINAL_FLAG + # print(f' {ord} {fun_name}') + iat_size += ctypes.sizeof(itb.thunks) + iat_rva = dkii_pe['.imports'].VirtualAddress + descriptors_rva = iat_rva + iat_size + oiat_rva = descriptors_rva + ctypes.sizeof(descriptors) + cur_iat_rva = iat_rva + cur_oiat_rva = oiat_rva + for i, itb in enumerate(by_lib.values()): + desc: pe_types.IMAGE_IMPORT_DESCRIPTOR = descriptors[i] + desc.FirstThunk = cur_iat_rva + cur_iat_rva += ctypes.sizeof(itb.thunks) + desc.OriginalFirstThunk = cur_oiat_rva + cur_oiat_rva += ctypes.sizeof(itb.thunks) + # print(f'{desc.FirstThunk:08X} {desc.OriginalFirstThunk:08X} {imports_rva:08X} {dll_name}') + with io.BytesIO() as f: + for thunks in all_thunks: + f.write(thunks) + f.write(descriptors) + for thunks in all_thunks: + f.write(thunks) + imports_data = f.getvalue() + dkii_pe['.imports'].VirtualSize = len(imports_data) + dkii_pe['.imports'].SizeOfRawData = my_pe.align_up(len(imports_data), dkii_pe.nt.OptionalHeader.FileAlignment) + import_data_dir: pe_types.IMAGE_DATA_DIRECTORY = dkii_pe.nt.OptionalHeader.DataDirectory[pe_types.IMAGE_DIRECTORY_ENTRY.IMPORT] + import_data_dir.VirtualAddress = descriptors_rva + import_data_dir.Size = ctypes.sizeof(descriptors) + iat_dir: pe_types.IMAGE_DATA_DIRECTORY = dkii_pe.nt.OptionalHeader.DataDirectory[pe_types.IMAGE_DIRECTORY_ENTRY.IAT] + iat_dir.VirtualAddress = iat_rva + iat_dir.Size = iat_size + dkii_pe.nt.OptionalHeader.SizeOfImage = my_pe.align_up( + dkii_pe['.imports'].VirtualAddress + dkii_pe['.imports'].VirtualSize, + dkii_pe.nt.OptionalHeader.SectionAlignment + ) + + with io.BytesIO() as f: + f.write((ctypes.c_ubyte * dkii_pe.size).from_address(dkii_pe.base)) + f.write((ctypes.c_ubyte * flame_data_size).from_address(flame_data_start)) + f.write(imports_data + (b'\x00' * (dkii_pe['.imports'].SizeOfRawData - len(imports_data)))) + result_data = f.getvalue() + + return my_pe.MyPe(result_data) + + +def resize_headers_to_fit_sections(dkii_pe: my_pe.MyPe, sections_planning_to_add: int) -> my_pe.MyPe: + # dkii_pe.nt.OptionalHeader.SizeOfHeaders + result_headers_size = (dkii_pe.sections_end - dkii_pe.base) + ctypes.sizeof(pe_types.IMAGE_SECTION_HEADER) * sections_planning_to_add + if result_headers_size > dkii_pe.nt.OptionalHeader.SizeOfHeaders: + # expand pe header + new_SizeOfHeaders = my_pe.align_up(result_headers_size, dkii_pe.nt.OptionalHeader.FileAlignment) + delta_file = new_SizeOfHeaders - dkii_pe.nt.OptionalHeader.SizeOfHeaders + print(f"expand dkii.exe headers by 0x{delta_file:X}") + + expand_offs = dkii_pe.sections_end - dkii_pe.base + headers = (ctypes.c_ubyte * expand_offs).from_address(dkii_pe.base) + content = (ctypes.c_ubyte * (dkii_pe.size - expand_offs)).from_address(dkii_pe.base + expand_offs) + for sec in dkii_pe.sections: + sec.PointerToRawData += delta_file + dkii_pe.nt.OptionalHeader.SizeOfHeaders += delta_file + with io.BytesIO() as f: + f.write(headers) + f.write(b'\x00' * delta_file) + f.write(content) + dkii_data = f.getvalue() + dkii_pe = my_pe.MyPe(dkii_data) + return dkii_pe + + +def unpack_data_jmp(pe: my_pe.MyPe, xrefs_map: dict[int, list[tuple[int, int, str]]], va: int, name: str): + # unpack jump by data pointer + xrefs = xrefs_map[va] + assert len(xrefs) == 1 + va, value, rel_ty = xrefs[0] + rva = va - pe.nt.OptionalHeader.ImageBase + jmpff25 = (ctypes.c_ubyte * 2).from_address(pe.base + pe.rva2raw(rva) - 2) + assert bytes(jmpff25) == b"\xFF\x25" + print(f'{va:08X} {name}') + xrefs = xrefs_map[va - 2] # require relative references cant risk + assert xrefs + return va - 2 + + +def main( + flame_map_file: pathlib.Path, + dkii_map_file: pathlib.Path, + references_file: pathlib.Path, + dkii_exe: pathlib.Path, + flame_exe: pathlib.Path, + version: str, + output_exe: pathlib.Path, +): + with open(dkii_exe, 'rb') as f: + _data = f.read() + dkii_pe = my_pe.MyPe(_data) + + with open(flame_exe, 'rb') as f: + _data = f.read() + flame_pe = my_pe.MyPe(_data) + + # int: pe_types.IMAGE_DATA_DIRECTORY = dkii_pe.nt.OptionalHeader.DataDirectory[pe_types.IMAGE_DIRECTORY_ENTRY.IMPORT] + # int_start = dkii_pe.base + dkii_pe.rva2raw(int.VirtualAddress) + # int_hex = bytes((ctypes.c_ubyte * int.Size).from_address(int_start)).hex(' ') + # while int_hex: + # print(int_hex[:16*3]) + # int_hex = int_hex[16*3:] + # + # iat: pe_types.IMAGE_DATA_DIRECTORY = dkii_pe.nt.OptionalHeader.DataDirectory[pe_types.IMAGE_DIRECTORY_ENTRY.IAT] + # iat_start = dkii_pe.base + dkii_pe.rva2raw(iat.VirtualAddress) + # iat_hex = bytes((ctypes.c_ubyte * iat.Size).from_address(iat_start)).hex(' ') + # while iat_hex: + # print(iat_hex[:16*3]) + # iat_hex = iat_hex[16*3:] + + # for sec in flame_pe.sections: + # sec_name = bytes(sec.Name).rstrip(b'\x00').decode('ascii') + # print(f'{sec.PointerToRawData:08X}-{sec.PointerToRawData + sec.SizeOfRawData:08X} {sec.VirtualAddress:08X}-{sec.VirtualAddress + sec.VirtualSize:08X} {sec_name}') + # for sec in dkii_pe.sections: + # sec_name = bytes(sec.Name).rstrip(b'\x00').decode('ascii') + # print(f'{sec.PointerToRawData:08X}-{sec.PointerToRawData + sec.SizeOfRawData:08X} {sec.VirtualAddress:08X}-{sec.VirtualAddress + sec.VirtualSize:08X} {sec_name}') + + dkii_pe = resize_headers_to_fit_sections(dkii_pe, 4) + # (flame_exe.parent / 'dkii_hdr_expanded.exe').write_bytes(dkii_pe.data) + merged_pe = append_dll_sections_into_exe(dkii_pe.data, flame_pe.data) # + imports merge + # (flame_exe.parent / 'dkii_result.exe').write_bytes(merged_pe.data) + + print("new section mapping:") + for sec in merged_pe.sections: + sec_name = bytes(sec.Name).rstrip(b'\x00').decode('ascii') + print(f' file:{sec.PointerToRawData:08X}-{sec.PointerToRawData + sec.SizeOfRawData:08X} virt:{sec.VirtualAddress:08X}-{sec.VirtualAddress + sec.VirtualSize:08X} name:{sec_name}') + print() + + dkii_xrefs: dict[int, list[tuple[int, int, str]]] = collect_xrefs(references_file) + flame_xrefs: dict[int, list[tuple[int, int, str]]] = {} + for ty, rva in flame_pe.relocs(): + assert ty is pe_types.IMAGE_REL_BASED.HIGHLOW + offs = flame_pe.rva2raw(rva) + src_val = wintypes.DWORD.from_address(flame_pe.base + offs) + dst_va = src_val.value + src_va = flame_pe.nt.OptionalHeader.ImageBase + rva + flame_xrefs.setdefault(dst_va, []).append((src_va, 0, 'VA32')) + + dkii_map, to_replace = read_symbols(dkii_map_file) + dkii2flame_replace: list[ReplaceRefInfo] = [] + flame_map: dict[str, int] = collect_replace_info(flame_map_file, to_replace, dkii2flame_replace, flame_pe.nt.OptionalHeader.ImageBase) + + def fill_xrefs(to_replace: list[ReplaceRefInfo], xrefs_map: dict[int, list[tuple[int, int, str]]]): + for rref in to_replace: + xrefs = xrefs_map.get(rref.target_va, None) + if xrefs is None: + print(f'{rref.target_va:08X} symbol {rref.name} has no xrefs') + raise Exception() + rref.target_xrefs = xrefs + fill_xrefs(dkii2flame_replace, dkii_xrefs) + + merged_imports_map: dict[str, int] = {} + for imp in merged_pe.imports(): + for rva, ord, name, thunk_rva in imp.thunks(): + str_id = f'{imp.name.lower()}:{name if name is not None else ord}' + merged_imports_map[str_id] = rva + flame2merge_replace: list[ReplaceRefInfo] = [] + flame2dkii_replace: list[ReplaceRefInfo] = [] + for imp in flame_pe.imports(): + id_dkii = imp.name == 'DKII.dll' + for rva, ord, name, thunk_rva in imp.thunks(): + if id_dkii: + str_id = name + target_va = flame_pe.nt.OptionalHeader.ImageBase + rva + # if "@@3" in name: + # target_va = unpack_data_jmp(flame_pe, flame_xrefs, target_va, name) + new_va = dkii_map[name] + flame2dkii_replace.append(ReplaceRefInfo(str_id, target_va, new_va)) + else: + str_id = f'{imp.name.lower()}:{name if name is not None else ord}' + target_va = flame_pe.nt.OptionalHeader.ImageBase + rva + new_va = merged_pe.nt.OptionalHeader.ImageBase + merged_imports_map[str_id] + flame2merge_replace.append(ReplaceRefInfo(str_id, target_va, new_va)) + fill_xrefs(flame2merge_replace, flame_xrefs) # '1004B58C 007FF627' + + dkii2merge_replace: list[ReplaceRefInfo] = [] + for imp in dkii_pe.imports(): + for rva, ord, name, thunk_rva in imp.thunks(): + str_id = f'{imp.name.lower()}:{name if name is not None else ord}' + new_rva = merged_imports_map[str_id] + dkii2merge_replace.append(ReplaceRefInfo( + str_id, + dkii_pe.nt.OptionalHeader.ImageBase + rva, + merged_pe.nt.OptionalHeader.ImageBase + new_rva + )) + fill_xrefs(dkii2merge_replace, dkii_xrefs) + + delta_virt = merged_pe['.flame_t'].VirtualAddress - flame_pe['.text'].VirtualAddress + + version_va = flame_map['_Flame_version'] + version_rva = version_va - flame_pe.nt.OptionalHeader.ImageBase + delta_virt + offs = merged_pe.rva2raw(version_rva) + version_val = (ctypes.c_char * 64).from_address(merged_pe.base + offs) + print(f'version: {version}') + pos = version.find('build') + if pos != -1: + version = ' V' + version[:pos - 1] + '\n ' + version[pos:] + version_val.value = version.encode('ascii') + + # if(LPCSTR pos = strstr(version, "build")) { + # char ver[64]; + # char build[64]; + # strncpy(ver, version, pos - version - 1); + # ver[pos - version - 1] = '\0'; + # strcpy(build, pos); + # sprintf(out, " V%s\n %s", ver, build); + # status = true; + # } + + for replace_ref in dkii2flame_replace: + dst_rva = replace_ref.new_va - flame_pe.nt.OptionalHeader.ImageBase + delta_virt + for dkii_va, value, rel_ty in replace_ref.target_xrefs: + src_rva = dkii_va - dkii_pe.nt.OptionalHeader.ImageBase + offs = merged_pe.rva2raw(src_rva) + src_val = wintypes.DWORD.from_address(merged_pe.base + offs) + if rel_ty == 'VA32': + src_val.value = dst_rva + merged_pe.nt.OptionalHeader.ImageBase + elif rel_ty == 'REL32': + src_val.value = dst_rva - (src_rva + 4) + + for replace_ref in flame2merge_replace: # imports + dst_rva = replace_ref.new_va - merged_pe.nt.OptionalHeader.ImageBase + for flame_va, value, rel_ty in replace_ref.target_xrefs: + src_rva = flame_va - flame_pe.nt.OptionalHeader.ImageBase + delta_virt + offs = merged_pe.rva2raw(src_rva) + src_val = wintypes.DWORD.from_address(merged_pe.base + offs) + assert rel_ty == 'VA32' + src_val.value = dst_rva + merged_pe.nt.OptionalHeader.ImageBase + + for replace_ref in dkii2merge_replace: # imports + dst_rva = replace_ref.new_va - merged_pe.nt.OptionalHeader.ImageBase + for dkii_va, value, rel_ty in replace_ref.target_xrefs: + src_rva = dkii_va - dkii_pe.nt.OptionalHeader.ImageBase + offs = merged_pe.rva2raw(src_rva) + src_val = wintypes.DWORD.from_address(merged_pe.base + offs) + assert rel_ty == 'VA32' + src_val.value = dst_rva + merged_pe.nt.OptionalHeader.ImageBase + + for replace_ref in flame2dkii_replace: + dst_rva = replace_ref.new_va - merged_pe.nt.OptionalHeader.ImageBase + src_rva = replace_ref.target_va - flame_pe.nt.OptionalHeader.ImageBase + delta_virt + offs = merged_pe.rva2raw(src_rva) + src_val = wintypes.DWORD.from_address(merged_pe.base + offs) + src_val.value = dst_rva + merged_pe.nt.OptionalHeader.ImageBase + + merged_pe.nt.OptionalHeader.Subsystem = pe_types.IMAGE_SUBSYSTEM_WINDOWS_CUI + + output_exe.write_bytes(merged_pe.data) + symbols_map = [] + for name, va in dkii_map.items(): + symbols_map.append((va, name)) + for name, va in flame_map.items(): + symbols_map.append((va - flame_pe.nt.OptionalHeader.ImageBase + delta_virt + merged_pe.nt.OptionalHeader.ImageBase, name)) + symbols_map.sort(key=lambda e: e[0]) + with open(output_exe.parent / f'{os.path.splitext(output_exe.name)[0]}.map', 'w') as f: + for va, name in symbols_map: + f.write(f'{va:08X} {name}\n') + + +def start(): + parser = argparse.ArgumentParser(description='Optional app description') + parser.add_argument('-flame_map_file', type=str, required=True) + parser.add_argument('-dkii_map_file', type=str, required=True) + parser.add_argument('-references_file', type=str, required=True) + parser.add_argument('-dkii_exe', type=str, required=True) + parser.add_argument('-flame_exe', type=str, required=True) + parser.add_argument('-version', type=str, required=True) + parser.add_argument('-output_exe', type=str, required=True) + args = parser.parse_args() + main( + pathlib.Path(args.flame_map_file), + pathlib.Path(args.dkii_map_file), + pathlib.Path(args.references_file), + pathlib.Path(args.dkii_exe), + pathlib.Path(args.flame_exe), + args.version, + pathlib.Path(args.output_exe) + ) + + +if __name__ == '__main__': + start() diff --git a/tools/merge_dkii/my_pe.py b/tools/merge_dkii/my_pe.py new file mode 100644 index 0000000..6366478 --- /dev/null +++ b/tools/merge_dkii/my_pe.py @@ -0,0 +1,163 @@ +import functools +import ctypes +import typing +from ctypes import wintypes +import pe_types + + +def align_up(val, align): + return (val + align - 1) & ~(align - 1) + + +class MyImport: + + def __init__(self, pe, desc: pe_types.IMAGE_IMPORT_DESCRIPTOR): + self.pe: MyPe = pe + self.desc = desc + + @property + def name(self): + return ctypes.string_at(self.pe.base + self.pe.rva2raw(self.desc.Name)).decode('ascii') + + def thunks(self) -> typing.Iterable[typing.Tuple[int, int, str]]: + start = self.pe.base + self.pe.rva2raw(self.desc.FirstThunk) + pos = start + while True: + val = wintypes.DWORD.from_address(pos).value + if val == 0: + break + if val & pe_types.IMAGE_ORDINAL_FLAG != 0: + thunk_rva = None + ord = val & 0xFFFF + name = None + else: + thunk_rva = val + thunk_pos = self.pe.base + self.pe.rva2raw(val) + ord = wintypes.WORD.from_address(thunk_pos).value + name = ctypes.string_at(thunk_pos + 2).decode('ascii') + rva = self.desc.FirstThunk + (pos - start) + yield rva, ord, name, thunk_rva + pos += ctypes.sizeof(wintypes.DWORD) + + def original_thunks(self) -> typing.Iterable[typing.Tuple[int, int, str]]: + start = self.pe.base + self.pe.rva2raw(self.desc.OriginalFirstThunk) + pos = start + while pos[0] != 0: + val = wintypes.DWORD.from_address(pos).value + name = None + if val & pe_types.IMAGE_ORDINAL_FLAG != 0: + ord = val & 0xFFFF + else: + thunk_pos = self.pe.base + self.pe.rva2raw(val) + ord = wintypes.WORD.from_address(thunk_pos).value + name = ctypes.string_at(thunk_pos + 2).decode('ascii') + rva = self.desc.OriginalFirstThunk + (pos - start) + yield rva, ord, name + pos += ctypes.sizeof(wintypes.DWORD) + + +class MyPe: + + def __init__(self, data: bytes): + self._data = bytearray(data) + self._c_data = (ctypes.c_char*len(self._data)).from_buffer(self._data) + + @property + def data(self) -> bytearray: + return self._data + + @functools.cached_property + def size(self) -> int: + return len(self._data) + + @functools.cached_property + def base(self) -> int: + return ctypes.addressof(self._c_data) + + @functools.cached_property + def dos(self) -> pe_types.IMAGE_DOS_HEADER: + return pe_types.IMAGE_DOS_HEADER.from_address(self.base) + + @functools.cached_property + def nt(self) -> pe_types.IMAGE_NT_HEADERS32: + return pe_types.IMAGE_NT_HEADERS32.from_address(self.base + self.dos.e_lfanew) + + @functools.cached_property + def sections_start(self) -> int: + return ctypes.addressof(self.nt.OptionalHeader) + self.nt.FileHeader.SizeOfOptionalHeader + + @property + def sections(self) -> typing.Iterable[pe_types.IMAGE_SECTION_HEADER]: + sections = ctypes.pointer(pe_types.IMAGE_SECTION_HEADER.from_address(self.sections_start)) + for i in range(self.nt.FileHeader.NumberOfSections): + yield sections[i] + + def rva2raw(self, rva): + for sec in self.sections: + if sec.VirtualAddress <= rva < (sec.VirtualAddress + sec.VirtualSize): + return rva - sec.VirtualAddress + sec.PointerToRawData + raise Exception(f'rva {rva:08X} cannot be converted to raw') + + @functools.lru_cache + def section_by_name(self, name) -> pe_types.IMAGE_SECTION_HEADER: + sections = ctypes.pointer(pe_types.IMAGE_SECTION_HEADER.from_address(self.sections_start)) + for i in range(self.nt.FileHeader.NumberOfSections): + sec = sections[i] + sec_name = bytes(sec.Name).rstrip(b'\x00').decode('ascii') + if sec_name == name: + return sec + raise Exception(f'section {name} is not found') + + def __getitem__(self, name: str) -> pe_types.IMAGE_SECTION_HEADER: + return self.section_by_name(name) + + @property + def sections_end(self) -> int: + sections = ctypes.pointer(pe_types.IMAGE_SECTION_HEADER.from_address(self.sections_start)) + return ctypes.addressof(sections[self.nt.FileHeader.NumberOfSections]) + + def relocs(self) -> typing.Iterable[typing.Tuple[pe_types.IMAGE_REL_BASED, int]]: + reloc = self.section_by_name('.reloc') + assert reloc.VirtualSize < reloc.SizeOfRawData + pos = self.base + reloc.PointerToRawData + relocs_end = pos + reloc.VirtualSize + while pos < relocs_end: + rel = pe_types.IMAGE_BASE_RELOCATION.from_address(pos) + rel_base = rel.VirtualAddress + block_end = pos + rel.SizeOfBlock + pos += ctypes.sizeof(pe_types.IMAGE_BASE_RELOCATION) + while pos < block_end: + rel_val = wintypes.WORD.from_address(pos).value + if rel_val: + rel_ty = rel_val >> 12 + rel_offs = rel_val & 0xFFF + rel_rva = rel_base + rel_offs + yield pe_types.IMAGE_REL_BASED(rel_ty), rel_rva + pos += 2 + pos = align_up(pos, 4) + + def exports(self) -> typing.Iterable[typing.Tuple[str, int]]: + export_data_dir: pe_types.IMAGE_DATA_DIRECTORY = self.nt.OptionalHeader.DataDirectory[pe_types.IMAGE_DIRECTORY_ENTRY.EXPORT] + if export_data_dir.VirtualAddress == 0: + return + export_dir = pe_types.IMAGE_EXPORT_DIRECTORY.from_address(self.base + self.rva2raw(export_data_dir.VirtualAddress)) + functions = ctypes.pointer(wintypes.DWORD.from_address(self.base + self.rva2raw(export_dir.AddressOfFunctions))) + names = ctypes.pointer(wintypes.DWORD.from_address(self.base + self.rva2raw(export_dir.AddressOfNames))) + ordinals = ctypes.pointer(wintypes.WORD.from_address(self.base + self.rva2raw(export_dir.AddressOfNameOrdinals))) + for i in range(export_dir.NumberOfNames): + exp_name = ctypes.string_at(self.base + self.rva2raw(names[i])).decode('ascii') + rva = functions[ordinals[i]] + yield exp_name, rva + + def imports(self) -> typing.Iterable[MyImport]: + import_data_dir: pe_types.IMAGE_DATA_DIRECTORY = self.nt.OptionalHeader.DataDirectory[pe_types.IMAGE_DIRECTORY_ENTRY.IMPORT] + if import_data_dir.VirtualAddress == 0: + return + + pos = self.base + self.rva2raw(import_data_dir.VirtualAddress) + while True: + desc = pe_types.IMAGE_IMPORT_DESCRIPTOR.from_address(pos) + if desc.Name == 0 and desc.FirstThunk == 0: + break + yield MyImport(self, desc) + pos += ctypes.sizeof(pe_types.IMAGE_IMPORT_DESCRIPTOR) diff --git a/tools/merge_dkii/pe_types.py b/tools/merge_dkii/pe_types.py new file mode 100644 index 0000000..bb18494 --- /dev/null +++ b/tools/merge_dkii/pe_types.py @@ -0,0 +1,286 @@ +import ctypes +import enum +from ctypes import wintypes + + +class IMAGE_DOS_HEADER(ctypes.Structure): + _fields_ = [ + ("e_magic", wintypes.WORD), + ("e_cblp", wintypes.WORD), + ("e_cp", wintypes.WORD), + ("e_crlc", wintypes.WORD), + ("e_cparhdr", wintypes.WORD), + ("e_minalloc", wintypes.WORD), + ("e_maxalloc", wintypes.WORD), + ("e_ss", wintypes.WORD), + ("e_sp", wintypes.WORD), + ("e_csum", wintypes.WORD), + ("e_ip", wintypes.WORD), + ("e_cs", wintypes.WORD), + ("e_lfarlc", wintypes.WORD), + ("e_ovno", wintypes.WORD), + ("e_res", wintypes.WORD * 4), + ("e_oemid", wintypes.WORD), + ("e_oeminfo", wintypes.WORD), + ("e_res2", wintypes.WORD * 10), + ("e_lfanew", wintypes.LONG), + ] + e_magic: int + e_cblp: int + e_cp: int + e_crlc: int + e_cparhdr: int + e_minalloc: int + e_maxalloc: int + e_ss: int + e_sp: int + e_csum: int + e_ip: int + e_cs: int + e_lfarlc: int + e_ovno: int + e_res: int + e_oemid: int + e_oeminfo: int + e_res2: int + e_lfanew: int + + +class IMAGE_FILE_HEADER(ctypes.Structure): + _fields_ = [ + ("Machine", wintypes.WORD), + ("NumberOfSections", wintypes.WORD), + ("TimeDateStamp", wintypes.DWORD), + ("PointerToSymbolTable", wintypes.DWORD), + ("NumberOfSymbols", wintypes.DWORD), + ("SizeOfOptionalHeader", wintypes.WORD), + ("Characteristics", wintypes.WORD), + ] + Machine: int + NumberOfSections: int + TimeDateStamp: int + PointerToSymbolTable: int + NumberOfSymbols: int + SizeOfOptionalHeader: int + Characteristics: int + + +class IMAGE_DATA_DIRECTORY(ctypes.Structure): + _fields_ = [ + ("VirtualAddress", wintypes.DWORD), + ("Size", wintypes.DWORD), + ] + VirtualAddress: int + Size: int + + +IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16 + + +class IMAGE_OPTIONAL_HEADER32(ctypes.Structure): + _fields_ = [ + ("Magic", wintypes.WORD), + ("MajorLinkerVersion", wintypes.BYTE), + ("MinorLinkerVersion", wintypes.BYTE), + ("SizeOfCode", wintypes.DWORD), + ("SizeOfInitializedData", wintypes.DWORD), + ("SizeOfUninitializedData", wintypes.DWORD), + ("AddressOfEntryPoint", wintypes.DWORD), + ("BaseOfCode", wintypes.DWORD), + ("BaseOfData", wintypes.DWORD), + ("ImageBase", wintypes.DWORD), + ("SectionAlignment", wintypes.DWORD), + ("FileAlignment", wintypes.DWORD), + ("MajorOperatingSystemVersion", wintypes.WORD), + ("MinorOperatingSystemVersion", wintypes.WORD), + ("MajorImageVersion", wintypes.WORD), + ("MinorImageVersion", wintypes.WORD), + ("MajorSubsystemVersion", wintypes.WORD), + ("MinorSubsystemVersion", wintypes.WORD), + ("Win32VersionValue", wintypes.DWORD), + ("SizeOfImage", wintypes.DWORD), + ("SizeOfHeaders", wintypes.DWORD), + ("CheckSum", wintypes.DWORD), + ("Subsystem", wintypes.WORD), + ("DllCharacteristics", wintypes.WORD), + ("SizeOfStackReserve", wintypes.DWORD), + ("SizeOfStackCommit", wintypes.DWORD), + ("SizeOfHeapReserve", wintypes.DWORD), + ("SizeOfHeapCommit", wintypes.DWORD), + ("LoaderFlags", wintypes.DWORD), + ("NumberOfRvaAndSizes", wintypes.DWORD), + ("DataDirectory", IMAGE_DATA_DIRECTORY * IMAGE_NUMBEROF_DIRECTORY_ENTRIES), + ] + Magic: int + MajorLinkerVersion: int + MinorLinkerVersion: int + SizeOfCode: int + SizeOfInitializedData: int + SizeOfUninitializedData: int + AddressOfEntryPoint: int + BaseOfCode: int + BaseOfData: int + ImageBase: int + SectionAlignment: int + FileAlignment: int + MajorOperatingSystemVersion: int + MinorOperatingSystemVersion: int + MajorImageVersion: int + MinorImageVersion: int + MajorSubsystemVersion: int + MinorSubsystemVersion: int + Win32VersionValue: int + SizeOfImage: int + SizeOfHeaders: int + CheckSum: int + Subsystem: int + DllCharacteristics: int + SizeOfStackReserve: int + SizeOfStackCommit: int + SizeOfHeapReserve: int + SizeOfHeapCommit: int + LoaderFlags: int + NumberOfRvaAndSizes: int + DataDirectory: IMAGE_DATA_DIRECTORY * IMAGE_NUMBEROF_DIRECTORY_ENTRIES + + +class IMAGE_NT_HEADERS32(ctypes.Structure): + _fields_ = [ + ("Signature", wintypes.DWORD), + ("FileHeader", IMAGE_FILE_HEADER), + ("OptionalHeader", IMAGE_OPTIONAL_HEADER32), + ] + Signature: int + FileHeader: IMAGE_FILE_HEADER + OptionalHeader: IMAGE_OPTIONAL_HEADER32 + + +IMAGE_SIZEOF_SHORT_NAME = 8 + + +class IMAGE_SECTION_HEADER(ctypes.Structure): + _fields_ = [ + ("Name", wintypes.BYTE * IMAGE_SIZEOF_SHORT_NAME), + ("VirtualSize", wintypes.DWORD), + ("VirtualAddress", wintypes.DWORD), + ("SizeOfRawData", wintypes.DWORD), + ("PointerToRawData", wintypes.DWORD), + ("PointerToRelocations", wintypes.DWORD), + ("PointerToLinenumbers", wintypes.DWORD), + ("NumberOfRelocations", wintypes.WORD), + ("NumberOfLinenumbers", wintypes.WORD), + ("Characteristics", wintypes.DWORD), + ] + Name: wintypes.BYTE * IMAGE_SIZEOF_SHORT_NAME + VirtualSize: int + VirtualAddress: int + SizeOfRawData: int + PointerToRawData: int + PointerToRelocations: int + PointerToLinenumbers: int + NumberOfRelocations: int + NumberOfLinenumbers: int + Characteristics: int + + +class IMAGE_BASE_RELOCATION(ctypes.Structure): + _fields_ = [ + ("VirtualAddress", wintypes.DWORD), + ("SizeOfBlock", wintypes.DWORD), + ] + VirtualAddress: int + SizeOfBlock: int + + +class IMAGE_REL_BASED(enum.IntEnum): + ABSOLUTE = 0 + HIGH = 1 + LOW = 2 + HIGHLOW = 3 + HIGHADJ = 4 + MACHINE_SPECIFIC_5 = 5 + RESERVED = 6 + MACHINE_SPECIFIC_7 = 7 + MACHINE_SPECIFIC_8 = 8 + MACHINE_SPECIFIC_9 = 9 + DIR64 = 10 + + +class IMAGE_DIRECTORY_ENTRY(enum.IntEnum): + EXPORT = 0 # Export Directory + IMPORT = 1 # Import Directory + RESOURCE = 2 # Resource Directory + EXCEPTION = 3 # Exception Directory + SECURITY = 4 # Security Directory + BASERELOC = 5 # Base Relocation Table + DEBUG = 6 # Debug Directory + ARCHITECTURE = 7 # Architecture Specific Data + GLOBALPTR = 8 # RVA of GP + TLS = 9 # TLS Directory + LOAD_CONFIG = 10 # Load Configuration Directory + BOUND_IMPORT = 11 # Bound Import Directory in headers + IAT = 12 # Import Address Table + DELAY_IMPORT = 13 # Delay Load Import Descriptors + COM_DESCRIPTOR = 14 # COM Runtime descriptor + + +class IMAGE_EXPORT_DIRECTORY(ctypes.Structure): + _fields_ = [ + ("Characteristics", wintypes.DWORD), + ("TimeDateStamp", wintypes.DWORD), + ("MajorVersion", wintypes.WORD), + ("MinorVersion", wintypes.WORD), + ("Name", wintypes.DWORD), + ("Base", wintypes.DWORD), + ("NumberOfFunctions", wintypes.DWORD), + ("NumberOfNames", wintypes.DWORD), + ("AddressOfFunctions", wintypes.DWORD), + ("AddressOfNames", wintypes.DWORD), + ("AddressOfNameOrdinals", wintypes.DWORD), + ] + Characteristics: int + TimeDateStamp: int + MajorVersion: int + MinorVersion: int + Name: int + Base: int + NumberOfFunctions: int + NumberOfNames: int + AddressOfFunctions: int + AddressOfNames: int + AddressOfNameOrdinals: int + + +class IMAGE_IMPORT_DESCRIPTOR(ctypes.Structure): + _fields_ = [ + ("OriginalFirstThunk", wintypes.DWORD), + ("TimeDateStamp", wintypes.DWORD), + ("ForwarderChain", wintypes.DWORD), + ("Name", wintypes.DWORD), + ("FirstThunk", wintypes.DWORD), + ] + OriginalFirstThunk: int + TimeDateStamp: int + ForwarderChain: int + Name: int + FirstThunk: int + + +IMAGE_ORDINAL_FLAG = 0x80000000 +IMAGE_SCN_CNT_CODE = 0x00000020 # Section contains code. +IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040 # Section contains initialized data. +IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080 # Section contains uninitialized data. +IMAGE_SCN_MEM_EXECUTE = 0x20000000 # Section is executable. +IMAGE_SCN_MEM_READ = 0x40000000 # Section is readable. +IMAGE_SCN_MEM_WRITE = 0x80000000 # Section is writeable. + +def format_acc(chars): + val = [] + val.append('R' if chars & IMAGE_SCN_MEM_READ else '-') + val.append('W' if chars & IMAGE_SCN_MEM_WRITE else '-') + val.append('X' if chars & IMAGE_SCN_MEM_EXECUTE else '-') + return val + + +IMAGE_SUBSYSTEM_WINDOWS_GUI = 2 +IMAGE_SUBSYSTEM_WINDOWS_CUI = 3