From 75154bbf18d6fb8f58b5aed8233c502646c0892e Mon Sep 17 00:00:00 2001 From: Willi Ballenthin Date: Wed, 20 May 2026 12:48:20 +0200 Subject: [PATCH] tests: add test for disabled code hook after import dispatch see #290 --- tests/bins/seh_iat_after_resolved_x86.c | 98 +++++++++++++++++++ tests/bins/seh_iat_after_resolved_x86.exe.xz | Bin 0 -> 1660 bytes tests/test_iat_after_seh.py | 11 +++ 3 files changed, 109 insertions(+) create mode 100644 tests/bins/seh_iat_after_resolved_x86.c create mode 100644 tests/bins/seh_iat_after_resolved_x86.exe.xz create mode 100644 tests/test_iat_after_seh.py diff --git a/tests/bins/seh_iat_after_resolved_x86.c b/tests/bins/seh_iat_after_resolved_x86.c new file mode 100644 index 0000000..36a6824 --- /dev/null +++ b/tests/bins/seh_iat_after_resolved_x86.c @@ -0,0 +1,98 @@ +#include + +#ifndef ExceptionContinueExecution +#define ExceptionContinueExecution 0 +#endif + +#define SPEAKEASY_RESERVED_DIVISOR 0xFEEE0000 + +typedef DWORD (WINAPI *fn_void_to_dword)(void); + +typedef struct seh_record { + struct seh_record *next; + void *handler; +} seh_record; + +static fn_void_to_dword resolved_get_tick_count; + +static fn_void_to_dword manual_resolve(HMODULE base, const char *name) { + BYTE *b = (BYTE *)base; + IMAGE_DOS_HEADER *dos = (IMAGE_DOS_HEADER *)b; + IMAGE_NT_HEADERS *nt = (IMAGE_NT_HEADERS *)(b + dos->e_lfanew); + IMAGE_EXPORT_DIRECTORY *exp = (IMAGE_EXPORT_DIRECTORY *)( + b + nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); + + DWORD *names = (DWORD *)(b + exp->AddressOfNames); + WORD *ords = (WORD *)(b + exp->AddressOfNameOrdinals); + DWORD *funcs = (DWORD *)(b + exp->AddressOfFunctions); + + for (DWORD i = 0; i < exp->NumberOfNames; i++) { + const char *sym = (const char *)(b + names[i]); + const char *a = sym; + const char *c = name; + while (*a && *c && *a == *c) { + a++; + c++; + } + if (*a == 0 && *c == 0) { + return (fn_void_to_dword)(b + funcs[ords[i]]); + } + } + return 0; +} + +static EXCEPTION_DISPOSITION __cdecl seh_handler( + EXCEPTION_RECORD *record, + void *frame, + CONTEXT *context, + void *dispatcher +) { + volatile DWORD tick = 0; + volatile DWORD pid; + seh_record *seh = (seh_record *)frame; + + (void)record; + (void)context; + (void)dispatcher; + + __asm__ __volatile__( + "movl %0, %%fs:0\n\t" + : + : "r"(seh->next) + : "memory" + ); + + if (resolved_get_tick_count) { + tick = resolved_get_tick_count(); + } + pid = GetCurrentProcessId(); + ExitProcess((UINT)(tick ^ pid)); + return ExceptionContinueExecution; +} + +void __cdecl mainCRTStartup(void) { + seh_record seh; + HMODULE k32 = GetModuleHandleA("kernel32.dll"); + resolved_get_tick_count = manual_resolve(k32, "GetTickCount"); + + __asm__ __volatile__( + "movl %%fs:0, %%eax\n\t" + "movl %%eax, (%0)\n\t" + "movl %1, 4(%0)\n\t" + "movl %0, %%fs:0\n\t" + : + : "r"(&seh), "r"(seh_handler) + : "eax", "memory" + ); + + __asm__ __volatile__( + "xorl %%edx, %%edx\n\t" + "movl $1, %%eax\n\t" + "divl (%%ecx)\n\t" + : + : "c"(SPEAKEASY_RESERVED_DIVISOR) + : "eax", "edx", "memory" + ); + + ExitProcess(1); +} diff --git a/tests/bins/seh_iat_after_resolved_x86.exe.xz b/tests/bins/seh_iat_after_resolved_x86.exe.xz new file mode 100644 index 0000000000000000000000000000000000000000..c6b76b7f6702bbf6c80fc9553a9d696621ecc8ca GIT binary patch literal 1660 zcmV-?27~$iH+ooF000E$*0e?f03iVu0001VFXf})8$Sj)T>vJQj&J}M_v{6`>h%AV z0WU=FeCG8D8AJjp|5MG@(G>okVVr^$_6d5dYqafD5S}K=anHp^E6D32MgBqhvssUF z1POp|I&5CWR0Z6qClLxbDXR&d;11xc0e zP2-m;PDjcpldwqXk2?Az_46ko>$6lhQYaaa4d4p-c-ODwT9e4eeg`p#gS zF(T<2JsGxO7=T>t=@PL*OTY11t$HRRU=Y>*WX7;&bo2!ZnDIdRX`A{2G{+3JVv!}j z0p6sBZbwL*adSsG8upNPjEi5y?R{?F8!4hic}Ml<_P9P5hHB*G#_>F*S|`erDXa<5 zkcA{=pH~HjT-hZ^QRUa3_4gg{0v3Gr`Mhe%yd?F2R_iAe57U;diD*v&$LKHqN7GFG zJORR13#TrC<@g%x$ZKXOOKci$g+$X5fXo9#KA;2L_VHM%QUsXJ;TV~F_akrcJpLwG z*p0fDYLBz^>0^tAZWF;&D8US&QXL*6!zSf0VG1JSG9E(aD|H#OPE$u!6t@gy6EKbE)8F$`HHWDQ$ z>x5!vtu{su+Aw`#rofvpm_i|TVOd|cX*5ih}k8t%+dLsg&? zp}P*M12{QEys z-W+6`a#PExVF!56#Uj;bFYX-D(!PLpxmUMN3)#unLR*a&AC#VJeY}_-nUPVE)k4nI zp{rIe^p94r4;W@L@~ap$oU^u#i0wdEGxpBksF3g)wk$_(MCWW_)cSPl^ZIrh1DqhQRDwF*@mj5GU1bY$Z(2*48umpwJ*0K^$1PWd(1|Dw?j`J|ghELAqVz&~?kQIl@iAxur%6lWl05h8q| zkzmE+Aq-YBL)!iUQd-{IAgo6lh<0jX_X}l7^zcT|-TjLQH)0(uwLk}deC@@bH%hS^ z0F2 z;gWM1e-TqoHn<480EJ(W?we9W3v_K@F6y-SG?{W=jx!IHgIW&^?YcQS8effp=D;@b z3>Nxg0i)omXEeQ_LS@G literal 0 HcmV?d00001 diff --git a/tests/test_iat_after_seh.py b/tests/test_iat_after_seh.py new file mode 100644 index 0000000..48d83cb --- /dev/null +++ b/tests/test_iat_after_seh.py @@ -0,0 +1,11 @@ +def test_iat_call_after_seh_resolved_pointer_call(config, load_test_bin, run_test): + data = load_test_bin("seh_iat_after_resolved_x86.exe.xz") + report = run_test(config, data) + ep = report.entry_points[0] + + apis = [e.api_name for e in (ep.events or []) if e.event == "api"] + assert "kernel32.GetTickCount" in apis + assert "kernel32.GetCurrentProcessId" in apis + assert "kernel32.ExitProcess" in apis + assert apis.index("kernel32.GetTickCount") < apis.index("kernel32.GetCurrentProcessId") + assert ep.error is None