dll文件怎么注入,简单的dll注入教程

首页 > 教育 > 作者:YD1662023-11-03 12:58:47

远程线程注入

远程线程函数顾名思义,指一个进程在另一个进程中创建线程。

核心函数

CreateRemoteThread

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

lpStartAddress:A pointer to the application-defined function of type LPTHREAD_START_ROUTINE to be executed by the thread and represents the starting address of the thread in the remote process. The function must exist in the remote process. For more information, see ThreadProc).

lpParameter:A pointer to a variable to be passed to the thread function.

lpStartAddress即线程函数,使用LoadLibrary的地址作为线程函数地址;lpParameter为线程函数参数,使用dll路径作为参数

VirtualAllocEx

是在指定进程的虚拟空间保留或提交内存区域,除非指定MEM_RESET参数,否则将该内存区域置0。

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

hProcess:申请内存所在的进程句柄

lpAddress:保留页面的内存地址;一般用NULL自动分配 。

dwSize:欲分配的内存大小,字节单位;注意实际分 配的内存大小是页内存大小的整数倍。

flAllocationType

可取下列值:

MEM_COMMIT:为特定的页面区域分配内存中或磁盘的页面文件中的物理存储

MEM_PHYSICAL :分配物理内存(仅用于地址窗口扩展内存)

MEM_RESERVE:保留进程的虚拟地址空间,而不分配任何物理存储。保留页面可通过继续调用VirtualAlloc()而被占用

MEM_RESET :指明在内存中由参数lpAddress和dwSize指定的数据无效

MEM_TOP_DOWN:在尽可能高的地址上分配内存(Windows 98忽略此标志)

MEM_WRITE_WATCH:必须与MEM_RESERVE一起指定,使系统跟踪那些被写入分配区域的页面(仅针对Windows 98)

flProtect

可取下列值:

PAGE_READONLY: 该区域为只读。如果应用程序试图访问区域中的页的时候,将会被拒绝访

PAGE_READWRITE 区域可被应用程序读写

PAGE_EXECUTE: 区域包含可被系统执行的代码。试图读写该区域的操作将被拒绝。

PAGE_EXECUTE_READ :区域包含可执行代码,应用程序可以读该区域。

PAGE_EXECUTE_READWrite: 区域包含可执行代码,应用程序可以读写该区域。

PAGE_GUARD: 区域第一次被访问时进入一个STATUS_GUARD_PAGE异常,这个标志要和其他保护标志合并使用,表明区域被第一次访问的权限

PAGE_NOACCESS: 任何访问该区域的操作将被拒绝

PAGE_NOCACHE: RAM中的页映射到该区域时将不会被微处理器缓存(cached)

注:PAGE_GUARD和PAGE_NOCHACHE标志可以和其他标志合并使用以进一步指定页的特征。PAGE_GUARD标志指定了一个防护页(guard page),即当一个页被提交时会因第一次被访问而产生一个one-shot异常,接着取得指定的访问权限。PAGE_NOCACHE防止当它映射到虚拟页的时候被微处理器缓存。这个标志方便设备驱动使用直接内存访问方式(DMA)来共享内存块。

WriteProcessMemory

此函数能写入某一进程的内存区域(直接写入会出Access Violation错误),故需此函数入口区必须可以访问,否则操作将失败。

BOOL WriteProcessMemory( HANDLE hProcess, //进程句柄 LPVOID lpBaseAddress, //写入的内存首地址 LPCVOID lpBuffer, //要写数据的指针 SIZE_T nSize, //x SIZE_T *lpNumberOfBytesWritten );实现原理

使用CreateRemoteThread这个API,首先使用CreateToolhelp32Snapshot拍摄快照获取pid,然后使用Openprocess打开进程,使用VirtualAllocEx

远程申请空间,使用WriteProcessMemory写入数据,再用GetProcAddress获取LoadLibraryW的地址(由于Windows引入了基址随机化ASLR安全机制,所以导致每次开机启动时系统DLL加载基址都不一样,有些系统dll(kernel,ntdll)的加载地址,允许每次启动基址可以改变,但是启动之后必须固定,也就是说两个不同进程在相互的虚拟内存中,这样的系统dll地址总是一样的),在注入进程中创建线程(CreateRemoteThread)

实现过程

首先生成一个dll文件,实现简单的弹窗即可

// dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "pch.h" BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: MessageBox(NULL, L"success!", L"Congratulation", MB_OK); case DLL_THREAD_ATTACH: MessageBox(NULL, L"success!", L"Congratulation", MB_OK); case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }

我们要想进行远程线程注入,那么就需要得到进程的pid,这里使用到的是CreateToolhelp32Snapshot这个api拍摄快照来进行获取,注意我这里定义了#include "tchar.h",所有函数都是使用的宽字符

// 通过进程快照获取PID DWORD _GetProcessPID(LPCTSTR lpProcessName) { DWORD Ret = 0; PROCESSENTRY32 p32; HANDLE lpSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (lpSnapshot == INVALID_HANDLE_VALUE) { printf("获取进程快照失败,请重试! Error:%d", ::GetLastError()); return Ret; } p32.dwSize = sizeof(PROCESSENTRY32); ::Process32First(lpSnapshot, &p32); do { if (!lstrcmp(p32.szExeFile, lpProcessName)) { Ret = p32.th32ProcessID; break; } } while (::Process32Next(lpSnapshot, &p32)); ::CloseHandle(lpSnapshot); return Ret; }

首先使用OpenProcess打开进程

hprocess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, _Pid);

然后使用VirtualAllocEx远程申请空间

pAllocMemory = ::VirtualAllocEx(hprocess, NULL, _Size, MEM_COMMIT, PAGE_READWRITE);

然后写入内存,使用WriteProcessMemory

Write = ::WriteProcessMemory(hprocess, pAllocMemory, DllName, _Size, NULL);

然后创建线程并等待线程函数结束,这里WaitForSingleObject的第二个参数要设置为-1才能够一直等待

//在另一个进程中创建线程 hThread = ::CreateRemoteThread(hprocess, NULL, 0, addr, pAllocMemory, 0, NULL); //等待线程函数结束,获得退出码 WaitForSingleObject(hThread, -1); GetExitCodeThread(hThread, &DllAddr);

综上完整代码如下

// RemoteThreadInject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #include <iostream> #include <windows.h> #include <TlHelp32.h> #include "tchar.h" char string_inject[] = "F:\\C \\Inject\\Inject\\Debug\\Inject.dll"; //通过进程快照获取PID DWORD _GetProcessPID(LPCTSTR lpProcessName) { DWORD Ret = 0; PROCESSENTRY32 p32; HANDLE lpSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (lpSnapshot == INVALID_HANDLE_VALUE) { printf("获取进程快照失败,请重试! Error:%d", ::GetLastError()); return Ret; } p32.dwSize = sizeof(PROCESSENTRY32); ::Process32First(lpSnapshot, &p32); do { if (!lstrcmp(p32.szExeFile, lpProcessName)) { Ret = p32.th32ProcessID; break; } } while (::Process32Next(lpSnapshot, &p32)); ::CloseHandle(lpSnapshot); return Ret; } //打开一个进程并为其创建一个线程 DWORD _RemoteThreadInject(DWORD _Pid, LPCWSTR DllName) { //打开进程 HANDLE hprocess; HANDLE hThread; DWORD _Size = 0; BOOL Write = 0; LPVOID pAllocMemory = NULL; DWORD DllAddr = 0; FARPROC pThread; hprocess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, _Pid); //Size = sizeof(string_inject); _Size = (_tcslen(DllName) 1) * sizeof(TCHAR); //远程申请空间 pAllocMemory = ::VirtualAllocEx(hprocess, NULL, _Size, MEM_COMMIT, PAGE_READWRITE); if (pAllocMemory == NULL) { printf("VirtualAllocEx - Error!"); return FALSE; } // 写入内存 Write = ::WriteProcessMemory(hprocess, pAllocMemory, DllName, _Size, NULL); if (Write == FALSE) { printf("WriteProcessMemory - Error!"); return FALSE; } //获取LoadLibrary的地址 pThread = ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "LoadLibraryW"); LPTHREAD_START_ROUTINE addr = (LPTHREAD_START_ROUTINE)pThread; //在另一个进程中创建线程 hThread = ::CreateRemoteThread(hprocess, NULL, 0, addr, pAllocMemory, 0, NULL); if (hThread == NULL) { printf("CreateRemoteThread - Error!"); return FALSE;1 } //等待线程函数结束,获得退出码 WaitForSingleObject(hThread, -1); GetExitCodeThread(hThread, &DllAddr); //释放DLL空间 VirtualFreeEx(hprocess, pAllocMemory, _Size, MEM_DECOMMIT); //关闭线程句柄 ::CloseHandle(hprocess); return TRUE; } int main() { DWORD PID = _GetProcessPID(L"test.exe"); _RemoteThreadInject(PID, L"F:\\C \\Inject\\Inject\\Debug\\Inject.dll"); }

然后这里生成一个test.exe来做测试

dll文件怎么注入,简单的dll注入教程(9)

编译并运行,实现效果如下

dll文件怎么注入,简单的dll注入教程(10)

突破session 0的远程线程注入

首先提一提session0的概念:

Intel的CPU将特权级别分为4个级别:RING0,RING1,RING2,RING3。Windows只使用其中的两个级别RING0和RING3,RING0只给操作系统用,RING3谁都能用。如果普通应用程序企图执行RING0指令,则Windows会显示“非法指令”错误信息。

ring0是指CPU的运行级别,ring0是最高级别,ring1次之,ring2更次之…… 拿Linux x86来说, 操作系统(内核)的代码运行在最高运行级别ring0上,可以使用特权指令,控制中断、修改页表、访问设备等等。 应用程序的代码运行在最低运行级别上ring3上,不能做受控操作。如果要做,比如要访问磁盘,写文件,那就要通过执行系统调用(函数),执行系统调用的时候,CPU的运行级别会发生从ring3到ring0的切换,并跳转到系统调用对应的内核代码位置执行,这样内核就为你完成了设备访问,完成之后再从ring0返回ring3。这个过程也称作用户态和内核态的切换。

RING设计的初衷是将系统权限与程序分离出来,使之能够让OS更好地管理当前系统资源,也使得系统更加稳定。举个RING权限的最简单的例子:一个停止响应的应用程式,它运行在比RING0更低的指令环上,你不必大费周章地想着如何使系统恢复运作,这期间,只需要启动任务管理器便能轻松终止它,因为它运行在比程式更低的RING0指令环中,拥有更高的权限,可以直接影响到RING0以上运行的程序,当然有利就有弊,RING保证了系统稳定运行的同时,也产生了一些十分麻烦的问题。比如一些OS虚拟化技术,在处理RING指令环时便遇到了麻烦,系统是运行在RING0指令环上的,但是虚拟的OS毕竟也是一个系统,也需要与系统相匹配的权限。而RING0不允许出现多个OS同时运行在上面,最早的解决办法便是使用虚拟机,把OS当成一个程序来运行。

核心函数

ZwCreateThreadEx

注意一下这个地方ZwCreateThreadEx这个函数在32位和64位中的定义不同

在32位的情况下

DWORD WINAPI ZwCreateThreadEx( PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, LPVOID ObjectAttributes, HANDLE ProcessHandle, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, BOOL CreateSuspended, DWORD dwStackSize, DWORD dw1, DWORD dw2, LPVOID pUnkown);

在64位的情况下

DWORD WINAPI ZwCreateThreadEx( PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, LPVOID ObjectAttributes, HANDLE ProcessHandle, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, ULONG CreateThreadFlags, SIZE_T ZeroBits, SIZE_T StackSize, SIZE_T MaximumStackSize, LPVOID pUnkown);

这里因为我们要进到session 0那么就势必要到system权限,所以这里还有几个提权需要用到的函数

OpenProcessToken

BOOL OpenProcessToken( __in HANDLE ProcessHandle, //要修改访问权限的进程句柄 __in DWORD DesiredAccess, //指定你要进行的操作类型 __out PHANDLE TokenHandle //返回的访问令牌指针 );

LookupPrivilegeValueA

BOOL LookupPrivilegeValueA( LPCSTR lpSystemName, //要查看的系统,本地系统直接用NULL LPCSTR lpName, //指向一个以零结尾的字符串,指定特权的名称 PLUID lpLuid //用来接收所返回的制定特权名称的信息 );

AdjustTokenPrivileges

BOOL AdjustTokenPrivileges( HANDLE TokenHandle, //包含特权的句柄 BOOL DisableAllPrivileges,//禁用所有权限标志 PTOKEN_PRIVILEGES NewState,//新特权信息的指针(结构体) DWORD BufferLength, //缓冲数据大小,以字节为单位的PreviousState的缓存区(sizeof) PTOKEN_PRIVILEGES PreviousState,//接收被改变特权当前状态的Buffer PDWORD ReturnLength //接收PreviousState缓存区要求的大小 );实现原理

ZwCreateThreadEx比 CreateRemoteThread函数更为底层,CreateRemoteThread函数最终是通过调用ZwCreateThreadEx函数实现远线程创建的。

通过调用CreateRemoteThread函数创建远线程的方式在内核6.0(Windows VISTA、7、8等)以前是完全没有问题的,但是在内核6.0 以后引入了会话隔离机制。它在创建一个进程之后并不立即运行,而是先挂起进程,在查看要运行的进程所在的会话层之后再决定是否恢复进程运行。

在Windows XP、Windows Server 2003,以及更老版本的Windows操作系统中,服务和应用程序使用相同的会话(Session)运行,而这个会话是由第一个登录到控制台的用户启动的。该会话就叫做Session 0,如下图所示,在Windows Vista之前,Session 0不仅包含服务,也包含标准用户应用程序。

dll文件怎么注入,简单的dll注入教程(11)

将服务和用户应用程序一起在Session 0中运行会导致安全风险,因为服务会使用提升后的权限运行,而用户应用程序使用用户特权(大部分都是非管理员用户)运行,这会使得恶意软件以某个服务为攻击目标,通过“劫持”该服务,达到提升自己权限级别的目的。

从Windows Vista开始,只有服务可以托管到Session 0中,用户应用程序和服务之间会被隔离,并需要运行在用户登录到系统时创建的后续会话中。例如第一个登录的用户创建 Session 1,第二个登录的用户创建Session 2,以此类推,如下图所示。

dll文件怎么注入,简单的dll注入教程(12)

上一页12345下一页

栏目热文

文档排行

本站推荐

Copyright © 2018 - 2021 www.yd166.com., All Rights Reserved.