102 Patches: Detours to the Rescue
C reference for DttR maintainers and modders.
Loading...
Searching...
No Matches
binary.c
Go to the documentation of this file.
1#define DTTR_TEST_BINARY_SUPPORT
2#include "dttr_test_support.h"
3#undef DTTR_TEST_BINARY_SUPPORT
4
5#include <dttr_path.h>
6#include <dttr_sigscan.h>
7
8#include <Zydis/Utils.h>
9#include <physfs.h>
10#include <xxhash.h>
11
12#include <stdlib.h>
13#include <string.h>
14
15static void dttr_test_pe_free_image(DTTR_TestPEImage *image);
17 const DTTR_TestBinaryFixture *fixture,
18 const DTTR_TestPEImage *image
19);
20
21// Returns mapped PE bytes only after the requested RVA range is proven in bounds.
22static const uint8_t *pe_rva_bytes(
23 const DTTR_TestPEImage *image,
24 uintptr_t rva,
25 size_t size
26) {
27 if (!image || !dttr_test_range_valid((size_t)rva, size, image->image_size)) {
28 return NULL;
29 }
30
31 return image->image + rva;
32}
33
34static sds dttr_test_join_path(const char *dir, const char *file) {
35 sds path = sdsnew(dir ? dir : "");
36
37 if (!path) {
38 return NULL;
39 }
40
41 if (!DTTR_Path_AppendSegment(&path, file ? file : "", '/')) {
42 sdsfree(path);
43 return NULL;
44 }
45
46 return path;
47}
48
49// Mounts fixture storage and records whether this helper owns the PhysicsFS lifetime.
50static bool mount_fixture_dir(const char *fixture_dir, bool *out_initialized) {
51 if (!fixture_dir || !out_initialized) {
52 return false;
53 }
54
55 *out_initialized = false;
56 if (PHYSFS_isInit()) {
57 return PHYSFS_mount(fixture_dir, NULL, 1) != 0;
58 }
59
60 if (!PHYSFS_init("dttr-tests")) {
61 return false;
62 }
63
64 *out_initialized = true;
65 if (PHYSFS_mount(fixture_dir, NULL, 1)) {
66 return true;
67 }
68
69 PHYSFS_deinit();
70 *out_initialized = false;
71 return false;
72}
73
74// Unmounts fixture storage and tears down PhysicsFS when owned.
75static void unmount_fixture_dir(const char *fixture_dir, bool initialized) {
76 if (fixture_dir) {
77 PHYSFS_unmount(fixture_dir);
78 }
79
80 if (initialized) {
81 PHYSFS_deinit();
82 }
83}
84
85// Reads a mounted fixture file into an owned buffer before PE validation maps it.
86static bool read_fixture_file(const char *filename, uint8_t **out, size_t *out_size) {
87 if (!filename || !out || !out_size) {
88 return false;
89 }
90
91 PHYSFS_File *file = PHYSFS_openRead(filename);
92
93 if (!file) {
94 return false;
95 }
96
97 const PHYSFS_sint64 len = PHYSFS_fileLength(file);
98
99 if (len < 0 || (PHYSFS_uint64)len > (PHYSFS_uint64)SIZE_MAX) {
100 PHYSFS_close(file);
101 return false;
102 }
103
104 const size_t size = (size_t)len;
105 uint8_t *data = malloc(size ? size : 1u);
106
107 if (!data) {
108 PHYSFS_close(file);
109 return false;
110 }
111
112 const bool read_ok = PHYSFS_readBytes(file, data, size) == (PHYSFS_sint64)size;
113 const bool close_ok = PHYSFS_close(file) != 0;
114
115 if (!read_ok || !close_ok) {
116 free(data);
117 return false;
118 }
119
120 *out = data;
121 *out_size = size;
122 return true;
123}
124
125bool dttr_test_range_valid(size_t offset, size_t size, size_t total) {
126 return offset <= total && size <= total - offset;
127}
128
129// Converts signed target offsets without overflowing INT32_MIN.
130static uintptr_t signed_offset_magnitude(int32_t offset) {
131 return offset < 0 ? (uintptr_t)(-(int64_t)offset) : (uintptr_t)offset;
132}
133
135 uintptr_t base,
136 int32_t offset,
137 size_t size,
138 size_t total
139) {
140 const uintptr_t magnitude = signed_offset_magnitude(offset);
141
142 if (offset < 0) {
143 if (base < magnitude) {
144 return false;
145 }
146
147 base -= magnitude;
148 return dttr_test_range_valid((size_t)base, size, total);
149 }
150
151 if (base > (uintptr_t)SIZE_MAX - magnitude) {
152 return false;
153 }
154
155 base += magnitude;
156 return dttr_test_range_valid((size_t)base, size, total);
157}
158
159uintptr_t dttr_test_offset_site(uintptr_t base, int32_t offset) {
160 const uintptr_t magnitude = signed_offset_magnitude(offset);
161 return offset < 0 ? base - magnitude : base + magnitude;
162}
163
165 const uint8_t *actual,
166 const uint8_t *expected,
167 const char *mask,
168 size_t size
169) {
170 if (!actual || !expected || !mask || strlen(mask) != size) {
171 return false;
172 }
173
174 for (size_t i = 0; i < size; i++) {
175 if (mask[i] == 'x' && actual[i] != expected[i]) {
176 return false;
177 }
178 }
179
180 return true;
181}
182
183bool dttr_test_case_equal(const char *a, const char *b) {
184 return a && b && PHYSFS_utf8stricmp(a, b) == 0;
185}
186
187bool dttr_test_fixture_required(DTTR_TestFixtureMask required, size_t fixture_index) {
188 return fixture_index < 64u && (required & DTTR_TEST_FIXTURE_BIT(fixture_index)) != 0;
189}
190
192 const DTTR_TestBinaryFixture *fixtures,
193 size_t fixture_count,
194 const char *fixture_dir
195) {
196 if (!fixtures || !fixture_dir) {
197 return false;
198 }
199
200 bool initialized = false;
201
202 if (!mount_fixture_dir(fixture_dir, &initialized)) {
203 return false;
204 }
205
206 bool ok = true;
207
208 for (size_t i = 0; i < fixture_count; i++) {
209 PHYSFS_Stat stat;
210
211 if (!PHYSFS_stat(fixtures[i].filename, &stat)
212 || stat.filetype != PHYSFS_FILETYPE_REGULAR) {
213 ok = false;
214 break;
215 }
216 }
217
218 unmount_fixture_dir(fixture_dir, initialized);
219 return ok;
220}
221
222// Finds the PE section table only after its numeric file range is validated.
224 const DTTR_TestPEImage *image,
225 size_t pe_offset,
226 const IMAGE_NT_HEADERS32 *nt,
227 const IMAGE_SECTION_HEADER **out_sections
228) {
229 if (!image || !nt || !out_sections) {
230 return false;
231 }
232
233 const size_t optional_offset = pe_offset
234 + offsetof(IMAGE_NT_HEADERS32, OptionalHeader);
235 if (optional_offset < pe_offset) {
236 return false;
237 }
238
239 const size_t section_header = optional_offset + nt->FileHeader.SizeOfOptionalHeader;
240 if (section_header < optional_offset) {
241 return false;
242 }
243
244 const size_t section_count = nt->FileHeader.NumberOfSections;
245 if (section_count > SIZE_MAX / sizeof(IMAGE_SECTION_HEADER)) {
246 return false;
247 }
248
249 const size_t section_table_size = section_count * sizeof(IMAGE_SECTION_HEADER);
250 if (!dttr_test_range_valid(section_header, section_table_size, image->file_size)) {
251 return false;
252 }
253
254 *out_sections = (const IMAGE_SECTION_HEADER *)(image->file + section_header);
255 return true;
256}
257
258// Copies one file-backed PE section to its virtual address in the mapped image.
259static bool pe_copy_section(DTTR_TestPEImage *image, const IMAGE_SECTION_HEADER *section) {
260 if (section->SizeOfRawData == 0) {
261 return true;
262 }
263
265 section->PointerToRawData,
266 section->SizeOfRawData,
267 image->file_size
268 )
270 section->VirtualAddress,
271 section->SizeOfRawData,
272 image->image_size
273 )) {
274 return false;
275 }
276
277 memcpy(
278 image->image + section->VirtualAddress,
279 image->file + section->PointerToRawData,
280 section->SizeOfRawData
281 );
282 return true;
283}
284
285// Parses a 32-bit PE fixture and builds the mapped image used by signature tests.
286static bool load_pe_image(uint8_t *file, size_t file_size, DTTR_TestPEImage *image) {
287 if (!file || !image) {
288 return false;
289 }
290
291 memset(image, 0, sizeof(*image));
292 image->file = file;
293 image->file_size = file_size;
294
295 if (image->file_size < sizeof(IMAGE_DOS_HEADER)) {
296 goto fail;
297 }
298
299 const IMAGE_DOS_HEADER *dos = (const IMAGE_DOS_HEADER *)image->file;
300
301 if (dos->e_magic != IMAGE_DOS_SIGNATURE || dos->e_lfanew < 0) {
302 goto fail;
303 }
304
305 const size_t pe_offset = (size_t)dos->e_lfanew;
306
307 if (!dttr_test_range_valid(pe_offset, sizeof(IMAGE_NT_HEADERS32), image->file_size)) {
308 goto fail;
309 }
310
311 const IMAGE_NT_HEADERS32 *nt = (const IMAGE_NT_HEADERS32 *)(image->file + pe_offset);
312
313 if (nt->Signature != IMAGE_NT_SIGNATURE
314 || nt->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC
315 || nt->OptionalHeader.SizeOfImage == 0) {
316 goto fail;
317 }
318
319 const IMAGE_OPTIONAL_HEADER32 *optional = &nt->OptionalHeader;
320 if (optional->SizeOfHeaders > optional->SizeOfImage) {
321 goto fail;
322 }
323
324 image->image = calloc(1, optional->SizeOfImage);
325
326 if (!image->image) {
327 goto fail;
328 }
329
330 image->image_size = optional->SizeOfImage;
331 image->imports_dir = optional->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
332
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) {
336 goto fail;
337 }
338
339 memcpy(image->image, image->file, header_copy);
340
341 const IMAGE_SECTION_HEADER *sections = NULL;
342
343 if (!pe_section_table(image, pe_offset, nt, &sections)) {
344 goto fail;
345 }
346
347 for (uint16_t i = 0; i < nt->FileHeader.NumberOfSections; i++) {
348 if (!pe_copy_section(image, &sections[i])) {
349 goto fail;
350 }
351 }
352
353 return true;
354
355fail:
357 return false;
358}
359
361 const DTTR_TestBinaryFixture *fixtures,
362 size_t fixture_count,
363 size_t fixture_index,
364 const char *fixture_dir,
365 sds *out_path,
366 DTTR_TestPEImage *out_image
367) {
368 if (!fixtures || !fixture_dir || fixture_index >= fixture_count || !out_path
369 || !out_image) {
370 return false;
371 }
372
373 const DTTR_TestBinaryFixture *fixture = &fixtures[fixture_index];
374 sds path = dttr_test_join_path(fixture_dir, fixture->filename);
375
376 if (!path) {
377 return false;
378 }
379
380 bool initialized = false;
381
382 if (!mount_fixture_dir(fixture_dir, &initialized)) {
383 sdsfree(path);
384 return false;
385 }
386
387 uint8_t *file = NULL;
388 size_t file_size = 0;
389 const bool loaded = read_fixture_file(fixture->filename, &file, &file_size)
390 && load_pe_image(file, file_size, out_image)
391 && dttr_test_pe_fixture_hash_matches(fixture, out_image);
392
393 unmount_fixture_dir(fixture_dir, initialized);
394
395 if (!loaded) {
396 dttr_test_pe_free_image(out_image);
397 sdsfree(path);
398 return false;
399 }
400
401 *out_path = path;
402 return true;
403}
404
406 const DTTR_TestBinaryFixture *fixtures,
407 size_t fixture_count,
408 const char *fixture_dir,
409 DTTR_TestPEFixtureVisitor visitor,
410 void *userdata
411) {
412 if (!fixtures || !fixture_dir || !visitor) {
413 return false;
414 }
415
416 for (size_t i = 0; i < fixture_count; i++) {
417 const DTTR_TestBinaryFixture *fixture = &fixtures[i];
418 sds path = NULL;
419 DTTR_TestPEImage image = {0};
420
422 fixtures,
423 fixture_count,
424 i,
425 fixture_dir,
426 &path,
427 &image
428 )) {
429 return false;
430 }
431
432 const bool ok = visitor(i, fixture, path, &image, userdata);
434 sdsfree(path);
435
436 if (!ok) {
437 return false;
438 }
439 }
440
441 return true;
442}
443
444static void dttr_test_pe_free_image(DTTR_TestPEImage *image) {
445 if (!image) {
446 return;
447 }
448
449 free(image->image);
450 free(image->file);
451 memset(image, 0, sizeof(*image));
452}
453
455 const uint8_t *bytes,
456 size_t size,
457 const uint8_t *sig,
458 const char *mask
459) {
460 if (!bytes || !sig || !mask) {
461 return 0;
462 }
463
464 const size_t mask_len = strlen(mask);
465 if (!mask_len || mask_len > size) {
466 return 0;
467 }
468
469 size_t anchor = 0;
470 while (anchor < mask_len && mask[anchor] != 'x') {
471 anchor++;
472 }
473
474 if (anchor == mask_len) {
475 return size - mask_len + 1u;
476 }
477
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);
481 size_t matches = 0;
482
483 while (cursor < end) {
484 const size_t remaining = (size_t)(end - cursor);
485 const uint8_t *candidate = memchr(cursor, anchor_byte, remaining);
486 if (!candidate) {
487 break;
488 }
489
490 const size_t offset = (size_t)(candidate - bytes) - anchor;
491 size_t i = 0;
492 for (; i < mask_len; i++) {
493 if (mask[i] != 'x') {
494 continue;
495 }
496
497 if (bytes[offset + i] != sig[i]) {
498 break;
499 }
500 }
501
502 if (i == mask_len) {
503 matches++;
504 }
505
506 cursor = candidate + 1;
507 }
508
509 return matches;
510}
511
513 const DTTR_TestPEImage *image,
514 const uint8_t *sig,
515 const char *mask
516) {
517 if (!image || !image->image || !sig || !mask) {
518 return 0;
519 }
520
521 return masked_sigscan_count(image->image, image->image_size, sig, mask);
522}
523
525 const DTTR_TestPEImage *image,
526 const uint8_t *sig,
527 const char *mask
528) {
529 if (!image || !sig || !mask) {
530 return DTTR_TEST_SIG_NOT_FOUND;
531 }
532
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;
535}
536
537static uint64_t dttr_test_pe_file_hash(const DTTR_TestPEImage *image) {
538 if (!image || !image->file) {
539 return 0;
540 }
541
542 return XXH3_64bits(image->file, image->file_size);
543}
544
546 const DTTR_TestBinaryFixture *fixture,
547 const DTTR_TestPEImage *image
548) {
549 if (!fixture || !image) {
550 return false;
551 }
552
553 return image->file_size == fixture->size
554 && dttr_test_pe_file_hash(image) == fixture->xxh3;
555}
556
557static const char *dttr_test_pe_cstr(const DTTR_TestPEImage *image, uintptr_t rva) {
558 const char *str = (const char *)pe_rva_bytes(image, rva, 1);
559
560 if (!str) {
561 return NULL;
562 }
563
564 return memchr(str, '\0', image->image_size - (size_t)rva) ? str : NULL;
565}
566
568 const DTTR_TestPEImage *image,
569 DTTR_TestImportEntry *imports,
570 size_t imports_cap
571) {
572 if (!image || !imports) {
573 return 0;
574 }
575
576 if (image->imports_dir.VirtualAddress == 0) {
577 return 0;
578 }
579
580 const uintptr_t imports_rva = image->imports_dir.VirtualAddress;
581 size_t count = 0;
582
583 for (uintptr_t desc_rva = imports_rva;; desc_rva += sizeof(IMAGE_IMPORT_DESCRIPTOR)) {
584 const IMAGE_IMPORT_DESCRIPTOR *desc = (const IMAGE_IMPORT_DESCRIPTOR *)
585 pe_rva_bytes(image, desc_rva, sizeof(*desc));
586
587 if (!desc || desc->Name == 0) {
588 return count;
589 }
590
591 const char *dll = dttr_test_pe_cstr(image, desc->Name);
592 uintptr_t name_thunk_rva = desc->OriginalFirstThunk ? desc->OriginalFirstThunk
593 : desc->FirstThunk;
594 uintptr_t addr_thunk_rva = desc->FirstThunk;
595
596 if (!dll || !name_thunk_rva || !addr_thunk_rva) {
597 continue;
598 }
599
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));
604
605 if (!name_thunk) {
606 return count;
607 }
608
609 if (name_thunk->u1.AddressOfData == 0) {
610 break;
611 }
612
613 if (IMAGE_SNAP_BY_ORDINAL32(name_thunk->u1.Ordinal)) {
614 continue;
615 }
616
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));
620 const char *import_name = dttr_test_pe_cstr(
621 image,
622 import_name_rva + offsetof(IMAGE_IMPORT_BY_NAME, Name)
623 );
624
625 if (!import || !import_name || count >= imports_cap) {
626 return count;
627 }
628
629 imports[count++] = (DTTR_TestImportEntry){
630 .dll = dll,
631 .name = import_name,
632 .iat_site = addr_thunk_rva,
633 };
634 }
635 }
636}
637
638// Lazily initializes the shared 32-bit Zydis decoder.
639static ZydisDecoder *zydis_decoder32() {
640 static ZydisDecoder decoder;
641 static bool initialized = false;
642
643 if (!initialized) {
644 if (!ZYAN_SUCCESS(ZydisDecoderInit(
645 &decoder,
646 ZYDIS_MACHINE_MODE_LEGACY_32,
647 ZYDIS_STACK_WIDTH_32
648 ))) {
649 return NULL;
650 }
651
652 initialized = true;
653 }
654
655 return &decoder;
656}
657
659 const uint8_t *bytes,
660 size_t size,
661 DTTR_TestDecodedInstruction *out
662) {
663 ZydisDecoder *decoder = zydis_decoder32();
664
665 if (!decoder || !bytes || !out) {
666 return false;
667 }
668
669 memset(out, 0, sizeof(*out));
670 return ZYAN_SUCCESS(
671 ZydisDecoderDecodeFull(decoder, bytes, size, &out->instruction, out->operands)
672 );
673}
674
676 const DTTR_TestPEImage *image,
677 uintptr_t rva,
678 DTTR_TestDecodedInstruction *out
679) {
680 if (!image || rva >= image->image_size) {
681 return false;
682 }
683
685 image->image + rva,
686 image->image_size - (size_t)rva,
687 out
688 );
689}
690
692 const DTTR_TestPEImage *image,
693 uintptr_t rva,
694 size_t required_size,
695 size_t *out_size
696) {
697 if (!image || rva >= image->image_size) {
698 return false;
699 }
700
701 size_t decoded_size = 0;
702
703 while (decoded_size < required_size) {
704 DTTR_TestDecodedInstruction decoded = {0};
705
706 if (!dttr_test_zydis_decode32_at(image, rva + decoded_size, &decoded)
707 || decoded.instruction.length == 0) {
708 return false;
709 }
710
711 decoded_size += decoded.instruction.length;
712 }
713
714 if (out_size) {
715 *out_size = decoded_size;
716 }
717
718 return true;
719}
720
722 const DTTR_TestDecodedInstruction *decoded,
723 size_t operand_index,
724 uintptr_t runtime_address,
725 uintptr_t *out_address
726) {
727 if (!decoded || !out_address || operand_index >= ZYDIS_MAX_OPERAND_COUNT) {
728 return false;
729 }
730
731 ZyanU64 address = 0;
732
733 if (!ZYAN_SUCCESS(ZydisCalcAbsoluteAddress(
734 &decoded->instruction,
735 &decoded->operands[operand_index],
736 (ZyanU64)runtime_address,
737 &address
738 ))) {
739 return false;
740 }
741
742 if (address > (ZyanU64)UINTPTR_MAX) {
743 return false;
744 }
745
746 *out_address = (uintptr_t)address;
747 return true;
748}
749
750// Validates the CALL-to-JMP pattern used by pointer targets.
752 const DTTR_TestBinaryFixture *fixture,
753 const DTTR_TestTargetExpectation *target,
754 const DTTR_TestPEImage *image,
755 uintptr_t match
756) {
757 DTTR_TestDecodedInstruction call = {0};
758 assert_true(dttr_test_zydis_decode32_at(image, match, &call));
759 assert_int_equal(call.instruction.mnemonic, ZYDIS_MNEMONIC_CALL);
760 assert_int_equal(call.operands[0].type, ZYDIS_OPERAND_TYPE_IMMEDIATE);
761
762 uintptr_t thunk = 0;
763 assert_true(dttr_test_zydis_absolute_operand(&call, 0, match, &thunk));
764
765 if (!dttr_test_range_valid((size_t)thunk, 6, image->image_size)) {
766 fail_msg(
767 "%s resolved outside image in %s: match=0x%08X thunk=0x%08X",
768 target->name,
769 fixture->id,
770 (unsigned)match,
771 (unsigned)thunk
772 );
773 }
774
775 DTTR_TestDecodedInstruction jmp = {0};
776 assert_true(dttr_test_zydis_decode32_at(image, thunk, &jmp));
777 assert_int_equal(jmp.instruction.mnemonic, ZYDIS_MNEMONIC_JMP);
778 assert_int_equal(jmp.operands[0].type, ZYDIS_OPERAND_TYPE_MEMORY);
779
780 uintptr_t target_address = 0;
781 assert_true(dttr_test_zydis_absolute_operand(&jmp, 0, thunk, &target_address));
782 assert_true(target_address != 0);
783}
784
785// Validates MOV-based pointer targets whose address is loaded from fixture memory.
787 const DTTR_TestPEImage *image,
788 uintptr_t match
789) {
790 DTTR_TestDecodedInstruction mov = {0};
791 assert_true(dttr_test_zydis_decode32_at(image, match, &mov));
792 assert_int_equal(mov.operands[1].type, ZYDIS_OPERAND_TYPE_MEMORY);
793
794 uintptr_t target_address = 0;
795 assert_true(dttr_test_zydis_absolute_operand(&mov, 1, match, &target_address));
796 assert_true(target_address != 0);
797}
798
799// Verifies byte-patch targets still expose the expected original instruction bytes.
801 const DTTR_TestBinaryFixture *fixture,
802 const DTTR_TestTargetExpectation *target,
803 const DTTR_TestPEImage *image,
804 uintptr_t match
805) {
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);
811 match,
812 target->site_offset,
813 target->patch_size,
814 image->image_size
815 ));
816
817 const uintptr_t site = dttr_test_offset_site(match, target->site_offset);
818 DTTR_TestDecodedInstruction decoded = {0};
819 assert_true(dttr_test_zydis_decode32_at(image, site, &decoded));
820 assert_true(decoded.instruction.length > 0);
821 assert_true(decoded.instruction.length <= target->patch_size);
822
823 const uint8_t *actual = image->image + site;
824
826 actual,
827 target->expected_original,
828 target->expected_original_mask,
829 target->patch_size
830 )) {
831 fail_msg(
832 "%s original bytes mismatch in %s at 0x%08X",
833 target->name,
834 fixture->id,
835 (unsigned)site
836 );
837 }
838
839 assert_memory_not_equal(actual, target->patch_bytes, target->patch_size);
840}
841
843 const DTTR_TestBinaryFixture *fixture,
844 const DTTR_TestTargetExpectation *target,
845 const DTTR_TestPEImage *image
846) {
847 assert_non_null(fixture);
848 assert_non_null(target);
849 assert_non_null(image);
850
851 const uintptr_t match = DTTR_TestPE_Sigscan(image, target->sig, target->mask);
852
853 if (match == DTTR_TEST_SIG_NOT_FOUND) {
854 fail_msg(
855 "required target %s did not resolve in %s (%s)",
856 target->name,
857 fixture->id,
858 fixture->filename
859 );
860 }
861
862 const size_t matches = DTTR_TestPE_SigscanCount(image, target->sig, target->mask);
863 if (matches != 1) {
864 fail_msg(
865 "required target %s resolved %zu times in %s (%s); expected exactly one "
866 "match",
867 target->name,
868 matches,
869 fixture->id,
870 fixture->filename
871 );
872 }
873
874 DTTR_TestDecodedInstruction decoded = {0};
875 assert_true(dttr_test_zydis_decode32_at(image, match, &decoded));
876 assert_true(decoded.instruction.length > 0);
877
878 switch (target->kind) {
879 case DTTR_TEST_TARGET_RESOLVE:
880 break;
881 case DTTR_TEST_TARGET_JMP_HOOK:
882 case DTTR_TEST_TARGET_TRAMPOLINE_HOOK: {
883 size_t decoded_size = 0;
884 assert_true(dttr_test_zydis_decode32_prefix(image, match, 5, &decoded_size));
885 assert_true(decoded_size >= 5);
886 break;
887 }
888 case DTTR_TEST_TARGET_POINTER_FF25_E8_TARGET:
889 assert_pointer_ff25_e8_target(fixture, target, image, match);
890 break;
891 case DTTR_TEST_TARGET_POINTER_U32_AT_MATCH_PLUS_2:
893 break;
894 case DTTR_TEST_TARGET_BYTE_PATCH:
895 assert_byte_patch_site(fixture, target, image, match);
896 break;
897 }
898}
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)
Definition binary.c:360
static uint64_t dttr_test_pe_file_hash(const DTTR_TestPEImage *image)
Definition binary.c:537
static void assert_pointer_ff25_e8_target(const DTTR_TestBinaryFixture *fixture, const DTTR_TestTargetExpectation *target, const DTTR_TestPEImage *image, uintptr_t match)
Definition binary.c:751
static const uint8_t * pe_rva_bytes(const DTTR_TestPEImage *image, uintptr_t rva, size_t size)
Definition binary.c:22
static void unmount_fixture_dir(const char *fixture_dir, bool initialized)
Definition binary.c:75
static bool dttr_test_bytes_match_mask(const uint8_t *actual, const uint8_t *expected, const char *mask, size_t size)
Definition binary.c:164
void dttr_test_assert_target_resolved(const DTTR_TestBinaryFixture *fixture, const DTTR_TestTargetExpectation *target, const DTTR_TestPEImage *image)
Definition binary.c:842
bool dttr_test_case_equal(const char *a, const char *b)
Definition binary.c:183
bool dttr_test_pe_for_each_fixture(const DTTR_TestBinaryFixture *fixtures, size_t fixture_count, const char *fixture_dir, DTTR_TestPEFixtureVisitor visitor, void *userdata)
Definition binary.c:405
static bool read_fixture_file(const char *filename, uint8_t **out, size_t *out_size)
Definition binary.c:86
static bool pe_copy_section(DTTR_TestPEImage *image, const IMAGE_SECTION_HEADER *section)
Definition binary.c:259
static bool dttr_test_zydis_decode32_prefix(const DTTR_TestPEImage *image, uintptr_t rva, size_t required_size, size_t *out_size)
Definition binary.c:691
static const char * dttr_test_pe_cstr(const DTTR_TestPEImage *image, uintptr_t rva)
Definition binary.c:557
bool dttr_test_fixtures_available(const DTTR_TestBinaryFixture *fixtures, size_t fixture_count, const char *fixture_dir)
Definition binary.c:191
static size_t masked_sigscan_count(const uint8_t *bytes, size_t size, const uint8_t *sig, const char *mask)
Definition binary.c:454
size_t DTTR_TestPE_CollectImports(const DTTR_TestPEImage *image, DTTR_TestImportEntry *imports, size_t imports_cap)
Definition binary.c:567
static void assert_pointer_u32_at_match_plus_2(const DTTR_TestPEImage *image, uintptr_t match)
Definition binary.c:786
uintptr_t DTTR_TestPE_Sigscan(const DTTR_TestPEImage *image, const uint8_t *sig, const char *mask)
Definition binary.c:524
static ZydisDecoder * zydis_decoder32()
Definition binary.c:639
static bool mount_fixture_dir(const char *fixture_dir, bool *out_initialized)
Definition binary.c:50
static sds dttr_test_join_path(const char *dir, const char *file)
Definition binary.c:34
bool dttr_test_signed_range_valid(uintptr_t base, int32_t offset, size_t size, size_t total)
Definition binary.c:134
bool dttr_test_fixture_required(DTTR_TestFixtureMask required, size_t fixture_index)
Definition binary.c:187
static bool dttr_test_zydis_absolute_operand(const DTTR_TestDecodedInstruction *decoded, size_t operand_index, uintptr_t runtime_address, uintptr_t *out_address)
Definition binary.c:721
static bool dttr_test_pe_fixture_hash_matches(const DTTR_TestBinaryFixture *fixture, const DTTR_TestPEImage *image)
Definition binary.c:545
static uintptr_t signed_offset_magnitude(int32_t offset)
Definition binary.c:130
bool dttr_test_zydis_decode32_at(const DTTR_TestPEImage *image, uintptr_t rva, DTTR_TestDecodedInstruction *out)
Definition binary.c:675
uintptr_t dttr_test_offset_site(uintptr_t base, int32_t offset)
Definition binary.c:159
bool dttr_test_range_valid(size_t offset, size_t size, size_t total)
Definition binary.c:125
static void dttr_test_pe_free_image(DTTR_TestPEImage *image)
Definition binary.c:444
static bool dttr_test_zydis_decode32(const uint8_t *bytes, size_t size, DTTR_TestDecodedInstruction *out)
Definition binary.c:658
static bool pe_section_table(const DTTR_TestPEImage *image, size_t pe_offset, const IMAGE_NT_HEADERS32 *nt, const IMAGE_SECTION_HEADER **out_sections)
Definition binary.c:223
static void assert_byte_patch_site(const DTTR_TestBinaryFixture *fixture, const DTTR_TestTargetExpectation *target, const DTTR_TestPEImage *image, uintptr_t match)
Definition binary.c:800
static bool load_pe_image(uint8_t *file, size_t file_size, DTTR_TestPEImage *image)
Definition binary.c:286
size_t DTTR_TestPE_SigscanCount(const DTTR_TestPEImage *image, const uint8_t *sig, const char *mask)
Definition binary.c:512
DTTR_Graphics_COM_Direct3DDevice7 void DWORD flags DWORD count
void DWORD DWORD * free
DTTR_Graphics_COM_DirectDraw7 DWORD void * desc
const DWORD size
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)
Definition path.c:276
static const uint8_t * DTTR_Sigscan_Bytes(const uint8_t *base, size_t size, const void *sig, const char *mask)
Definition dttr_sigscan.h:9
static DTTR_Result target_address(const DTTR_Core_Context *ctx, const DTTR_Core_TargetSpec *target, uintptr_t *out_address)
Definition core.c:927
static ZydisDecoder decoder