1#define DTTR_TEST_BINARY_SUPPORT
3#undef DTTR_TEST_BINARY_SUPPORT
8#include <Zydis/Utils.h>
17 const DTTR_TestBinaryFixture *fixture,
18 const DTTR_TestPEImage *image
23 const DTTR_TestPEImage *image,
31 return image->image + rva;
35 sds path = sdsnew(dir ? dir :
"");
51 if (!fixture_dir || !out_initialized) {
55 *out_initialized =
false;
56 if (PHYSFS_isInit()) {
57 return PHYSFS_mount(fixture_dir,
NULL, 1) != 0;
60 if (!PHYSFS_init(
"dttr-tests")) {
64 *out_initialized =
true;
65 if (PHYSFS_mount(fixture_dir,
NULL, 1)) {
70 *out_initialized =
false;
77 PHYSFS_unmount(fixture_dir);
87 if (!filename || !out || !out_size) {
91 PHYSFS_File *file = PHYSFS_openRead(filename);
97 const PHYSFS_sint64 len = PHYSFS_fileLength(file);
99 if (len < 0 || (PHYSFS_uint64)len > (PHYSFS_uint64)SIZE_MAX) {
104 const size_t size = (size_t)len;
112 const bool read_ok = PHYSFS_readBytes(file,
data,
size) == (PHYSFS_sint64)
size;
113 const bool close_ok = PHYSFS_close(file) != 0;
115 if (!read_ok || !close_ok) {
126 return offset <= total &&
size <= total - offset;
131 return offset < 0 ? (uintptr_t)(-(int64_t)offset) : (uintptr_t)offset;
143 if (base < magnitude) {
151 if (base > (uintptr_t)SIZE_MAX - magnitude) {
161 return offset < 0 ? base - magnitude : base + magnitude;
165 const uint8_t *actual,
166 const uint8_t *expected,
170 if (!actual || !expected || !mask || strlen(mask) !=
size) {
174 for (
size_t i = 0; i <
size; i++) {
175 if (mask[i] ==
'x' && actual[i] != expected[i]) {
184 return a && b && PHYSFS_utf8stricmp(a, b) == 0;
188 return fixture_index < 64u && (required & DTTR_TEST_FIXTURE_BIT(fixture_index)) != 0;
192 const DTTR_TestBinaryFixture *fixtures,
193 size_t fixture_count,
194 const char *fixture_dir
196 if (!fixtures || !fixture_dir) {
200 bool initialized =
false;
208 for (
size_t i = 0; i < fixture_count; i++) {
211 if (!PHYSFS_stat(fixtures[i].filename, &stat)
212 || stat.filetype != PHYSFS_FILETYPE_REGULAR) {
224 const DTTR_TestPEImage *image,
226 const IMAGE_NT_HEADERS32 *nt,
227 const IMAGE_SECTION_HEADER **out_sections
229 if (!image || !nt || !out_sections) {
233 const size_t optional_offset = pe_offset
234 + offsetof(IMAGE_NT_HEADERS32, OptionalHeader);
235 if (optional_offset < pe_offset) {
239 const size_t section_header = optional_offset + nt->FileHeader.SizeOfOptionalHeader;
240 if (section_header < optional_offset) {
244 const size_t section_count = nt->FileHeader.NumberOfSections;
245 if (section_count > SIZE_MAX /
sizeof(IMAGE_SECTION_HEADER)) {
249 const size_t section_table_size = section_count *
sizeof(IMAGE_SECTION_HEADER);
254 *out_sections = (
const IMAGE_SECTION_HEADER *)(image->file + section_header);
259static bool pe_copy_section(DTTR_TestPEImage *image,
const IMAGE_SECTION_HEADER *section) {
260 if (section->SizeOfRawData == 0) {
265 section->PointerToRawData,
266 section->SizeOfRawData,
270 section->VirtualAddress,
271 section->SizeOfRawData,
278 image->image + section->VirtualAddress,
279 image->file + section->PointerToRawData,
280 section->SizeOfRawData
286static bool load_pe_image(uint8_t *file,
size_t file_size, DTTR_TestPEImage *image) {
287 if (!file || !image) {
291 memset(image, 0,
sizeof(*image));
293 image->file_size = file_size;
295 if (image->file_size <
sizeof(IMAGE_DOS_HEADER)) {
299 const IMAGE_DOS_HEADER *dos = (
const IMAGE_DOS_HEADER *)image->file;
301 if (dos->e_magic != IMAGE_DOS_SIGNATURE || dos->e_lfanew < 0) {
305 const size_t pe_offset = (size_t)dos->e_lfanew;
311 const IMAGE_NT_HEADERS32 *nt = (
const IMAGE_NT_HEADERS32 *)(image->file + pe_offset);
313 if (nt->Signature != IMAGE_NT_SIGNATURE
314 || nt->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC
315 || nt->OptionalHeader.SizeOfImage == 0) {
319 const IMAGE_OPTIONAL_HEADER32 *optional = &nt->OptionalHeader;
320 if (optional->SizeOfHeaders > optional->SizeOfImage) {
324 image->image = calloc(1, optional->SizeOfImage);
330 image->image_size = optional->SizeOfImage;
331 image->imports_dir = optional->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
333 size_t header_copy = optional->SizeOfHeaders;
334 header_copy = header_copy < image->file_size ? header_copy : image->file_size;
335 if (header_copy > image->image_size) {
339 memcpy(image->image, image->file, header_copy);
341 const IMAGE_SECTION_HEADER *sections =
NULL;
347 for (uint16_t i = 0; i < nt->FileHeader.NumberOfSections; i++) {
361 const DTTR_TestBinaryFixture *fixtures,
362 size_t fixture_count,
363 size_t fixture_index,
364 const char *fixture_dir,
366 DTTR_TestPEImage *out_image
368 if (!fixtures || !fixture_dir || fixture_index >= fixture_count || !out_path
373 const DTTR_TestBinaryFixture *fixture = &fixtures[fixture_index];
380 bool initialized =
false;
387 uint8_t *file =
NULL;
388 size_t file_size = 0;
406 const DTTR_TestBinaryFixture *fixtures,
407 size_t fixture_count,
408 const char *fixture_dir,
409 DTTR_TestPEFixtureVisitor visitor,
412 if (!fixtures || !fixture_dir || !visitor) {
416 for (
size_t i = 0; i < fixture_count; i++) {
417 const DTTR_TestBinaryFixture *fixture = &fixtures[i];
419 DTTR_TestPEImage image = {0};
432 const bool ok = visitor(i, fixture, path, &image, userdata);
451 memset(image, 0,
sizeof(*image));
455 const uint8_t *bytes,
460 if (!bytes || !sig || !mask) {
464 const size_t mask_len = strlen(mask);
465 if (!mask_len || mask_len >
size) {
470 while (anchor < mask_len && mask[anchor] !=
'x') {
474 if (anchor == mask_len) {
475 return size - mask_len + 1u;
478 const uint8_t anchor_byte = sig[anchor];
479 const uint8_t *cursor = bytes + anchor;
480 const uint8_t *
const end = cursor + (
size - mask_len + 1u);
483 while (cursor < end) {
484 const size_t remaining = (size_t)(end - cursor);
485 const uint8_t *candidate = memchr(cursor, anchor_byte, remaining);
490 const size_t offset = (size_t)(candidate - bytes) - anchor;
492 for (; i < mask_len; i++) {
493 if (mask[i] !=
'x') {
497 if (bytes[offset + i] != sig[i]) {
506 cursor = candidate + 1;
513 const DTTR_TestPEImage *image,
517 if (!image || !image->image || !sig || !mask) {
525 const DTTR_TestPEImage *image,
529 if (!image || !sig || !mask) {
530 return DTTR_TEST_SIG_NOT_FOUND;
533 const uint8_t *match =
DTTR_Sigscan_Bytes(image->image, image->image_size, sig, mask);
534 return match ? (uintptr_t)(match - image->image) : DTTR_TEST_SIG_NOT_FOUND;
538 if (!image || !image->file) {
542 return XXH3_64bits(image->file, image->file_size);
546 const DTTR_TestBinaryFixture *fixture,
547 const DTTR_TestPEImage *image
549 if (!fixture || !image) {
553 return image->file_size == fixture->size
558 const char *str = (
const char *)
pe_rva_bytes(image, rva, 1);
564 return memchr(str,
'\0', image->image_size - (
size_t)rva) ? str :
NULL;
568 const DTTR_TestPEImage *image,
569 DTTR_TestImportEntry *imports,
572 if (!image || !imports) {
576 if (image->imports_dir.VirtualAddress == 0) {
580 const uintptr_t imports_rva = image->imports_dir.VirtualAddress;
583 for (uintptr_t desc_rva = imports_rva;; desc_rva +=
sizeof(IMAGE_IMPORT_DESCRIPTOR)) {
584 const IMAGE_IMPORT_DESCRIPTOR *
desc = (
const IMAGE_IMPORT_DESCRIPTOR *)
592 uintptr_t name_thunk_rva =
desc->OriginalFirstThunk ?
desc->OriginalFirstThunk
594 uintptr_t addr_thunk_rva =
desc->FirstThunk;
596 if (!dll || !name_thunk_rva || !addr_thunk_rva) {
600 for (;; name_thunk_rva +=
sizeof(IMAGE_THUNK_DATA32),
601 addr_thunk_rva +=
sizeof(IMAGE_THUNK_DATA32)) {
602 const IMAGE_THUNK_DATA32 *name_thunk = (
const IMAGE_THUNK_DATA32 *)
603 pe_rva_bytes(image, name_thunk_rva,
sizeof(*name_thunk));
609 if (name_thunk->u1.AddressOfData == 0) {
613 if (IMAGE_SNAP_BY_ORDINAL32(name_thunk->u1.Ordinal)) {
617 const uintptr_t import_name_rva = name_thunk->u1.AddressOfData;
618 const IMAGE_IMPORT_BY_NAME *
import = (const IMAGE_IMPORT_BY_NAME *)
619 pe_rva_bytes(image, import_name_rva, sizeof(WORD));
622 import_name_rva + offsetof(IMAGE_IMPORT_BY_NAME, Name)
625 if (!
import || !import_name ||
count >= imports_cap) {
629 imports[
count++] = (DTTR_TestImportEntry){
632 .iat_site = addr_thunk_rva,
641 static bool initialized =
false;
644 if (!ZYAN_SUCCESS(ZydisDecoderInit(
646 ZYDIS_MACHINE_MODE_LEGACY_32,
659 const uint8_t *bytes,
661 DTTR_TestDecodedInstruction *out
665 if (!
decoder || !bytes || !out) {
669 memset(out, 0,
sizeof(*out));
671 ZydisDecoderDecodeFull(
decoder, bytes,
size, &out->instruction, out->operands)
676 const DTTR_TestPEImage *image,
678 DTTR_TestDecodedInstruction *out
680 if (!image || rva >= image->image_size) {
686 image->image_size - (
size_t)rva,
692 const DTTR_TestPEImage *image,
694 size_t required_size,
697 if (!image || rva >= image->image_size) {
701 size_t decoded_size = 0;
703 while (decoded_size < required_size) {
704 DTTR_TestDecodedInstruction decoded = {0};
707 || decoded.instruction.length == 0) {
711 decoded_size += decoded.instruction.length;
715 *out_size = decoded_size;
722 const DTTR_TestDecodedInstruction *decoded,
723 size_t operand_index,
724 uintptr_t runtime_address,
725 uintptr_t *out_address
727 if (!decoded || !out_address || operand_index >= ZYDIS_MAX_OPERAND_COUNT) {
733 if (!ZYAN_SUCCESS(ZydisCalcAbsoluteAddress(
734 &decoded->instruction,
735 &decoded->operands[operand_index],
736 (ZyanU64)runtime_address,
742 if (address > (ZyanU64)UINTPTR_MAX) {
746 *out_address = (uintptr_t)address;
752 const DTTR_TestBinaryFixture *fixture,
753 const DTTR_TestTargetExpectation *
target,
754 const DTTR_TestPEImage *image,
757 DTTR_TestDecodedInstruction call = {0};
759 assert_int_equal(call.instruction.mnemonic, ZYDIS_MNEMONIC_CALL);
760 assert_int_equal(call.operands[0].type, ZYDIS_OPERAND_TYPE_IMMEDIATE);
767 "%s resolved outside image in %s: match=0x%08X thunk=0x%08X",
775 DTTR_TestDecodedInstruction jmp = {0};
777 assert_int_equal(jmp.instruction.mnemonic, ZYDIS_MNEMONIC_JMP);
778 assert_int_equal(jmp.operands[0].type, ZYDIS_OPERAND_TYPE_MEMORY);
787 const DTTR_TestPEImage *image,
790 DTTR_TestDecodedInstruction mov = {0};
792 assert_int_equal(mov.operands[1].type, ZYDIS_OPERAND_TYPE_MEMORY);
801 const DTTR_TestBinaryFixture *fixture,
802 const DTTR_TestTargetExpectation *
target,
803 const DTTR_TestPEImage *image,
806 assert_non_null(
target->patch_bytes);
807 assert_true(
target->patch_size > 0);
808 assert_non_null(
target->expected_original);
809 assert_non_null(
target->expected_original_mask);
818 DTTR_TestDecodedInstruction decoded = {0};
820 assert_true(decoded.instruction.length > 0);
821 assert_true(decoded.instruction.length <=
target->patch_size);
823 const uint8_t *actual = image->image + site;
827 target->expected_original,
828 target->expected_original_mask,
832 "%s original bytes mismatch in %s at 0x%08X",
839 assert_memory_not_equal(actual,
target->patch_bytes,
target->patch_size);
843 const DTTR_TestBinaryFixture *fixture,
844 const DTTR_TestTargetExpectation *
target,
845 const DTTR_TestPEImage *image
847 assert_non_null(fixture);
849 assert_non_null(image);
853 if (match == DTTR_TEST_SIG_NOT_FOUND) {
855 "required target %s did not resolve in %s (%s)",
865 "required target %s resolved %zu times in %s (%s); expected exactly one "
874 DTTR_TestDecodedInstruction decoded = {0};
876 assert_true(decoded.instruction.length > 0);
879 case DTTR_TEST_TARGET_RESOLVE:
881 case DTTR_TEST_TARGET_JMP_HOOK:
882 case DTTR_TEST_TARGET_TRAMPOLINE_HOOK: {
883 size_t decoded_size = 0;
885 assert_true(decoded_size >= 5);
888 case DTTR_TEST_TARGET_POINTER_FF25_E8_TARGET:
891 case DTTR_TEST_TARGET_POINTER_U32_AT_MATCH_PLUS_2:
894 case DTTR_TEST_TARGET_BYTE_PATCH:
static bool dttr_test_pe_load_fixture(const DTTR_TestBinaryFixture *fixtures, size_t fixture_count, size_t fixture_index, const char *fixture_dir, sds *out_path, DTTR_TestPEImage *out_image)
static uint64_t dttr_test_pe_file_hash(const DTTR_TestPEImage *image)
static void assert_pointer_ff25_e8_target(const DTTR_TestBinaryFixture *fixture, const DTTR_TestTargetExpectation *target, const DTTR_TestPEImage *image, uintptr_t match)
static const uint8_t * pe_rva_bytes(const DTTR_TestPEImage *image, uintptr_t rva, size_t size)
static void unmount_fixture_dir(const char *fixture_dir, bool initialized)
static bool dttr_test_bytes_match_mask(const uint8_t *actual, const uint8_t *expected, const char *mask, size_t size)
void dttr_test_assert_target_resolved(const DTTR_TestBinaryFixture *fixture, const DTTR_TestTargetExpectation *target, const DTTR_TestPEImage *image)
bool dttr_test_case_equal(const char *a, const char *b)
bool dttr_test_pe_for_each_fixture(const DTTR_TestBinaryFixture *fixtures, size_t fixture_count, const char *fixture_dir, DTTR_TestPEFixtureVisitor visitor, void *userdata)
static bool read_fixture_file(const char *filename, uint8_t **out, size_t *out_size)
static bool pe_copy_section(DTTR_TestPEImage *image, const IMAGE_SECTION_HEADER *section)
static bool dttr_test_zydis_decode32_prefix(const DTTR_TestPEImage *image, uintptr_t rva, size_t required_size, size_t *out_size)
static const char * dttr_test_pe_cstr(const DTTR_TestPEImage *image, uintptr_t rva)
bool dttr_test_fixtures_available(const DTTR_TestBinaryFixture *fixtures, size_t fixture_count, const char *fixture_dir)
static size_t masked_sigscan_count(const uint8_t *bytes, size_t size, const uint8_t *sig, const char *mask)
size_t DTTR_TestPE_CollectImports(const DTTR_TestPEImage *image, DTTR_TestImportEntry *imports, size_t imports_cap)
static void assert_pointer_u32_at_match_plus_2(const DTTR_TestPEImage *image, uintptr_t match)
uintptr_t DTTR_TestPE_Sigscan(const DTTR_TestPEImage *image, const uint8_t *sig, const char *mask)
static ZydisDecoder * zydis_decoder32()
static bool mount_fixture_dir(const char *fixture_dir, bool *out_initialized)
static sds dttr_test_join_path(const char *dir, const char *file)
bool dttr_test_signed_range_valid(uintptr_t base, int32_t offset, size_t size, size_t total)
bool dttr_test_fixture_required(DTTR_TestFixtureMask required, size_t fixture_index)
static bool dttr_test_zydis_absolute_operand(const DTTR_TestDecodedInstruction *decoded, size_t operand_index, uintptr_t runtime_address, uintptr_t *out_address)
static bool dttr_test_pe_fixture_hash_matches(const DTTR_TestBinaryFixture *fixture, const DTTR_TestPEImage *image)
static uintptr_t signed_offset_magnitude(int32_t offset)
bool dttr_test_zydis_decode32_at(const DTTR_TestPEImage *image, uintptr_t rva, DTTR_TestDecodedInstruction *out)
uintptr_t dttr_test_offset_site(uintptr_t base, int32_t offset)
bool dttr_test_range_valid(size_t offset, size_t size, size_t total)
static void dttr_test_pe_free_image(DTTR_TestPEImage *image)
static bool dttr_test_zydis_decode32(const uint8_t *bytes, size_t size, DTTR_TestDecodedInstruction *out)
static bool pe_section_table(const DTTR_TestPEImage *image, size_t pe_offset, const IMAGE_NT_HEADERS32 *nt, const IMAGE_SECTION_HEADER **out_sections)
static void assert_byte_patch_site(const DTTR_TestBinaryFixture *fixture, const DTTR_TestTargetExpectation *target, const DTTR_TestPEImage *image, uintptr_t match)
static bool load_pe_image(uint8_t *file, size_t file_size, DTTR_TestPEImage *image)
size_t DTTR_TestPE_SigscanCount(const DTTR_TestPEImage *image, const uint8_t *sig, const char *mask)
DTTR_Graphics_COM_Direct3DDevice7 void DWORD flags DWORD count
DTTR_Graphics_COM_DirectDraw7 DWORD void * desc
DTTR_Graphics_COM_DirectDrawSurface7 DWORD flags void DTTR_Graphics_COM_DirectDrawSurface7 *self DTTR_Graphics_COM_DirectDrawSurface7 DWORD flags DTTR_Graphics_COM_DirectDrawSurface7 void void * data
DTTR_Graphics_COM_DirectDrawSurface7 DWORD flags void NULL
DTTR_Graphics_COM_DirectDrawSurface7 DWORD DWORD void void DWORD flags DTTR_Graphics_COM_DirectDrawSurface7 void void *cb void * target
bool DTTR_Path_AppendSegment(sds *path, const char *segment, char separator)
static const uint8_t * DTTR_Sigscan_Bytes(const uint8_t *base, size_t size, const void *sig, const char *mask)
static DTTR_Result target_address(const DTTR_Core_Context *ctx, const DTTR_Core_TargetSpec *target, uintptr_t *out_address)
static ZydisDecoder decoder