前言
在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、 使用自定义端口,穿透力有限 。
所以此木马如果想用在实战中,还需做进一步处理。