「深度解析」有关某木马编程解析插图

去年年末和社团好友聊天的时候,突然萌生了写一个属于自己的木马的想法。然后有幸遇到了一位超级厉害的大师傅,他给我介绍了很多有关木马的知识,受益匪浅。他建议我先写远程SHELL和文件上传这两个最基础的功能,然后后续的功能可以作为组件推送给被控端。他还建议我上驱动,因为r3现在能做的事情不是很多。等等。

寒假过了几天才有时间开始翻资料动手写,满打满算写了20天左右。本来还想继续完善的,但是开学了要准备考研了,索性直接把目前的版本开源吧,写这篇文章主要是记录一下我目前的进展。因为时间问题,写的还简陋,目前只实现了远程SHELL和文件上传下载,而且未经大量测试,程序的健壮性还不行,劳请师傅们多多包涵。

论坛里的大佬太多了,本文仅供抛砖引玉。我也是第一次写木马,没啥经验,而且水平有限,如有谬误,请您批评指正O(∩_∩)O

代码开源在:iyzyi/Mua-Remote-Control-Trojan: MUA远控木马 (github.com)

虽然代码很简陋,也就供师傅们看一乐,但是还是得加一句:请勿用于非法用途。

通信模型

首先,一个木马最基础的自然就是通信了。通信模型的好坏直接决定了被检测出恶意流量的几率。

gh0st的通信

举个例子,gh0st的封包的开头5个字节都是Gh0st,这就可以作为流量检测的匹配规则。

相关源码:

// Packet Flag;
BYTE bPacketFlag[] = {'G', 'h', '0', 's', 't'};
memcpy(m_bPacketFlag, bPacketFlag, sizeof(bPacketFlag));

// 中间省略无关代码

LONG nBufLen = destLen + HDR_SIZE;
// 5 bytes packet flag
pContext->m_WriteBuffer.Write(m_bPacketFlag, sizeof(m_bPacketFlag));
// 4 byte header [整个数据包的大小]
pContext->m_WriteBuffer.Write((PBYTE) &nBufLen, sizeof(nBufLen));
// 4 byte header [解压缩整个包的大小]
pContext->m_WriteBuffer.Write((PBYTE) &nSize, sizeof(nSize));
// Write Data
pContext->m_WriteBuffer.Write(pDest, destLen);
delete [] pDest;

当然,用这个作为流量检测也不是很方便,首先EDR也得跟着处理粘包才能将封包从TCP数据流中分离出来(吧),其次手里有源码,可以随便改这个封包头。

由于我没做过EDR的工作,这里就不深入展开了。免得贻笑大方。但是我个人还是觉得,这种明显的标志最好不要出现在传输的流量中,增加了暴露的可能。

gh0st的作者为啥要安排这5个字节?翻阅源码可以知道答案:(我添加了一些方便理解的注释)

// 缓冲区数据长度是否大于封包头部长度(这是处理粘包的标准操作)
while (pContext->m_CompressionBuffer.GetBufferLen() > HDR_SIZE)
{
    BYTE bPacketFlag[FLAG_SIZE];
    CopyMemory(bPacketFlag, pContext->m_CompressionBuffer.GetBuffer(), sizeof(bPacketFlag));
    
    // 封包是否合法 通过 封包头部五个字节是否是Gh0st 来判断的
    if (memcmp(m_bPacketFlag, bPacketFlag, sizeof(m_bPacketFlag)) != 0)
        throw "bad buffer";

    int nSize = 0;
    CopyMemory(&nSize, pContext->m_CompressionBuffer.GetBuffer(FLAG_SIZE), sizeof(int));

    // Update Process Variable
    pContext->m_nTransferProgress = pContext->m_CompressionBuffer.GetBufferLen() * 100 / nSize;

    if (nSize && (pContext->m_CompressionBuffer.GetBufferLen()) >= nSize)
    {
        int nUnCompressLength = 0;
        // Read off header
        pContext->m_CompressionBuffer.Read((PBYTE) bPacketFlag, sizeof(bPacketFlag));

        pContext->m_CompressionBuffer.Read((PBYTE) &nSize, sizeof(int));
        pContext->m_CompressionBuffer.Read((PBYTE) &nUnCompressLength, sizeof(int));

        ////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////
        // SO you would process your data here
        // 
        // I'm just going to post message so we can see the data
        int    nCompressLength = nSize - HDR_SIZE;
        PBYTE pData = new BYTE[nCompressLength];
        PBYTE pDeCompressionData = new BYTE[nUnCompressLength];

        if (pData == NULL || pDeCompressionData == NULL)
            throw "bad Allocate";

        pContext->m_CompressionBuffer.Read(pData, nCompressLength);

        //////////////////////////////////////////////////////////////////////////
        unsigned long    destLen = nUnCompressLength;
        int    nRet = uncompress(pDeCompressionData, &destLen, pData, nCompressLength);
        //////////////////////////////////////////////////////////////////////////
        if (nRet == Z_OK)
        {
            pContext->m_DeCompressionBuffer.ClearBuffer();
            pContext->m_DeCompressionBuffer.Write(pDeCompressionData, destLen);
            m_pNotifyProc((LPVOID) m_pFrame, pContext, NC_RECEIVE_COMPLETE);
        }
        else
        {
            throw "bad buffer";
        }

        delete [] pData;
        delete [] pDeCompressionData;
        pContext->m_nMsgIn++;
    }
    else
        break;
}

所以,这5个字节仅仅是用来判断是否是正确的封包的。除此之外没有别的用处。而且作用不大,很难分辨出封包是否是攻击者伪装的,因为攻击者只要使他伪造的封包也以Gh0st开头,就能通过这个检测。唯一的作用可能是处理粘包失败的时候可以及时抛出异常。

依我之见,判断封包是否合法,完全可以靠序列号/检验和来实现。

流量加密

gh0st应该是没有加密的,我没找到相关的代码,应该只是用zlib压缩了下数据。

ForsShare的加密算法就是简单的异或了一下:

void MyMainFunc::EncryptByte(LPVOID pData, DWORD nLen)
{
    BYTE* pTmpData = (BYTE*) pData;
    for(DWORD i = 0; i < nLen; i++)
    {
        pTmpData[i] = pTmpData[i] ^ PS_ENTRY_COMM_KEY;    
    }
}

大灰狼是在gh0st基础上发展的,它使用的是RC4。不过我之前好像在哪里听说过RC4不安全了,好像是在TLS那里。

我选用的加密算法是AES-128-CFB,然后密钥和IV是通过RSA来传递的。

封包结构

首先介绍下封包结构:

我设想的封包结构是这样的:

封包长度(4字节)+ 校验和(4字节)+ 命令号(2字节)+ 其他包头(5字节)+ 包体

加密的话,只加密命令号(2字节)+ 其他包头(5字节)+ 包体。封包长度和校验和是不能加密的。封包长度用于处理粘包;校验和也不加密是因为:如果校验和加密了,则必须先解密整个封包才能解密出校验和,才能校验封包的有效性。这样的话,攻击者发来一个极大的封包,我们先解密,再校验封包的有效性,不管封包是否有效,我们都花了很大的代价解密这个封包,如果攻击者大量发送构造的假封包,我们很容易被拒绝服务。

目前实现的封包结构是这样的:

封包长度(4字节)+ 命令号(2字节)+ 暂未启用(4字节)+ 暂未启用(1字节)

目前包头有效的字段其实只有命令号。

通信流程

然后介绍下通信流程:

主控端和被控端均使用AES-128-CFB进行加密通信。

首先,被控端随机产生AES-128-CFB的密钥和IV,使用主控端的RSA公钥加密后,发送给主控端。

主控端收到后,使用自己的RSA私钥解密出AES-128-CFB的密钥和IV,然后生成命令号为CRYPEO_KEY的封包,使用密钥加密后,发给被控端。

被控端收到上述封包后,向主控端发送上线包,上线包包含了一些基本的环境信息。

主控端收到上线包后,向被控端发送上线包的响应包,双方正式建立通信。