分析一款远控木马的通讯机制

 

前言

在github上闲逛,发现国人写的一款远控木马。于是决定利用源代码分析此款木马客户端与服务端之间的通讯机制。

在这里感谢远控的作者,给我这次学习的机会。

 

一、运行效果

把源码下载,搭建好虚拟机后,编译并运行,效果如图:

配置客户端和服务端,如图:

客户端可以正常上线。

我们首先分析下客户端:

 

二、客户端数据包发送

我们从客户端程序入口main()函数开始


main()函数前半部分是实现客户端的隐藏性,运行前将自己拷贝到一个较隐蔽的目录,此处我们略过。

main()后半部分的StartConnect()函数,是客户端进行启动反弹连接的函数。在分析这个函数前我们看一个StartHearbeat()心跳函数。这里为了提高程序的响应,StartHearbeat()心跳函数单独用一个线程执行。

我们进入StartHearbeat()心跳函数

下面这两端代码不断地发送一个心跳包,具体如何发送我们后面会详细分析。

byte[] packet = CodecFactory.Instance.EncodeOject(ePacketType.PACKET_HEART_BEAR, null);

oServer.Send(packet);

当发生异常时,下面两段代码启动了客户端的重连机制。

Console.WriteLine(“心跳发送异常,” + ex.Message);

StartConnect();

好了,StartHearbeat()心跳函数我们就分析到这里。

StartHearbeat()心跳函数的作用就是不断发送心跳包,当连接错误时,调用StartConnect()函数启动客户端的重连机制。

接下来我们开始分析启动连接的StartConnect()函数。

进入StartConnect()函数,

下面两段代码是启动连接,这里可以看出使用了socket套接字,用了TCP协议,主动向服务端发出连接申请。

oServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

oServer.Connect(clientParameters.GetIPEndPoint());

连接成功后,StartRecvData()函数就开始接收数据包

我们进入StartRecvData()函数,在接收数据包前,客户端首先向外发送了一个数据包。根据变量名我们可以猜出是发送主机名、程序执行目录、以及一个OnlineAvatar(暂时不知道是什么)。我们姑且管他们叫”主机信息数据”。下面我们看它如何发送的这个“主机信息数据”包。

session.Send(ePacketType.PACKET_GET_HOST_NAME_RESPONSE, resp);

我们进入这个send()函数

发现里面又调用了 this.SocketObj.Send(CodecFactory.Instance.EncodeOject(packetType, obj));

我们继续进入SocketObj.Send()函数,发现无法进入了,原来这里已经是调用了socket套接字的send()函数。

我们看看msdn的文档

我们通过文档知道,socket套接字的send()函数只能发送字节数据。所以真正的数据包构建应该在这里。

CodecFactory.Instance.EncodeOject(packetType, obj)

我们进到CodecFactory.Instance.EncodeOject(packetType, obj)函数看一下。

我们知道参数obj代表了”主机信息数据”。也就是主机名、程序执行目录、以及一个OnlineAvatar。如下:

resp.HostName = Dns.GetHostName();

resp.AppPath = Application.ExecutablePath;

resp.OnlineAvatar = clientParameters.OnlineAvatar;

进入到函数ToJsonBytes(obj)是把”主机信息数据”转换成json格式的字节数据,存入bodyBytes字节数组中。

string json = JsonConvert.SerializeObject(obj);   包数据转成json格式

return System.Text.Encoding.UTF8.GetBytes(json);  //编码后数组

我们在进入Encode(packetType, bodyBytes)函数,我们在这里终于发现了真正封装数据包的函数

result字节数组里面存放的才是真正发送的数据包。

result字节数组数据包都包括了什么

int packetLength = bodyData.Length + 1 + 4;

result.AddRange(BitConverter.GetBytes(packetLength));  // 包长

result.Add((byte)packetType); //包类型

result.AddRange(bodyData); // 包数据主体

这里出现了一个包类型packetType,我管它叫包头,它代表了不同数据。我们看下它的定义


我们到这里大概已经猜出了发送数据包包括了3部分:包长 包头 和包数据

这里包长是       int packetLength = bodyData.Length + 1 + 4;

因为包长被定义成了一个整型,也就是4字节。包头是1字节,最后还要加上包数据的长度。


我们根据上面画出客户端发送数据包的构成图:

包长:4字节(本身)+1字节(包头)

包头:PACKET_GET_HOST_NAME_RESPONSE

包数据:主机信息数据

 

三、服务端数据包接送

我们的服务端程序入口首先选择自动按钮,这是因为我们通过点击这个按钮,实现了与客户端的连接

自动上线 按钮(toolstripbutton4)单击事件对应的函数 如下:

服务端首先获取了自己的IP地址和从配置文件中获取了监听端口。

List<string> ips = RSCApplication.GetLocalIPV4s();

int iServerPort = Settings.CurrentSettings.ServerPort;

最后通过Start()函数启动了监听

RSCApplication.oRemoteControlServer.Start(ips, iServerPort);

我们进入Start()函数

发现了socket套接字启动监听的过程,bing()、listen()和accept()

接着进入StartServerAccept(oServer);

client = server.Accept(); 等待客户端上线,每个客户端上线后,建立一个新的连接,都会单独开一个线程

创建会话对象SocketSession session = new SocketSession(client.RemoteEndPoint, client); 也就是新的连接

连接建立后,开始接收数据StartClientRecv(session)


我们进入StartClientRecv(session)

byte[] buffer = new byte[1024];  应用程序的缓存定义为1kb

recvSize = session.SocketObj.Receive(buffer);   调用socket的receiver()函数将接收的数据包全部复制到data字节数组中。

接下来就应该是对数据包进行处理,因为我们前面已经知道数据包并不仅仅包含包数据,还包括包长和包头。

我们进入数据包处理函数DoRecvBytes(),在这个函数中,发现数据包被传给了DecodeObject()函数

我们接着进入DecodeObject()函数。真正的处理数据包的函数马上就要来了,发现就是Decode()函数。

我们进入Decode()函数,具体的拆解数据包由如下三段代码完成。

int packetLength = BitConverter.ToInt32(packetData, 0); //从0开始,获取4字节的包长度

packetType = (ePacketType)packetData[4];  //第5个字节表示的是1字节的包头

bodyData = new byte[packetLength – 4 – 1]; //获取包数据

 

四、验证

我们在FromJsonBytes()函数中将接收的包数据打印出来,发现果然是json格式的”主机信息数据”

我们把接收的包头打印出来,发现启动连接后,客户端首先会发过来一个”主机信息数据”包  ,接下来客户端不断地发送”心跳包”。

 

五、继续分析

接下来,服务端还需要把接收到的”主机信息数据”,显示出来,那么显示在哪里呢?我们继续分析。

我们发现数据包被处理后,被作为参数输入到PacketReceived(this, args); 那么这个函数是什么呢?


PacketReceived(this, args);是一个事件

当有数据包被处理完后,会触发PacketReceived(this, args)事件,事件触发后,调用RemoteControlServer_PacketReceived(object sender, PacketReceivedEventArgs e){}

RemoteControlServer_PacketReceived()函数根据数据包中的包头,对数据进行不同的处理和使用。

比如对”主机信息数据”包处理,它的包头是PACKET_GET_HOST_NAME_RESPONSE,所以”主机信息数据”中的主机名数据就会显示在toolStripTextBox2框内

下图红线框就是toolStripTextBox2控件。

private System.Windows.Forms.ToolStripTextBox toolStripTextBox2;

六、结论

1)客户端和服务端的通讯数据包构成有一定的格式

2)数据包格式设计如下:

组包:包长(本身的4字节+包头1字节+包数据长度)、包头、报数据

拆包:首先获取4字节的包长 ,然后获取1字节的包头 ,最后获取(包长-4-1)的包数据长度

3)数据包通讯流程大概如下:

 

七 、个人看法

1 ,没发现有考虑不同cpu大小端存储的问题,因为我测试的两台机器都用的intelCPU,所以数据才能正常接收。

2、数据包明文在网络中的传输

3、 使用自定义端口,穿透力有限 。

所以此木马如果想用在实战中,还需做进一步处理。

(完)