首页
社区
课程
招聘
[翻译]Qt内部机制及逆向
发表于: 2011-4-30 15:51 19679

[翻译]Qt内部机制及逆向

2011-4-30 15:51
19679

【翻译】Qt内部机制及逆向
原作者:Daniel Pistelli ;
翻   译:zouzhin
参加看雪有很长一段时间了,一直无所贡献,真是有愧各位同坛好友。前不久发了个Qt求助帖http://bbs.pediy.com/showthread.php?t=132491,没人回复,刚好看到了国外牛人Daniel Pistelli 写的《Qt Internals & Reversing》,就翻译一下给需要的人做个参考。由于E文水平不高,有不对的地方多包涵,高手请直接看原文http://ntcore.com/files/qtrev.htm。
开始之前,简单补充介绍一下Qt:
用官网的话说:Qt——一个开源跨平台应用程序和UI开发框架。优点有:
针对多个平台只需编写一次代码
使用 Qt 您只需编写一次应用程序和 UI,无须重新编写源代码,便可跨不同的桌面和嵌入式操作系统进行部署,既节省了时间又降低开发成本。
创建令人意想不到的用户体验
QQt 提供了应用程序生成块,包括庞大的可定制 widget 集合、图形画布、风格引擎和其他内容,您可用来生成新颖的用户界面。由于集成了 3D 图形、多媒体音频或视频、视觉效果、动画和自定义风格,使其在竞争中脱颖而出。
事半功倍(且倍道而进)
无论是使用全新的 Qt Creator 跨平台 IDE 还是仅是 Qt 本身,Qt 都易学易用。而且由于有了 Qt 模块化的类库,您可以更多地关注创新,无须在平台本身编码上花费过多时间,这样就可将软件快速推向市场。

在单一应用程序中混合网络和本地代码
由于 Qt 集成了 WebKit 网络渲染引擎, 您可以快速地(查看混合方式) 将网络内容和服务集成到本地应用程序中,还可以利用网络环境提供您的服务和功能,让您的用户在使用过程中留下深刻印象。

用本文原作者的话:“in my opinion, the Qt framework will be used more and more by software developers”.个人也觉得Qt是有其优势,有兴趣的可以利用一下。
—————————————————————以下正式开始———————————————————
(一)        内部机制
我见过的最严谨的C++框架就是Qt框架,Qt将C++带入了一个新的高度。Qt引入的信号(signal)和槽(slot)技术很有创意,其中一点就是,一个对象可以不要声明就可以调用其它对象的方法。为了运作信号和槽,Qt采用了动态化机制(dynamism)。这种动态化机制可以由Qt框架自动实现,也可以由开发人员通过QMetaObject类手动实现。有关信号和槽的内容可以参考http://doc.trolltech.com/4.7/signalsandslots.html。
我们看一个简单的信号和槽的例子:
// 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宏将括号中的内容封装成一个字符串,同时还附加一个ID号,如下所示:
#define SLOT(a)          "1"#a
#define SIGNAL(a)        "2"#a
所以,也可以直接这么写connect函数:
QObject::connect(&a, "2valueChanged(int)", &b, "1setValue(int)");
signals和slots是Qt关键词,可以在头文件中找到,只用于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方法,而slots宏可以是任意类型。
我们接下来看看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用于按照类名或它的某个基类名进行动态转换(dynamic cast)【Qt显然不依赖运行时类型检查(RTTI)】。qt_metacall通过索引调用内部信号和槽。以下是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;
};
需要注意的是它的d结构。d结构的第一个成员是一个QMetaObject类指针,指向父Qt元数据类。用户设计的类可以从多个类派生,但只能拥有一个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 }
};
如果在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结构的第二个成员是一个字符数值,表示类的字面元数据。第三个成员是一个无符号整型数组,这个数组是一个表,包含所有元数据的偏移、特征等等。所以,如果你想枚举一个类的信号和槽,那就应该遍历这个表,通过偏移量从stringdata数组中获得方法名。它也涉及到属性(properties)和枚举类型(enums)。第四个成员是以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调用。
下面是我们的Counter 类被moc生成的代码:
/****************************************************************************
** 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;
        }
另外一个需要关注的是valueChanged,它将调用QMetaObject的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, &currentSender);
            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, &currentSender, 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, &currentSender, 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相关的事件即被确定,同时对应的槽被调用。
至于动态调用(dynamic invokes),主要通过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_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
};
这个表不仅仅告诉了我们方法的数量,同时也给出了方法的偏移。下面是此结构的C++头:
struct QMetaObjectPrivate
{
    int revision;
    int className;
    int classInfoCount, classInfoData;
    int methodCount, methodData;
    int propertyCount, propertyData;
    int enumeratorCount, enumeratorData;
};
我们已经知道了方法的数量和偏移,接下来就是分析方法本身的数据。这个数据可以分为五个整数,其意义分别是:签名、参数、类型、标签、特征。签名(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.)。最后一个段——特征(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"
};
同样,我们也可以得到属性和枚举的数量和偏移。对属性而言,包含3个整数:名字(name)、类型(type)和特征(flags)。类型对应的是属性的类型,我们例子中就是Priority。特征值可分为:
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。Key指向当前条目的名字,value是此条目的实际值。
为了获得二进制文件的元数据信息,作者特意用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类,这里略去数百字)……
如何找到类的元数据呢?让我们看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
最后一个汇编指令设置了虚函数表指针。Q_OBJECT类的虚函数表类似于:
.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的地址可以从虚函数表获得。在分析此函数代码前,我们先看这个枚举:
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。考虑"_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块中的函数。但是要注意的是:switch块是平台和编译器相关的,因此脚本需要频繁调整。此外,一些小的方法可能被内联(inline)进qt_metacall方法中。


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

上传的附件:
收藏
免费 8
支持
分享
最新回复 (10)
雪    币: 138
活跃值: (475)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
2
恩,如此好文章为什么没人来支持~~

可怜了LZ的一片苦心
2011-6-7 09:03
0
雪    币: 1660
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
先标记一下,有时间好好学习一下
2011-6-7 09:15
0
雪    币: 8201
活跃值: (2701)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
mark一下
2011-6-7 09:20
0
雪    币: 135
活跃值: (719)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
好文章呀,辛苦了!
2011-6-9 20:43
0
雪    币: 54
活跃值: (43)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
好长的文章啊,留着慢慢看。。
2011-6-9 20:51
0
雪    币: 206
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
最近处理到的都是qt框架下开发的程序,唉
相关资料太少了
谢谢楼主无私奉献
2011-8-11 17:25
0
雪    币: 199
活跃值: (74)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
Up!!!!!!!!!!
2011-11-16 22:25
0
雪    币: 334
活跃值: (78)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
9
有源代码需要逆向么?
2011-11-28 14:19
0
雪    币: 38
活跃值: (52)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
10
好东西!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1
2011-12-18 09:56
0
雪    币: 107
活跃值: (404)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
确实是篇好文章...
2011-12-18 10:31
0
游客
登录 | 注册 方可回帖
返回
//