分析一款远控木马的通讯过程

一、运行效果
在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()函数
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、 使用自定义端口,穿透力有限 。
  所以此木马如果想用在实战中,还需做进一步处理。
4 对于超大数据传输,比如数据长度存储长度值超出32位,那么数据包格式会出错,同理,对于数据长度存储远小于32位,会造成传输浪费。
 
 
补充调试部分
 
 
    [0]    39    byte
        [1]    1    byte
        [2]    0    byte
        [3]    0    byte
        [4]    65    byte
        [5]    123    byte
        [6]    34    byte
        [7]    72    byte H  十六进制  48
        [8]    111    byte o    十六进制  6F
        [9]    115    byte  s
        [10]    116    byte
        [11]    78    byte
        [12]    97    byte
        [13]    109    byte
        [14]    101    byte
        [15]    34    byte
        [16]    58    byte
        [17]    34    byte
        [18]    76    byte
        [19]    65    byte
        [20]    80    byte
        [21]    84    byte
        [22]    79    byte
        [23]    80    byte
        [24]    45    byte
        [25]    74    byte
        [26]    71    byte
 
 
 
 
 
 
295包长  
 
 
295为何 变成了  39 1 0 0   
295代表整个包的包长  包数据  是290 包类型 是  1 包长自身  4
 
39 十六进制  27
1   十六进制   1 
127 十进制  295
 
 
 
测试的位置不同  客户端在本机和虚拟机中运行的区别
 
包长  
ce=206  数据包一共211 还有一个包头   
 
 
收到,数据 
 
 
 
 
65在 ePacketType包中是 位置正好是packet_GET_HOST_NAME_RESPONSE
posted @ 2020-03-16 20:32  土八路2020  阅读(1039)  评论(0)    收藏  举报