|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionHalf of the text of this article comes from my larger paper "Dynamic C++ Proposal". I decided that it would be useful to take the part about Qt internals, put it into another article, and extend it by adding a reversing part. Because of its nature, this is not the usual kind of article I write. In fact, I wrote the reversing part in less than a day. So, this is a very easy one. However, I think it is useful for people who need to reverse a Qt application and certainly wouldn't consider reading my other paper about Dynamic C++, which doesn't sound like a paper about Qt and, in fact, isn't a paper about Qt: the paragraph about Qt is only one among many others. Moreover, I haven't seen serious articles about this subject. The first thing which needs to be considered when reversing Qt applications is what Qt brought to the C++ language. Events (inside the Qt framework) are just virtual functions, so nothing new there. This is not a C++ reversing guide. What is new in Qt are signals and slots, which rely on the dynamism of the Qt framework. So, the first thing I'm going to show is how this dynamism works. The second part focuses on the reversing and, at that point, I will show how to obtain all the metadata we need when disassembling a " InternalsThe only serious C++ framework I've seen around is the Qt framework. Qt brought C++ to a new level. While the signals and slots technology introduced by Qt is very original, what really was interesting in the whole idea was that an object could call other object methods regardless of the object declaration. In order for signals and slots to work, dynamism was brought into C++. Well, of course, when using only signals and slots, the developer doesn't directly notice this behavior, it's all handled by the framework. However, this dynamism can be used by the developer through the I'm not going to explain the basics of signals and slots. The reader might check out the Qt documentation page. What I will do is to briefly show the internal workings of Qt dynamism. The current version of the Qt framework at the time I'm writing this paper is 4.4.3. Let's consider a simple signals and slots example: // 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;
}
The #define SLOT(a) "1"#a
#define SIGNAL(a) "2"#a
So, one might as well write: QObject::connect(&a, "2valueChanged(int)", &b, "1setValue(int)");
The Qt keywords " # 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
In fact, as you can see, even the /* 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:
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;
};
The important part of class ConvDialog : public QDialog, private Ui::ConvDialog
{
Q_OBJECT
Which makes the moc produce this code: const QMetaObject ConvDialog::staticMetaObject = {
{ &QDialog::staticMetaObject, qt_meta_stringdata_ConvDialog,
qt_meta_data_ConvDialog, 0 }
};
But, if const QMetaObject ConvDialog::staticMetaObject = {
{ &Ui::ConvDialog::staticMetaObject, qt_meta_stringdata_ConvDialog,
qt_meta_data_ConvDialog, 0 }
};
Which is wrong, because The second member of the " 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;
}
This function gets called only by the " And, here's the moc generated code for our /****************************************************************************
** 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
The Arguments are passed through a pointer to pointer array, and casted appropriately when calling the method. Using pointers, of course, is the only way to put all kinds of types in an array. Arguments start from position 1, because position 0 is reserved for the data to return. The signals and slots in the example are declared 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;
}
The other interesting method generated by the moc is 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);
}
This method does lots of stuff, including checking whether the current connection should be processed immediately or put into the event queue. If so, it calls the appropriate 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);
And, this tells us all we need to know about the internals of signals and slots. When calling the " The last part which needs to be discussed are dynamic invokes. The 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;
}
The method ID is retrieved through And, this should give you a general idea about Qt internals. ReversingWhen reversing, we want to use all the metadata Qt offers. In order to do that, let's reconsider the metadata table: 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
};
As we can notice, the table tells us not only the method count, but also the offset of the methods (10). Here's the C++ declared header of this structure: struct QMetaObjectPrivate
{
int revision;
int className;
int classInfoCount, classInfoData;
int methodCount, methodData;
int propertyCount, propertyData;
int enumeratorCount, enumeratorData;
};
This structure is contained in "qmetaobject.cpp"; like the rest of the information, we need to parse the metadata table. Before taking into account properties and enums, let's consider methods. We already have the method count and offset, what now has to be analyzed is the data of the methods itself. This data is subdivided into five integers, which represent, according to the moc: signature, parameters, type, tag, flags. The "signature" field is an offset into the string data, and refers a partial declaration (without return type) of the method: " 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
};
This is all we need to know about methods. Let's now consider enums and properties. For this purpose, I added one of each to the code example above: 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;
};
And the moc generates: 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"
};
Again, we have the properties and enums count and their offsets. Let's start with properties. The data of properties is made of three integers: name, type, flags. The "name" field refers, of course, to the name. The "type" field refers to the type of the property, in our case: "Priority". Finally, the "flags" field contains flags, and here's the list: 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
};
Let's now consider the enum: name, flags, count, data. We already know what the "name" field is by now. The "flags" field isn't used. The "count" field represents the numbers of items contained in the enum. The "data" field is an offset into the metadata table, and points to the items of the enum. Every item is represented by two integers: key, value. The "key" field points to the name of the current item, whereas "value" is the actual value of the item. In order to retrieve the metadata information from a binary file, I wrote a script for IDA. I wrote the script in Python, thanks to the IDAPython plug-in. Many reasons for using Python: writing the same code with IDC script would be way too much effort, and Python can be re-used even outside the IDA. The script extracts methods, properties, and enums from the layout of a " # ---------------------------------------------
# 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)
I'll explain how to reach the metadata of a class in a moment. The " .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
Sometimes, the layout is already set in the physical file: .data:00406000 dd 0 ; SuperData
.data:00406004 dd offset MetaStrings ; "Counter"
.data:00406008 dd offset MetaTable
In that case, the script can be directly launched at the address of the " -------------------------------------------------
--- 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,
};
-------------------------------------------------
Nice, isn't it? The signatures of the methods contain even argument names (when available). To check the quality of the script, I tested it on the larger class " -------------------------------------------------
--- 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:
-------------------------------------------------
The output is 100% right when compared against the " Now, I'll show you how to find the metadata of a class. Let's consider the allocation of the " .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
The last instruction of the assembly above sets the vtable pointer. The vtable of the " .rdata:00402158 off_402158 dd offset metaObject
.rdata:0040215C dd offset qt_metacast
.rdata:00402160 dd offset qt_metacall
The method " .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
" The next step, after retrieving the metadata of a class, is linking method (and property) names to their actual disassembled code. The script prints the index of each method and property. To obtain a method address from an index, it is necessary to consider the " 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;
}
This method can solve both method names and property get/set method names. The address of " enum Call {
InvokeMetaMethod, // 0
ReadProperty, // 1
WriteProperty, // 2
ResetProperty, // 3
QueryPropertyDesignable, // 4
QueryPropertyScriptable, // 5
QueryPropertyStored, // 6
QueryPropertyEditable, // 7
QueryPropertyUser // 8
};
Now, the disassembly: .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
.te | ||||||||||||||||||||