dll 调用exe_Windows编程技术:4种Dll注入技术(下)

    上期说了4种常用注入中的2种,今天说下后两种,即“Session 0隔离的远线程注入”和“APC注入”(文章最后提供代码下载地址)。

一、Session 0 隔离的远线程注入

1、什么是 Session 0 隔离?

在Windows XP、Windows Server 2003 或早期Windows 系统时代,当第一个用户登录系统后服务和应用程序是在同一个Session 中运行的。这就是Session 0 如下图所示:

a03c4e9bd974a297f3d71e2a58e590b1.png

但是这种运行方式提高了系统安全风险,因为服务是通过提升了用户权限运行的,而应用程序往往是那些不具备管理员身份的普通用户运行的,其中的危险显而易见。

     从Vista 开始Session 0 中只包含系统服务,其他应用程序则通过分离的Session 运行,将服务与应用程序隔离提高系统的安全性。如下图所示:

09e3c0618ebae19214db662e0a403d6b.png

我们来看看实际的程序所属的Session:

c857e22302ecb85adc0755865af9d23d.png

这是services.exe,明显属于Session 0;

93b839f94e384f36a26dfd9857cf492a.png

这是360se.exe,明显属于Session 1;

看到两者的区别了吧,一是系统服务,一是应用程序。

2、ZwCreateThreadEx函数    

    由于Session 0隔离机制,导致远程线程注入系统服务进程失效,最终发现用ZwCreateThreadEx函数进行远程线程注入可以突破Session 0 隔离,成功注入。

    ZwCreateThreadEx函数是比CreateRemoteThread函数更为底层的函数,将Dll成功注入到Session 0隔离的系统服务进程中,由于ZwCreateThreadEx函数没有在ntdll.dll中声明,所以需要使用GetProcAddress从ntdll.dll中获取该函数的导出地址。

64 位下,ZwCreateThreadEx函数声明为:

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);

32 位下,ZwCreateThreadEx 函数声明为:

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);

    要使系统服务进程远线程注入成功,需要将函数的第7个参数CreateSuspended的值设置为0,这样线程创建完成后就会恢复运行,成功注入。

3、代码学习

// 使用 ZwCreateThreadEx 实现远线程注入

BOOL ZwCreateThreadExInjectDll(DWORD dwProcessId, char *pszDllFileName)

{

HANDLE hProcess = NULL;

SIZE_T dwSize = 0;

LPVOID pDllAddr = NULL;

FARPROC pFuncProcAddr = NULL;

HANDLE hRemoteThread = NULL;

DWORD dwStatus = 0;

// 打开注入进程,获取进程句柄(略)

// 在注入进程中申请内存(略)

// 向申请的内存中写入数据(略)

// 加载 ntdll.dll

HMODULE hNtdllDll = ::LoadLibrary("ntdll.dll");

// 获取LoadLibraryA函数地址

pFuncProcAddr = ::GetProcAddress(::GetModuleHandle("Kernel32.dll"), "LoadLibraryA");

// 获取ZwCreateThread函数地址

typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx)::GetProcAddress(hNtdllDll, "ZwCreateThreadEx");

// 使用 ZwCreateThreadEx 创建远线程, 实现 DLL 注入

dwStatus = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, hProcess, (LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, 0, 0, 0, NULL);

// 关闭句柄(略)

return TRUE;

}

4、测试注入Svchost.exe中:

94a0c2b80d1aa857bc6666870878a9a3.png

它是属于Session 0;

找到svchost.exe的PID=10028,写入程序中,Vs2019编译成功 ZwCreateThreadEx_Test.exe 和 TestDll.dll;

管理员权限运行ZwCreateThreadEx_Test.exe,

297556422626de10966e64b7bcf0de2a.png

在PID=10028的svchost.exe中注入了TestDll.dll文件;说明成功注入Session 0。

二、APC注入

1、什么是APC?

APC:Asynchronous Procedure Call,异步过程调用,指函数在特定线程中被异步执行,是一种并发机制,常用于异步IO或定时器。

APC队列:每个线程都有一个APC队列,在线程处于可警醒状态时,线程会执行APC队列中APC函数。

2、APC 在什么时候调用?

1)线程已经创建,系统在调用线程函数时会检查APC队列,如果不为空,则调用APC队列中的APC函数,直到队列为空后,才开始调用线程函数。

2)线程通过WaitForSingleObjectEx等函数进入可警醒状态时,会先检查APC队列。如果不为空,则调用APC队列中的 APC函数。

直到队列为空后,才开始等待要等待的对象,此时如果要等待的对象没有进入激发态且没有超时,WaitForSingleObjectEx 不会返回。

3)线程通过WaitForSingleObjectEx等函数进入可警醒状态时,APC队列为空,且要等待的对象没有进入激发态,也没有超时,则线程进入睡眠等待状态。

此时往该APC队列添加APC函数后,该线程会被唤醒执行完所有APC队列中的函数,然后不去看要等待的对象是否进入激发态,立即从WaitForSingleObjectEx中返回,返回值是 WAIT_IO_COMPLETION。

往线程APC队列添加APC,系统会产生一个软中断。在线程下一次被调度的时候就会执行APC函数,APC有两种形式,由系统产生的APC称为内核模式APC,由应用程序产生的 APC被称为用户模式APC。

3、代码

// APC注入

BOOL ApcInjectDll(char *pszProcessName, char *pszDllName)

{

BOOL bRet = FALSE;

DWORD dwProcessId = 0;

DWORD *pThreadId = NULL;

DWORD dwThreadIdLength = 0;

HANDLE hProcess = NULL, hThread = NULL;

PVOID pBaseAddress = NULL;

PVOID pLoadLibraryAFunc = NULL;

SIZE_T dwRet = 0, dwDllPathLen = 1 + ::lstrlen(pszDllName);

DWORD i = 0;

do

{

// 根据进程名称获取PID

dwProcessId = GetProcessIdByProcessName(pszProcessName);

// 根据PID获取所有的相应线程ID

bRet = GetAllThreadIdByProcessId(dwProcessId, &pThreadId, &dwThreadIdLength);

// 打开注入进程

hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);

// 在注入进程空间申请内存

pBaseAddress = ::VirtualAllocEx(hProcess, NULL, dwDllPathLen, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

// 向申请的空间中写入DLL路径数据 

::WriteProcessMemory(hProcess, pBaseAddress, pszDllName, dwDllPathLen, &dwRet);

// 获取 LoadLibrary 地址

pLoadLibraryAFunc = ::GetProcAddress(::GetModuleHandle("kernel32.dll"), "LoadLibraryA");

// 遍历线程, 插入APC

for (i = 0; i < dwThreadIdLength; i++)

{

// 打开线程

hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadId[i]);

if (hThread)

{

// 插入APC

::QueueUserAPC((PAPCFUNC)pLoadLibraryAFunc, hThread, (ULONG_PTR)pBaseAddress);

// 关闭线程句柄

::CloseHandle(hThread);

hThread = NULL;

}

}

bRet = TRUE;

} while (FALSE);

// 释放内存(略)

return bRet;

}

这里用到一个非常重要的函数:QueueUserAPC,把一个APC对象加入到指定线程的APC队列中。

QueueUserAPC:

第1个参数:指向应用程序提供的APC函数的指针;

第2个参数:插入APC的线程句柄;

第3个参数:传递给执行函数的参数。

与远线程注入类似,如果QueueUserAPC函数的第一个参数,即函数地址设置的是LoadLibraryA函数地址,第三个参数,即传递参数设置的是DLL的路径。那么,当执行APC的时候,便会调用LoadLibraryA函数加载指定路径的DLL,完成DLL注入操作。

一个进程中,包含有多个线程,为了确保插入的APC能够被执行,所以,向目标进程的所有线程都插入相同的APC,实现加载DLL的操作。这样,只要进程中任意线程被唤醒,开始执行APC的时候,便会执行插入的APC,实现DLL注入。

那么,实现APC注入的具体流程如下所示。

  • 首先,通过OpenProcess函数打开目标进程,获取目标进程的句柄。

  • 然后,通过调用WIN32 API函数CreateToolhelp32Snapshot、Thread32First以及Thread32Next遍历线程快照,获取目标进程的所有线程ID。

  • 接着,调用VirtualAllocEx函数在目标进程中申请一块内存,并通过WriteProcessMemory函数向内存中写入注入的DLL路径。

  • 最后,遍历上述获取的线程ID,并调用OpenThread函数以THREAD_ALL_ACCESS访问权限打开线程,获取线程句柄。并调用QueueUserAPC函数向线程插入APC 函数,设置APC函数的地址为LoadLibraryA函数的地址,并设置APC函数参数为上述DLL路径地址。

经过上述操作,便可完成APC注入操作。只要目标进程中任意线程被唤醒,便会执行APC,完成注入DLL操作。

4、测试

562297b6e89a6ba46f766bbe7cd8dc86.png

   在Win10 x64上,管理员权限运行APC_Test.exe,成功向explorer.exe进行了APC注入,注入完成后TestDll.dll进行了弹窗显示。

     APC注入原理是利用当线程被唤醒时APC中的注册函数会执行的机制,并以此去执行Dll加载代码,进而完成Dll注入。为了增加APC执行的可能性,应向目标进程中所有的线程都插入APC。如果出现向指定进程的所有线程插入APC导致进程崩溃的问题,则可以采取倒序遍历线程ID的方式进行倒序插入来解决程序崩溃的问题。

    至此,4种Dll常见的注入编程技术讲解完毕,希望结合代码来看文章。

本期代码地址:

链接:https://pan.baidu.com/s/1-ubqlbo1eh3u78u0RwqyHA

提取码:oev1

81b0b39232ddddc31389beade8260213.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值