Qt 内部和逆向工程






4.85/5 (27投票s)
本文的第一部分展示了 Qt 框架的动态内部机制,这些机制实现了信号和槽机制。第二部分侧重于如何使用 IDAPython 脚本从二进制文件中检索 Qt moc 生成的元数据信息,以及如何在反汇编中使用它。
引言
本文的一半内容来自我更长的论文“动态 C++ 提案”。我决定将有关 Qt 内部机制的部分单独作为一个文章,并加上逆向工程部分。由于其性质,这并非我通常撰写的文章类型。事实上,我不到一天就写完了逆向工程部分。所以,这是一篇非常容易的文章。不过,我认为它对于那些需要逆向 Qt 应用程序的人很有用,而且他们肯定不会考虑阅读我关于动态 C++ 的其他论文,因为那听起来不像一篇关于 Qt 的论文,事实上它也不是一篇关于 Qt 的论文:关于 Qt 的段落只是其中的许多段落之一。此外,我没有看到过关于这个主题的严肃文章。
逆向 Qt 应用程序时需要考虑的第一件事是 Qt 为 C++ 语言带来了什么。事件(在 Qt 框架内)只是虚函数,所以这没什么新东西。这不是 C++ 逆向工程指南。Qt 的新之处在于信号和槽,它们依赖于 Qt 框架的动态性。
因此,我将要展示的第一件事是这种动态性是如何工作的。第二部分侧重于逆向工程,届时,我将展示如何在反汇编一个“Q_OBJECT
”类时获得所有需要的元数据。
内部
我所见过的唯一严肃的 C++ 框架是 Qt 框架。Qt 将 C++ 推向了一个新的水平。虽然 Qt 引入的信号和槽技术非常新颖,但整个想法真正有趣之处在于,一个对象可以调用其他对象的成员函数,而无需考虑对象的声明。为了使信号和槽工作,动态性被引入了 C++。当然,当仅使用信号和槽时,开发人员不会直接注意到这种行为,一切都由框架处理。但是,开发人员也可以通过 QMetaObject
类来使用这种动态性。
我不会解释信号和槽的基础知识。读者可以查阅 Qt 文档页面。我将做的是简要介绍 Qt 动态性的内部工作原理。在我撰写本文时,Qt 框架的当前版本是 4.4.3。
让我们来看一个简单的信号和槽示例
// sas.h
#include <QObject>
class Counter : public QObject
{
Q_OBJECT
public:
Counter() { m_value = 0; };
int value() const { return m_value; };
public slots:
void setValue(int value)
{
if (value != m_value)
{
m_value = value;
emit valueChanged(value);
}
};
signals:
void valueChanged(int newValue);
private:
int m_value;
};
// main.cpp
#include "sas.h"
int main(int argc, char *argv[])
{
Counter a, b;
QObject::connect(&a, SIGNAL(valueChanged(int)),
&b, SLOT(setValue(int)));
a.setValue(12); // a.value() == 12, b.value() == 12
b.setValue(48); // a.value() == 12, b.value() == 48
return 0;
}
SIGNAL
和 SLOT
宏用方括号将它们的内容括起来,使其成为一个字符串。嗯,它们还做了另一件事。它们在字符串中放入了一个标识号。
#define SLOT(a) "1"#a
#define SIGNAL(a) "2"#a
所以,也可以这样写
QObject::connect(&a, "2valueChanged(int)", &b, "1setValue(int)");
Qt 关键字“signals
”和“slots
”,可以在类头文件中找到,仅对 Qt 元编译器 (moc) 有用。
# if defined(QT_NO_KEYWORDS)
# define QT_NO_EMIT
# else
# define slots
# define signals protected
# endif
# define Q_SLOTS
# define Q_SIGNALS protected
# define Q_PRIVATE_SLOT(d, signature)
# define Q_EMIT
#ifndef QT_NO_EMIT
# define emit
#endif
事实上,正如你所见,即使是 emit
宏也只是提高了可读性。只有“signals
”宏有点不同,因为它将 Qt 信号限定为保护(protected)成员函数,而槽可以是任何类型。第一个有趣的部分是“Q_OBJECT
”宏。
/* tmake ignore Q_OBJECT */
#define Q_OBJECT_CHECK \
template <typename T> inline
void qt_check_for_QOBJECT_macro(const T &_q_argument)
const \
{ int i = qYouForgotTheQ_OBJECT_Macro(this, &_q_argument); i = i; }
template <typename T>
inline int qYouForgotTheQ_OBJECT_Macro(T, T) { return 0; }
template <typename T1, typename T2>
inline void qYouForgotTheQ_OBJECT_Macro(T1, T2) {}
#endif // QT_NO_MEMBER_TEMPLATES
/* tmake ignore Q_OBJECT */
#define Q_OBJECT \
public: \
Q_OBJECT_CHECK \
static const QMetaObject staticMetaObject; \
virtual const QMetaObject *metaObject() const; \
virtual void *qt_metacast(const char *); \
QT_TR_FUNCTIONS \
virtual int qt_metacall(QMetaObject::Call, int, void **); \
private:
staticMetaObject
是 QMetaObject
,它是静态的,因为作为元数据类,它被同一个类的所有实例共享。metaObject
方法只是返回 staticMetaObject
。QT_TR_FUNCTIONS
是一个宏,它定义了所有用于多语言支持的“tr”函数。qt_metacast
在给定类名或其基类之一的名称时执行动态转换(Qt 显然不依赖于 RTTI)。qt_metacall
通过索引调用内部信号或槽。在我讨论 moc 生成的代码之前,这里是 QMetaObject
的声明。
struct Q_CORE_EXPORT QMetaObject
{
const char *className() const;
const QMetaObject *superClass() const;
QObject *cast(QObject *obj) const;
#ifndef QT_NO_TRANSLATION
// ### Qt 4: Merge overloads
QString tr(const char *s, const char *c) const;
QString trUtf8(const char *s, const char *c) const;
QString tr(const char *s, const char *c, int n) const;
QString trUtf8(const char *s, const char *c, int n) const;
#endif // QT_NO_TRANSLATION
int methodOffset() const;
int enumeratorOffset() const;
int propertyOffset() const;
int classInfoOffset() const;
int methodCount() const;
int enumeratorCount() const;
int propertyCount() const;
int classInfoCount() const;
int indexOfMethod(const char *method) const;
int indexOfSignal(const char *signal) const;
int indexOfSlot(const char *slot) const;
int indexOfEnumerator(const char *name) const;
int indexOfProperty(const char *name) const;
int indexOfClassInfo(const char *name) const;
QMetaMethod method(int index) const;
QMetaEnum enumerator(int index) const;
QMetaProperty property(int index) const;
QMetaClassInfo classInfo(int index) const;
QMetaProperty userProperty() const;
static bool checkConnectArgs(const char *signal, const char *method);
static QByteArray normalizedSignature(const char *method);
static QByteArray normalizedType(const char *type);
// internal index-based connect
static bool connect(const QObject *sender, int signal_index,
const QObject *receiver, int method_index,
int type = 0, int *types = 0);
// internal index-based disconnect
static bool disconnect(const QObject *sender, int signal_index,
const QObject *receiver, int method_index);
// internal slot-name based connect
static void connectSlotsByName(QObject *o);
// internal index-based signal activation
static void activate(QObject *sender, int signal_index, void **argv);
static void activate(QObject *sender, int from_signal_index,
int to_signal_index, void **argv);
static void activate(QObject *sender, const QMetaObject *,
int local_signal_index, void **argv);
static void activate(QObject *sender, const QMetaObject *,
int from_local_signal_index,
int to_local_signal_index, void
**argv);
// internal guarded pointers
static void addGuard(QObject **ptr);
static void removeGuard(QObject **ptr);
static void changeGuard(QObject **ptr, QObject *o);
static bool invokeMethod(QObject *obj, const char *member,
Qt::ConnectionType,
QGenericReturnArgument ret,
QGenericArgument val0 = QGenericArgument(0),
QGenericArgument val1 = QGenericArgument(),
QGenericArgument val2 = QGenericArgument(),
QGenericArgument val3 = QGenericArgument(),
QGenericArgument val4 = QGenericArgument(),
QGenericArgument val5 = QGenericArgument(),
QGenericArgument val6 = QGenericArgument(),
QGenericArgument val7 = QGenericArgument(),
QGenericArgument val8 = QGenericArgument(),
QGenericArgument val9 = QGenericArgument());
// [ ... several invokeMethod overloads ...]
enum Call {
InvokeMetaMethod,
ReadProperty,
WriteProperty,
ResetProperty,
QueryPropertyDesignable,
QueryPropertyScriptable,
QueryPropertyStored,
QueryPropertyEditable,
QueryPropertyUser
};
#ifdef QT3_SUPPORT
QT3_SUPPORT const char *superClassName() const;
#endif
struct { // private data
const QMetaObject *superdata;
const char *stringdata;
const uint *data;
const QMetaObject **extradata;
} d;
};
QMetaObject
中重要的部分是内部的“d
”结构体。这个结构体的第一个成员是 QMetaObject
类的指针。这个成员指向父 Qt 对象元数据类。一个像我们这样的类可以继承自不止一个类,但它只能有一个 QObject
(或从中派生的)基类,这就是超类。此外,moc 依赖于这样一个事实:在 QObject
派生类的声明中,第一个继承的类是 QObject
(或派生)基类。让我们来看一个 Qt 对话框,它在实现中经常使用多重继承。
class ConvDialog : public QDialog, private Ui::ConvDialog
{
Q_OBJECT
这使得 moc 生成以下代码
const QMetaObject ConvDialog::staticMetaObject = {
{ &QDialog::staticMetaObject, qt_meta_stringdata_ConvDialog,
qt_meta_data_ConvDialog, 0 }
};
但是,如果 ConvDialog
在 QDialog
之前继承 Ui::ConvDialog
,则 moc 会生成
const QMetaObject ConvDialog::staticMetaObject = {
{ &Ui::ConvDialog::staticMetaObject, qt_meta_stringdata_ConvDialog,
qt_meta_data_ConvDialog, 0 }
};
这是错误的,因为 Ui::ConvDialog
不是 QObject
的派生类,因此没有 staticMetaObject
成员。这样做会导致编译错误。
“d
”结构体的第二个成员是一个 char
数组,其中包含类的文字元数据。第三个成员是一个无符号整数数组。这个数组是一个表,其中包含所有元数据偏移量、标志等。因此,如果我们想枚举一个类的槽和信号,我们就必须遍历这个表,获取正确的偏移量,以便从 stringdata
数组中获取方法名。“d
”结构体的第四个成员是一个以 null 结尾的 QMetaObject
类数组。这个成员为附加类提供元数据信息的存储。我从未见过它被使用,但它被 QMetaObject_findMetaObject
函数引用。
static const QMetaObject *QMetaObject_findMetaObject(const QMetaObject *self,
const char *name)
{
while (self) {
if (strcmp(self->d.stringdata, name) == 0)
return self;
if (self->d.extradata) {
const QMetaObject **e = self->d.extradata;
while (*e) {
if (const QMetaObject *m =
QMetaObject_findMetaObject((*e), name))
return m;
++e;
}
}
self = self->d.superdata;
}
return self;
}
这个函数仅由“property
”方法调用,而“property
”方法又由 propertyCount
、propertyOffset
和 indexOfProperty
调用。
这是 moc 为我们的 Counter
类生成的代码。
/****************************************************************************
** Meta object code from reading C++ file 'sas.h'
**
** Created: Mon 3. Nov 15:20:11 2008
** by: The Qt Meta Object Compiler version 59 (Qt 4.4.3)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/
#include "../sas.h"
#if !defined(Q_MOC_OUTPUT_REVISION)
#error "The header file 'sas.h' doesn't include <QObject>."
#elif Q_MOC_OUTPUT_REVISION != 59
#error "This file was generated using the moc from 4.4.3. It"
#error "cannot be used with the include files from this version of Qt."
#error "(The moc has changed too much.)"
#endif
QT_BEGIN_MOC_NAMESPACE
static const uint qt_meta_data_Counter[] = {
// content:
1, // revision
0, // classname
0, 0, // classinfo
2, 10, // methods
0, 0, // properties
0, 0, // enums/sets
// signals: signature, parameters, type, tag, flags
18, 9, 8, 8, 0x05,
// slots: signature, parameters, type, tag, flags
42, 36, 8, 8, 0x0a,
0 // eod
};
static const char qt_meta_stringdata_Counter[] = {
"Counter\0\0newValue\0valueChanged(int)\0"
"value\0setValue(int)\0"
};
const QMetaObject Counter::staticMetaObject = {
{ &QObject::staticMetaObject, qt_meta_stringdata_Counter,
qt_meta_data_Counter, 0 }
};
const QMetaObject *Counter::metaObject() const
{
return &staticMetaObject;
}
void *Counter::qt_metacast(const char *_clname)
{
if (!_clname) return 0;
if (!strcmp(_clname, qt_meta_stringdata_Counter))
return static_cast<void*>(const_cast< Counter*>(this));
return QObject::qt_metacast(_clname);
}
int Counter::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
_id = QObject::qt_metacall(_c, _id, _a);
if (_id < 0)
return _id;
if (_c == QMetaObject::InvokeMetaMethod) {
switch (_id) {
case 0: valueChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
case 1: setValue((*reinterpret_cast< int(*)>(_a[1]))); break;
}
_id -= 2;
}
return _id;
}
// SIGNAL 0
void Counter::valueChanged(int _t1)
{
void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
QT_END_MOC_NAMESPACE
qt_metacall
方法通过索引调用类的其他内部方法。Qt 动态性依赖于索引,避免了指针。调用方法的实际工作由编译器完成。这种实现使得信号和槽机制相当快速。
参数通过指向指针的数组传递,并在调用方法时进行适当的类型转换。当然,使用指针是将所有类型的参数放入数组的唯一方法。参数从位置 1 开始,因为位置 0 保留给要返回的数据。示例中的信号和槽声明为 void
,因此没有要返回的数据。如果槽有要返回的数据,switch
中的代码将如下所示。
if (_c == QMetaObject::InvokeMetaMethod) {
switch (_id) {
case 0: valueChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
case 1: setValue((*reinterpret_cast< int(*)>(_a[1]))); break;
case 2: { int _r = exampleMethod((*reinterpret_cast< int(*)>(_a[1])));
if (_a[0]) *reinterpret_cast< int*>(_a[0]) = _r; } break;
}
moc 生成的另一个有趣的方 法是 valueChanged
,它表示发出“valueChanged
”信号的代码。此代码调用 QMetaObject
的 Activate
方法,该方法只是此 Activate
方法的重载。
void QMetaObject::activate(QObject *sender, int from_signal_index,
int to_signal_index, void **argv)
{
// [... other code ...]
// emit signals in the following order: from_signal_index
// <= signals <= to_signal_index, signal < 0
for (int signal = from_signal_index;
(signal >= from_signal_index &&
signal <= to_signal_index) || (signal == -2);
(signal == to_signal_index ? signal = -2 : ++signal))
{
if (signal >= connectionLists->count()) {
signal = to_signal_index;
continue;
}
const QObjectPrivate::ConnectionList &connectionList =
connectionLists->at(signal);
int count = connectionList.count();
for (int i = 0; i < count; ++i) {
const QObjectPrivate::Connection *c = &connectionList[i];
if (!c->receiver)
continue;
QObject * const receiver = c->receiver;
// determine if this connection should be sent immediately or
// put into the event queue
if ((c->connectionType == Qt::AutoConnection
&& (currentThreadData != sender->d_func()->threadData
|| receiver->d_func()->threadData !=
sender->d_func()->threadData))
|| (c->connectionType == Qt::QueuedConnection)) {
queued_activate(sender, signal, *c, argv);
continue;
} else if (c->connectionType == Qt::BlockingQueuedConnection) {
blocking_activate(sender, signal, *c, argv);
continue;
}
const int method = c->method;
QObjectPrivate::Sender currentSender;
currentSender.sender = sender;
currentSender.signal = signal < 0 ? from_signal_index : signal;
QObjectPrivate::Sender * const previousSender =
QObjectPrivate::setCurrentSender(receiver, ¤tSender);
locker.unlock();
if (qt_signal_spy_callback_set.slot_begin_callback != 0) {
qt_signal_spy_callback_set.slot_begin_callback(receiver,
method,
argv ? argv : empty_argv);
}
#if defined(QT_NO_EXCEPTIONS)
receiver->qt_metacall(QMetaObject::InvokeMetaMethod,
method, argv ? argv : empty_argv);
#else
try {
receiver->qt_metacall(QMetaObject::InvokeMetaMethod,
method, argv ? argv : empty_argv);
} catch (...) {
locker.relock();
QObjectPrivate::resetCurrentSender(receiver,
¤tSender, previousSender);
--connectionLists->inUse;
Q_ASSERT(connectionLists->inUse >= 0);
if (connectionLists->orphaned && !connectionLists->inUse)
delete connectionLists;
throw;
}
#endif
locker.relock();
if (qt_signal_spy_callback_set.slot_end_callback != 0)
qt_signal_spy_callback_set.slot_end_callback(receiver, method);
QObjectPrivate::resetCurrentSender(receiver,
¤tSender, previousSender);
if (connectionLists->orphaned)
break;
}
if (connectionLists->orphaned)
break;
}
--connectionLists->inUse;
Q_ASSERT(connectionLists->inUse >= 0);
if (connectionLists->orphaned && !connectionLists->inUse)
delete connectionLists;
locker.unlock();
if (qt_signal_spy_callback_set.signal_end_callback != 0)
qt_signal_spy_callback_set.signal_end_callback(sender,
from_signal_index);
}
此方法执行许多操作,包括检查当前连接是应立即处理还是放入事件队列。如果是,它会调用适当的 Activate
方法变体,然后继续处理 ConnectionList
中的下一个连接。否则,如果连接应立即处理,则从当前连接中检索要调用的方法的 ID,然后调用接收者的 qt_metacall
方法。为了简化执行流程。
const QObjectPrivate::ConnectionList &connectionList = connectionLists->at(signal);
int count = connectionList.count();
for (int i = 0; i < count; ++i) {
const QObjectPrivate::Connection *c = &connectionList[i];
QObject * const receiver = c->receiver;
const int method = c->method;
receiver->qt_metacall(QMetaObject::InvokeMetaMethod, method,
argv ? argv : empty_argv);
这告诉了我们关于信号和槽内部机制所需的一切。调用“connect
”函数时,信号和槽的签名被转换为它们的 ID,然后存储在 Connection
类中。每次发出信号时,都会检索信号 ID 的连接,并通过其 ID 调用槽。
需要讨论的最后一部分是动态调用。QMetaObject
类提供了 invokeMethod
方法来动态调用一个方法。这个方法与信号和槽略有不同,因为它需要根据方法的返回类型、名称和参数类型来构建一个签名,然后查找元数据以检索其 ID,最后调用对象的 qt_metacall
方法。
bool QMetaObject::invokeMethod(QObject *obj,
const char *member, Qt::ConnectionType type,
QGenericReturnArgument ret,
QGenericArgument val0,
QGenericArgument val1,
QGenericArgument val2,
QGenericArgument val3,
QGenericArgument val4,
QGenericArgument val5,
QGenericArgument val6,
QGenericArgument val7,
QGenericArgument val8,
QGenericArgument val9)
{
if (!obj)
return false;
QVarLengthArray<char, 512> sig;
int len = qstrlen(member);
if (len <= 0)
return false;
sig.append(member, len);
sig.append('(');
enum { MaximumParamCount = 11 };
const char *typeNames[] = {ret.name(), val0.name(), val1.name(),
val2.name(), val3.name(),
val4.name(), val5.name(), val6.name(),
val7.name(), val8.name(), val9.name()};
int paramCount;
for (paramCount = 1; paramCount < MaximumParamCount; ++paramCount) {
len = qstrlen(typeNames[paramCount]);
if (len <= 0)
break;
sig.append(typeNames[paramCount], len);
sig.append(',');
}
if (paramCount == 1)
sig.append(')'); // no parameters
else
sig[sig.size() - 1] = ')';
sig.append('\0');
int idx = obj->metaObject()->indexOfMethod(sig.constData());
if (idx < 0) {
QByteArray norm = QMetaObject::normalizedSignature(sig.constData());
idx = obj->metaObject()->indexOfMethod(norm.constData());
}
if (idx < 0)
return false;
// check return type
if (ret.data()) {
const char *retType = obj->metaObject()->method(idx).typeName();
if (qstrcmp(ret.name(), retType) != 0) {
// normalize the return value as well
// the trick here is to make a function signature out of the return type
// so that we can call normalizedSignature() and avoid duplicating code
QByteArray unnormalized;
int len = qstrlen(ret.name());
unnormalized.reserve(len + 3);
unnormalized = "_("; // the function is called "_"
unnormalized.append(ret.name());
unnormalized.append(')');
QByteArray normalized =
QMetaObject::normalizedSignature(unnormalized.constData());
normalized.truncate(normalized.length() - 1); // drop the ending ')'
if (qstrcmp(normalized.constData() + 2, retType) != 0)
return false;
}
}
void *param[] = {ret.data(), val0.data(), val1.data(), val2.data(),
val3.data(), val4.data(),
val5.data(), val6.data(), val7.data(),
val8.data(), val9.data()};
if (type == Qt::AutoConnection) {
type = QThread::currentThread() == obj->thread()
? Qt::DirectConnection
: Qt::QueuedConnection;
}
if (type == Qt::DirectConnection) {
return obj->qt_metacall(QMetaObject::InvokeMetaMethod, idx, param) < 0;
} else {
if (ret.data()) {
qWarning("QMetaObject::invokeMethod: Unable to invoke"
" methods with return values in queued "
"connections");
return false;
}
int nargs = 1; // include return type
void **args = (void **) qMalloc(paramCount * sizeof(void *));
int *types = (int *) qMalloc(paramCount * sizeof(int));
types[0] = 0; // return type
args[0] = 0;
for (int i = 1; i < paramCount; ++i) {
types[i] = QMetaType::type(typeNames[i]);
if (types[i]) {
args[i] = QMetaType::construct(types[i], param[i]);
++nargs;
} else if (param[i]) {
qWarning("QMetaObject::invokeMethod: Unable to handle"
" unregistered datatype '%s'",
typeNames[i]);
for (int x = 1; x < i; ++x) {
if (types[x] && args[x])
QMetaType::destroy(types[x], args[x]);
}
qFree(types);
qFree(args);
return false;
}
}
if (type == Qt::QueuedConnection) {
QCoreApplication::postEvent(obj,
new QMetaCallEvent(idx, 0, -1, nargs, types, args));
} else {
if (QThread::currentThread() == obj->thread()) {
qWarning("QMetaObject::invokeMethod: Dead lock detected"
" in BlockingQueuedConnection: "
"Receiver is %s(%p)",
obj->metaObject()->className(), obj);
}
// blocking queued connection
#ifdef QT_NO_THREAD
QCoreApplication::postEvent(obj,
new QMetaCallEvent(idx, 0, -1, nargs, types, args));
#else
QSemaphore semaphore;
QCoreApplication::postEvent(obj, new QMetaCallEvent(idx, 0, -1,
nargs, types, args, &semaphore));
semaphore.acquire();
#endif // QT_NO_THREAD
}
}
return true;
}
方法 ID 通过 indexOfMethod
检索。如果找不到方法签名,invokeMethod
将返回 false
。
这应该能让你对 Qt 内部机制有一个大致的了解。
逆向工程
在逆向工程时,我们希望利用 Qt 提供的所有元数据。为了做到这一点,让我们重新考虑元数据表。
QT_BEGIN_MOC_NAMESPACE
static const uint qt_meta_data_Counter[] = {
// content:
1, // revision
0, // classname
0, 0, // classinfo
2, 10, // methods
0, 0, // properties
0, 0, // enums/sets
// signals: signature, parameters, type, tag, flags
18, 9, 8, 8, 0x05,
// slots: signature, parameters, type, tag, flags
42, 36, 8, 8, 0x0a,
0 // eod
};
正如我们所注意到的,该表不仅告诉我们方法的数量,还告诉我们方法的偏移量(10)。这是该结构的 C++ 声明头文件。
struct QMetaObjectPrivate
{
int revision;
int className;
int classInfoCount, classInfoData;
int methodCount, methodData;
int propertyCount, propertyData;
int enumeratorCount, enumeratorData;
};
此结构包含在“qmetaobject.cpp”中;就像其他信息一样,我们需要解析元数据表。在考虑属性和枚举之前,让我们先考虑方法。我们已经有了方法的数量和偏移量,现在需要分析的是方法本身的数据。这些数据细分为五个整数,根据 moc 的说法,它们代表:签名、参数、类型、标签、标志。“signature”字段是字符串数据中的一个偏移量,指向方法的局部声明(不含返回类型):“valueChanged(int)
”。“parameters”字段是指向参数名称的另一个偏移量。是的,名称:“newValue”。名称用逗号分隔,所以如果我们的槽有两个参数,它将显示:“newValue1,newValue2”。“type”字段指向方法的返回类型。如果它是空字符串,就像在本例中一样,则该方法声明为“void
”。“tag”方法目前未使用。我粘贴了在源代码中找到的描述:“Tags are special macros recognized by moc that make it possible to add extra information about a method. For the moment, moc doesn't support any special tags.”(标签是 moc 识别的特殊宏,可以为方法添加额外信息。目前,moc 不支持任何特殊标签。)最后一个字段“flags”不是偏移量,而是顾名思义的标志。这是可能的标志列表。
enum MethodFlags {
AccessPrivate = 0x00,
AccessProtected = 0x01,
AccessPublic = 0x02,
AccessMask = 0x03, //mask
MethodMethod = 0x00,
MethodSignal = 0x04,
MethodSlot = 0x08,
MethodTypeMask = 0x0c,
MethodCompatibility = 0x10,
MethodCloned = 0x20,
MethodScriptable = 0x40
};
这就是我们需要了解的关于方法的全部内容。现在让我们考虑枚举和属性。为此,我在上面的代码示例中添加了一个枚举和一个属性。
class Counter : public QObject
{
Q_OBJECT
Q_PROPERTY(Priority priority READ priority WRITE setPriority)
Q_ENUMS(Priority)
public:
Counter() { m_value = 0; };
enum Priority { High, Low, VeryHigh, VeryLow };
void setPriority(Priority priority) { m_priority = priority; };
Priority priority() const { return m_priority; };
int value() const { return m_value; };
public slots:
void setValue(int value)
{
if (value != m_value)
{
m_value = value;
emit valueChanged(value);
}
};
signals:
void valueChanged(int newValue);
private:
int m_value;
Priority m_priority;
};
moc 生成如下。
static const uint qt_meta_data_Counter[] = {
// content:
1, // revision
0, // classname
0, 0, // classinfo
2, 10, // methods
1, 20, // properties
1, 23, // enums/sets
// signals: signature, parameters, type, tag, flags
18, 9, 8, 8, 0x05,
// slots: signature, parameters, type, tag, flags
42, 36, 8, 8, 0x0a,
// properties: name, type, flags
65, 56, 0x0009510b,
// enums: name, flags, count, data
56, 0x0, 4, 27,
// enum data: key, value
74, uint(Counter::High),
79, uint(Counter::Low),
83, uint(Counter::VeryHigh),
92, uint(Counter::VeryLow),
0 // eod
};
static const char qt_meta_stringdata_Counter[] = {
"Counter\0\0newValue\0valueChanged(int)\0"
"value\0setValue(int)\0Priority\0priority\0"
"High\0Low\0VeryHigh\0VeryLow\0"
};
同样,我们有属性和枚举的数量以及它们的偏移量。让我们先从属性开始。属性的数据由三个整数组成:名称、类型、标志。“name”字段当然是指名称。“type”字段是指属性的类型,在本例中是:“Priority”。最后,“flags”字段包含标志,这是列表。
enum PropertyFlags {
Invalid = 0x00000000,
Readable = 0x00000001,
Writable = 0x00000002,
Resettable = 0x00000004,
EnumOrFlag = 0x00000008,
StdCppSet = 0x00000100,
// Override = 0x00000200,
Designable = 0x00001000,
ResolveDesignable = 0x00002000,
Scriptable = 0x00004000,
ResolveScriptable = 0x00008000,
Stored = 0x00010000,
ResolveStored = 0x00020000,
Editable = 0x00040000,
ResolveEditable = 0x00080000,
User = 0x00100000,
ResolveUser = 0x00200000
};
现在让我们考虑枚举:名称、标志、计数、数据。到目前为止,我们已经知道“name”字段是什么。“flags”字段未使用。“count”字段表示枚举中包含的项的数量。“data”字段是元数据表中的一个偏移量,指向枚举的项。每个项由两个整数表示:键、值。“key”字段指向当前项的名称,而“value”是项的实际值。
为了从二进制文件中检索元数据信息,我为 IDA 编写了一个脚本。我用 Python 编写了脚本,这要归功于 IDAPython 插件。使用 Python 的原因有很多:用 IDC 脚本编写相同的代码将花费太多精力,而且 Python 甚至可以在 IDA 之外重用。该脚本从“Q_OBJECT
”类的布局中提取方法、属性和枚举。这是代码。
# ---------------------------------------------
# MetaData Parser Class
# ---------------------------------------------
# change for 64 bit exes
b64bit = False
# i'm assuming that the exe is Little Endian
# the external methods used by this class are Byte(addr) and Dword(addr)
def AddressSize():
if b64bit == True:
return 8
return 4
def ReadAddress(addr):
if b64bit == True:
return (Dword(addr+4) << 32) | Dword(addr)
return Dword(addr)
class MetaParser:
def __init__(self, stringsaddr, tableaddr):
self.MetaStrings = stringsaddr
self.MetaTable = tableaddr
def ReadString(self, addr):
c = addr
b = Byte(c)
str = ""
while b != 0:
# set a limit, just in case
if (c - addr) > 1000:
return "error"
str += chr(b)
c += 1
b = Byte(c)
return str
# read metadata
"""
struct QMetaObjectPrivate
{
int revision;
int className;
int classInfoCount, classInfoData;
int methodCount, methodData;
int propertyCount, propertyData;
int enumeratorCount, enumeratorData;
};
"""
# ---------------------------------------------
# enums (quick way to convert them to python)
# ---------------------------------------------
class Enum:
def __init__(self, **entries): self.__dict__.update(entries)
MethodFlags = Enum( \
AccessPrivate = 0x00, \
AccessProtected = 0x01, \
AccessPublic = 0x02, \
AccessMask = 0x03, \
MethodMethod = 0x00, \
MethodSignal = 0x04, \
MethodSlot = 0x08, \
MethodTypeMask = 0x0c, \
MethodCompatibility = 0x10, \
MethodCloned = 0x20, \
MethodScriptable = 0x40)
PropertyFlags = Enum( \
Invalid = 0x00000000, \
Readable = 0x00000001, \
Writable = 0x00000002, \
Resettable = 0x00000004, \
EnumOrFlag = 0x00000008, \
StdCppSet = 0x00000100, \
Designable = 0x00001000, \
ResolveDesignable = 0x00002000, \
Scriptable = 0x00004000, \
ResolveScriptable = 0x00008000, \
Stored = 0x00010000, \
ResolveStored = 0x00020000, \
Editable = 0x00040000, \
ResolveEditable = 0x00080000, \
User = 0x00100000, \
ResolveUser = 0x00200000)
# ---------------------------------------------
# methods
# ---------------------------------------------
def GetClassName(self):
stringaddr = Dword(self.MetaTable + 4) + self.MetaStrings
return self.ReadString(stringaddr)
def GetMethodNumber(self):
return Dword(self.MetaTable + 16)
def GetMethodSignature(self, method_index):
if method_index >= self.GetMethodNumber():
return "error: method index out of range"
method_offset = (Dword(self.MetaTable + 20) * 4) +
(method_index * (5 * 4))
# get accessibility
access_flags = self.GetMethodAccess(method_index)
access_type = "private: "
if access_flags == self.MethodFlags.AccessProtected:
access_type = "protected: "
elif access_flags == self.MethodFlags.AccessPublic:
access_type = "public: "
# read return type
rettype = self.ReadString(Dword(self.MetaTable + method_offset + 8) +
self.MetaStrings)
if rettype == "":
rettype = "void"
# read partial signature
psign = self.ReadString(Dword(self.MetaTable + method_offset) +
self.MetaStrings)
# retrieve argument types
par_index = psign.find("(")
arg_types = psign[(par_index + 1):(len(psign) - 1)].split(",")
# read argument names
arg_names = self.ReadString(Dword(self.MetaTable + method_offset + 4) \
+ self.MetaStrings).split(",")
# if argument types and names are not the same number,
# then show signature without argument names
if len(arg_types) != len(arg_names):
return access_type + rettype + " " + psign
# build signatrue with argument names
ntypes = len(arg_types)
x = 0
args = ""
while x < ntypes:
if x != 0:
args += ", "
if arg_types[x] == "":
args += arg_names[x]
elif arg_names[x] == "":
args += arg_types[x]
else:
args += (arg_types[x] + " " + arg_names[x])
# increment loop
x += 1
return access_type + rettype + " " +
psign[0:(par_index + 1)] + args + ")"
def GetMethodFlags(self, method_index):
if method_index >= self.GetMethodNumber():
return -1
method_offset = (Dword(self.MetaTable + 20) * 4) +
(method_index * (5 * 4))
return Dword(self.MetaTable + method_offset + 16)
def GetMethodType(self, method_index):
return self.GetMethodFlags(method_index) &
self.MethodFlags.MethodTypeMask
def GetMethodAccess(self, method_index):
return self.GetMethodFlags(method_index) &
self.MethodFlags.AccessMask
def GetPropertyNumber(self):
return Dword(self.MetaTable + 24)
def GetPropertyDecl(self, property_index):
if property_index >= self.GetPropertyNumber():
return "error: property index out of range"
property_offset = (Dword(self.MetaTable + 28) * 4) +
(property_index * (3 * 4))
# read name
pr_name = self.ReadString(Dword(self.MetaTable + property_offset) +
self.MetaStrings)
# read type
pr_type = self.ReadString(Dword(self.MetaTable + property_offset + 4) +
self.MetaStrings)
return pr_type + " " + pr_name
def GetPropertyFlags(self, property_index):
if property_index >= self.GetPropertyNumber():
return -1
property_offset = (Dword(self.MetaTable + 28) * 4) +
(property_index * (3 * 4))
return Dword(self.MetaTable + property_offset + 8)
def PropertyFlagsToString(self, flags):
if flags == 0:
return "Invalid"
fstr = ""
if flags & self.PropertyFlags.Readable:
fstr += " | Readable"
if flags & self.PropertyFlags.Writable:
fstr += " | Writable"
if flags & self.PropertyFlags.Resettable:
fstr += " | Resettable"
if flags & self.PropertyFlags.EnumOrFlag:
fstr += " | EnumOrFlag"
if flags & self.PropertyFlags.StdCppSet:
fstr += " | StdCppSet"
if flags & self.PropertyFlags.Designable:
fstr += " | Designable"
if flags & self.PropertyFlags.ResolveDesignable:
fstr += " | ResolveDesignable"
if flags & self.PropertyFlags.Scriptable:
fstr += " | Scriptable"
if flags & self.PropertyFlags.ResolveScriptable:
fstr += " | ResolveScriptable"
if flags & self.PropertyFlags.Stored:
fstr += " | Stored"
if flags & self.PropertyFlags.ResolveStored:
fstr += " | ResolveStored"
if flags & self.PropertyFlags.Editable:
fstr += " | Editable"
if flags & self.PropertyFlags.ResolveEditable:
fstr += " | ResolveEditable"
if flags & self.PropertyFlags.User:
fstr += " | User"
if flags & self.PropertyFlags.ResolveUser:
fstr += " | ResolveUser"
return fstr[3:]
def GetEnumNumber(self):
return Dword(self.MetaTable + 32)
def GetEnumDecl(self, enum_index):
if enum_index >= self.GetPropertyNumber():
return "error: property index out of range"
enum_offset = (Dword(self.MetaTable + 36) * 4) + (enum_index * (4 * 4))
# read name
enum_name = self.ReadString(Dword(self.MetaTable + enum_offset) +
self.MetaStrings)
# read number of items
items_num = Dword(self.MetaTable + enum_offset + 8)
# items addr
items_addr = (Dword(self.MetaTable + enum_offset + 12) * 4) +
self.MetaTable
decl = "enum " + enum_name + "\n{\n"
# add items
x = 0
while x < items_num:
# read item name
item_name = self.ReadString(Dword(items_addr) + self.MetaStrings)
# read data
item_data = "0x%X" % Dword(items_addr + 4)
# add
decl += " " + item_name + " = " + item_data + ",\n"
# inc loop
x += 1
items_addr += 8
decl += "\n};"
return decl
# ---------------------------------------------
# Display MetaData
# ---------------------------------------------
def DisplayMethod(parser, method_index):
print(str(method_index) + " - " + parser.GetMethodSignature(method_index))
def DisplayProperty(parser, property_index):
print(str(property_index) + " - " + parser.GetPropertyDecl(property_index))
flags = parser.GetPropertyFlags(property_index)
print(" flags: " + parser.PropertyFlagsToString(flags))
def DisplayEnum(parser, enum_index):
print("[" + str(enum_index) + "]\n" +
parser.GetEnumDecl(enum_index) + "\n")
def DisplayMetaData(stringsaddr, tableaddr):
parser = MetaParser(stringsaddr, tableaddr)
print("\n-------------------------------------------------")
print("--- " + "Qt MetaData Displayer by Daniel Pistelli")
print("--- " + "metadata of the class: " + parser.GetClassName() + "\n")
num_methods = parser.GetMethodNumber()
num_properties = parser.GetPropertyNumber()
num_enums = parser.GetEnumNumber()
# ---------------------------------------------
# methods
# ---------------------------------------------
# signals
print("--- Signals:\n")
x = 0
while x < num_methods:
# print if it's a signal
if parser.GetMethodType(x) == parser.MethodFlags.MethodSignal:
DisplayMethod(parser, x)
# increment loop
x += 1
# slots
print("\n--- Slots:\n")
x = 0
while x < num_methods:
# print if it's a slot
if parser.GetMethodType(x) == parser.MethodFlags.MethodSlot:
DisplayMethod(parser, x)
# increment loop
x += 1
# other methods
print("\n--- Other Methods:\n")
x = 0
while x < num_methods:
# print if it's a slot
if parser.GetMethodType(x) == parser.MethodFlags.MethodMethod:
DisplayMethod(parser, x)
# increment loop
x += 1
# ---------------------------------------------
# properties
# ---------------------------------------------
print("\n--- Properties:\n")
x = 0
while x < num_properties:
DisplayProperty(parser, x)
# increment loop
x += 1
# ---------------------------------------------
# enums
# ---------------------------------------------
print("\n--- Enums:\n")
x = 0
while x < num_enums:
DisplayEnum(parser, x)
# increment loop
x += 1
print("-------------------------------------------------\n")
# ---------------------------------------------
# Main
# ---------------------------------------------
addrtoparse = ScreenEA()
if addrtoparse != 0:
stringsaddr = ReadAddress(addrtoparse + AddressSize())
tableaddr = ReadAddress(addrtoparse + AddressSize() * 2)
if stringsaddr != 0 or tableaddr != 0:
DisplayMetaData(stringsaddr, tableaddr)
我稍后会解释如何访问类的元数据。“DisplayMetaData
”函数接受两个参数:元数据表地址和元数据字符串地址。这是因为有时类的布局是在运行时设置的,就像 VC++ 应用程序那样。
.text:00401D60 sub_401D60 proc near ; DATA XREF: .rdata:00402108
.text:00401D60 push ebp
.text:00401D61 mov ebp, esp
.text:00401D63 mov eax, ds:?staticMetaObject@QObject@@2UQMetaObject@@B
; QMetaObject const QObject::staticMetaObject
.text:00401D68 mov dword_403070, eax
.text:00401D6D mov dword_403074, offset Str2 ; "Counter"
.text:00401D77 mov dword_403078, offset unk_4021A8
.text:00401D81 mov dword_40307C, 0
.text:00401D8B pop ebp
.text:00401D8C retn
.text:00401D8C sub_401D60 endp
有时,布局已经设置在物理文件中。
.data:00406000 dd 0 ; SuperData
.data:00406004 dd offset MetaStrings ; "Counter"
.data:00406008 dd offset MetaTable
在这种情况下,脚本可以直接在“SuperData
”项的地址处运行,该项是“QMetaObject
”中“d
”结构体的第一个项。上面“Counter
”类的脚本输出是。
-------------------------------------------------
--- Qt MetaData Displayer by Daniel Pistelli
--- metadata of the class: Counter
--- Signals:
0 - protected: void valueChanged(int newValue)
--- Slots:
1 - public: void setValue(int value)
--- Other Methods:
--- Properties:
0 - Priority priority
flags: Readable | Writable | EnumOrFlag | StdCppSet | Designable |
Scriptable | Stored | ResolveEditable
--- Enums:
[0]
enum Priority
{
High = 0x0,
Low = 0x1,
VeryHigh = 0x2,
VeryLow = 0x3,
};
-------------------------------------------------
很不错,不是吗?方法的签名甚至包含参数名称(如果可用)。为了检查脚本的质量,我在更大的类“QWidget
”(位于“QtGui4
”模块中)上对其进行了测试。结果如下。
-------------------------------------------------
--- Qt MetaData Displayer by Daniel Pistelli
--- metadata of the class: QWidget
--- Signals:
0 - protected: void customContextMenuRequested(QPoint pos)
--- Slots:
1 - public: void setEnabled(bool)
2 - public: void setDisabled(bool)
3 - public: void setWindowModified(bool)
4 - public: void setWindowTitle(QString)
5 - public: void setStyleSheet(QString styleSheet)
6 - public: void setFocus()
7 - public: void update()
8 - public: void repaint()
9 - public: void setVisible(bool visible)
10 - public: void setHidden(bool hidden)
11 - public: void show()
12 - public: void hide()
13 - public: void setShown(bool shown)
14 - public: void showMinimized()
15 - public: void showMaximized()
16 - public: void showFullScreen()
17 - public: void showNormal()
18 - public: bool close()
19 - public: void raise()
20 - public: void lower()
21 - protected: void updateMicroFocus()
22 - private: void _q_showIfNotHidden()
--- Other Methods:
--- Properties:
0 - bool modal
flags: Readable | Designable | Scriptable | Stored | ResolveEditable
1 - Qt::WindowModality windowModality
flags: Readable | Writable | EnumOrFlag | StdCppSet | Designable | Scriptable |
Stored | ResolveEditable
2 - bool enabled
flags: Readable | Writable | StdCppSet | Designable | Scriptable |
Stored | ResolveEditable
3 - QRect geometry
flags: Readable | Writable | StdCppSet | Designable | Scriptable |
Stored | ResolveEditable
4 - QRect frameGeometry
flags: Readable | Designable | Scriptable | Stored | ResolveEditable
5 - QRect normalGeometry
flags: Readable | Designable | Scriptable | Stored | ResolveEditable
6 - int x
flags: Readable | Designable | Scriptable | Stored | ResolveEditable
7 - int y
flags: Readable | Designable | Scriptable | Stored | ResolveEditable
8 - QPoint pos
flags: Readable | Writable | Scriptable | ResolveEditable
9 - QSize frameSize
flags: Readable | Designable | Scriptable | Stored | ResolveEditable
10 - QSize size
flags: Readable | Writable | Scriptable | ResolveEditable
11 - int width
flags: Readable | Designable | Scriptable | Stored | ResolveEditable
12 - int height
flags: Readable | Designable | Scriptable | Stored | ResolveEditable
13 - QRect rect
flags: Readable | Designable | Scriptable | Stored | ResolveEditable
14 - QRect childrenRect
flags: Readable | Designable | Scriptable | Stored | ResolveEditable
15 - QRegion childrenRegion
flags: Readable | Designable | Scriptable | Stored | ResolveEditable
16 - QSizePolicy sizePolicy
flags: Readable | Writable | StdCppSet | Designable | Scriptable |
Stored | ResolveEditable
17 - QSize minimumSize
flags: Readable | Writable | StdCppSet | Designable | Scriptable |
Stored | ResolveEditable
18 - QSize maximumSize
flags: Readable | Writable | StdCppSet | Designable | Scriptable |
Stored | ResolveEditable
19 - int minimumWidth
flags: Readable | Writable | StdCppSet | Scriptable | ResolveEditable
20 - int minimumHeight
flags: Readable | Writable | StdCppSet | Scriptable | ResolveEditable
21 - int maximumWidth
flags: Readable | Writable | StdCppSet | Scriptable | ResolveEditable
22 - int maximumHeight
flags: Readable | Writable | StdCppSet | Scriptable | ResolveEditable
23 - QSize sizeIncrement
flags: Readable | Writable | StdCppSet | Designable | Scriptable |
Stored | ResolveEditable
24 - QSize baseSize
flags: Readable | Writable | StdCppSet | Designable | Scriptable |
Stored | ResolveEditable
25 - QPalette palette
flags: Readable | Writable | StdCppSet | Designable | Scriptable |
Stored | ResolveEditable
26 - QFont font
flags: Readable | Writable | StdCppSet | Designable | Scriptable |
Stored | ResolveEditable
27 - QCursor cursor
flags: Readable | Writable | Resettable | StdCppSet | Designable | Scriptable |
Stored | ResolveEditable
28 - bool mouseTracking
flags: Readable | Writable | StdCppSet | Designable | Scriptable |
Stored | ResolveEditable
29 - bool isActiveWindow
flags: Readable | Designable | Scriptable | Stored | ResolveEditable
30 - Qt::FocusPolicy focusPolicy
flags: Readable | Writable | EnumOrFlag | StdCppSet | Designable | Scriptable |
Stored | ResolveEditable
31 - bool focus
flags: Readable | Designable | Scriptable | Stored | ResolveEditable
32 - Qt::ContextMenuPolicy contextMenuPolicy
flags: Readable | Writable | EnumOrFlag | StdCppSet | Designable | Scriptable |
Stored | ResolveEditable
33 - bool updatesEnabled
flags: Readable | Writable | StdCppSet | Scriptable | Stored | ResolveEditable
34 - bool visible
flags: Readable | Writable | StdCppSet | Scriptable | Stored | ResolveEditable
35 - bool minimized
flags: Readable | Designable | Scriptable | Stored | ResolveEditable
36 - bool maximized
flags: Readable | Designable | Scriptable | Stored | ResolveEditable
37 - bool fullScreen
flags: Readable | Designable | Scriptable | Stored | ResolveEditable
38 - QSize sizeHint
flags: Readable | Designable | Scriptable | Stored | ResolveEditable
39 - QSize minimumSizeHint
flags: Readable | Designable | Scriptable | Stored | ResolveEditable
40 - bool acceptDrops
flags: Readable | Writable | StdCppSet | Designable | Scriptable |
Stored | ResolveEditable
41 - QString windowTitle
flags: Readable | Writable | StdCppSet | Designable | Scriptable |
Stored | ResolveEditable
42 - QIcon windowIcon
flags: Readable | Writable | StdCppSet | Designable | Scriptable |
Stored | ResolveEditable
43 - QString windowIconText
flags: Readable | Writable | StdCppSet | Designable | Scriptable |
Stored | ResolveEditable
44 - double windowOpacity
flags: Readable | Writable | StdCppSet | Designable | Scriptable |
Stored | ResolveEditable
45 - bool windowModified
flags: Readable | Writable | StdCppSet | Designable | Scriptable |
Stored | ResolveEditable
46 - QString toolTip
flags: Readable | Writable | StdCppSet | Designable | Scriptable |
Stored | ResolveEditable
47 - QString statusTip
flags: Readable | Writable | StdCppSet | Designable | Scriptable |
Stored | ResolveEditable
48 - QString whatsThis
flags: Readable | Writable | StdCppSet | Designable | Scriptable |
Stored | ResolveEditable
49 - QString accessibleName
flags: Readable | Writable | StdCppSet | Designable | Scriptable |
Stored | ResolveEditable
50 - QString accessibleDescription
flags: Readable | Writable | StdCppSet | Designable | Scriptable |
Stored | ResolveEditable
51 - Qt::LayoutDirection layoutDirection
flags: Readable | Writable | Resettable | EnumOrFlag | StdCppSet | Designable |
Scriptable | Stored | ResolveEditable
52 - bool autoFillBackground
flags: Readable | Writable | StdCppSet | Designable | Scriptable |
Stored | ResolveEditable
53 - QString styleSheet
flags: Readable | Writable | StdCppSet | Designable | Scriptable |
Stored | ResolveEditable
54 - QLocale locale
flags: Readable | Writable | Resettable | StdCppSet | Designable | Scriptable |
Stored | ResolveEditable
55 - QString windowFilePath
flags: Readable | Writable | StdCppSet | Designable | Scriptable |
Stored | ResolveEditable
--- Enums:
-------------------------------------------------
与“QWidget
”源代码相比,输出是 100% 正确的。
现在,我将向您展示如何查找类的元数据。让我们考虑“Counter
”类的分配。
.text:004012F2 mov eax, ds:_ZN7QObjectC2EPS_
.text:004012F7 mov [esp+88h+var_84], ecx
.text:004012FB mov [ebp+var_3C], 2
.text:00401302 call eax ; _ZN7QObjectC2EPS_
.text:00401304 mov eax, [ebp+var_4C]
.text:00401307 mov dword ptr [eax], offset virtual_ptrs
上面的汇编的最后一条指令设置了 vtable 指针。“Q_OBJECT
”类的 vtable 如下所示。
.rdata:00402158 off_402158 dd offset metaObject
.rdata:0040215C dd offset qt_metacast
.rdata:00402160 dd offset qt_metacall
“metaObject
”方法可用于获取元数据表。
.text:00401430 metaObject proc near
.text:00401430 push ebp
.text:00401431 mov eax, offset dword_406000 ; QMetaObject class layout
.text:00401436 mov ebp, esp
.text:00401438 pop ebp
.text:00401439 retn
.text:00401439 metaObject endp
“dword_406000
”指向 QMetaObject
的类布局,这是执行脚本所必需的。
检索类的元数据后的下一步是将方法(和属性)名称与其实际的反汇编代码关联起来。该脚本打印每个方法和属性的索引。要从索引获取方法地址,需要考虑 moc 生成的“qt_metacall
”方法。
int Counter::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
_id = QObject::qt_metacall(_c, _id, _a);
if (_id < 0)
return _id;
if (_c == QMetaObject::InvokeMetaMethod) {
switch (_id) {
case 0: valueChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
case 1: setValue((*reinterpret_cast< int(*)>(_a[1]))); break;
}
_id -= 2;
}
#ifndef QT_NO_PROPERTIES
else if (_c == QMetaObject::ReadProperty) {
void *_v = _a[0];
switch (_id) {
case 0: *reinterpret_cast< Priority*>(_v) = priority(); break;
}
_id -= 1;
} else if (_c == QMetaObject::WriteProperty) {
void *_v = _a[0];
switch (_id) {
case 0: setPriority(*reinterpret_cast< Priority*>(_v)); break;
}
_id -= 1;
} else if (_c == QMetaObject::ResetProperty) {
_id -= 1;
} else if (_c == QMetaObject::QueryPropertyDesignable) {
_id -= 1;
} else if (_c == QMetaObject::QueryPropertyScriptable) {
_id -= 1;
} else if (_c == QMetaObject::QueryPropertyStored) {
_id -= 1;
} else if (_c == QMetaObject::QueryPropertyEditable) {
_id -= 1;
} else if (_c == QMetaObject::QueryPropertyUser) {
_id -= 1;
}
#endif // QT_NO_PROPERTIES
return _id;
}
此方法可以解决方法名称和属性的 get/set 方法名称。
“qt_metacall
”的地址从 vtable 获取。在分析此方法代码之前,有必要考虑这个枚举。
enum Call {
InvokeMetaMethod, // 0
ReadProperty, // 1
WriteProperty, // 2
ResetProperty, // 3
QueryPropertyDesignable, // 4
QueryPropertyScriptable, // 5
QueryPropertyStored, // 6
QueryPropertyEditable, // 7
QueryPropertyUser // 8
};
现在,反汇编。
.text:004014D0 qt_metacall proc near
.text:004014D0
.text:004014D0 var_28 = dword ptr -28h
.text:004014D0 var_24 = dword ptr -24h
.text:004014D0 var_20 = dword ptr -20h
.text:004014D0 var_1C = dword ptr -1Ch
.text:004014D0 var_10 = dword ptr -10h
.text:004014D0 var_C = dword ptr -0Ch
.text:004014D0 var_8 = dword ptr -8
.text:004014D0 var_4 = dword ptr -4
.text:004014D0 arg_0 = dword ptr 8
.text:004014D0 arg_4 = dword ptr 0Ch
.text:004014D0 arg_8 = dword ptr 10h
.text:004014D0 arg_C = dword ptr 14h
.text:004014D0
.text:004014D0 push ebp
.text:004014D1 mov ebp, esp
.text:004014D3 sub esp, 28h
.text:004014D6 mov [ebp+var_C], ebx
.text:004014D9 mov eax, [ebp+arg_0]
.text:004014DC mov ebx, [ebp+arg_8]
.text:004014DF mov [ebp+var_8], esi
.text:004014E2 mov esi, [ebp+arg_4] ; esi = _c
.text:004014E5 mov [ebp+var_4], edi
.text:004014E8 mov edi, [ebp+arg_C]
.text:004014EB mov [ebp+var_10], eax
.text:004014EE mov [esp+28h+var_20], ebx
.text:004014F2 mov [esp+28h+var_1C], edi
.text:004014F6 mov [esp+28h+var_24], esi
.text:004014FA mov [esp+28h+var_28], eax
.text:004014FD call _ZN7QObject11qt_metacallEN11QMetaObject4CallEiPPv
.text:00401502 test eax, eax ; eax = _id
.text:00401504 mov ebx, eax
.text:00401506 js short loc_40151C ; _id < 0 ?
.text:00401508 test esi, esi
.text:0040150A jnz short loc_401530 ; _c != InvokeMetaMethod
.text:0040150C test eax, eax ; _id == 0 ?
.text:0040150E jz loc_4015B8
.text:00401514 cmp eax, 1 ; _id == 1 ?
.text:00401517 jz short loc_401590
如果 esi
寄存器的值不等于 0,那么它是一个 InvokeMetaMethod
调用。switch
由最后的指令表示。让我们跟随“_id == 0
”的情况。
.text:004015B8 loc_4015B8:
.text:004015B8 mov eax, [edi+4]
.text:004015BB mov edx, [ebp+var_10]
.text:004015BE mov eax, [eax]
.text:004015C0 mov [esp+28h+var_28], edx
.text:004015C3 mov [esp+28h+var_24], eax
.text:004015C7 call sub_401490
因此,我们现在可以确定“sub_401490
”实际上是“valueChanged
”信号。
.text:00401490 ; void __cdecl signal_valueChanged(int this, int newValue)
.text:00401490 signal_valueChanged proc near
.text:00401490
.text:00401490 var_18 = dword ptr -18h
.text:00401490 var_14 = dword ptr -14h
.text:00401490 var_10 = dword ptr -10h
.text:00401490 var_C = dword ptr -0Ch
.text:00401490 var_8 = dword ptr -8
.text:00401490 var_4 = dword ptr -4
.text:00401490 this = dword ptr 8
.text:00401490 newValue = dword ptr 0Ch
.text:00401490
.text:00401490 push ebp
.text:00401491 xor ecx, ecx
.text:00401493 mov ebp, esp
.text:00401495 lea eax, [ebp+newValue]
.text:00401498 sub esp, 18h
.text:0040149B mov [ebp+var_8], 0
.text:004014A2 mov edx, offset dword_406000
.text:004014A7 mov [ebp+var_4], eax
.text:004014AA lea eax, [ebp+var_8]
.text:004014AD mov [esp+18h+var_C], eax
.text:004014B1 mov eax, [ebp+this]
.text:004014B4 mov [esp+18h+var_10], ecx
.text:004014B8 mov [esp+18h+var_14], edx
.text:004014BC mov [esp+18h+var_18], eax
.text:004014BF call ds:_ZN11QMetaObject8activateEP7QObjectPKS_iPPv
.text:004014C5 leave
.text:004014C6 retn
现在该方法就可以理解了。可以使用相同的方法来解决属性的 get/set 方法名称。当然,对于一个文件的大型分析,可以编写一个小的 switch
块解析器,并将其与 Qt MetaData Parser 脚本结合使用,以便自动解析所有方法。但是,switch
块是高度依赖于平台和编译器的;因此,该脚本需要经常修改。此外,小程序有时会内联到“qt_metacall
”方法中,在编写此类脚本时应考虑这一点。
结论
这篇文章我只花了一天时间,写得很愉快。虽然这是一篇简单的文章,但有些人可能会觉得它有用,因为在我看来,Qt 框架将被越来越多的软件开发人员使用。