详解最实用的几种dll注入方式

14 篇文章 8 订阅

目录

概念

注入方式

钩子注入

原理

示例代码

代码注解

运行截图

远线程注入

原理

示例代码 

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

示例代码 

*入口点注入

原理

参考代码

 APC注入

一、APC注入

二、API

三、实现

四、示例代码


概念

要谈dll注入,首先则要了解dll ,对dll的概念和使用不熟悉的读者可移步 dll概念和使用方式详解

所谓DLL注入就是将一个DLL放进某个进程的地址空间里,让它成为那个进程的一部分。这样该进程和dll共享同一内存空间,这样dll可以使用该进程的所有资源,随时监控程序运行。通常,我们将需要实现的功能封装生成dll文件,然后将其注入到某一进程中,从而在该进程中添加或扩展我们需要的功能。因此dll注入技术被广泛运用在恶意攻击、游戏外挂、木马等程序中。

注入方式

钩子注入

原理

利用函数SetWindowsHookEx来实现注入。该函数将一个应用程序定义的挂钩处理函数安装到挂钩链中去,可以通过安装挂钩处理过程来对系统的某些类型事件进行监控,这些事件与某个特定的线程或系统中的所有事件相关。如果我们需要向某一进程注入dll,则首先获取该进程中某一线程ID,在dll文件中封装我们的挂钩处理函数,对该线程设置钩子,当触发类型事件时,线程则调用dll文件中的挂钩函数。此时,如果线程想要成功地调用该挂钩函数,必须先加载该dll文件,这样也就实现了我们的dll注入了。

函数原型

HHOOK SetWindowsHookExA(
    [in] int       idHook,
    [in] HOOKPROC  lpfn,
    [in] HINSTANCE hmod,
    [in] DWORD     dwThreadId
);

[in] lpfn

指向挂接过程的指针。如果 dwThreadId 参数为零或指定由其他进程创建的线程的标识符,则 lpfn 参数必须指向 DLL 中的挂接过程。否则,lpfn 可以指向与当前进程关联的代码中的挂接过程。

[in] hmod

DLL 的句柄,其中包含 lpfn 参数所指向的挂钩过程。如果 dwThreadId 参数指定由当前进程创建的线程,并且挂接过程位于与当前进程关联的代码中,则必须将 hMod 参数设置为 NULL

[in] dwThreadId

挂接过程要与之关联的线程的标识符。对于桌面应用程序,如果此参数为零,则挂钩过程将与在与调用线程相同的桌面上运行的所有现有线程相关联

示例代码

dll文件

#include "pch.h"
#include<windows.h>
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}
HHOOK hhk = 0;

LRESULT CALLBACK KeyboardProc(
   int code,
   WPARAM wParam,
   LPARAM lParam
)
{
    if (code == HC_ACTION)
    {
        if (wParam == VK_HOME && (lParam & (1<<30)))
            MessageBoxA(NULL, "您按下了HOME键", "HOME",MB_OK);
    }

    return  CallNextHookEx(hhk, code, wParam, lParam);

}

extern "C" _declspec(dllexport)  HOOKPROC getDllPoint(void)
{
    return KeyboardProc;
}

设置钩子

typedef HOOKPROC(*func)(void);
void CInjectTestDlg::OnBnClickedButton1()
{
	HWND  hwnd = ::FindWindowA(NULL, "陈子青注入工具2.0");
	DWORD pid;
	DWORD thread_id = ::GetWindowThreadProcessId(hwnd, &pid);
	
	HMODULE mod = LoadLibraryA("myDll.dll");
	func proc = (func)::GetProcAddress(mod, "getDllPoint");

	HOOKPROC fp=proc();
	
	HHOOK hhk= ::SetWindowsHookEx(WH_KEYBOARD, fp, mod, thread_id);
}

代码注解

首先获取窗口线程,加载dll文件获取getDllPoint函数,该函数返回钩子处理函数的地址,然后给窗口线程设置键盘钩子,当触发键盘事件时,该线程调用myDll.dll文件中的处理函数,那么该线程必须先加载该dll文件,这时也就等同于我们将myDll.dll注入到该程序中了。

运行截图

点击注入,当按下HOME键时,弹出信息框。

远线程注入

原理

远线程注入比较好理解,通过CreateRemoteThread函数在需要注入的进程中创建一个自己的线程,在该线程中调用系统的LoadLibraryA加载dll文件即可。

LoadLibraryA函数Kernel32.dll中,因此可以通过加载Kernel32.dll以来获取LoadLibraryA函数地址,其函数的参数就是dll文件的路径。此时可以通过VirtualAllocEx函数在该进程中创建一块内存用来保存需要被注入dll文件路径。

CreateRemoteThread函数原型

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

[in] hProcess

要在其中创建线程的进程的句柄。句柄必须具有PROCESS_CREATE_THREADPROCESS_QUERY_INFORMATIONPROCESS_VM_OPERATIONPROCESS_VM_WRITEPROCESS_VM_READ访问权限,并且在某些平台上没有这些权限可能会失败。有关详细信息,请参阅进程安全性和访问权限

[in] lpThreadAttributes

指向SECURITY_ATTRIBUTES结构的指针,该结构指定新线程的安全描述符,并确定子进程是否可以继承返回的句柄。如果 lpThreadAttributes 为 NULL,则线程将获得默认的安全描述符,并且句柄无法继承。

[in] lpStartAddress

指向类型为 LPTHREAD_START_ROUTINE的应用程序定义函数的指针将由线程执行,并表示远程进程中线程的起始地址。该函数必须存在于远程进程中。有关详细信息,请参阅线程过程

[in] lpParameter

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

[in] dwCreationFlags

控制线程创建的标志。

价值意义

0

线程在创建后立即运行。

CREATE_SUSPENDED

0x00000004

线程是在挂起状态下创建的,并且在调用 ResumeThread 函数之前不会运行。

STACK_SIZE_PARAM_IS_A_RESERVATION

0x00010000

dwStackSize 参数指定堆栈的初始保留大小。如果未指定此标志,则 dwStackSize 指定提交大小。

[out] lpThreadId

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

如果此参数为 NULL,则不返回线程标识符。

VirtualAllocEx函数原型

LPVOID VirtualAllocEx(
  [in]           HANDLE hProcess,
  [in, optional] LPVOID lpAddress,
  [in]           SIZE_T dwSize,
  [in]           DWORD  flAllocationType,
  [in]           DWORD  flProtect
); 

[in] hProcess

进程的句柄。该函数在此进程的虚拟地址空间内分配内存。

句柄必须具有PROCESS_VM_OPERATION访问权限。

[in, optional] lpAddress

为要分配的页面区域指定所需起始地址的指针。

如果要预留内存,该函数会将此地址向下舍入到分配粒度的最接近的倍数。

如果要提交已预留的内存,则该函数会将此地址向下舍入到最近的页边界。若要确定主计算机上的页面大小和分配粒度,请使用 GetSystemInfo 函数。

[in] dwSize

要分配的内存区域的大小,以字节为单位。

如果 lpAddress 为 NULL,则该函数将 dwSize 向上舍入到下一页边界。

如果 lpAddress 不是 NULL,则该函数将分配包含从 lpAddress 到 lpAddress+dwSize 范围内的一个或多个字节的所有页面。这意味着,例如,跨越页面边界的 2 字节范围会导致函数同时分配这两个页面。

[in] flAllocationType

内存分配的类型。此参数必须包含以下值之一。

价值意义

MEM_COMMIT

0x00001000

为指定的保留内存页分配内存费用(从内存的总大小和磁盘上的分页文件)。该函数还保证当调用方稍后最初访问内存时,内容将为零。除非/直到实际访问虚拟地址,否则不会分配实际的物理页。

要一步到位地保留和提交页面,请使用 调用 VirtualAllocExMEM_COMMIT | MEM_RESERVE

尝试通过指定不MEM_RESERVE和非 NULL lpAddress 的MEM_COMMIT来提交特定地址范围将失败,除非已保留整个范围。生成的错误代码为 ERROR_INVALID_ADDRESS

尝试提交已提交的页不会导致函数失败。这意味着您可以提交页面,而无需首先确定每个页面的当前提交状态。

如果 lpAddress 指定安全区内的地址,则必须MEM_COMMIT flAllocationType

MEM_RESERVE

0x00002000

保留进程的一系列虚拟地址空间,而不在内存或磁盘上的分页文件中分配任何实际的物理存储。

您可以通过使用MEM_COMMIT再次调用VirtualAllocEx来提交保留页面。要一步到位地保留和提交页面,请使用 调用 VirtualAllocExMEM_COMMIT | MEM_RESERVE

其他内存分配函数(如 malloc 和 LocalAlloc)在释放保留内存之前无法使用保留内存。

MEM_RESET

0x00080000

指示不再对 lpAddress 和 dwSize 指定的内存范围中的数据感兴趣。不应从分页文件中读取或写入分页文件。但是,稍后将再次使用内存块,因此不应将其取消提交。此值不能与任何其他值一起使用。

使用此值并不能保证使用MEM_RESET操作的范围将包含零。如果希望范围包含零,请取消提交内存,然后重新提交。

当您使用MEM_RESET时,VirtualAllocEx 函数将忽略 fProtect 的值。但是,您仍必须将 fProtect 设置为有效的保护值,如PAGE_NOACCESS

如果您使用MEM_RESET并且内存范围映射到文件,则VirtualAllocEx将返回错误。共享视图只有在映射到分页文件时才可接受。

MEM_RESET_UNDO

0x1000000

MEM_RESET_UNDO应仅在之前已成功应用MEM_RESET的地址范围上调用。它指示调用方对 lpAddress 和 dwSize 指定的指定内存范围内的数据感兴趣,并尝试反转MEM_RESET的影响。如果函数成功,则意味着指定地址范围内的所有数据都完好无损。如果函数失败,则地址范围内至少有一些数据已替换为零。

此值不能与任何其他值一起使用。如果在之前未MEM_RESET的地址范围上调用MEM_RESET_UNDO,则行为未定义。指定MEM_RESET时,VirtualAllocEx 函数将忽略 flProtect 的值。但是,您仍必须将 flProtect 设置为有效的保护值,如PAGE_NOACCESS

Windows Server 2008 R2、Windows 7、Windows Server 2008、Windows Vista、Windows Server 2003 和 Windows XP: MEM_RESET_UNDO标志在 Windows 8 和 Windows Server 2012 之前不受支持。

 

此参数还可以指定以下值,如所示。

价值意义

MEM_LARGE_PAGES

0x20000000

使用大页面支持分配内存。

大小和对齐方式必须是大页最小值的倍数。若要获取此值,请使用 GetLargePageMinimum 函数。

如果指定此值,则还必须指定MEM_RESERVEMEM_COMMIT

MEM_PHYSICAL

0x00400000

保留可用于映射地址窗口化扩展插件 (AWE) 页的地址范围。

此值必须与MEM_RESERVE一起使用,不得与其他值一起使用。

MEM_TOP_DOWN

0x00100000

在可能的最高地址分配内存。这可能比常规分配慢,尤其是在分配很多时。

[in] flProtect

要分配的页面区域的内存保护。如果要提交页,则可以指定任何一个内存保护常量

如果 lpAddress 指定安全区内的地址,则 flProtect 不能是以下任何值:

  • PAGE_NOACCESS
  • PAGE_GUARD
  • PAGE_NOCACHE
  • PAGE_WRITECOMBINE

为安全区分配动态内存时,必须PAGE_READWRITEPAGE_EXECUTE_READWRITE flProtect 参数。

示例代码 

void CInjectTestDlg::OnBnClickedButton2()
{
	const char* path = "E:\\工程文件\\myDll\\Debug\\mydll2.dll";//要注入的dll路径

	HWND  hwnd = ::FindWindowA(NULL, "陈子青注入工具2.0");
	DWORD pid;
	GetWindowThreadProcessId(hwnd, &pid);

	HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);

	LPVOID lpAddr =  VirtualAllocEx(hProc,NULL, strlen(path)+1, MEM_COMMIT, PAGE_READWRITE);
	WriteProcessMemory(hProc, lpAddr, path,strlen(path), NULL);

	HMODULE sysMod = LoadLibraryA("Kernel32.dll");


	
	LPTHREAD_START_ROUTINE fp =(LPTHREAD_START_ROUTINE) GetProcAddress(sysMod, "LoadLibraryA");
	
	CreateRemoteThread(hProc, NULL, NULL,(LPTHREAD_START_ROUTINE)fp, lpAddr, 0, NULL);
	

}

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

使用ZwCreateThreadEx函数,突破SESSION 0隔离,向system等权限的系统服务器进程中入时,由于系统的SESSION 0隔离机制,导致注入失败;我们可使用ZwCreateThreadEx函数进行远程线程注入。ZwCreateThreadEx函数在ntdll.dll中没有被声明,所以需要使用GetProcAddressntdll.dll中获取函数导出地址。该函数在官网文档里是查询不到的。其函数原型如下

X64
DWORD 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
)

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

示例代码 

 typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx)GetProcAddress(hNtdll, "ZwCreateThreadEx");
    if (NULL == ZwCreateThreadEx)
    {
        printf("获取ZwCreateThreadEx函数地址失败!");
        CloseHandle(hProcess);
        return FALSE;
    }

    //在目标进程中创建线程
    HANDLE hRemoteThread = NULL;
    //pfnStartAddress 为loadlibrary函数地址, pBuf 传参
    DWORD dwStatus = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, hProcess,
            pfnStartAddress, pBuf, 0, 0, 0, 0, NULL);
    if (NULL == hRemoteThread)
    {
        printf("目标进程中创建线程失败!");
        CloseHandle(hProcess);
        return FALSE;
    }

*入口点注入

原理

在以CREATE_SUSPENDED的方式启动程序,进程启动时被挂起,此时该进程无法拿到任何权限和数据,此时修改PE文件的入口点代码,在进程入口点跳转到实现注入的代码段,并在此恢复入口点代码再跳转回入口点,让程序正常运行。

这样做的好处是:第一我们注入的代码时运行在进程的主线程中的;第二进程在拉起时就被暂定完成注入之后才开始运行,实现“神不知鬼不觉”的效果

实现过程中涉及如下关键技术点

① 获取程序入口点  C++获取程序入口点

② 进程的创建挂起  进程的创建与使用

③  jmp 远跳 对应机器码 0xE9

④ 如何定位写入数据 技巧 :通过 0xCCCCCCCC 来转换

⑤ 关于写入函数的问题,函数中涉及调用都是通过地址寻址来调用的,如果直接写入,该地址在对方内存空间中是无效的 。

⑥ Jmp调转的相对地址计算 = 跳转的目标地址 - 当前地址 - 0x5

该方法需要对注入进程的内存进行反复改写和恢复,修正过程中要在对方进程中进行Debug,因此需要一定的逆向基础真正完善,否则程序常常是容易崩溃的,

下面提供一段入口点注入的代码,仅供参考

参考代码

#include<stdio.h>
#include<cstring>
#include<Windows.h>
using namespace std;


char dllPath[MAX_PATH] = "E:\\工程文件\\EntryPointDemo\\Debug\\Dll1.dll";
const char exePath[MAX_PATH] = "C:\\Users\\Administrator\\Desktop\\game.exe";

typedef HMODULE(*_loadLibrayA)(LPCSTR);
typedef HMODULE (*_getModuleHandle)(LPCSTR);
typedef BOOL (*_virtualProtect)(LPVOID ,SIZE_T,DWORD,PDWORD);

typedef struct ROMOTE_DATA
{
	char path[MAX_PATH];//dll地址
	_loadLibrayA loadLibrary;
	DWORD entryPoint;
	_getModuleHandle getModuleHande;
	_virtualProtect  virtualProtect;
	char code[0x5];
	LPVOID injectAddr;

}*PROMOTE_DATA;

PROMOTE_DATA rotData;

DWORD GetEntryPointAddr();
void initRotData()
{
	//strcpy_s(rotData->path,dllPath);
	memcpy(rotData->path,dllPath,sizeof(dllPath));

	HMODULE hMod =  LoadLibraryA("Kernel32.dll");
	rotData->loadLibrary = (_loadLibrayA)GetProcAddress(hMod, "LoadLibraryA");
	//printf("loadLibrary :%x\n", rotData->loadLibrary);
	rotData->entryPoint = GetEntryPointAddr();

	rotData->getModuleHande = (_getModuleHandle)GetProcAddress(hMod, "GetModuleHandleA");
	//printf("getModuleHande:%x\n", rotData->getModuleHande);
	rotData->virtualProtect = (_virtualProtect)GetProcAddress(hMod, "VirtualProtect");
	//printf("virtualProtect: %x\n", rotData->virtualProtect);

}

DWORD GetEntryPointAddr()
{
	HANDLE hFile = CreateFileA(exePath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	int szFile = GetFileSize(hFile, NULL);
	LPBYTE lpFile = new BYTE[szFile];
	int ret = ReadFile(hFile, lpFile, szFile, NULL, NULL);
	if (ret == 0)
	{
		printf("read file error:%d", GetLastError());
		return NULL;
	}

	PIMAGE_DOS_HEADER imgDosHeader = (PIMAGE_DOS_HEADER)lpFile;
	PIMAGE_NT_HEADERS imgNtHeader = (PIMAGE_NT_HEADERS)(lpFile + imgDosHeader->e_lfanew);
	PIMAGE_OPTIONAL_HEADER imgOptionHeader = &(imgNtHeader->OptionalHeader);

	DWORD bRet = imgOptionHeader->AddressOfEntryPoint;

	if (hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile);
	if (lpFile != NULL) delete[] lpFile;

	return bRet;
}

void InjectFunc()
{
	PROMOTE_DATA pData = (PROMOTE_DATA)0xCCCCCCCC;
	pData->loadLibrary(pData->path);

	char* prevcode = (char*)pData->entryPoint;
	for (int i = 0; i < 0x5; i++) prevcode[i] = pData->code[i];

	unsigned point = pData->entryPoint;
	__asm
	{
		mov eax, point
		jmp eax
	}
}

void modifyEntryPoint(PROMOTE_DATA p)
{
	DWORD baseAddr= (DWORD)p->getModuleHande(0);
	p->entryPoint += baseAddr;

	DWORD lwt{};
	p->virtualProtect((LPVOID)p->entryPoint, 0x5, PAGE_EXECUTE_READWRITE, &lwt);

	char *prevcode = (char*)p->entryPoint;
	for (int i = 0; i < 5; ++i) p->code[i] = prevcode[i];
	prevcode[0] = 0xE9;//jmp
	unsigned distance = (unsigned)( (DWORD)p->injectAddr - p->entryPoint - 0x5);

	unsigned *_code = (unsigned*)(p->entryPoint+1);
	_code[0] = distance;

}
int main()
{
	STARTUPINFOA startUp;
	ZeroMemory(&startUp, sizeof(STARTUPINFOA));
	PROCESS_INFORMATION  proInfo;
	ZeroMemory(&proInfo, sizeof(PROCESS_INFORMATION));
	int bRet = CreateProcessA(exePath, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL,
		"C:\\Users\\Administrator\\Desktop\\",
		&startUp, &proInfo);
	if (bRet == 0)
	{
		printf("createprocess error:%d\n", GetLastError());
		return 0;
	}

	//进行入口点注入
	rotData = new ROMOTE_DATA;

	DWORD rotAddr=(DWORD)VirtualAllocEx(proInfo.hProcess, NULL, 0x3000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	DWORD rotDataAddr = rotAddr + 0x200;
	
	initRotData();

	char modifyCode[0x200];
		
	memcpy(modifyCode, InjectFunc, sizeof(modifyCode));

	for (int i = 0; i < 0x200; i++)
	{ 
		unsigned* pcode = (unsigned*)(&modifyCode[i]);
		if(pcode[0] == 0xCCCCCCCC)
		{
			pcode[0] = (unsigned)rotDataAddr;
			break;
		}
	}

	DWORD injectAddr = rotAddr + 0x1000; //注入dll的函数
	rotData->injectAddr = (LPVOID)injectAddr;
	WriteProcessMemory(proInfo.hProcess, (LPVOID)rotDataAddr, rotData, 0x200, NULL);

	WriteProcessMemory(proInfo.hProcess, (LPVOID)injectAddr, modifyCode, 0x200, NULL);

	DWORD rotFuncAddr = rotAddr + 0x2000; //写入远程函数
	WriteProcessMemory(proInfo.hProcess, (LPVOID)rotFuncAddr, modifyEntryPoint, 0x500, NULL);

	HANDLE hProcHnd = CreateRemoteThread(proInfo.hProcess, NULL, 0,
		(LPTHREAD_START_ROUTINE)rotFuncAddr, (LPVOID)rotDataAddr, 0, NULL);
	if (hProcHnd != INVALID_HANDLE_VALUE) WaitForSingleObject(hProcHnd, INFINITE);
	else printf("CreateRemoteThread error : %d", GetLastError());

	//注入完成后恢复进程运行
	ResumeThread(proInfo.hThread);


	
	return 0;
}

 APC注入

一、APC注入

什么是APC?

[原创]小Win,点一份APC(Apc机制详解)(一)-编程技术-看雪论坛-安全社区|安全招聘|bbs.pediy.com
每一个线程都有自己的APC队列,使用QueueUserAPC函数把一个APC函数压入APC队列中。当处于处于用户模式的APC压入线程APC队列后,该线程并不直接调用APC函数,除非该线程处于可通知状态,调用的顺序为先入先出(FIFO)。

二、API

QueueUserAPC函数
QueueUserAPC function (processthreadsapi.h) - Win32 apps | Microsoft Docs

三、实现

一个进程包含多个线程,为了确保能够执行插入的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路径地址。

四、示例代码

#include "stdafx.h"
#include "ApcInject.h"


void ShowError(char *pszText)
{
	char szErr[MAX_PATH] = { 0 };
	::wsprintf(szErr, "%s Error[%d]\n", pszText);
	::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 (NULL == hSnapshot)
	{
		ShowError("CreateToolhelp32Snapshot");
		return dwProcessId;
	}

	// 获取第一条进程快照信息
	bRet = ::Process32First(hSnapshot, &pe32);
	while (bRet)
	{
		// 获取快照信息
		if (0 == ::lstrcmpi(pe32.szExeFile, pszProcessName))
		{
			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 (NULL == pThreadId)
		{
			ShowError("new");
			bRet = FALSE;
			break;
		}
		::RtlZeroMemory(pThreadId, (dwBufferLength * sizeof(DWORD)));

		// 获取线程快照
		::RtlZeroMemory(&te32, sizeof(te32));
		te32.dwSize = sizeof(te32);
		hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
		if (NULL == hSnapshot)
		{
			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 (FALSE == bRet)
	{
		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, hThread = NULL;
	PVOID pBaseAddress = NULL;
	PVOID pLoadLibraryAFunc = NULL;
	SIZE_T dwRet = 0, dwDllPathLen = 1 + ::lstrlen(pszDllName);
	DWORD i = 0;

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

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

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

		// 在注入进程空间申请内存
		pBaseAddress = ::VirtualAllocEx(hProcess, NULL, dwDllPathLen, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
		if (NULL == pBaseAddress)
		{
			ShowError("VirtualAllocEx");
			bRet = FALSE;
			break;
		}
		// 向申请的空间中写入DLL路径数据 
		::WriteProcessMemory(hProcess, pBaseAddress, pszDllName, dwDllPathLen, &dwRet);
		if (dwRet != dwDllPathLen)
		{
			ShowError("WriteProcessMemory");
			bRet = FALSE;
			break;
		}

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

		// 遍历线程, 插入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);

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

	return bRet;
}

  • 17
    点赞
  • 155
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
远程注入DLL方法有很多种,也是很多木马病毒所使用的隐藏进程的方法,因为通过程序加载的DLL在进程管理器是没有显示的.这里介绍一种用 CreateRemoteThread 远程建立线程的方式注入DLL. 首先,我们要提升自己的权限,因为远程注入必不可免的要访问到目标进程的内存空间,如果没有足够的系统权限,将无法作任何事.下面是这个函数是用来提升我们想要的权限用的. function EnableDebugPriv: Boolean; var hToken: THandle; tp: TTokenPrivileges; rl: Cardinal; begin Result := false; //打开进程令牌环 OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, hToken); //获得进程本地唯一ID if LookupPrivilegeValue(nil, 'SeDebugPrivilege', tp.Privileges[0].Luid) then begin tp.PrivilegeCount := 1; tp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED; //调整权限 Result := AdjustTokenPrivileges(hToken, false, tp, SizeOf(tp), nil, rl); end; end; 关于 OpenProcessToken() 和 AdjustTokenPrivileges() 两个 API 的简单介绍: OpenProcessToken():获得进程访问令牌的句柄. function OpenProcessToken( ProcessHandle: THandle; //要修改访问权限的进程句柄 DesiredAccess: DWORD; //指定你要进行的操作类型 var TokenHandle: THandle//返回的访问令牌指针 ): BOOL; AdjustTokenPrivileges() :调整进程的权限. function AdjustTokenPrivileges( TokenHandle: THandle; // 访问令牌的句柄 DisableAllPrivileges: BOOL; // 决定是进行权限修改还是除能(Disable)所有权限 const NewState: TTokenPrivileges; { 指明要修改的权限,是一个指向TOKEN_PRIVILEGES结构的指针,该结构包含一个数组, 数据组的每个项指明了权限的类型和要进行的操作; } BufferLength: DWORD; //结构PreviousState的长度,如果PreviousState为空,该参数应为 0 var PreviousState: TTokenPrivileges; // 指向TOKEN_PRIVILEGES结构的指针,存放修改前的访问权限的信息 var ReturnLength: DWORD //实际PreviousState结构返回的大小 ) : BOOL; 远程注入DLL其实是通过 CreateRemoteThread 建立一个远程线程调用 LoadLibrary 函数来加载我们指定的DLL,可是如何能让远程线程知道我要加载DLL呢,要知道在Win32系统下,每个进程都拥有自己的4G虚拟地址空间,各个进程之间都是相互独立的。所我们需要在远程进程的内存空间里申请一块内存空间,写入我们的需要注入DLL 的路径. 需要用到的 API 函数有: OpenProcess():打开目标进程,得到目标进程的操作权限,详细参看MSDN function OpenProcess( dwDesiredAccess: DWORD; // 希望获得的访问权限 bInheritHandle: BOOL; // 指明是否希望所获得的句柄可以继承 dwProcessId: DWORD // 要访问的进程ID ): THandle; VirtualAllocEx():用于在目标进程内存空间中申请内存空间以写入DLL的文件名 function VirtualAllocEx( hProcess: THandle; // 申请内存所在的进程句柄 lpAddress: Pointer; // 保留页面的内存地址;一般用nil自动分配 dwSize, // 欲分配的内存大小,字节单位;注意实际分 配的内存大小是页内存大小的整数倍 flAllocationType: DWORD; flProtect: DWORD ): Pointer; WriteProcessMemory():往申请到的空间中写入DLL的文件名 function WriteProcessMemory( hProcess: THandle; //要写入内存数据的目标进程句柄 const lpBaseAddress: Pointer; //要写入的目标进程的内存指针, 需以 VirtualAllocEx() 来申请 lpBuffer: Pointer; //要写入的数据 nSize: DWORD; //写入数据的大小 var lpNumberOfBytesWritten: DWORD //实际写入的大小 ): BOOL; 然后就可以调用 CreateRemoteThread 建立远程线程调用 LoadLibrary 函数来加载我们指定的DLL. CreateRemoteThread() //在一个远程进程中建立线程 function CreateRemoteThread( hProcess: THandle; //远程进程的句柄 lpThreadAttributes: Pointer; //线程安全描述字,指向SECURITY_ATTRIBUTES结构的指针 dwStackSize: DWORD; //线程栈大小,以字节表示 lpStartAddress: TFNThreadStartRoutine; // 一个TFNThreadStartRoutine类型的指针,指向在远程进程中执行的函数地址 lpParameter: Pointer; //传入参数的指针 dwCreationFlags: DWORD; //创建线程的其它标志 var lpThreadId: DWORD //线程身份标志,如果为0, 则不返回 ): THandle; 整个远程注入DLL的具体实现代码如下: function InjectDll(const DllFullPath: string; const dwRemoteProcessId: Cardinal): Boolean; var hRemoteProcess, hRemoteThread: THandle; pszLibFileRemote: Pointer; pszLibAFilename: PwideChar; pfnStartAddr: TFNThreadStartRoutine; memSize, WriteSize, lpThreadId: Cardinal; begin Result := false; // 调整权限,使程序可以访问其他进程的内存空间 if EnableDebugPriv then begin //打开远程线程 PROCESS_ALL_ACCESS 参数表示打开所有的权限 hRemoteProcess := OpenProcess(PROCESS_ALL_ACCESS, false, dwRemoteProcessId); try // 为注入dll文件路径分配内存大小,由于为WideChar,故要乘2 GetMem(pszLibAFilename, Length(DllFullPath) * 2 + 1); // 之所以要转换成 WideChar, 是因为当DLL位于有中文字符的路径下时不会出错 StringToWideChar(DllFullPath, pszLibAFilename, Length(DllFullPath) * 2 + 1); // 计算 pszLibAFilename 的长度,注意,是以字节为单元的长度 memSize := (1 + lstrlenW(pszLibAFilename)) * SizeOf(WCHAR); //使用VirtualAllocEx函数在远程进程的内存地址空间分配DLL文件名空间 pszLibFileRemote := VirtualAllocEx(hRemoteProcess, nil, memSize, MEM_COMMIT, PAGE_READWRITE); if Assigned(pszLibFileRemote) then begin //使用WriteProcessMemory函数将DLL的路径名写入到远程进程的内存空间 if WriteProcessMemory(hRemoteProcess, pszLibFileRemote, pszLibAFilename, memSize, WriteSize) and (WriteSize = memSize) then begin lpThreadId := 0; // 计算LoadLibraryW的入口地址 pfnStartAddr := GetProcAddress(LoadLibrary('Kernel32.dll'), 'LoadLibraryW'); // 启动远程线程LoadLbraryW,通过远程线程调用创建新的线程 hRemoteThread := CreateRemoteThread(hRemoteProcess, nil, 0, pfnStartAddr, pszLibFileRemote, 0, lpThreadId); // 如果执行成功返回 True; if (hRemoteThread 0) then Result := true; // 释放句柄 CloseHandle(hRemoteThread); end; end; finally // 释放句柄 CloseHandle(hRemoteProcess); end; end; end; 接下来要说的是如何卸载注入目标进程中的DLL,其实原理和注入DLL是完全相同的,只是远程调用调用的函数不同而已,这里要调用的是FreeLibrary,代码如下: function UnInjectDll(const DllFullPath: string; const dwRemoteProcessId: Cardinal): Boolean; // 进程注入和取消注入其实都差不多,只是运行的函数不同而已 var hRemoteProcess, hRemoteThread: THandle; pszLibFileRemote: PChar; pszLibAFilename: PwideChar; pfnStartAddr: TFNThreadStartRoutine; memSize, WriteSize, lpThreadId, dwHandle: Cardinal; begin Result := false; // 调整权限,使程序可以访问其他进程的内存空间 if EnableDebugPriv then begin //打开远程线程 PROCESS_ALL_ACCESS 参数表示打开所有的权限 hRemoteProcess := OpenProcess(PROCESS_ALL_ACCESS, false, dwRemoteProcessId); try // 为注入dll文件路径分配内存大小,由于为WideChar,故要乘2 GetMem(pszLibAFilename, Length(DllFullPath) * 2 + 1); // 之所以要转换成 WideChar, 是因为当DLL位于有中文字符的路径下时不会出错 StringToWideChar(DllFullPath, pszLibAFilename, Length(DllFullPath) * 2 + 1); // 计算 pszLibAFilename 的长度,注意,是以字节为单元的长度 memSize := (1 + lstrlenW(pszLibAFilename)) * SizeOf(WCHAR); //使用VirtualAllocEx函数在远程进程的内存地址空间分配DLL文件名空间 pszLibFileRemote := VirtualAllocEx(hRemoteProcess, nil, memSize, MEM_COMMIT, PAGE_READWRITE); if Assigned(pszLibFileRemote) then begin //使用WriteProcessMemory函数将DLL的路径名写入到远程进程的内存空间 if WriteProcessMemory(hRemoteProcess, pszLibFileRemote, pszLibAFilename, memSize, WriteSize) and (WriteSize = memSize) then begin // 计算GetModuleHandleW的入口地址 pfnStartAddr := GetProcAddress(LoadLibrary('Kernel32.dll'), 'GetModuleHandleW'); //使目标进程调用GetModuleHandleW,获得DLL在目标进程中的句柄 hRemoteThread := CreateRemoteThread(hRemoteProcess, nil, 0, pfnStartAddr, pszLibFileRemote, 0, lpThreadId); // 等待GetModuleHandle运行完毕 WaitForSingleObject(hRemoteThread, INFINITE); // 获得GetModuleHandle的返回值,存在dwHandle变量中 GetExitCodeThread(hRemoteThread, dwHandle); // 计算FreeLibrary的入口地址 pfnStartAddr := GetProcAddress(LoadLibrary('Kernel32.dll'), 'FreeLibrary'); // 使目标进程调用FreeLibrary,卸载DLL hRemoteThread := CreateRemoteThread(hRemoteProcess, nil, 0, pfnStartAddr, Pointer(dwHandle), 0, lpThreadId); // 等待FreeLibrary卸载完毕 WaitForSingleObject(hRemoteThread, INFINITE); // 如果执行成功返回 True; if hRemoteProcess 0 then Result := true; // 释放目标进程中申请的空间 VirtualFreeEx(hRemoteProcess, pszLibFileRemote, Length(DllFullPath) + 1, MEM_DECOMMIT); // 释放句柄 CloseHandle(hRemoteThread); end; end; finally // 释放句柄 CloseHandle(hRemoteProcess); end; end; end;
1. 机器学习算法:机器学习算法可以用于各种场景,包括但不限于自然语言处理、图像识别、预测分析、推荐系统和智能风控等。例如,使用机器学习算法可以对大量数据进行分析和处理,从而提高业务效率和数据决策的准确性。 2. 深度学习算法:深度学习算法可以用于自然语言处理、图像识别、语音识别等领域。例如,在自然语言处理中,深度学习算法可以用于建立文本分类、情感分析、自动摘要或翻译等模型,从而帮助人们更好地理解和应用自然语言。 3. 神经网络算法:神经网络算法可以用于各种场景,如自然语言处理、图像识别、语音识别、智能推荐和智能风控等。例如,在自然语言处理中,神经网络算法可以用于词嵌入、文本分类、情感分析等任务,从而帮助人们更好地理解和处理自然语言。 4. 贝叶斯算法:贝叶斯算法可以用于分类、聚类、推荐、预测等领域。例如,在推荐系统中,贝叶斯算法可以用于个性化推荐,根据用户历史行为和偏好来推荐相似或相关的商品或服务。 5. 决策树算法:决策树算法可以用于分类、聚类、预测和推荐等领域。例如,在金融风控中,决策树算法可以用于识别和预测潜在的风险,从而帮助金融机构更好地管理和控制风险。 总的来说,机器学习算法、深度学习算法、神经网络算法、贝叶斯算法和决策树算法都有广泛的应用场景,可以帮助人们更好地理解和处理数据,并从中获取有价值的信息和洞见。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员陈子青

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值