QObjectPrivate找到了接下来是ConnectionList。通过逆向addConnectionList函数,可以发现ConnectionList在QObjectPrivat+0x24处
int QMetaObjectPrivate::indexOfSignalRelative(const QMetaObject *baseObject, const QByteArray &name, int argc, const QArgumentType types) { int i = indexOfMethodRelative<MethodSignal>(baseObject, name, argc, types);
#ifndef QT_NO_DEBUG const QMetaObject m = baseObject; if (i >= 0 && m && m->d.superdata) { int conflict = indexOfMethod(m->d.superdata, name, argc, types); if (conflict >= 0) { QMetaMethod conflictMethod = m->d.superdata->method(conflict); qWarning("QMetaObject::indexOfSignal: signal %s from %s redefined in %s", conflictMethod.methodSignature().constData(), objectClassName(m->d.superdata), objectClassName(m)); } }
#endif return i; }
template<int MethodType> static inline int indexOfMethodRelative(const QMetaObject *baseObject, const QByteArray &name, int argc, const QArgumentType types) { for (const QMetaObject m = baseObject; m; m = m->d.superdata) { Q_ASSERT(priv(m->d.data)->revision >= 7); int i = (MethodType == MethodSignal) ? (priv(m->d.data)->signalCount - 1) : (priv(m->d.data)->methodCount - 1); const int end = (MethodType == MethodSlot) ? (priv(m->d.data)->signalCount) : 0;
}
static bool methodMatch(const QMetaObject m, int handle, const QByteArray &name, int argc, const QArgumentType types) { Q_ASSERT(priv(m->d.data)->revision >= 7); if (int(m->d.data[handle + 1]) != argc) return false;
}
int QMetaObjectPrivate::signalOffset(const QMetaObject *m) { Q_ASSERT(m != 0); int offset = 0;
}
enum AllocationOption { CapacityReserved = 0x1,
#if !defined(QT_NO_UNSHARABLE_CONTAINERS) Unsharable = 0x2,
#endif RawData = 0x4, Grow = 0x8, Default = 0};
QArrayData *QArrayData::allocate(size_t objectSize, size_t alignment, size_t capacity, AllocationOptions options) Q_DECL_NOTHROW { // Alignment is a power of two Q_ASSERT(alignment >= Q_ALIGNOF(QArrayData) && !(alignment & (alignment - 1)));
#if !defined(QT_NO_UNSHARABLE_CONTAINERS) if (options & Unsharable) return const_cast<QArrayData *>(&qt_array_unsharable_empty);
#endif return const_cast<QArrayData *>(&qt_array_empty); }
#if !defined(QT_NO_UNSHARABLE_CONTAINERS) header->ref.atomic.store(bool(!(options & Unsharable)));
#else header->ref.atomic.store(1);
#endif header->size = 0; header->alloc = capacity; header->capacityReserved = bool(options & CapacityReserved); header->offset = data - quintptr(header); }
}
查看 QArrayData QArrayData::allocate的分配逻辑可以发现 QArrayData分为两个部分头和数据,数据连续分配,类似于windows的堆结构。 查看QTypedArrayData 的分配逻辑: Q_REQUIRED_RESULT static QTypedArrayData allocate(size_t capacity, AllocationOptions options = Default) { Q_STATIC_ASSERT(sizeof(QTypedArrayData) == sizeof(QArrayData)); return static_cast<QTypedArrayData *>(QArrayData::allocate(sizeof(T), Q_ALIGNOF(AlignmentDummy), capacity, options)); }
void data() { Q_ASSERT(size == 0 || offset < 0 || size_t(offset) >= sizeof(QArrayData)); return reinterpret_cast<char >(this) + offset; }
const void data() const { Q_ASSERT(size == 0 || offset < 0 || size_t(offset) >= sizeof(QArrayData)); return reinterpret_cast<const char >(this) + offset; }
iterator begin(iterator = iterator()) { return data(); } QVector更是只重用了QTypedArrayData的迭代器来制作operator[]。
struct D{ DWORD A; DWORD B; };
int main(int argc, char *argv[]) { D l; l.A=0x123; l.B=0x456; QVector<D> d; d.push_back(l); d.push_back(l); stringstream ss; ss<<hex<<&d; MessageBoxA(nullptr,ss.str().c_str(),"",MB_OK); QApplication a(argc, argv); MainWindow w; w.Print(); return a.exec(); }
struct Connection { QObject sender; QObject receiver; union { StaticMetaCallFunction callFunction; QtPrivate::QSlotObjectBase slotObj; }; // The next pointer for the singly-linked ConnectionList Connection nextConnectionList; //senders linked list Connection next; Connection * prev; QAtomicPointer<const int> argumentTypes; QAtomicInt ref_; ushort method_offset; ushort method_relative; uint signalindex : 27; // In signal range (see QObjectPrivate::signalIndex()) ushort connectionType : 3; // 0 == auto, 1 == direct, 2 == queued, 4 == blocking ushort isSlotObject : 1; ushort ownArgumentTypes : 1; Connection() : nextConnectionList(0), ref (2), ownArgumentTypes(true) { //ref_ is 2 for the use in the internal lists, and for the use in QMetaObject::Connection } ~Connection(); int method() const { Q_ASSERT(!isSlotObject); return method_offset + methodrelative; } void ref() { ref .ref(); } void deref() { if (!ref_.deref()) { Q_ASSERT(!receiver); delete this; } } }; 关于Qt5的脚本暂时没有写好,我也没有想到一天就找到了索引槽函数的方法,接下来有机会会补上x32dbg qt5_resolver的脚本源码。
关于Qt的槽函数与信号的组织逻辑可以参考站内文章“QT内部机制及逆向”和https:
/
/
blog.
51cto
.com
/
9291927
/
2070398
。珠玉在前,我做的只是锦上添花
QT中最重要的结构莫过于QMetaObject和QObjectPrivate。前者保存了一个QObject的所有函数名,参数名,参数类型以及属性。后者是查找槽函数的重要途径。
一个简单的类作为测试
class
MainWindow : public QObject
{
Q_OBJECT
private:
int
value;
public:
explicit MainWindow();
~MainWindow();
signals:
int
Value();
public slots:
void ChangeValue(
int
value){
this
-
>value
=
value;
}
};
Qt官方的源码中有一个重要的函数metaObject为我们提供了查找QMetaObject的途径。
virtual const QMetaObject
*
metaObject() const;
metaObject是一个虚函数,存在于QObject虚函数表的第一项,通过他,就可以查找到QMetaObject的地址。
QMetaObject在内存中的结构为
struct {
/
/
private data
const QMetaObject
*
superdata;
const char
*
stringdata;
const uint
*
data;
const QMetaObject
*
*
extradata;
} ;
const QMetaObject MainWindow::staticMetaObject
=
{
{ &QObject::staticMetaObject, qt_meta_stringdata_MainWindow.data,
qt_meta_data_MainWindow, qt_static_metacall, nullptr, nullptr}
};
superdata是指向父类的QMetaObject的指针,stringdata为字面元数据,data保存着关于函数及参数个数,属性等信息。extradata被填充为qt_static_metacall,qt的所有信号通过activate路由到这里,并找到对应的槽函数执行。
qt5相对qt4有一点点小改动具体在QMetaObject的QMetaObject.stringdata中。由原来static const char qt_meta_stringdata_MainWindow []
=
{
"MainWindow\0SetValue\0\ChangeValue"
};
变为了
struct qt_meta_stringdata_MainWindow_t {
QByteArrayData data[
4
];
char stringdata0[
33
];
};
static const qt_meta_stringdata_MainWindow_t static const qt_meta_stringdata_MainWindow_t qt_meta_stringdata_MainWindow
=
{
{
QT_MOC_LITERAL(
0
,
0
,
10
),
/
/
"MainWindow"
QT_MOC_LITERAL(
1
,
11
,
5
),
/
/
"Value"
QT_MOC_LITERAL(
2
,
17
,
0
),
/
/
""
QT_MOC_LITERAL(
3
,
18
,
11
),
/
/
"ChangeValue"
QT_MOC_LITERAL(
4
,
30
,
5
)
/
/
"value"
},
"MainWindow\0Value\0\0ChangeValue\0value"
};
不讨论源码,直接看在内存中的存储方式
关于Qt的槽函数与信号的组织逻辑可以参考站内文章“QT内部机制及逆向”和https:
/
/
blog.
51cto
.com
/
9291927
/
2070398
。珠玉在前,我做的只是锦上添花
QT中最重要的结构莫过于QMetaObject和QObjectPrivate。前者保存了一个QObject的所有函数名,参数名,参数类型以及属性。后者是查找槽函数的重要途径。
一个简单的类作为测试
class
MainWindow : public QObject
{
Q_OBJECT
private:
int
value;
public:
explicit MainWindow();
~MainWindow();
signals:
int
Value();
public slots:
void ChangeValue(
int
value){
this
-
>value
=
value;
}
};
Qt官方的源码中有一个重要的函数metaObject为我们提供了查找QMetaObject的途径。
virtual const QMetaObject
*
metaObject() const;
metaObject是一个虚函数,存在于QObject虚函数表的第一项,通过他,就可以查找到QMetaObject的地址。
QMetaObject在内存中的结构为
struct {
/
/
private data
const QMetaObject
*
superdata;
const char
*
stringdata;
const uint
*
data;
const QMetaObject
*
*
extradata;
} ;
const QMetaObject MainWindow::staticMetaObject
=
{
{ &QObject::staticMetaObject, qt_meta_stringdata_MainWindow.data,
qt_meta_data_MainWindow, qt_static_metacall, nullptr, nullptr}
};
superdata是指向父类的QMetaObject的指针,stringdata为字面元数据,data保存着关于函数及参数个数,属性等信息。extradata被填充为qt_static_metacall,qt的所有信号通过activate路由到这里,并找到对应的槽函数执行。
qt5相对qt4有一点点小改动具体在QMetaObject的QMetaObject.stringdata中。由原来static const char qt_meta_stringdata_MainWindow []
=
{
"MainWindow\0SetValue\0\ChangeValue"
};
变为了
struct qt_meta_stringdata_MainWindow_t {
QByteArrayData data[
4
];
char stringdata0[
33
];
};
static const qt_meta_stringdata_MainWindow_t static const qt_meta_stringdata_MainWindow_t qt_meta_stringdata_MainWindow
=
{
{
QT_MOC_LITERAL(
0
,
0
,
10
),
/
/
"MainWindow"
QT_MOC_LITERAL(
1
,
11
,
5
),
/
/
"Value"
QT_MOC_LITERAL(
2
,
17
,
0
),
/
/
""
QT_MOC_LITERAL(
3
,
18
,
11
),
/
/
"ChangeValue"
QT_MOC_LITERAL(
4
,
30
,
5
)
/
/
"value"
},
"MainWindow\0Value\0\0ChangeValue\0value"
};
不讨论源码,直接看在内存中的存储方式
接下来发挥我们出色的数学能力,
0x404100
+
0x50
=
0x404150
,刚好是QMetaObject.stringdata.stringdata0第一项的起始地址。那么QMetaObject.stringdata.data结构就可以表示为。
struct QMetaStringDataContent {
dword sep;
/
/
分隔符
0xFFFFFFF
dword
len
;
/
/
字符串的长度
dword fill;
/
/
填充为
0
dword offset;
/
/
这里的偏移是相对当前项起始地址的偏移
};
有了索引函数名的方法,那么如何分清楚槽和信号呢?大家有没有注意到QMetaObject.stringdata中有一个填充为
0
的项。
qt_meta_stringdata_MainWindow
=
{
{
QT_MOC_LITERAL(
0
,
0
,
10
),
/
/
"MainWindow"
QT_MOC_LITERAL(
1
,
11
,
5
),
/
/
"Value"
QT_MOC_LITERAL(
2
,
17
,
0
),
/
/
""
QT_MOC_LITERAL(
3
,
18
,
11
),
/
/
"ChangeValue"
QT_MOC_LITERAL(
4
,
30
,
5
)
/
/
"value"
},
这就是分割信号和槽的关键。分割项的上面为信号,下面为槽,不过也不绝对,更可靠的方法是根据data结构中的signalCount来计算。然而在QMetaObject.stringdata中还保存着函数参数名干扰视线。那么如何分清楚参数名,接下来看data结构。data分为两个部分第一部分为目录也就是QMetaObjectPrivate。
static const uint qt_meta_data_MainWindow[]
=
{
/
/
content:
7
,
/
/
revision
0
,
/
/
classname
0
,
0
,
/
/
classinfo
2
,
14
,
/
/
函数个数,函数偏移
0
,
0
,
/
/
properties
0
,
0
,
/
/
enums
/
sets
0
,
0
,
/
/
constructors
0
,
/
/
flags
1
,
/
/
signalCount
/
/
signals: name, argc, parameters, tag, flags
1
,
0
,
24
,
2
,
0x06
/
*
Public
*
/
,
/
/
slots: name, argc, parameters, tag, flags
3
,
1
,
25
,
2
,
0x0a
/
*
Public
*
/
,
/
/
signals: parameters
QMetaType::
Int
,
/
/
slots: parameters
QMetaType::Void, QMetaType::
Int
,
4
,
0
/
/
eod
};
中间有两个被我标注的数据,其中函数个数为
2
,函数偏移为
14
大家可以数一数,从第一项到signalCount正好
14
项,下面被注释为signal和slot的项,为函数签名,其中包含两个重要信息,name(函数名的索引),argc参数大小.
而name为QMetaObject.stringdata.data的索引,可以看到name并不是连续的中间正好可以放一个argc的大小。并且与QMetaObject.stringdata.data中的存储方式正好相同。
那么可以通过name索引QMetaObject.stringdata.data来分别查找函数名,通过signalCount来界定信号和槽,同过QMetaObject.stringdata.data的第一项是否为
0xFFFFFFFF
来界定QMetaObject.stringdata.data大小,而QMetaObject.stringdata.stringdata0的第一项为类名称。通过在是否存在QMetaObject.data的函数签名,判断函数名和参数名.
到此我们已经查找到了函数名以及参数,接下来就是索引槽函数了。
qt因为松耦合的关系导致非常难以找到槽函数。qt通过QObjectPrivate中的ConnectionList来保存对应的槽函数,可以有多个槽函数,这些槽函数通过链表存储,通过信号索引ConnectionList。如此这个链表就形成了一张极其庞大的网,横向是信号,纵向是连接的槽.
接下来发挥我们出色的数学能力,
0x404100
+
0x50
=
0x404150
,刚好是QMetaObject.stringdata.stringdata0第一项的起始地址。那么QMetaObject.stringdata.data结构就可以表示为。
struct QMetaStringDataContent {
dword sep;
/
/
分隔符
0xFFFFFFF
dword
len
;
/
/
字符串的长度
dword fill;
/
/
填充为
0
dword offset;
/
/
这里的偏移是相对当前项起始地址的偏移
};
有了索引函数名的方法,那么如何分清楚槽和信号呢?大家有没有注意到QMetaObject.stringdata中有一个填充为
0
的项。
qt_meta_stringdata_MainWindow
=
{
{
QT_MOC_LITERAL(
0
,
0
,
10
),
/
/
"MainWindow"
QT_MOC_LITERAL(
1
,
11
,
5
),
/
/
"Value"
QT_MOC_LITERAL(
2
,
17
,
0
),
/
/
""
QT_MOC_LITERAL(
3
,
18
,
11
),
/
/
"ChangeValue"
QT_MOC_LITERAL(
4
,
30
,
5
)
/
/
"value"
},
这就是分割信号和槽的关键。分割项的上面为信号,下面为槽,不过也不绝对,更可靠的方法是根据data结构中的signalCount来计算。然而在QMetaObject.stringdata中还保存着函数参数名干扰视线。那么如何分清楚参数名,接下来看data结构。data分为两个部分第一部分为目录也就是QMetaObjectPrivate。
static const uint qt_meta_data_MainWindow[]
=
{
/
/
content:
7
,
/
/
revision
0
,
/
/
classname
0
,
0
,
/
/
classinfo
2
,
14
,
/
/
函数个数,函数偏移
0
,
0
,
/
/
properties
0
,
0
,
/
/
enums
/
sets
0
,
0
,
/
/
constructors
0
,
/
/
flags
1
,
/
/
signalCount
/
/
signals: name, argc, parameters, tag, flags
1
,
0
,
24
,
2
,
0x06
/
*
Public
*
/
,
/
/
slots: name, argc, parameters, tag, flags
3
,
1
,
25
,
2
,
0x0a
/
*
Public
*
/
,
/
/
signals: parameters
QMetaType::
Int
,
/
/
slots: parameters
QMetaType::Void, QMetaType::
Int
,
4
,
0
/
/
eod
};
中间有两个被我标注的数据,其中函数个数为
2
,函数偏移为
14
大家可以数一数,从第一项到signalCount正好
14
项,下面被注释为signal和slot的项,为函数签名,其中包含两个重要信息,name(函数名的索引),argc参数大小.
而name为QMetaObject.stringdata.data的索引,可以看到name并不是连续的中间正好可以放一个argc的大小。并且与QMetaObject.stringdata.data中的存储方式正好相同。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)