前言

Cobalt Strike已经成了目前工作中经常用渗透工具了,通常我们会通过写一下插件来武装自己的Cobalt Strike,比如我们会用bexecute_assembly来对自己编写的Csharp进行内存加载实现不落地。那么其他语言的呢?同样也提供了bdllspawn来反射DLL。本文章主要讲的就是利用反射DLL来武装自己的Cobalt Strike。

C++ ReflectiveDLL

首先将C/C++编写的程序如何进行ReflectiveDLL,Cobalt Strike的bdllspawn是基于项目ReflectiveDLLInjection实现的。我们只需要把C++编写的功能写到ReflectiveDll.c里即可,这里参考倾旋师傅的文章和工具。改写ReflectiveDll.c来实现传参。

参数转换

引用与定义
#include "ReflectiveLoader.h"
#include <string>
#include <shellapi.h>
#pragma comment(lib, "Shell32.lib")

std::string szargs;
std::wstring wszargs;
std::wstring wsHostFile;
int argc = 0;
LPWSTR* argv = NULL;
参数类型转换

ReflectiveDll.c里是通过DLLMain函数的lpReserved来当做参数传递,我们可以做一个类型的转换,将lpReserved转换成命令行参数格式。

szargs = (PCHAR)lpReserved;
                wszargs = StringToWString(szargs);
                argv = CommandLineToArgvW(wszargs.data(), &argc);

功能编写

在转换参数后,我们就可以把一些C++功能代码写入,我这就简单编写一个参数输出功能,进行测试。

hAppInstance = hinstDLL;
            printf("C++ ReflectiveDLL\n");

            /* print some output to the operator */
            if (lpReserved != NULL) {
                    szargs = (PCHAR)lpReserved;
                    wszargs = StringToWString(szargs);
                    argv = CommandLineToArgvW(wszargs.data(), &argc);
            }
            if (argv == NULL) {
                printf("[+] Error Arguments ! \n");
                break;
            }
            printf("[+] Args Count : %d \n", argc);
            for (size_t i = 0; i < argc; i++)
            {
                wprintf(TEXT("[%d] %s \n"), i, argv[i]);
            }

            /* flush STDOUT */
            fflush(stdout);

            /* we're done, so let's exit */
            ExitProcess(0);
            break;

Aggressor Script

编译ReflectiveDll.c得到一个dll文件,然后编写一个Aggressor Script脚本来加载它。

Aggressor Script脚本提供了一些关于反射DLL的接口:https://cobaltstrike.com/aggressor-script/functions.html#bdllspawn

alias hello {
    $args = substr($0, 6);
    bdllspawn($1, script_resource("reflective_dll.dll"),$args, "test dll", 5000, false);
}

测试效果

这样我们就实现了将c++编写的dll通过ReflectiveDll来实现不落地传参执行了。

Golang ReflectiveDLL

Golang也成了现在一些安全工作者用得比较多的一种语言了,使用Golang开发的安全工具也越来越多,所以我们也可以通过ReflectiveDLL来对Golang程序进行利用。

这里参考WBGlIl师傅的项目go-ReflectiveDLL和国外大佬的文章Weaponizing your favorite Go program for Cobalt Strike

特别感谢一下WBGlIl师傅,在我遇到问题的时候给予我的帮助~

main.go

参考WBGlIl师傅的项目,将main.go改成一个传入参数并输出参数的功能,并将test函数设置为导出函数。

package main

import "C"

import (
    "fmt"
    "os"
    gsq "github.com/kballard/go-shellquote"
)



//export test
func test(arg string) {

    args, err := gsq.Split(arg)
    if err == nil {
        fmt.Println("Golang ReflectiveDLL")
        os.Args = args
        fmt.Printf("Args Count %d\n",len(os.Args))
        for i := 0; i < len(os.Args); i++ {
            fmt.Printf("[%d] %s\n",i,os.Args[i])
        }
    }
}

func main()  {

}

dllmain.c

参考老外文章将lpReserved转换为 GoString,传入到dll的导出函数test里。

#include "dllmain.h"
#include<Windows.h>


BOOL WINAPI DllMain(
    HINSTANCE hinstDLL,  // handle to DLL module
    DWORD fdwReason,     // reason for calling function
    LPVOID lpReserved)   // reserved
{
    switch (fdwReason) {
    case DLL_PROCESS_ATTACH:
        {
            GoString goArgs = {0};
            if(lpReserved != NULL){
                goArgs.p = (char*)lpReserved;
                goArgs.n = strlen(lpReserved);
            }else{
                goArgs.p = "";
                goArgs.n = 0;
            }
            test(goArgs);
        }
        break;
    case DLL_PROCESS_DETACH:
        // Perform any necessary cleanup.
        break;
    case DLL_THREAD_DETACH:
        // Do thread-specific cleanup.
        break;
    case DLL_THREAD_ATTACH:
        // Do thread-specific initialization.
        break;
    }
    return TRUE; // Successful.
}

然后运行WBGlIl师傅的项目里的x64.bat来进行编译得到dll文件,但是Golang有一个最大的缺点就是编译出来的文件特别大,这里一个简单的输入输出工具生成的dll就有差不多2M。而在Cobalt Strike限制了反射DLL的DLL大小必须在1M以内,所以这里我们不能用Cobalt Strike进行测试。

Inject.c修改

​ ReflectiveDLLInjection项目中的inject是不能给DLL进行传参的,所以我们这里需要修改代码来进行传参。

注入当前进程

如果对当前进程进行注入可以修改代码,将输入参数传入到LoadRemoteLibraryR函数的第四个参数即可。

LPVOID lpParameter = argv[3];
hModule = LoadRemoteLibraryR( hProcess, lpBuffer, dwLength, lpParameter);

注入到其他进程

如果注入到其他进程的话,需要将参数写入到目标进程得到一个参数指针,再讲这个指针传入LoadRemoteLibraryR函数。

lpRemoteMem = arg;
        // 申请内存
        argSize = strlen(arg);
        lpRemoteMem = VirtualAllocEx(hProcess, NULL, argSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        if (!lpRemoteMem)
        {
            BREAK_WITH_ERROR("\t\t[!] FAILED to allocate memory in process.\n");
            CloseHandle(hProcess);
            break;
        }

        printf("[+] Memory allocated at : 0x%d in process %d\n", lpRemoteMem, dwProcessId);
        printf("[+] Attempting to write parameter in  process %d \n", dwProcessId);


        //将参数写入目标进程
        bWriteSuccess = WriteProcessMemory(hProcess, lpRemoteMem, arg, argSize, &numBytes);

        if (!bWriteSuccess)
        {
            printf("[!] FAILED to write parameter. Wrote %d  bytes instead of %d bytes.\n ", numBytes ,argSize);

            CloseHandle(hProcess);
            break;
        }
        printf("[+] Wrote parameter in remote process %d memory.\n", dwProcessId);

        //将参数指针传入
        hModule = LoadRemoteLibraryR( hProcess, lpBuffer, dwLength, lpRemoteMem);
        if( !hModule )
            BREAK_WITH_ERROR( "Failed to inject the DLL" );

        printf( "[+] Injected the '%s' DLL into process %d.\n", cpDllFile, dwProcessId);

因为这里是注入到了其他进程里,所以当前进程是没有输出的。如果需要输出的话,可以修改DLL文件和Inject.c,即当前进程和DLL注入的进程之间用命名管道进行通信。这里以C++写的DLL为例。

inject.c
//接收

        srand(time(NULL));

        char buf[256] = "";
        DWORD rlen = 0;
        HANDLE hPipe = CreateNamedPipe(
            TEXT("\\\\.\\Pipe\\mypipe"),                        //管道名
            PIPE_ACCESS_DUPLEX,                                 //管道类型 
            PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,  //管道参数
            PIPE_UNLIMITED_INSTANCES,                           //管道能创建的最大实例数量
            0,                                                  //输出缓冲区长度 0表示默认
            0,                                                  //输入缓冲区长度 0表示默认
            NMPWAIT_WAIT_FOREVER,                               //超时时间
            NULL);                                                  //指定一个SECURITY_ATTRIBUTES结构,或者传递零值.
        if (INVALID_HANDLE_VALUE == hPipe)
        {
            printf("[+] Create Pipe Error(%d)\n", GetLastError());
        }
        else
        {
            printf("[+] Create Pipe Success\n");
            printf("[+] Waiting For Client Connection...\n");
            if (ConnectNamedPipe(hPipe, NULL) == NULL)  //阻塞等待客户端连接。
            {
                printf("[+] Connection failed!\n");
            }
            else
            {
                printf("[+] Connection Success!\n");
            }
            printf("[+] Data From Pipe :\n\n");
            while (1)
            {
                if (ReadFile(hPipe, buf, 256, &rlen, NULL)) //接受客户端发送过来的内容
                {
                    printf("\t%s", buf);
                }
                else
                {
                    printf("\n[+] Read Data From Pipe End!\n");
                    break;
                }
            }
            CloseHandle(hPipe);//关闭管道
        }
ReflectiveDll.cpp
//利用命名管道传输
            srand(time(NULL));
            DWORD wlen = 0;

            BOOL bRet = WaitNamedPipe(TEXT("\\\\.\\Pipe\\mypipe"), NMPWAIT_WAIT_FOREVER);

            if (!bRet)
            {
                printf("connect the namedPipe failed!\n");
                break;
            }

            HANDLE hPipe = CreateFile(          //管道属于一种特殊的文件
                TEXT("\\\\.\\Pipe\\mypipe"),    //创建的文件名
                GENERIC_READ | GENERIC_WRITE,   //文件模式
                0,                              //是否共享
                NULL,                           //指向一个SECURITY_ATTRIBUTES结构的指针
                OPEN_EXISTING,                  //创建参数
                FILE_ATTRIBUTE_NORMAL,          //文件属性(隐藏,只读)NORMAL为默认属性
                NULL);                          //模板创建文件的句柄


            hAppInstance = hinstDLL;
            char buf[256] = "";
            sprintf(buf, "C++ ReflectiveDLL\n");
            WriteFile(hPipe, buf, sizeof(buf), &wlen, 0);   //向服务器发送内容

            /* print some output to the operator */
            if (lpReserved != NULL) {
                szargs = (PCHAR)lpReserved;
                wszargs = StringToWString(szargs);
                argv = CommandLineToArgvW(wszargs.data(), &argc);
            }
            else {
                sprintf(buf, "Hello from test.dll. There is no parameter\n");
                WriteFile(hPipe, buf, sizeof(buf), &wlen, 0);   //向服务器发送内容
            }
            if (argv == NULL) {
                sprintf(buf, "[+] Error Arguments ! \n");
                WriteFile(hPipe, buf, sizeof(buf), &wlen, 0);   //向服务器发送内容
                break;
            }
            sprintf(buf, "[+] Args Count : %d \n", argc);
            WriteFile(hPipe, buf, sizeof(buf), &wlen, 0);   //向服务器发送内容
            for (size_t i = 0; i < argc; i++)
            {
                sprintf(buf, "[%d] %s \n", i, argv[i]);
                WriteFile(hPipe, buf, sizeof(buf), &wlen, 0);   //向服务器发送内容
            }
                CloseHandle(hPipe);//关闭管道

                /* flush STDOUT */
                fflush(stdout);

                /* we're done, so let's exit */
                ExitProcess(0);

这样就可以实现读取注入目标进程的输出了。

示例代码

https://github.com/uknowsec/ReflectiveDLLInjection-Notes

结语

WBGlIl师傅的帮助下,学习了ReflectiveDLL。本意是想用于Cobalt Strike插件的开发,但是Golang编译后的文件过大,导致Cobalt Strike并不能进行加载反射。有请教过别的师傅如何解决这个问题,方案好像都必须得落地待反射的Golang DLL,效果并不是太好。同时在最近新发布的Cobalt Strike4.1中有了一个新功能Beacon Object File (BOF),他可以解决文件过大的问题。

在官方文档help-beacon-object-files有如下的一段话:

BOFs are also very small. A UAC bypass privilege escalation Reflective DLL implementation may weigh in at 100KB+. The same exploit, built as a BOF, is <3KB. This can make a big difference when using bandwidth constrained channels, such as DNS.

所以现在就差一个Cobalt Strike4.1啦~

References

[1] bdllspawn: https://cobaltstrike.com/aggressor-script/functions.html#bdllspawn

[2] ReflectiveDLLInjection: https://github.com/stephenfewer/ReflectiveDLLInjection

[3] 倾旋: https://payloads.online/archivers/2020-03-02/1

[4] WBGlIl: https://wbglil.github.io/

[5] go-ReflectiveDLL: https://github.com/WBGlIl/go-ReflectiveDLL

[6] Weaponizing your favorite Go program for Cobalt Strike: https://ethicalchaos.dev/2020/01/26/weaponizing-your-favorite-go-program-for-cobalt-strike/

[7] help-beacon-object-files: https://www.cobaltstrike.com/help-beacon-object-files

点击收藏 | 0 关注 | 2
  • 动动手指,沙发就是你的了!
登录 后跟帖