QT框架软件及其中tcp/udp网络逆向分析要点记录

一. 概述

前一段时间有逆向分析一个windows系统上QT5的软件,要获取其中显示界面中对应数据生成方式。在分析过程中额外去了解QT框架的相关知识是必不可少的,最主要的一点是信号和槽函数怎么对应起来,以及QT中自封装的数据结构怎么处理。框架分析的难点是除了分析基本功能外,还得分析框架自身的API和框架常见的特征,只有这样才能更好地定位功能代码,但只要分析一次,后面如果是碰到相同的框架,则能很快上手。下面对QT框架分析心得做个简单记录。

二. 信号和槽函数的定位

信号与槽函数 对应 Activate 和 connect ,他们有一个参数是一样的,可以通过这个对应上,从而找到信号对应槽函数。正常开发时,信号和槽函数的注册不是Activate 和 connect,但编译生成的可执行文件导入到ida中会看到这两个函数。可以通过 frida hook这两个函数,打印参数和调用堆栈,来确定信号和槽函数对应关系及在代码中位置。

三. QT 中常用数据类型处理

QT 中常用数据类型有QString,QVector等,想以字符串或基本数据类型打印出来,就需要了解这些数据类型的结构,因为有时返回内容直接是这些类型的结构体,而不是指针,所以在frida hook中也要做额外处理,类似std::string类型在frida中打印。不过我也没有分析这些的数据类型结构,我是自己用QT直接编译了一个动态库(版本最好和软件的QT框架库对应,这里如果是可执行文件也可以),其中加入了QString,QVector等转基础数据类型的函数,然后将这个动态库用frida加载,然后找到对应的函数地址,并定义成frida函数,在frida中调用这些函数来转化基础数据类型,如下:

var func_QString2charptr = null
var func_QStringLength = null
var func_QVector2doublePtrr = null
var func_QVectorLength = null
var func_QVariant2QString = null
var func_QVariantLength = null
var func_QVariant2charptr = null
var func_QVariant2charptr2 = null
function use_self_dll(){
  if (func_QString2charptr == null){
    var module = Module.load("D:\\UseForFrida.dll"); // 这里替换成自实现的dll,函数在dll中实现
    var addr_QString2charptr = module.getExportByName("_ZN11UseForFrida15QString2charptrER7QStringPcj");
    var addr_QStringLength = module.getExportByName("_ZN11UseForFrida13QStringLengthER7QString");
    var addr_QVector2doublePtrr = module.getExportByName("_ZN11UseForFrida17QVector2doublePtrER7QVectorIdEPdi");
    var addr_QVectorLength = module.getExportByName("_ZN11UseForFrida13QVectorLengthER7QVectorIdE");
    var addr_QVariant2QString = module.getExportByName("_ZN11UseForFrida16QVariant2QStringER8QVariant");
    var addr_QVariantLength = module.getExportByName("_ZN11UseForFrida14QVariantLengthER8QVariant");
    var addr_QVariant2charptr = module.getExportByName("_ZN11UseForFrida16QVariant2charptrER8QVariantPcj");
    var addr_QVariant2charptr2 = module.getExportByName("_ZN11UseForFrida16QVariant2charptrER8QVariant");
    func_QString2charptr = new NativeFunction(addr_QString2charptr, 'void', ['pointer','pointer','int']);
    func_QStringLength = new NativeFunction(addr_QStringLength, 'int', ['pointer']);
    func_QVector2doublePtrr = new NativeFunction(addr_QVector2doublePtrr, 'void', ['pointer','pointer','int']);
    func_QVectorLength = new NativeFunction(addr_QVectorLength, 'int', ['pointer']);
    func_QVariant2QString = new NativeFunction(addr_QVariant2QString, 'pointer', ['pointer']);
    func_QVariantLength = new NativeFunction(addr_QVariantLength, 'int', ['pointer']);
    func_QVariant2charptr = new NativeFunction(addr_QVariant2charptr, 'void', ['pointer','pointer','int']);
    func_QVariant2charptr2 = new NativeFunction(addr_QVariant2charptr2, 'pointer', ['pointer']); //这里函数便可以拿来使用
  }
  console.log(func_QString2charptr, func_QStringLength, func_QVector2doublePtrr, func_QVectorLength, func_QVariant2charptr, func_QVariantLength, func_QVariant2charptr2);
}

四. 额外记录下软件中网络方面分析

通过wireshark对那个软件进行抓包,发现也没有https的包,大多是tcp和udp的包。为了定位这些发包和接受包的位置,只需要对系统库中网络接发包相关api进行hook就能定位到对应代码位置。要知道是那个系统库中哪些api处理了这些发包和接收包,可以在网上搜。或者用一种更准确的方法,自己在对应的操作系统(我这里是windows)上用socket写简单的tcp和udp通信的demo,然后用调试器(gdb,windows上更好的是x64dbg)调试自己写的demo,跟到系统库,看是哪些库及那些api处理了底层socket的发送和接收数据。

windows上是 ws2_32.dll 的 closesocket,connect,getpeername,getsockname,htons,inet_ntoa,recv,recvfrom,send,sendto,WSAConnect,WSAConnectByList,WSAConnectByNameA,WSAConnectByNameW,WSARecv,WSARecvDisconnect,WSARecvFrom,WSASend,WSASendDisconnect,WSASendMsg,WSASendTo 等,可以使用frida hook对应的api,打印socket ip和端口,及recv、send、connect等的代码位置。

function hook_win_socketabout(){
  // var modules = Process.enumerateModules();
  // for (var i = 0; i < modules.length; i++){
  //   console.log(modules[i].name, modules[i].base);
  // }
  
  var module = Process.getModuleByName("ws2_32.dll");
  
  //analysis ip port
  // htons   
  // inet_ntoa
  // getsockname
  // getpeername
  var addr_htons = module.getExportByName("htons");
  var addr_inet_ntoa = module.getExportByName("inet_ntoa");
  var addr_getsockname = module.getExportByName("getsockname");
  var addr_getpeername = module.getExportByName("getpeername");
  // console.log("addr_htons", addr_htons, "addr_inet_ntoa", addr_inet_ntoa, 
  // "addr_getsockname", addr_getsockname, "addr_getpeername", addr_getpeername);

  var getpeername = new NativeFunction(addr_getpeername, "int", ["int", "pointer", "pointer"]);
  var getsockname = new NativeFunction(addr_getsockname, "int", ["int", "pointer", "pointer"]);
  //  var symbols = module.enumerateExports();
  var addr_connect = module.getExportByName("connect");
  var addr_recv = module.getExportByName("recv");
  var addr_send = module.getExportByName("send");
  var addr_closesocket = module.getExportByName("closesocket");
  var htons = new NativeFunction(addr_htons, "uint16", ["uint16"]);
  var inet_ntoa = new NativeFunction(addr_inet_ntoa, "pointer", ["uint32"]);

    Interceptor.attach(addr_connect, {
          onEnter:function(argvs){
              //console.log("Come", "addr_connect", addr_connect);
              //connect(clntSock, (SOCKADDR*)& sockAddr, sizeof(SOCKADDR));
              var sockAddr = ptr(argvs[1]);
              var type = sockAddr.readS16();
              var port_bf_turn = sockAddr.add(2).readS16();
              var ip_bf_turn = sockAddr.add(4).readS32();
              var port_af_turn = htons(port_bf_turn);
              var ip_af_turn = inet_ntoa(ip_bf_turn);
              console.log("connect server ---> ", "type:", type, "server ip:", ip_af_turn.readCString(), "port:", port_af_turn);
              console.log('socket connect called from:\n' +
        Thread.backtrace(this.context, Backtracer.FUZZY).map(DebugSymbol.fromAddress).join('\n') +'\n');
            },onLeave:function(retvalue){
            //retvalue.replace()
          }
      });

    //follow 3   get  server client  ip  port
    // getsockname(clntSock, (struct sockaddr *)&connectedAddr, &connectedAddrLen);//获取connfd表示的连接上的本地地址
    // printf("connected server address = %s:%d\n", inet_ntoa(connectedAddr.sin_addr), htons(connectedAddr.sin_port)); //本地ip和端口
    // getpeername(clntSock, (struct sockaddr *)&peerAddr, &peerLen); //获取connfd表示的连接上的对端地址
    // printf("connected peer address = %s:%d\n", inet_ntoa(peerAddr.sin_addr), htons(peerAddr.sin_port)); //远程ip和端口
    Interceptor.attach(addr_recv, {
        onEnter:function(argvs){
            //console.log("Come", "addr_recv", addr_recv);
            var clntSock = argvs[0].toInt32();
            var localSockAddr = Socket.localAddress(clntSock);
            var remoteSockAddr = Socket.peerAddress(clntSock);
            if (localSockAddr != null && remoteSockAddr != null){
              console.log("socket recv ---> ", "localSockAddr", localSockAddr.ip, localSockAddr.port, "remoteSockAddr", remoteSockAddr.ip, remoteSockAddr.port);
            }
            console.log('socket recv called from:\n' +
        Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') +'\n');
        },onLeave:function(retvalue){
          //retvalue.replace()
          console.log("recv ret len", retvalue);
        }
    });

    Interceptor.attach(addr_send, {
      onEnter:function(argvs){
          //console.log("Come", "addr_send", addr_send);
          var clntSock = argvs[0].toInt32();
          var localSockAddr = Socket.localAddress(clntSock);
          var remoteSockAddr = Socket.peerAddress(clntSock);
          if (localSockAddr != null && remoteSockAddr != null){
            console.log("socket send ---> ", "localSockAddr", localSockAddr.ip, localSockAddr.port, "remoteSockAddr", remoteSockAddr.ip, remoteSockAddr.port);
          }
          console.log('socket send called from:\n' +
        Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') +'\n');
      },onLeave:function(retvalue){
        //retvalue.replace()
      }
  });

    Interceptor.attach(addr_closesocket, {
      onEnter:function(argvs){
          //console.log("Come", "addr_closesocket", addr_closesocket);
          var clntSock = argvs[0].toInt32();
          var localSockAddr = Socket.localAddress(clntSock);
          var remoteSockAddr = Socket.peerAddress(clntSock);
          if (localSockAddr != null && remoteSockAddr != null){
            console.log("closesocket ---> ", "localSockAddr", localSockAddr.ip, localSockAddr.port, "remoteSockAddr", remoteSockAddr.ip, remoteSockAddr.port);
          }
          console.log('closesocket called from:\n' +
          Thread.backtrace(this.context, Backtracer.FUZZY).map(DebugSymbol.fromAddress).join('\n') +'\n');
      },onLeave:function(retvalue){
        //retvalue.replace()
      }
  });

  var WSAConnect = module.getExportByName("WSAConnect");
  Interceptor.attach(WSAConnect, {
      onEnter:function(argvs){
          // console.log("Come", "WSAConnect", WSAConnect);

        var sockAddr = ptr(argvs[1]);
        var type = sockAddr.readS16();
        var port_bf_turn = sockAddr.add(2).readS16();
        var ip_bf_turn = sockAddr.add(4).readS32();
        var port_af_turn = htons(port_bf_turn);
        var ip_af_turn = inet_ntoa(ip_bf_turn);
        console.log("WSAConnect server ---> ", "type:", type, "server ip:", ip_af_turn.readCString(), "port:", port_af_turn);
        console.log('socket WSAConnect called from:\n' +
  Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') +'\n');
      },onLeave:function(retvalue){}
  });

  var WSARecv = module.getExportByName("WSARecv");
  Interceptor.attach(WSARecv, {
      onEnter:function(argvs){
          // console.log("Come", "WSARecv", WSARecv);
          var clntSock = argvs[0].toInt32();
          var localSockAddr = Socket.localAddress(clntSock);
          var remoteSockAddr = Socket.peerAddress(clntSock);
          if (localSockAddr != null && remoteSockAddr != null){
            console.log("WSARecv ---> ", "localSockAddr", localSockAddr.ip, localSockAddr.port, "remoteSockAddr", remoteSockAddr.ip, remoteSockAddr.port);
          }
          console.log('WSARecv called from:\n' +
          Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') +'\n');
      },onLeave:function(retvalue){}
  });

  var WSARecvFrom = module.getExportByName("WSARecvFrom");
  Interceptor.attach(WSARecvFrom, {
      onEnter:function(argvs){
          // console.log("Come", "WSARecvFrom", WSARecvFrom);
          var clntSock = argvs[0].toInt32();
          var localSockAddr = Socket.localAddress(clntSock);
          var remoteSockAddr = Socket.peerAddress(clntSock);
          if (localSockAddr != null && remoteSockAddr != null){
            console.log("WSARecvFrom ---> ", "localSockAddr", localSockAddr.ip, localSockAddr.port, "remoteSockAddr", remoteSockAddr.ip, remoteSoc33446kAddr.port);
          }
          console.log('WSARecvFrom called from:\n' +
          Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') +'\n');

      },onLeave:function(retvalue){}
  });

  var WSASend = module.getExportByName("WSASend");
  Interceptor.attach(WSASend, {
      onEnter:function(argvs){
          // console.log("Come", "WSASend", WSASend);
          var clntSock = argvs[0].toInt32();
          var localSockAddr = Socket.localAddress(clntSock);
          var remoteSockAddr = Socket.peerAddress(clntSock);
          if (localSockAddr != null && remoteSockAddr != null){
            console.log("WSASend ---> ", "localSockAddr", localSockAddr.ip, localSockAddr.port, "remoteSockAddr", remoteSockAddr.ip, remoteSockAddr.port);
          }
          console.log('WSASend called from:\n' +
          Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') +'\n');
      },onLeave:function(retvalue){}
  });

}

当时分析时,有一个端口的数据通过wireshark抓到后,在程序中一直hook不到,后来发现是在子进程中实现的tcp通信,然后通过内存映射和主进程进行数据共享。

五. 小结

记地有点简单,因为只有之前电脑环境上有那个分析的软件及安装了QT框架,所以这里也没有贴出QT相关代码,只记录了一些思路。

  • 19
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Qt是一个跨平台的应用程序开发框架,可以用来实现各种应用程序的开发,包括图像处理和视频传输等。Qt提供了简单易用的API,使得开发者可以轻松编写网络传输代码。 图片及视频TCP/UDP网络传输使用QtQtNetwork模块来实现。通过创建QTcpServer或QUdpSocket对象来监听传入的连接或数据报,使用QTcpSocket或QUdpSocket对象来建立连接或发送数据。可以使用Qt的QImage和QPixmap类来处理图像,使用QVideoFrame和QVideoSurface类来处理视频。在使用网络传输时,需要将图像和视频转换成字节流,然后在网络传输。 为了保证网络传输的可靠性,TCP协议应该被选择。TCP协议提供了重传机制和流量控制,因此可以保证传输的准确性和稳定性。在需要实时传输的情况下,UDP协议应该被选择。UDP协议不提供重传机制和流量控制,但是传输速度更快,因此可以保证传输时延较低。 在Qt,可以使用QDataStream类来将数据流转换成字节流,然后发送到网络。接收方可以使用QDataStream类来接收网络的字节流,并将其转换回原始数据流。接收方可以使用QImage、QPixmap、QVideoFrame或QVideoSurface类来处理接收到的图像和视频。 总之,Qt是一个功能强大的跨平台框架,可以用来实现图片和视频的TCP/UDP网络传输。Qt提供了易用的API和丰富的类库,使得开发者可以轻松实现网络传输的代码。在实际应用,需要根据传输的要求和需求选择合适的TCPUDP协议。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值