2021-09-18


前言

注入技术可以方便的对目标进程空间进行修改,将shellcode和dll注入到目标进程执行。dll注入使用最广泛。因为dll 不需要像shellcode一样需要获取kernel32.dll加载基址并根据导出表获取导出函数的地址。如果dll注入成功,则表示dll已经成功加载到目标进程空间中,导入表、导出表、重定位表均已经修改完毕,dll中的代码可以正常执行,正式由于的力量的简单易用,一些特殊程序常常使用这种技术。


一、常见的dll注入技术

1.全局钩子注入
2.远程线程钩子(利用CreateRemoteThread和LoadLibarary函数的相似性)
3.突破Session 0隔离的远程线程注入(利用底层函数ZwCreateThreadEx)
4.利用APC的机制

二、使用步骤

1.全局钩子注入

代码如下(示例):

实现过程

为了能够让DLL注入到所有的进程中,程序设置WH_GETMESSAGE消息的全局钩子。因为WH_GETMESSAGE类型的钩子会监视消息队列,并且Windows系统是基于消息驱动的,所以所有进程都会有自己的一个消息队列,都会加载WH_GETMESSAGE类型的全局钩子DLL。
// 共享内存
#pragma data_seg("mydata")
  HHOOK g_hHook = NULL;
#pragma data_seg()
#pragma comment(linker, "/SECTION:mydata,RWS")    
设置WH_GETMESSAGE全局钩子具体实现的代码如下所示。
// 设置全局钩子
BOOL SetGlobalHook()
{
  g_hHook =SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMsgProc, g_hDllModule, 0);
  if (NULL == g_hHook)
  {
    return FALSE;
  }
  return TRUE;
}

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

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

2.远程线程注入

实现过程

// 使用 CreateRemoteThread 实现远线程注入
BOOL CreateRemoteThreadInjectDll(DWORD dwProcessId, char *pszDllFileName)
{
  HANDLE hProcess = NULL;
  DWORD dwSize = 0;
  LPVOID pDllAddr = NULL;
  FARPROC pFuncProcAddr = NULL;
  // 打开注入进程,获取进程句柄
  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_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;
  }
  // 关闭句柄
  :CloseHandle(hProcess);
  return TRUE;
}

注意:注意,如果注入失败的话,那么可以尝试以管理员身份运行程序。由于OpenProcess函数的缘故,在打开高权限进程时,程序会因权限不足而无法打开进程,获取进程句柄。如果对一些系统进程进行注入测试,就会发现一个问题,即不能成功注入到一些系统服务进程。这是由于系统存在SESSION 0隔离的安全机制,传统的远线程注入DLL方法并不能突破SESSION 0隔离。接下来,继续介绍突破 SESSION 0 隔离的远线程注入,向系统服务进程注入DLL。

3.突破Session 0隔离的远程线程注入(利用ZwCreateThreadEx函数)

ZwCreateThreadEx函数可以突破SESSION 0隔离,将DLL成功注入到SESSION 0隔离的系统服务进程中。其中,由于ZwCreateThreadEx在ntdll.dll中并没有声明,所以需要使用GetProcAddress从ntdll.dll中获取该函数的导出地址。

// 使用 ZwCreateThreadEx 实现远线程注入
BOOL ZwCreateThreadExInjectDll(DWORD dwProcessId, char *pszDllFileName)
{
  // 打开注入进程,获取进程句柄(略)
  // 在注入进程中申请内存(略)
  // 向申请的内存写入数据(略)
  // 加载 ntdll.dll
  HMODULE hNtdllDll =LoadLibrary("ntdll.dll");
  if (NULL == hNtdllDll)
  {
    ShowError("LoadLirbrary");
    return FALSE;
  }
  // 获取LoadLibraryA函数地址
  pFuncProcAddr =GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
  if (NULL == pFuncProcAddr)
  {
    ShowError("GetProcAddress_LoadLibraryA");
    return FALSE;
  }
  // 获取ZwCreateThreadEx函数地址
  typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx)GetProcAddress(hNtdllDll, "ZwCreateThreadEx");
  if (NULL == ZwCreateThreadEx)
  {
    ShowError("GetProcAddress_ZwCreateThread");
    return FALSE;
  }
  // 使用 ZwCreateThreadEx 创建远线程, 实现 DLL 注入
  dwStatus = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, hProcess, (LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, 0, 0, 0, NULL);
  if (NULL == hRemoteThread)
  {
    ShowError("ZwCreateThreadEx");
    return FALSE;}
  // 关闭句柄(略)
  return TRUE;
}

注意:如果在DLL中想通过调用MessageBox弹窗提示来判断是否DLL注入成功,那么就会大失所望了。由于会话隔离,在系统服务程序里不能显示程序窗体,也不能用常规方式创建用户进程。通常情况下,可以通过查看进程加载模块的信息(包括模块名称、模块路径等),来判断出进程中是否存在可疑模块。

4.APC注入

代码如下:
实现过程

APC注入的具体实现代码如下所示。
// APC注入
BOOL ApcInjectDll(char *pszProcessName, char *pszDllName)
{
  // 变量 (略)
  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;
    }
    // 获取 LoadLibraryA 地址
    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);
  // 释放内存 (略)
  return bRet;
}

注意:APC注入原理是利用当线程被唤醒时APC中的注册函数会执行的机制,并以此去执行DLL加载代码,进而完成DLL注入。为了增加APC执行的可能性,应向目标进程中所有的线程都插入APC。
如果出现向指定进程的所有线程插入APC导致进程崩溃的问题,则可以采取倒序遍历线程ID的方式进行倒序插入来解决程序崩溃的问题。

总结

与之前介绍的远线程注入类似,注入操作通常实现的是加载DLL的功能,因此可以通过查看进程加载模块的信息,来判断进程是否注入到了其他模块。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值