本文原作者为看雪论坛 Arcade 由于作者将原文章删除了,我在这里做个备份,方便以后查阅。
以下为文章原文:
水平有限,错误在所难免,求指点。
Qt 是跨平台的 C++ UI 库,不少的商业公司都采用 Qt 开发,有的时候我们需要去分析一下商业软件的实现, 但是 Qt 的程序有和普通的 Windows 程序有所区别,其界面上的控件都是没有句柄的,所以我们需要用特殊的办法去定位相关响应函数。
编译一份 Qt 官方自带的例子先:D:\Qt\Examples\Qt-5.6\widgets\widgets\calculator
Qt里面有2个比较重要的宏,一个是SLOT, 一个是SIGNAL。
1 2 3 4 |
D:\Qt\5.6\msvc2013\include\QtCore\qobjectdefs.h # define SLOT(a) "1"#a # define SIGNAL(a) "2"#a |
其实这2个宏只是个字符串的链接而已。例如 SLOT(HelloFunction), 其实变成 “1HelloFunction”,SIGNAL(HelloFunction) 其实变成 “2HelloFunction”。
其次要想使用这2个宏还需要在类的定义中加上 Q_OBJECT。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class Calculator : public QWidget { Q_OBJECT public: Calculator(QWidget *parent = 0); private slots: void digitClicked(); void unaryOperatorClicked(); void additiveOperatorClicked(); void multiplicativeOperatorClicked(); void equalClicked(); void pointClicked(); void changeSignClicked(); void backspaceClicked(); void clear(); void clearAll(); void clearMemory(); void readMemory(); void setMemory(); void addToMemory(); ...... //省略部分代码 }; |
Q_OBJECT 是个宏,其实帮你加上了以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/* qmake ignore Q_OBJECT */ #define Q_OBJECT \ public: \ Q_OBJECT_CHECK \ QT_WARNING_PUSH \ Q_OBJECT_NO_OVERRIDE_WARNING \ static const QMetaObject staticMetaObject; \ virtual const QMetaObject *metaObject() const; \ virtual void *qt_metacast(const char *); \ virtual int qt_metacall(QMetaObject::Call, int, void **); \ QT_TR_FUNCTIONS \ private: \ Q_OBJECT_NO_ATTRIBUTES_WARNING \ Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \ QT_WARNING_POP \ struct QPrivateSignal {}; \ QT_ANNOTATE_CLASS(qt_qobject, "") |
在 void Calculator::digitClicked() 函数下个断点,点个数字按钮,栈回溯大概如下。
Calculator::qt_static_metacall 在 moc_calculator.cpp 文件里面,这个文件是用 Moc 程序自动生成的,此文件其实是 Q_OBJECT 宏所加上的那几个函数的实现。
搜索 Calculator::staticMetaObject, 发现被 Calculator::staticMetaObject 引用。
Calculator::staticMetaObject 类型是 QMetaObject,QMetaObject 中包含的结构体变量如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
struct Q_CORE_EXPORT QMetaObject { ...... //省略成员函数 struct { // private data const QMetaObject *superdata; const QByteArrayData *stringdata; const uint *data; typedef void (*StaticMetacallFunction)(QObject *, QMetaObject::Call, int, void **); StaticMetacallFunction static_metacall; const QMetaObject * const *relatedMetaObjects; void *extradata; //reserved for future use } d; }; |
1 2 3 4 |
const QMetaObject Calculator::staticMetaObject = { { &QWidget::staticMetaObject, qt_meta_stringdata_Calculator.data, qt_meta_data_Calculator, qt_static_metacall, Q_NULLPTR, Q_NULLPTR} }; |
这里主要看下 staticMetaObject 里面的 stringdata, data, StaticMetacallFunction, 也就是 qt_meta_stringdata_Calculator.data, qt_meta_data_Calculator, qt_static_metacall 3个。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
struct qt_meta_stringdata_Calculator_t { QByteArrayData data[16]; char stringdata0[221]; }; #define QT_MOC_LITERAL(idx, ofs, len) \ Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \ qptrdiff(offsetof(qt_meta_stringdata_Calculator_t, stringdata0) + ofs \ - idx * sizeof(QByteArrayData)) \ ) static const qt_meta_stringdata_Calculator_t qt_meta_stringdata_Calculator = { { QT_MOC_LITERAL(0, 0, 10), // "Calculator" QT_MOC_LITERAL(1, 11, 12), // "digitClicked" QT_MOC_LITERAL(2, 24, 0), // "" QT_MOC_LITERAL(3, 25, 20), // "unaryOperatorClicked" QT_MOC_LITERAL(4, 46, 23), // "additiveOperatorClicked" QT_MOC_LITERAL(5, 70, 29), // "multiplicativeOperatorClicked" QT_MOC_LITERAL(6, 100, 12), // "equalClicked" QT_MOC_LITERAL(7, 113, 12), // "pointClicked" QT_MOC_LITERAL(8, 126, 17), // "changeSignClicked" QT_MOC_LITERAL(9, 144, 16), // "backspaceClicked" QT_MOC_LITERAL(10, 161, 5), // "clear" QT_MOC_LITERAL(11, 167, 8), // "clearAll" QT_MOC_LITERAL(12, 176, 11), // "clearMemory" QT_MOC_LITERAL(13, 188, 10), // "readMemory" QT_MOC_LITERAL(14, 199, 9), // "setMemory" QT_MOC_LITERAL(15, 209, 11) // "addToMemory" }, "Calculator\0digitClicked\0\0unaryOperatorClicked\0" "additiveOperatorClicked\0" "multiplicativeOperatorClicked\0" "equalClicked\0pointClicked\0changeSignClicked\0" "backspaceClicked\0clear\0clearAll\0" "clearMemory\0readMemory\0setMemory\0" "addToMemory" }; #undef QT_MOC_LITERAL static const uint qt_meta_data_Calculator[] = { // content: 7, // revision 0, // classname 0, 0, // classinfo 14, 14, // methods 0, 0, // properties 0, 0, // enums/sets 0, 0, // constructors 0, // flags 0, // signalCount // slots: name, argc, parameters, tag, flags 1, 0, 84, 2, 0x08 /* Private */, 3, 0, 85, 2, 0x08 /* Private */, 4, 0, 86, 2, 0x08 /* Private */, 5, 0, 87, 2, 0x08 /* Private */, 6, 0, 88, 2, 0x08 /* Private */, 7, 0, 89, 2, 0x08 /* Private */, 8, 0, 90, 2, 0x08 /* Private */, 9, 0, 91, 2, 0x08 /* Private */, 10, 0, 92, 2, 0x08 /* Private */, 11, 0, 93, 2, 0x08 /* Private */, 12, 0, 94, 2, 0x08 /* Private */, 13, 0, 95, 2, 0x08 /* Private */, 14, 0, 96, 2, 0x08 /* Private */, 15, 0, 97, 2, 0x08 /* Private */, // slots: parameters QMetaType::Void, QMetaType::Void, QMetaType::Void, QMetaType::Void, QMetaType::Void, QMetaType::Void, QMetaType::Void, QMetaType::Void, QMetaType::Void, QMetaType::Void, QMetaType::Void, QMetaType::Void, QMetaType::Void, QMetaType::Void, 0 // eod }; void Calculator::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) { if (_c == QMetaObject::InvokeMetaMethod) { Calculator *_t = static_cast<Calculator *>(_o); Q_UNUSED(_t) switch (_id) { case 0: _t->digitClicked(); break; case 1: _t->unaryOperatorClicked(); break; case 2: _t->additiveOperatorClicked(); break; case 3: _t->multiplicativeOperatorClicked(); break; case 4: _t->equalClicked(); break; case 5: _t->pointClicked(); break; case 6: _t->changeSignClicked(); break; case 7: _t->backspaceClicked(); break; case 8: _t->clear(); break; case 9: _t->clearAll(); break; case 10: _t->clearMemory(); break; case 11: _t->readMemory(); break; case 12: _t->setMemory(); break; case 13: _t->addToMemory(); break; default: ; } } Q_UNUSED(_a); } |
qt_meta_data_Calculator 实际对应的类是 QMetaObjectPrivate。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
struct QMetaObjectPrivate { enum { OutputRevision = 7 }; // Used by moc, qmetaobjectbuilder and qdbus int revision; int className; int classInfoCount, classInfoData; int methodCount, methodData; int propertyCount, propertyData; int enumeratorCount, enumeratorData; int constructorCount, constructorData; //since revision 2 int flags; //since revision 3 int signalCount; //since revision 4 // revision 5 introduces changes in normalized signatures, no new members // revision 6 added qt_static_metacall as a member of each Q_OBJECT and inside QMetaObject itself // revision 7 is Qt 5 ... //省略的其他代码 }; |
可以看到 qt_static_metacall 函数的 _id 次序和 qt_meta_stringdata_Calculator 的 stringdata 的字符串信息是一一对应的。所以我们就可以通过 qt_meta_stringdata_Calculator 的字符串,定位到具体的实现。
以 Genymotion 2.4.0 版本。在注册对话框,如果没有输入符合格式的注册码,注册按钮是灰色的,不可用:
以正向编码猜测 注册对话框的某个 slot 函数 绑定了 textview 的 textchanged singal。
hooper 查找 2textchanged,发现多次字符串引用:
一一排除,得到 1serialChanged(), 去掉1, 查找 serialChanged(), 来到 0x00000001000cbee0
1 2 3 4 5 6 7 8 9 |
0x00000001000cbee0 db "AboutDialog", 0 ; XREF=sub_1000bc430+29 0x00000001000cbeec db 0x00 ; '.' 0x00000001000cbeed db "registerLicense()", 0 0x00000001000cbeff db "serialChanged()", 0 0x00000001000cbf0f db "hideRegistrationStatus()", 0 |
查找对 0x00000001000cbee0 引用,来到 sub_1000bc430,伪代码如下:
对比 Calculator 类,其对应的是 Calculator::qt_metacast。
在 sub_1000bc430 函数,上下3个函数去找,如何有间断的 数字判断跳转语句,得到 sub_1000bc4a0 ,也就是对应的 qt_static_metacall 函数了。(标准的是继续通过字符串的引用去找,这边是用了取巧的办法)。
qt_static_metacall 函数伪代码如下:
判断注册按钮是否可用的函数伪代码如下(也就是 sub_10005bb20 AboutDialog::serialChanged 函数)
点击注册按钮的函数伪代码如下,(也就是 sub_10005ed40 AboutDialog::registerLicense)
简单 patch 了一下 player (注意不是 genymotion),从字符串入手(例如Network),免费版本和付费版本的按钮在初始化时候传递的状态参数不同,定位以下2处代码,patch 一下强制所有按钮可用。
修改 0x00000001000c8a00 指令为 xor edx , edx 补上需要的 nop。
修改 0x00000001000c9a64 指令为 mov esi, 1 , 补上需要的 nop
开启付费功能后
本例的例子是很简单的,实际中 Qt 中还有有包含 properties, enums 等情况,包含 slot 或者 singal 函数也可以带参数的,需要再进行分析,可以自己写个包含各种属性的 Qt 测试程序来辅助练习。
- 本文固定链接: https://blog.kuoruan.com/104.html
- 转载请注明: Index 于 扩软博客 发表