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,说明没有父类了,把这些信号数量累积起来就可以得到所有基类的信号总数了
由上图可知,元数据对象的地址为0x0000000140021100,在内存窗口打开
由上图可知,父类的元数据对象的地址为0x00007FFF7C34EE08,在内存窗口中打开
由上图可知,由于data的偏移为0x10,故data的地址为0x00007FFF7C34EC20,在内存窗口中打开
由于 signalCount偏移为0x34 ,所以这个父类的signalCount为3
回到0x00007FFF7C34EE08
找到下一个父类的元数据对象地址 0x00007FFF7C4DD130
按照相同的办法找到signalCount 这里为4
继续找下一个父类的元数据对象地址 0x00007FFF7BDFB700
发现SuperData superdata;的值为0,说明没有父类了
这里的signalCount为3
所有父类的信号数量为3+4+3 = 10
获取本地信号索引(local_signal_index)
由上图可知,local_signal_index值为0
signal_index = local_signal_index + 所有父类的信号数量 = 10
获取槽对象 分发函数 槽对象函数索引值
上面的表展示了如何找到 槽对象 分发函数 槽对象函数索引值
下面我将通过例子讲解
发送对象为函数activate的第一个参数,地址为0x000000000014FE20,首先转到偏移0x8的位置获取QObjectPrivate sp
在sp中转到偏移为0x40中获取pSpConnections
在spConnections中转到0x8 pSignalVector
SignalVector是一个向量,在0x8处 count表示向量元素的数量,从0x20开始为向量的第一个元素,元素大小为16个字节
我们可以用前面计算到的signal_index,获取到connections,SignalVector[10].first,值为0x000000000058EC80
转到connections
其中 0x28 pReceiver 是槽对象指针,0x38 callFunction 分发函数指针 0x52 method_relative 槽函数索引
由上图可知 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;
}
}
}