From ea182f8ef0e83319145c71c2ab4d2c5993ac8194 Mon Sep 17 00:00:00 2001 From: friendlyanon Date: Sun, 14 Apr 2024 22:31:46 +0200 Subject: [PATCH] Implement `InterlockedCompareExchange` using CAS This implementation is only usable on 486 or newer CPUs, so there is also code that detects at runtime whether the AC bit of the EFLAGS register can be set. On 386, this register always returns the same value. The CAS implementation uses the "lock cmpxchg" instruction, which is only executed if the above runtime check is satisfied. The overhead of calling this implementation is a "cmp" followed by a "jnz" instruction, which should have a negligible cost when execution already had to cross a DLL boundary to get here. Using the command find -name "*.dll" -o -name "*.vxd" | perl -nE "print if grep { $_ =~ /InterlockedCompareExchange/ } `dumpbin /imports $_`;" Here is a list of .dll and .vxd files in the installer that import the "InterlockedCompareExchange" symbol and thus benefit from this: $WINDIR/Microsoft.NET/Framework/sbscmp10.dll $WINDIR/Microsoft.NET/Framework/sbscmp20_mscorwks.dll $WINDIR/Microsoft.NET/Framework/sbscmp20_perfcounter.dll $WINDIR/Microsoft.NET/Framework/SharedReg12.dll $WINDIR/Microsoft.NET/Framework/v1.0.3705/mscormmc.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/AdoNetDiag.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/alink.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/aspnet_filter.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/aspnet_isapi.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/Aspnet_perf.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/CORPerfMonExt.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/cscomp.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/Culture.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/dfdll.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/diasymreader.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/fusion.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/MmcAspExt.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/mscordacwks.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/mscordbc.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/mscordbi.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/mscorie.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/mscorjit.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/mscorld.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/mscorpe.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/mscorsec.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/mscorsn.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/mscorsvc.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/mscortim.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/mscorwks.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/normalization.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/PerfCounter.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/peverify.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/sbscmp20_mscorlib.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/shfusion.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/ShFusRes.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/System.Data.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/System.Data.OracleClient.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/System.EnterpriseServices.Wrapper.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/System.Transactions.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/VsaVb7rt.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/webengine.dll $WINDIR/Microsoft.NET/Framework/v2.0.50727/WMINet_Utils.dll $WINDIR/RegisteredPackages/{D5D40355-5FB0-48fb-A231-CDC637FA16E0}/NETFXMigration.dll $WINDIR/System/dfshim.dll $WINDIR/System/mscoree.dll $WINDIR/System/mscories.dll $WINDIR/System/msvcm80.dll $WINDIR/System/msvcp80.dll $WINDIR/System/WBEM/Wmidcad.dll $WINDIR/winsxs/x86_microsoft.vc80.crt_1fc8b3b9a1e18e3b_8.0.50727.42_none_db5f52fb98cb24ad/msvcm80.dll $WINDIR/winsxs/x86_microsoft.vc80.crt_1fc8b3b9a1e18e3b_8.0.50727.42_none_db5f52fb98cb24ad/msvcp80.dll $WINDIR/winsxs/x86_Microsoft.VC80.CRT_1fc8b3b9a1e18e3b_8.0.50727.42_x-ww_0de06acd/msvcm80.dll $WINDIR/winsxs/x86_Microsoft.VC80.CRT_1fc8b3b9a1e18e3b_8.0.50727.42_x-ww_0de06acd/msvcp80.dll --- wrappers/CMakeLists.txt | 38 +++++++++++++++++++++++++++++++------- wrappers/advapi32.c | 1 - wrappers/cas.asm | 17 +++++++++++++++++ wrappers/cas.h | 10 ++++++++++ wrappers/comdlg32.c | 1 - wrappers/detect486.asm | 28 ++++++++++++++++++++++++++++ wrappers/detect486.h | 10 ++++++++++ wrappers/kernel32.c | 23 +++++++++++++++++++---- wrappers/ntdll.c | 6 ++++-- wrappers/user32.c | 1 - 10 files changed, 119 insertions(+), 16 deletions(-) create mode 100644 wrappers/cas.asm create mode 100644 wrappers/cas.h create mode 100644 wrappers/detect486.asm create mode 100644 wrappers/detect486.h diff --git a/wrappers/CMakeLists.txt b/wrappers/CMakeLists.txt index 5558c74..402974c 100644 --- a/wrappers/CMakeLists.txt +++ b/wrappers/CMakeLists.txt @@ -1,30 +1,54 @@ cmake_minimum_required(VERSION 3.13) -project(corkel32 C) +project(corkel32 C ASM_MASM) -set(CMAKE_BUILD_TYPE Release) +add_compile_definitions(_WIN32_WINNT=0x0400) -add_library(corkdebug OBJECT +add_library( + corkdebug OBJECT debug.c ) -link_libraries(corkdebug) +target_compile_definitions(corkdebug PRIVATE _CRT_SECURE_NO_WARNINGS=1) + +add_library( + detect486 OBJECT + detect486.asm +) + +add_library( + cas OBJECT + cas.asm +) + +include(CheckSymbolExists) +check_symbol_exists(RtlUnwind Windows.h HAS_RTL_UNWIND) # KERNEL32 => CORKEL32 -add_library(corkel32 SHARED +add_library( + corkel32 MODULE kernel32.c advapi32.c comdlg32.c corkel32.def ) +target_link_libraries(corkel32 PRIVATE corkdebug detect486 cas) +target_compile_definitions(corkel32 PRIVATE _CRT_SECURE_NO_WARNINGS=1) # NTDLL => CORNT -add_library(cornt SHARED +add_library( + cornt MODULE ntdll.c cornt.def ) +target_link_libraries(cornt PRIVATE corkdebug) +if(HAS_RTL_UNWIND) + target_compile_definitions(cornt PRIVATE HAS_RTL_UNWIND=1) +endif() # USER32 => CORUSR -add_library(corusr SHARED +add_library( + corusr MODULE user32.c corusr.def ) +target_link_libraries(corusr PRIVATE corkdebug) diff --git a/wrappers/advapi32.c b/wrappers/advapi32.c index 3e56288..2533945 100644 --- a/wrappers/advapi32.c +++ b/wrappers/advapi32.c @@ -1,4 +1,3 @@ -#define _WIN32_WINNT 0x0400 #include #include "debug.h" diff --git a/wrappers/cas.asm b/wrappers/cas.asm new file mode 100644 index 0000000..6e95593 --- /dev/null +++ b/wrappers/cas.asm @@ -0,0 +1,17 @@ +.486 +.MODEL FLAT + +.CODE + +OPTION PROLOGUE:NONE +OPTION EPILOGUE:NONE + +_InterlockedCompareExchange_486@12 PROC + mov ecx, DWORD PTR [esp + 4] ; dest + mov edx, DWORD PTR [esp + 8] ; exchange + mov eax, DWORD PTR [esp + 12] ; compare + lock cmpxchg DWORD PTR [ecx], edx + ret 12 +_InterlockedCompareExchange_486@12 ENDP + +END diff --git a/wrappers/cas.h b/wrappers/cas.h new file mode 100644 index 0000000..c2a0ef3 --- /dev/null +++ b/wrappers/cas.h @@ -0,0 +1,10 @@ +#ifndef CAS +#define CAS + +#ifndef STDCALL +#define STDCALL __stdcall +#endif + +long STDCALL InterlockedCompareExchange_486(long* dest, long exchange, long compare); + +#endif // CAS diff --git a/wrappers/comdlg32.c b/wrappers/comdlg32.c index b154e77..1d9eb36 100644 --- a/wrappers/comdlg32.c +++ b/wrappers/comdlg32.c @@ -1,4 +1,3 @@ -#define _WIN32_WINNT 0x0400 #include #include "debug.h" diff --git a/wrappers/detect486.asm b/wrappers/detect486.asm new file mode 100644 index 0000000..dbd4686 --- /dev/null +++ b/wrappers/detect486.asm @@ -0,0 +1,28 @@ +.386 +.MODEL FLAT, STDCALL + +.CODE + +is_cpu_486_or_newer PROC + pushfd + pop eax + mov ebx, eax + xor eax, 40000h ; toggle the AC bit in EFLAGS (only available in 486 or newer) + push eax + popfd + pushfd + pop eax + cmp eax, ebx + jz is_386 + push ebx + popfd + xor eax, eax + inc eax + ret + +is_386: + xor eax, eax + ret +is_cpu_486_or_newer ENDP + +END diff --git a/wrappers/detect486.h b/wrappers/detect486.h new file mode 100644 index 0000000..2670d23 --- /dev/null +++ b/wrappers/detect486.h @@ -0,0 +1,10 @@ +#ifndef DETECT_486 +#define DETECT_486 + +#ifndef STDCALL +#define STDCALL __stdcall +#endif + +int STDCALL is_cpu_486_or_newer(void); + +#endif // DETECT_486 diff --git a/wrappers/kernel32.c b/wrappers/kernel32.c index 7e06ebf..aa3d271 100644 --- a/wrappers/kernel32.c +++ b/wrappers/kernel32.c @@ -1,9 +1,11 @@ -#define _WIN32_WINNT 0x0400 - #include #include +#include "cas.h" #include "debug.h" +#include "detect486.h" + +static int has_cmpxchg = 0; DWORD WINAPI CORKEL32_GetLastError() { @@ -154,10 +156,14 @@ BOOL WINAPI CORKEL32_DeviceIoControl(HANDLE hDevice, DWORD dwIoControlCode, LPVO } // Reimplemented -LONG WINAPI CORKEL32_InterlockedCompareExchange(LONG *dest, LONG xchg, LONG compare) +LONG WINAPI CORKEL32_InterlockedCompareExchange(LONG* dest, LONG xchg, LONG compare) { - LONG temp = *dest; + LONG temp; + if (has_cmpxchg) { + return InterlockedCompareExchange_486(dest, xchg, compare); + } + temp = *dest; Trace(TRACE_FORCE_DONT_PRINT, "InterlockedCompareExchange"); if (compare == *dest) { @@ -303,3 +309,12 @@ BOOL WINAPI CORKEL32_InitializeCriticalSectionAndSpinCount(LPCRITICAL_SECTION cr return TRUE; } + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + if (fdwReason == DLL_PROCESS_ATTACH) { + has_cmpxchg = is_cpu_486_or_newer(); + } + + return TRUE; +} diff --git a/wrappers/ntdll.c b/wrappers/ntdll.c index f0486ae..5eca877 100644 --- a/wrappers/ntdll.c +++ b/wrappers/ntdll.c @@ -1,16 +1,18 @@ -#define _WIN32_WINNT 0x0400 #include #include "debug.h" -ULONG WINAPI CORNT_RtlNtStatusToDosError(NTSTATUS) +ULONG WINAPI CORNT_RtlNtStatusToDosError(NTSTATUS param_0) { Trace(TRACE_UNIMPLEMENTED, "RtlNtStatusToDosError"); // TODO: Stub return 0; } +#ifndef HAS_RTL_UNWIND extern NTAPI RtlUnwind(void* param_0, void* param_1, struct _EXCEPTION_RECORD* param_2, void* param_3); +#endif + VOID NTAPI CORNT_RtlUnwind(void *p0,void *p1,struct _EXCEPTION_RECORD *p2, void *p3) { Trace(TRACE_PASSTHROUGH, "RtlUnwind"); diff --git a/wrappers/user32.c b/wrappers/user32.c index 4cdc081..6ee9ec2 100644 --- a/wrappers/user32.c +++ b/wrappers/user32.c @@ -1,4 +1,3 @@ -#define _WIN32_WINNT 0x0400 #include #include "debug.h"