QT初步逆向分析一:由信号发送的地方找到槽

环境

msvc 2019 64位 Release
QT 5.15.1

源码

tsignal.h

#include <QMainWindow>
#include <QObject>
// 必须继承QObject才能使用信号和槽
class TsignalApp :public QMainWindow
{
public:
    TsignalApp();
    void slotFileNew();
    Q_OBJECT
        // 信号声明区
signals:
    // 声明信号 mySignal()
    void mySignal();
    // 声明信号 mySignal(int)
    void mySignal(int x);
    // 声明信号 mySignalParam(int,int)
    void mySignalParam(int x, int y);
    // 槽声明区
public slots:
    // 声明槽函数 mySlot()
    void mySlot();
    // 声明槽函数 mySlot(int)
    void mySlot(int x);
    // 声明槽函数 mySignalParam (int,int)
    void mySlotParam(int x, int y);
};

tsignal.cpp

#include "tsignal.h"
#include <QMessageBox>
TsignalApp::TsignalApp()
{
    // 将信号 mySignal() 与槽 mySlot() 相关联
    connect(this, SIGNAL(mySignal()), SLOT(mySlot()));
    // 将信号 mySignal(int) 与槽 mySlot(int) 相关联
    connect(this, SIGNAL(mySignal(int)), SLOT(mySlot(int)));
    // 将信号 mySignalParam(int,int) 与槽 mySlotParam(int,int) 相关联
    connect(this, SIGNAL(mySignalParam(int, int)), SLOT(mySlotParam(int, int)));
}
// 定义槽函数 mySlot()
void TsignalApp::mySlot()
{
    QMessageBox::about(this, "Tsignal", "This is a signal/slot sample withoutparameter.");
}
// 定义槽函数 mySlot(int)
void TsignalApp::mySlot(int x)
{
    QMessageBox::about(this, "Tsignal", "This is a signal/slot sample with oneparameter.");
}
// 定义槽函数 mySlotParam(int,int)
void TsignalApp::mySlotParam(int x, int y)
{
    char s[256];
    sprintf(s, "x:%d y:%d", x, y);
    QMessageBox::about(this, "Tsignal", s);
}
void TsignalApp::slotFileNew()
{
    // 发射信号 mySignal()
    emit mySignal();
    // 发射信号 mySignal(int)
    emit mySignal(5);
    // 发射信号 mySignalParam(5,100)
    emit mySignalParam(5, 100);
}

main.cpp

#include "tsignal.h"
#include <QApplication>

int main(int argc, char* argv[])
{
    QApplication a(argc, argv);
    TsignalApp w;
    w.slotFileNew();
    return a.exec();
}

材料

用于分析的测试程序
https://wwmf.lanzout.com/iYx5G0haftbi

获取已知信息

我们使用IDA和x64dbg定位到 emit mySignal();信号发送的地方,看看我们已知的信息有哪些

void __fastcall sub_140001470(struct QObject *a1)
{
  QMetaObject::activate(a1, (const struct QMetaObject *)&qword_140006100, 0, 0i64);
}

struct QObject *a1
是发送信号的对象
const struct QMetaObject qword_140006100
元数据对象
int local_signal_index
信号在信号发送类的本地索引
void **argv
传递的参数
后面我们所有的信息都是通过这几个数得到的

获取发送信号在类中的全局索引

信号全局索引(signal_index)是由元数据对象local_signal_index得到的
元数据对象

 struct { // private data
        SuperData superdata;	// 0x0 父类元数据对象的指针   由此可知这是一个链表结构
        const QByteArrayData *stringdata;
        const uint *data; //0x10 类的元数据  里面包含信号数量
        typedef void (*StaticMetacallFunction)(QObject *, QMetaObject::Call, int, void **);
        StaticMetacallFunction static_metacall;
        const SuperData *relatedMetaObjects;
        void *extradata; //reserved for future use
    } d;

const uint *data

struct QMetaObjectPrivate
{
    int revision;
    int className;
    int classInfoCount, classInfoData;
    int methodCount, methodData;
    int propertyCount, propertyData;
    int enumeratorCount, enumeratorData;
    int constructorCount, constructorData;
    int flags;
    int signalCount; //0x34  类的信号数量(不包含基类)
}

获取所有父类的信号数量

我们通过SuperData superdata;获取父类的元数据对象,然后通过data.signalCount获取父类的信号数量,不断重复,直到superdata值为0,说明没有父类了,把这些信号数量累积起来就可以得到所有基类的信号总数了

image
由上图可知,元数据对象的地址为0x0000000140021100,在内存窗口打开
image
由上图可知,父类的元数据对象的地址为0x00007FFF7C34EE08,在内存窗口中打开
image
由上图可知,由于data的偏移为0x10,故data的地址为0x00007FFF7C34EC20,在内存窗口中打开
image
由于 signalCount偏移为0x34 ,所以这个父类的signalCount为3
回到0x00007FFF7C34EE08
image
找到下一个父类的元数据对象地址 0x00007FFF7C4DD130
image
按照相同的办法找到signalCount 这里为4
继续找下一个父类的元数据对象地址 0x00007FFF7BDFB700
image
发现SuperData superdata;的值为0,说明没有父类了
image
这里的signalCount为3

所有父类的信号数量为3+4+3 = 10

获取本地信号索引(local_signal_index)

image
由上图可知,local_signal_index值为0

signal_index = local_signal_index + 所有父类的信号数量 = 10

获取槽对象 分发函数 槽对象函数索引值

image

上面的表展示了如何找到 槽对象 分发函数 槽对象函数索引值
下面我将通过例子讲解
image
发送对象为函数activate的第一个参数,地址为0x000000000014FE20,首先转到偏移0x8的位置获取QObjectPrivate sp
image
在sp中转到偏移为0x40中获取pSpConnections
image
在spConnections中转到0x8 pSignalVector
image
SignalVector是一个向量,在0x8处 count表示向量元素的数量,从0x20开始为向量的第一个元素,元素大小为16个字节
image
我们可以用前面计算到的signal_index,获取到connections,SignalVector[10].first,值为0x000000000058EC80
image
转到connections
其中 0x28 pReceiver 是槽对象指针,0x38 callFunction 分发函数指针 0x52 method_relative 槽函数索引
image
由上图可知 pReceiver = 0x0000000000014FE20,callFunction = 0x00000001400015B0 method_relative = 3
转到分发函数,用IDA查看反编译,可知槽函数为sub_1400011C0

void __fastcall sub_1400015B0(struct QObject *a1, int a2, int a3, __int64 a4)
{
  __int64 v4; // rcx
  _DWORD *v5; // rdx
  __int64 (__fastcall *v6)(); // rax

  if ( a2 )
  {
    if ( a2 == 10 )
    {
      v4 = *(_QWORD *)(a4 + 8);
      v5 = *(_DWORD **)a4;
      v6 = *(__int64 (__fastcall **)())v4;
      if ( *(__int64 (__fastcall **)())v4 != sub_140001470 || v6 && *(_DWORD *)(v4 + 8) )
      {
        if ( (char *)v6 != (char *)sub_140001430 || v6 && *(_DWORD *)(v4 + 8) )
        {
          if ( (char *)v6 == (char *)sub_140001490 && (!v6 || !*(_DWORD *)(v4 + 8)) )
            *v5 = 2;
        }
        else
        {
          *v5 = 1;
        }
      }
      else
      {
        *v5 = 0;
      }
    }
  }
  else
  {
    switch ( a3 )
    {
      case 0:
        QMetaObject::activate(a1, (const struct QMetaObject *)qword_140021100, 0, 0i64);
        break;
      case 1:
        sub_140001430(a1, **(unsigned int **)(a4 + 8));
        break;
      case 2:
        sub_140001490(a1, **(unsigned int **)(a4 + 8), **(unsigned int **)(a4 + 16));
        break;
      case 3:
        sub_1400011C0(a1);
        break;
      case 4:
        sub_140001150(a1, **(unsigned int **)(a4 + 8));
        break;
      case 5:
        sub_140001230(a1, **(unsigned int **)(a4 + 8), **(unsigned int **)(a4 + 16));
        break;
      default:
        return;
    }
  }
}
posted @ 2022-11-30 11:41  乘舟凉  阅读(1603)  评论(1编辑  收藏  举报