![「深度解析」有关某木马编程解析插图 「深度解析」有关某木马编程解析插图](https://blog.eswlnk.com/wp-content/uploads/wpcy/96637ce85c8b2b6ebd4f626bcf5c9573.jpg)
去年年末和社团好友聊天的时候,突然萌生了写一个属于自己的木马的想法。然后有幸遇到了一位超级厉害的大师傅,他给我介绍了很多有关木马的知识,受益匪浅。他建议我先写远程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的封包,使用密钥加密后,发给被控端。
被控端收到上述封包后,向主控端发送上线包,上线包包含了一些基本的环境信息。
主控端收到上线包后,向被控端发送上线包的响应包,双方正式建立通信。
📮评论