【机翻】 使用 Ghidra 逆转基于 C++ 的 Qt 应用

[Reversing C++, Qt based applications using Ghidra 使用 Ghidra 逆转基于 C++ 的 Qt 应用


原文链接

Gianluca Pacchiella 吉安卢卡·帕基耶拉


This post is going to be too ambitious probably: I want to introduce you to reversing C++ code, applying this knowledge in particular to Qt applications and since we are at it, explaining some ghidra scripting to automate the process.
这篇帖子可能太过雄心勃勃:我想向你介绍如何逆转 C++ 代码,特别是将这些知识应用于 Qt 应用程序,既然说到这里,就解释一些 Ghidra 脚本来自动化流程。

Here a little table of contents to jump where needed
这里有个目录,方便你在需要的地方跳转

Introduction to reversing C++ code C++代码反转介绍

Reversing C++ code is a little more involved with respect to reversing simple C code, but at end of the day it's not impossible. C++ is sure a more "complicated language" if compared with C, in particular for the possibility to instantiate classes, that are structs on steroid but also for the more advanced "features" like polymorphism, inheritance and templating.
逆转 C++ 代码相比简单 C 代码更复杂一些,但归根结底并非不可能。如果与 C 相比,C++ 无疑是更“复杂的语言”,特别是因为它可以实例化类, 这些类是结构 S 的强化版,同时还有更高级的“特性”,比如多态性、继承和模板。

But, also after all that, remember that however a language is defined, objects will be layed out in memory (hopefully) in continous chunks and methods will be always called as usual, maybe with an extended usage of function pointers.
但同样,记住无论语言如何定义,对象都会在内存中(希望如此)以连续的块形式排列,方法也会像往常一样调用,可能会使用函数指针。

If you want to know in detail how the ABI for the C++ language works, here the specification; this covers memory layout for C++ data objects (predefined, user-defined and compiler-generated, e.g. virtual tables) but also function calling interfaces, exception handling interfaces, global naming, and various object code conventions. Probably I'll expand a little more later when needed.
如果你想详细了解 C++ 语言的 ABI 是如何工作的, 这里是规范 ;这涵盖了 C++ 数据对象的内存布局(预定义、用户自定义和编译器生成的 ,例如虚拟表 ),还包括函数调用接口、异常处理接口、全局命名以及各种目标代码约定。可能需要时我可能会稍微详细说明。

Note: if, like me, sometimes you try to compile some code to double check that makes sense of your expectations, make sure to strip the binary and use some optimization (like -O2) so to have the compiler to "simplify" a bunch of nested constructors calls. In case you need something quick to play with take in mind that compiler explorer exists.
注意: 如果你像我一样,有时试图编译代码以确认符合你的期望,务必剔除二进制并使用优化(比如 -O2),这样编译器才能“简化”一堆嵌套的构造调用。如果你需要快速玩玩,记得编译器资源管理器存在。

In the following sections I'm going to explore the low-level "implementation" of the building blocks of C++, it's assumed that you know how reverse generic C code, what follows builds on that.
在接下来的章节中,我将探讨 C++ 构建模块的底层“实现”,假设你知道如何反向泛化 C语言代码,接下来的内容就是在此基础上发展的。

Classes layout 布局类别

The first thing that differentiate C++ from C is the possibility to instantiate classes: a class is an encapsulation of data types and functions that act on them; this last point has some complication given from the virtual keyword: it allows for runtime polymorphism, i.e., it tells the compiler to resolve the function call at runtime (read more here).
C++C 的第一个区别在于可以实例化类:类是数据类型和作用于它们的函数的封装;这一点因虚拟关键词而带来一些复杂性:它允许运行时多态性,即告诉编译器在运行时解析函数调用(详见此处 )。

In order to allow this to happen, the compiler generates a so called virtual table, containing an array of function pointers, and place its address at the start of the memory region allocated for the instance of the class.
为了实现这一点,编译器生成一个所谓的虚拟表 ,包含函数指针数组,并将其地址放置在为类实例分配的内存区域起始位置。

The data associated with the classed is placed just after that; obviously if the class has not virtual functions defined, no virtual table is necessary and only the data will be present. Another complication regarding classes is the possibility of having an inheritance tree: a class can derive from one or more classes and the compiler must generate a "blueprint" accordingly.
与分类相关的数据紧随其后;显然,如果类没有定义虚拟函数,则不需要虚拟表,只有数据存在。关于类的另一个复杂性是可能存在继承树:一个类可以源自一个或多个类,编译器必须相应生成“蓝图”。

What does it mean? if you have a class defined as follow
这意味着什么?如果你有一个定义为 的类,如下

class A {
    memberType1 memberA1;
    ...
    memberTypeN memberAN;

    virtual methodA1();
    ...
    virtual methodAM();
};

class B {
    memberType1 memberB1;
    ...
    memberTypeN memberBP;

    virtual methodB1();
    ...
    virtual methodBQ();
};

class C : A, B {
    memberType1 memberC1;
    ...
    memberTypeR memberCR;

    virtual methodC1();
    ...
    virtual methodCS();
}

the layout in memory will be something like the following (where the offset is loosely intended as the index in the "right" array describing the object in memory)
内存中的布局大致如下(偏移量大致指“正确”数组中描述内存对象的索引)

offset 偏移 description 描述
0 vtable C:A vtable C:A
1 memberA1 成员 A1
... ...
AN memberAN 一名成员
AN + 1 THE + 1 vtable B:A vtable B:A
AN + 2 THE + 2 memberB1 成员 B1
... ...
AN + 1 + BP AN+1+BP memberBP 成员 BP
AN + BP + 2 AN+BP+2 memberC1 成员 C1
... ...
AN + BP + 1 + CR AN+BP+1+CR memberCR 成员 CR

Obviously, depending on the size of a given datatype, could be present padding.
显然,根据数据类型的大小,可能会存在现在填充。

Note that the virtual methods of C will be appended to the virtual table of the first parent's
注意,C 的虚拟方法将附加到第一个父的虚拟表中

ghidra has the possibility to define classes in the "Symbol tree" panel; when you define a class you are defining two things, a GhidraClass instance that is a Namespace and a struct associated with it (it would be hopefully clearer in the section regarding ghidra).
Ghidra 可以在“符号树”面板中定义类;当你定义一个类时,你定义的是两个东西,一个是 GhidraClass 实例(命名空间 ),另一个是与之关联的结构体(希望在关于 Ghidra 的部分会更清楚)。

There is no support for virtual tables out of the box, you have to manually add the first entry of the structure with an array of pointers.
开箱即用不支持虚拟表,你必须手动添加结构的第一个条目和指针数组。

Methods 方法

The virtual functions contained in the virtual tables are a particular case of methods associated with a class instance, a large number of methods are not virtual and must be deducted from their behaviour.
虚拟表中包含的虚拟函数是与类实例关联的方法的特殊情况,许多方法并非虚拟的,必须从其行为中推导出来。

In particular methods are functions "attached" to an instance of a class (if not static) and behave like normal functions in the C language if not for the implicit parameter named this that is possible to access when you are inside the method; when you look at the code when reversing you'll have this parameter passed (usually) as a first argument with type equal to a pointer to the struct associated with the class. In ghidra is possible to indicate that the function belongs to a class moving the function to the Namespace of the class (implicitely created with the class?) via the "rename" functionality, using a scheme like <class name>::<function name> or moving the function by hand into the namespace in the "Symbol tree" panel.
具体方法是“附加”到类实例上的函数(如果不是静态的),如果没有隐式 ``该参数命名为该参数,当你在 方法;当你在反向时查看代码时,会有这个参数 通常作为第一个参数传递,类型为指向对应结构的指针 和班级一起。在 Ghidra 可以表示该函数属于一个类 将函数移动到 类的命名空间 (是否是用类隐式创建的?)通过“重命名”功能,使用类似 <class name>::<function name> 这样的方案 或者手动将函数移动到“符号树”面板的命名空间中。

Note: after you assigned a namespace to a function, when you rename the function itself, you won't see the namespace prepended to function's name anymore but it's on the namespace's drop down menu just below (of course); if you want to change the namespace the function belongs you have to select from the drop down menu "Global" and prepend the namespace and the two colons to the function's name. I think also that ghidra accepts nested namespaces but the class handling of that is tricky (I must investigate further).
注意: 在你为函数分配命名空间后,当你重新命名函数本身时,你将不再看到该命名空间在函数名称前面,但它会出现在命名空间的下拉菜单下方(当然);如果你想更改函数所属的命名空间,必须从下拉菜单中选择“全局”,并在函数名称前加上命名空间和两个冒号。我也认为 ghidra 接受嵌套命名空间,但类的处理比较棘手(我得进一步调查)。

Once you have done that you can "Edit function signature" and indicate __thiscall for the calling convention: automatically it will assign the first argument in the right way (probably there is a bug: it doesn't change only the first argument but prepend it to the list causing a wrong number of arguments assigned to the function)·
完成后,你可以“编辑函数签名”并表示 __thiscall 调用约定:它会自动正确地分配第一个参数(可能存在 bug:它不仅更改第一个参数,而是将其置于列表前,导致函数分配的参数数量错误)·

This is not valid if the return value is not "simple": from the specification
如果返回值不是“简单”的:即来自规范

If the return type is a class type that is non-trivial for the purposes of
calls, the caller passes an address as an implicit parameter. The callee
then constructs the return value into this address. bla bla bla

this means that you have to add another argument before this but it's pretty simple to catch once you know about it and you are pretty sure the class the method belongs because it's not returning nothing and you know it must return something. Also, in this case change the return value to void, in same cases not doing that can generate confusion with ghidra.
这意味着你必须在此之前再添加一个参数,但一旦你知道了这个参数,并且你很确定该方法属于的类,这个过程就很容易捕捉,因为它没有返回任何东西,你知道它一定会返回某些东西。此外,在这种情况下,将返回值改为虚无,同样情况下不这样做可能会导致与基德拉混淆。

Note: it seems that __thiscall is generated automatically in Ghidra/Features/Decompiler/src/decompile/cpp/architecture.cc from the <default_proto> entry in the <architecture>.cspec.
注意: 似乎 __thiscall 是自动生成的 Ghidra/Features/Decompiler/src/decompile/cpp/architecture.cc 来自 %3Cdefault_proto> 条目在 <architecture>.cspec 中。

Regarding the handling of C++ methods, the signature of external functions (i.e. functions that are imported from external libraries) is deducted from the "mangled" name of the imported symbol: to allow for polymorphism, that is functions with the same name but different signature, the "real" name of a function encodes the argument that the function receives (but not the return value). I didn't investigate but it seems that ghidra doesn't know if a method belongs to a class or if it's static unless are constructors (i.e. named ClassName::ClassName), so be aware to this fact when you see analyze code involving imported functions.
关于 C++ 方法的处理,外部函数(即从外部库导入的函数)的签名是从导入符号的“扭曲”名称中推导出来的:为了实现多态性,即同名但签名不同的函数,函数的“实”名编码函数接收的参数(但不包含返回值)。我没深入研究,但 ghidra 似乎不知道某个方法属于类还是静态的,除非是构造函数(比如名为 ClassName::ClassName),所以当你看到分析代码涉及导入函数时,要注意这一点。

Note: usually ghidra automatically "translate" to the correct name for a function, but you have to indicate the right compiler spec at import time; how do you know which is the correct one? you have to guess my friend.
注意: 通常 ghidra 会自动“翻译”为函数的正确名称,但导入时必须说明正确的编译器规范;你怎么知道哪个才是正确的?你得猜猜,我的朋友。

Note: this is a general advise regarding reversing functions: read about the calling convention and parameters passing convention of the architecture you are working with, because when you see something non-sensical probably ghidra hasn't guessed something right or maybe is a bug (for example in my experience, when a long long is passed as argument in ARM, it uses two registers, starting from an even register index, so if it's the second argument and the first is an int, you have r0 for the first argument and r2, r3 for the second, leaving r1 untouched).
注意: 这是关于反转函数的一个通用建议:阅读你所使用的架构的调用约定和参数传递惯例,因为当你看到一些不合理的内容时,通常是 ghidra。 没猜对或者可能是个 bug(比如根据我的经验, 当在 ARM 中作为参数传递 long long 时,它使用两个寄存器,从偶数寄存器索引开始,所以如果是第二个参数且第一个变量是整数 ,第一个参数是 r0,r2r3 ``对于第二个,R1 保持不动)。

Since I'm writing this post after an activity of reversing involving ARM binaries, the examples below involve that architecture, to see how the calling convention is defined for it see Procedure Call Standard for the ARM® Architecture and The ARM-THUMB Procedure Call Standard.
因为我写这篇帖子是在一次涉及 ARM 的反转操作之后 二进制,下面的示例涉及该架构,以了解调用方式 该惯例对其有定义,参见 ARM®架构的过程调用标准 以及 ARM-THUMB 程序呼叫标准

Templates 模板

A feature of C++ are the so called templates: pratically is possible to define an "abstract" implementation of an algorithm/data having as a free "variable" a data type; this means that templates generate code "inline", not in external libraries, the only thing that you will see will be some calls to weird named functions like _List_node_base::_M_hook() that are the internal implementation of the complex data structure the code is the implementation of. The idea is that usually a data structure has an internal "private" implementation (Pimpl?) and so you won't see method of this class but the internal one: in the section about Qt you'll see an example of that with the QList/QListData class.
C++ 的一个特性是所谓的模板 :实际上可以定义一个以自由“变量”为数据类型为自由变量的算法/数据的“抽象”实现;这意味着模板 S 生成的代码是“内联”的,而不是在外部库中, 你唯一会看到的是调用一些奇怪的命名函数,比如 _List_node_base::_M_hook() 是复杂数据结构的内部实现,代码是 实现。 其理念是,通常数据结构内部有一个“私有” 实现(Pimpl?),因此你不会看到该类的方法,而是 内部的:在关于 Qt 的部分,你会看到一个例子,包括 QList/QListData 类。

So you should probably tries to get a look at widely used data structure in the STL and see their implementation to have the feeling of what you should expect, for example shared_ptr is a good candidate because is a data type you'll probably encounter in reversing.
所以你最好看看 STL 中广泛使用的数据结构,看看它们的实现,这样你就能大致了解应该期待什么 ,比如 shared_ptr 是个好候选人,因为 是你在反向时可能会遇到的数据类型。

Take in mind that templates can also take a variable number of arguments (they are called variadic template) and are used a lot in Qt. This is more of a hint if you are going to read a lot of C++ source code, that if you are not used to it can be overwhelming at first.
请记住,模板也可以接受变量数量的变量(称为变数模板 ),并且在 Qt被广泛使用。如果你打算大量阅读 C++ 源代码,这更多是提示,如果你不习惯,刚开始可能会感到不知所措。

Qt source code Qt 源代码

Before passing to Qt let me give you some tips to navigate code you want to understand: first of all you need a tool to navigate the code, and for studying this library I used sourcetrail but bad enough it's not maintained anymore 😦.
在交给 Qt 之前,让我给你一些你想理解的代码导航建议:首先你需要一个工具来导航代码,而在学习这个库时,我用了 sourcetrail 但已经够糟糕了,:(已经不再维护了。

In order to use it you have to obtain a "database" containing the list of files and you can bake one compiling the library from source via some extra tools; first of all you have to configure it (the process is a little tricky): after you have cloned it
要使用它,你必须获得一个包含文件列表的“数据库”,并且可以通过一些额外工具从源代码编译库来烘焙一个;首先你得配置它(过程有点复杂):在克隆完成后

$ ./init-repository -f --module-subset=default,-qtwebengine
$ git submodule foreach --recursive "git clean -dfx" && git clean -dfx
$ mkdir qt5-build/ && cd qt5-build
$ ../configure -developer-build -opensource -nomake examples -nomake tests --recheck-all -confirm-license \
    -fontconfig -sql-sqlite -no-sql-odbc -system-freetype -qt-zlib -qt-libpng \
    -qt-libjpeg -no-compile-examples  -no-opengl -no-feature-concurrent \
    -no-feature-xml -no-feature-testlib \
    -skip qt3d -skip qtactiveqt -skip qtandroidextras -skip qtcanvas3d \
    -skip qtcharts -skip qtconnectivity -skip qtdatavis3d -skip qtdoc \
    -skip qtgamepad -skip qtgraphicaleffects -skip qtimageformats \
    -skip qtlocation -skip qtmacextras -skip qtmultimedia -skip qtnetworkauth \
    -skip qtpurchasing -skip qtquickcontrols -skip qtquickcontrols2 \
    -skip qtremoteobjects -skip qtscript -skip qtscxml -skip qtsensors \
    -skip qtserialbus -skip qtserialport -skip qtsvg -skip qtspeech \
    -skip qttools -skip qttranslations -skip qtvirtualkeyboard -skip qtwayland \
    -skip qtwebchannel -skip qtwebsockets -skip qtwebview -skip qtwinextras \
    -skip qtx11extras -skip qtxmlpatterns -skip qtwebengine

Then you can build and generate the database that can be opened with sourcetrail using bear
然后你可以构建并生成可以打开的数据库 使用 BearSourcetrail

$ bear -- make -j2

Remember that any time you start bear it overwrites the compile_commands.json generated file, you can use the --append flag to avoid that.
记住,每次你开始 Bear 时,它都会覆盖 compile_commands.json 生成文件时,你可以用 --append 标志来避免这种情况。

Note: This process is not perfect, for example, I was not able to build completely the library, failed at some point but the generated database was usable. Another point where the process can fail is loading into sourcetrail, if the database is too big it's possible to have a crash 😃 this is the reason I removed a lot of stuff during the configuration steps.
注: 这个过程并不完美,比如我无法建造 完全是库,某个时候失败了,但生成的数据库是 可用。进程可能失败的另一个点是加载到 Sourcetrail,如果数据库太大,可能会导致崩溃:)这也是我在配置步骤中删除了很多内容的原因。

For example a reason of failure is the compiler: for me, only using g++-8 I was able to start the compilation
例如,失败的原因之一是编译器:我只有用 g++-8 才能启动编译

diff --git a/mkspecs/common/g++-base.conf b/mkspecs/common/g++-base.conf
index c337696304..2df381a399 100644
--- a/mkspecs/common/g++-base.conf
+++ b/mkspecs/common/g++-base.conf
@@ -15,7 +15,7 @@ QMAKE_CC                = $${CROSS_COMPILE}gcc
 QMAKE_LINK_C            = $$QMAKE_CC
 QMAKE_LINK_C_SHLIB      = $$QMAKE_CC

-QMAKE_CXX               = $${CROSS_COMPILE}g++
+QMAKE_CXX               = $${CROSS_COMPILE}g++-8

 QMAKE_LINK              = $$QMAKE_CXX
 QMAKE_LINK_SHLIB        = $$QMAKE_CXX

An alternative is the online code viewer Woboq, you don't have to do anything and it's ready to navigate but you don't have the visual cues about attributes and methods that you have with sourcetrail.
另一个选择是在线代码查看器 Woboq,你无需做任何操作,它随时可以导航,但你没有 sourcetrail 那样的属性和方法的视觉提示。

Other ways 其他方式

A little overview of Qt datatypes Qt 数据类型的简要概述

Before starting reversing it's necessary to have a minimal understanding of the data types used in this library. Let's start with strings: Qt uses a data type name QString; as I said above, generally "complicated" data structures "hide" the data and let you interact via methods, in the case of the QString you have an internal pointer typedefed to be a subclass of QArrayData, a generic container(?) that handles also the reference counting of the object itself: here some snippet of the source code
在开始逆转之前,需要对该库中使用的数据类型有基本了解。我们先从字符串说起:Qt 使用名为 QString 的数据类型;正如我上面说的,通常“复杂”的数据结构“隐藏”数据,并允许你通过方法进行交互,比如 QString 你有一个内部指针 TypeDefEd 作为 QArrayData 的子类,QArrayData 是一个通用容器(?),同时处理对象本身的引用计数:这里是源代码的一段摘录

struct Q_CORE_EXPORT QArrayData
{
    QtPrivate::RefCount ref;
    int size;
    uint alloc : 31;
    uint capacityReserved : 1;
    qptrdiff offset; // in bytes from beginning of header
    ...
    static const QArrayData shared_null[2];
    static QArrayData *sharedNull() noexcept { return const_cast<QArrayData*>(shared_null); }
};

template <class T>
struct QTypedArrayData : QArrayData { ... };

typedef QTypedArrayData<ushort> QStringData;

class Q_CORE_EXPORT QString
{
    public:
        typedef QStringData Data;
        ...
        Data *d;
        ...
};

From this convoluted construction we can deduce that the QString class is simply a wrapper around a pointer of type Data (that by a couple of definition later) is linked to QArrayData. This last data type contains all the information to dereference the data contained into it:
通过这种复杂的构造,我们可以推断 QString 类只是包裹着类型为 Data 的指针(根据几个定义后),该指针与 QArrayData 相关联。最后一个数据类型包含所有用于去引用其数据的信息:

  • ref is the reference counter, when 0 the object can be removed from memory, if -1 means the object is static
    ref 是参考计数器,当 0 时可以从内存中移除对象,如果 -1 表示对象是静态的
  • size is the data size
    大小是数据大小
  • alloc how many bytes are actually allocated
    alloc 实际分配的字节数
  • capacityReserved I don't know
    容量保留我不知道
  • offset is where the actual data is located with respect to the start of the struct
    偏移量是实际数据相对于结构体起始点的位置

Note: QArrayData is an example of packing and alignment of structs.
注意:QArrayData结构体 s 打包和对齐的一个例子。

(gdb) ptype /o QArrayData
/* offset    |  size */  type = struct QArrayData {
/*    0      |     4 */    class QtPrivate::RefCount {
                             public:
/*    0      |     4 */        QBasicAtomicInt atomic;
                               /* total size (bytes):    4 */
                           } ref;
/*    4      |     4 */    int size;
/*    8: 0   |     4 */    uint alloc : 31;
/*   11: 7   |     4 */    uint capacityReserved : 1;
/* XXX  4-byte hole  */
/*   16      |     8 */    qptrdiff offset;
                           static const QArrayData shared_null[2];

                           /* total size (bytes):   24 */
                         }

Read more here: "The Lost Art of Structure Packing".
点击此处阅读更多:《 失落的结构包装艺术 》。

The interesting thing is that when you initialize an empty QString, the d attribute is built from shared_null
有趣的是,当你初始化一个空的 QString 时,d 属性是基于 shared_null

inline QString::QString() noexcept : d(Data::sharedNull()) {}

so when you see something like this in the decompiler panel
所以当你在反编译器面板中看到类似的情况时

somevariable = QArrayData::shared_null;

it's propably initializing an empty string.
它很可能是在初始化一个空字符串。

An interesting application of the internal of QString regards string literals implemented via QStringLiteral: observe this macro
QString 内部的一个有趣应用是通过以下方式实现的字符串文字 QString 字面意义 :观察这个宏

#define Q_REFCOUNT_INITIALIZE_STATIC { Q_BASIC_ATOMIC_INITIALIZER(-1) }

#define QT_UNICODE_LITERAL(str) u"" str
#define QStringLiteral(str) \
    ([]() noexcept -> QString { \
        enum { Size = sizeof(QT_UNICODE_LITERAL(str))/2 - 1 }; \
        static const QStaticStringData<Size> qstring_literal = { \
            Q_STATIC_STRING_DATA_HEADER_INITIALIZER(Size), \
            QT_UNICODE_LITERAL(str) }; \
        QStringDataPtr holder = { qstring_literal.data_ptr() }; \
        return QString(holder); \
    }()) \
    /**/

#define Q_STATIC_STRING_DATA_HEADER_INITIALIZER_WITH_OFFSET(size, offset) \
    { Q_REFCOUNT_INITIALIZE_STATIC, size, 0, 0, offset } \
    /**/

#define Q_STATIC_STRING_DATA_HEADER_INITIALIZER(size) \
    Q_STATIC_STRING_DATA_HEADER_INITIALIZER_WITH_OFFSET(size, sizeof(QStringData)) \
    /**/

For a more involved explanation see "QStringLiteral explained".
更详细的解释请参见“QStringLiteral explained”。

Why am I telling you this? simply because messages inside the application (that are using QString) are not going to have references to the chars of the string itself but to the QArrayData that points to it; after a while you will be able to see sequences of 0xffffffff (-1) as indication of static objects allocations. Take in mind that could be some data not "string"-related since the real data type should be QTypedArrayData that is a template but that fact is "lost in translation" in the binary.
我为什么要告诉你这些?因为应用程序内部的消息(使用 QString 的)不会引用字符串本身的字符 s,而是引用 QArrayData 指出了这一点;过一段时间后,你会看到 0xffffffff-1)序列,作为静态对象分配的指示。请记住,这可能涉及一些与“字符串”无关的数据,因为实际数据类型应该是 QTypedArrayData,它是一个模板,但在二进制文件中这个事实“在翻译中丢失”了。

An example of code that you can encounter is the following
你可以遇到的代码示例如下

void FUN_00172460(int **param_1,int param_2)

{
  bool bVar1;
  int *piVar2;

  piVar2 = *(int **)(param_2 + 0x24);
  *param_1 = piVar2;
  if (1 < *piVar2 + 1U) {
    DataMemoryBarrier(0xb);
    do {
      bVar1 = (bool)hasExclusiveAccess(piVar2);
    } while (!bVar1);
    *piVar2 = *piVar2 + 1;
    DataMemoryBarrier(0xb);
    return;
  }
  return;
}

if you set the param_1 to be a QString you see that this code simply copy the pointer of the data from the second parameter to the first and increase the reference counter; the if is necessary in order to check if the object is static (ref = -1) or "dead" (ref = 0).
如果你将 param_1 设置为 QString,你会看到这段代码只是将第二个参数的数据指针复制到第一个参数,并增加参考计数器;如果是检查对象是静态(ref = -1)还是“死”(ref = 0)是必要的。

Indeed the "real code" is like the following (note here that although this is a class method, the this parameter is not the first one since the return value is a "not simple" object); note how the QArrayData pointer is copied to the _d field of the returning QString
事实上,“真实代码”如下(注意,虽然这是一个类方法, 但该参数不是第一个,因为返回值是一个“非简单”对象);注意 QArrayData 指针如何被复制到 _d 返回的 QString

void UserInfo::getSomeString(QString *result,UserInfo *this)

{
  bool bVar1;
  QArrayData *pQVar2;

  pQVar2 = (this->plan_string)._d;
  result->_d = pQVar2;
  if (1 < pQVar2->ref + 1U) {
    DataMemoryBarrier(0xb);
    do {
      bVar1 = (bool)hasExclusiveAccess(pQVar2);
    } while (!bVar1);
    pQVar2->ref = pQVar2->ref + 1;
    DataMemoryBarrier(0xb);
    return;
  }
  return;
}

The instruction DataMemoryBarrier(0xb) is an actual machine instruction (dmb) used in ARM to enforce data consistency among cpus.
指令 DataMemoryBarrier(0xb)ARM 中用于强制 CPU 间数据一致性的实际机器指令(dmb)。

It's very important to recognize this pattern of reference counting because you'll encounter it everywhere.
认识到这种参照计数的模式非常重要,因为你会在任何地方遇到。

A very similar class is
一个非常相似的类是

class Q_CORE_EXPORT QByteArray
{
    private:
        typedef QTypedArrayData<char> Data;
 ...
}

If you need to reverse Qt application, probably you are going to encounter QSettings, it's a global object that you can query for values (usually used for configuration); it's internal structure it's not important (should be empty) since it actions are performed via QVariant that you can think as a "catch-all" object (here the documentation)
如果你需要逆向使用 Qt,可能会遇到 QSettings,是一个可以查询值的全局对象(通常用于配置);它是内部结构,不重要(应该是空的),因为它的操作是通过 QVariant 执行的 你可以把它当作一个“万用”对象来看( 这里有文档

From the documentation 摘自文献

QSettings stores settings. Each setting consists of a QString that specifies
the setting's name (the key) and a QVariant that stores the data associated
with the key

Just below the definition of QVariant, as you can see it's pratically a union on steroids (remember that a union is the longest-contained-element wide, so data is 8bytes since it contains a long long, but be aware that this could be different from other architectures):
就在 QVariant 的定义之下,正如你所见,它实际上是 a 类固醇并集 (记住, 并集是包含元素最长的宽度,因此数据为 8 字节,因为它包含 内容很长 ,但请注意这可能与其他架构不同:

class Q_CORE_EXPORT QVariant
{
 ...
    struct Private
    {
    ...
        union Data
        {
            char c;
            uchar uc;
            short s;
            signed char sc;
            ushort us;
            int i;
            uint u;
            long l;
            ulong ul;
            bool b;
            double d;
            float f;
            qreal real;
            qlonglong ll;
            qulonglong ull;
            QObject *o;
            void *ptr;
            PrivateShared *shared;
        } data;
        uint type : 30;
        uint is_shared : 1;
        uint is_null : 1;
    };
    ...
    public:
        Private d;
    ...
};

Another data structure you are going to encounter is the QList (documentation), a data container that behaves like a list.
你还会遇到另一种数据结构是 QList。 ( 文档), 一个表现得像列表的数据容器。

By its definition you can see that is a template
从定义来看,你可以看到这是一个模板

template <typename T>
class QList
{
   ...
    struct Node { void *v;
        Q_INLINE_TEMPLATE T &t()
        { return *reinterpret_cast<T*>(QTypeInfo<T>::isLarge || QTypeInfo<T>::isStatic
                                       ? v : this); }
    };
   ...
    union { QListData p; QListData::Data *d; };
   ...
};

struct Q_CORE_EXPORT QListData {
   ...
    struct Data {
        QtPrivate::RefCount ref;
        int alloc, begin, end;
        void *array[1];
    };
   ...
    Data *d;
   ...
};

so at the end of the day, QList is simply a wrapper around a QListData::Data pointer and that is the data structure that you'll see passing around.
归根结底,QList 只是包裹着 QListData::D ata 指针的包装器,这就是你会看到的数据结构传递。

Moreover being QList a template, you won't see calls to QList<someobject>::begin() but inlined code like
而且作为 QList 模板,你不会看到调用 QList<someobject>::begin(), 但内联代码如

template <typename T>
class QList<T> {
    ...
    inline iterator begin() { detach(); return reinterpret_cast<Node*>(p.begin()); }
    ...
    inline void detach() { if (d->ref.isShared()) detach_helper(); }
    ...
};

class QListData {
    ...
    inline void **begin() const noexcept { return d->array + d->begin; }
    ...
};

template <typename T>
Q_OUTOFLINE_TEMPLATE void QList<T>::detach_helper()
{
    detach_helper(d->alloc);
}

template <typename T>
Q_OUTOFLINE_TEMPLATE void QList<T>::detach_helper(int alloc)
{
    Node *n = reinterpret_cast<Node *>(p.begin());
    QListData::Data *x = p.detach(alloc);
    QT_TRY {
        node_copy(reinterpret_cast<Node *>(p.begin()), reinterpret_cast<Node *>(p.end()), n);
    } QT_CATCH(...) {
        p.dispose();
        d = x;
        QT_RETHROW;
    }

    if (!x->ref.deref())
        dealloc(x);
}

this last function is still a template but it's not inlined, so you should see a function calling QListData::detach() inside; it's a difficult process the first time but if the "realized" QList is used elsewhere you will be able to easily resolve the begin() call promptly.
最后这个函数仍然是模板,但它不是内联的,所以你应该会看到一个函数调用 QListData::d etach() 内;第一次处理时过程很复杂,但如果“已识别” 的 QList 在其他地方被使用,你就能轻松迅速解决 bestart() 调用。

This is how it looks like
这就是它的样子

void QList<QNetworkAddressEntry>_detach_helper(QListData *list,int alloc)

{
  bool bVar1;
  Data *d;
  Data *pDVar2;
  int iVar3;
  void **ppvVar4;
  void **this;

  ppvVar4 = list->_d->array + list->_d->begin;
  d = (Data *)list;
  QListData::detach(&list->_d,alloc);
  pDVar2 = list->_d;
  iVar3 = pDVar2->end;
  for (this = pDVar2->array + pDVar2->begin; pDVar2->array + iVar3 != this; this = this + 1) {
    QNetworkAddressEntry::QNetworkAddressEntry
              ((QNetworkAddressEntry *)this,(QNetworkAddressEntry *)ppvVar4);
    ppvVar4 = ppvVar4 + 1;
  }
  if (d->ref != 0) {
    if (d->ref == -1) {
      return;
    }
    DataMemoryBarrier(0xb);
    do {
      iVar3 = d->ref + -1;
      bVar1 = (bool)hasExclusiveAccess(d);
    } while (!bVar1);
    d->ref = iVar3;
    DataMemoryBarrier(0xb);
    if (iVar3 != 0) {
      return;
    }
  }
  Qlist<QNetworkAddressEntry>_dealloc(d);
  return;
}

As you can see the memory reference management is always there.
如你所见,内存引用管理始终存在。

QObjects

Now in this section will reach the highest achievement in reversing Qt based binaries, understanding the QObject trickery, i.e. the base class used throughout all the Qt library.
本节将达到最高成就,即逆转基于 Qt 的二进制,理解 QObject 技巧,即整个 Qt 库中使用的基类。

All what I'm going to describe you could seem unnecessary but I assure you that is going to help you analyze a binary using this library in unexpected ways, take in mind that all the reflexivity tha library has builtin (via QObject's internals) is going to tell you exactly all the methods, signals and properties of a given subclass of QObject.
我接下来要描述的内容可能看起来多余,但我保证这会帮助你以意想不到的方式分析这个库的二进制,记住这个库内置的所有反身能力(通过 QObject 的内部结构) 要告诉你 你完全拥有给定子类的所有方法、信号和性质 请退

First of all, a class that inherits from QObject is a normal C++ class, but it has also attached another struct that describes it, QMetaObject
首先,继承自 QObject 的类是一个普通的 C++ 类,但它还附加了一个描述它的结构体 QMetaObject

struct Q_CORE_EXPORT QMetaObject
{
 ...
    struct { // private data
        SuperData superdata;
        const QByteArrayData *stringdata;
        const uint *data;
        typedef void (*StaticMetacallFunction)(QObject *, QMetaObject::Call, int, void **);
        StaticMetacallFunction static_metacall;
        const SuperData *relatedMetaObjects;
        void *extradata; //reserved for future use
    } d;
 ...
};

the way this struct is "attached" to the class itself is a little more involved: in particular you need moc, the "MetaObject compiler": you see, some constructs on Qt code is not legal C++ code, in order to allow all this machinery to work you need to have you Qt code pre-compiled with moc so to obtain new C++ files that then can be compiled by your loved C++ compiler.
这个结构体与类本身“附加”的方式更复杂:特别是你需要 MOC,也就是“元对象编译器”:你看,Qt 代码中的某些构造不是合法的 C++ 代码,为了让这些机制正常工作,你需要先用 moc 预编译你的 Qt 代码,这样才能获得新的 C++ 文件,然后你所爱的人可以编译 C++ 编译器。

Every class that wants this malackery needs to use the macro Q_OBJECT
每个想要这种恶意的职业都必须使用宏级 Q_OBJECT

/* qmake ignore Q_OBJECT */
#define Q_OBJECT \
public: \
    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, "")

that for what matters to us, sets the first three functions in the virtual table of the class to be
对于我们来说重要的,将该类虚拟表中的前三个函数定为

Function 功能 Description 描述
QMetaObject *metaObject() QMetaObject *metaObject() it simply returns the corresponding QMetaObject associated with this class 它仅返回与该类关联的对应 QMetaObject
void *qt_metacast(const char *) 虚空 *qt_metacast(const char *) it's the function used for casting 这是铸造时用的功能
int qt_metacall(QMetaObject::Call, int, void **) 整 qt_metacall(QMetaObject::Call, int, void **) it's the function that "resolves" attributes, methods and signals 它是用来“解析”属性、方法和信号的函数

These methods are important because allow us to obtain important information about the class, in particular
这些方法很重要,因为它能让我们获得关于该类的重要信息,特别是

  • metaObject() it's the method that returns the QMetaObject instance, so it has a direct reference to the QMetaObject vtable
    metaObject() 是返回 QMetaObject 实例的方法,因此它直接引用到 QMetaObject vtable(vtable)
  • qt_metacast() can tell us the name of the class and possible inheritance
    qt_metacast() 可以告诉我们类的名称和可能的继承
  • qt_metacall() is probably a big switch() construct with each case resolving a signal or method of a given instance
    qt_metacall() 很可能是每个情况下大型开关() 构造 解析给定实例的信号或方法

Note: the two functions that I usually see following after these three are two destructors.
注意: 我通常在这三个之后看到的两个函数是两个破坏函数。

Note: the data type just after the virtual table is a pointer to QObjectData, it's like private data but I won't elaborate further on it.
注意: 虚拟表之后的数据类型是指向 QObjectData,它就像私有数据,但我不会详细说明。

Once you have a reference to the QMetaObject struct you can extract all the information of a class, indeed (taking inspiration from these posts 1 and 2) we have for example for the class used as example in the documentation, the following generated metadata: the data just after content is the header the indicate where and how many instances of each property (methods, properties, enums, etc...) there are
一旦你获得了对 QMetaObject 结构体的引用,就可以提取所有 确实是一门课的信息(灵感来自这些帖子) 1 2)例如文档中用作示例的类,生成的元数据如下: 内容后的数据是头部,表示每个属性(方法、属性、枚举等)实例的位置和数量

static const uint qt_meta_data_Counter[] = {

 // content:
       7,       // revision
       0,       // classname
       0,    0, // classinfo
       2,   14, // methods
       0,    0, // properties
       0,    0, // enums/sets
       0,    0, // constructors
       0,       // flags
       1,       // signalCount

 // signals: name, argc, parameters, tag, flags
       1,    1,   24,    2, 0x06 /* Public */,

 // slots: name, argc, parameters, tag, flags
       4,    1,   27,    2, 0x0a /* Public */,

 // signals: parameters
    QMetaType::Void, QMetaType::Int,    3,

 // slots: parameters
    QMetaType::Void, QMetaType::Int,    5,

       0        // eod
};

From this info is possible to obtain, for example, all the methods, their names and arguments.
通过这些信息,例如可以获得所有方法、它们的名称和参数。

Being a library used to build event-driven GUIs, it uses signals extensively, so it's important to understand how the code connect together different classes when a signal is raised. In Qt a signal is a message that an object can send, most of the time to inform of a status change.
作为一个用于构建事件驱动图形界面的库,它广泛使用信号,因此理解代码如何连接不同类别非常重要。在 Qt 中,信号是对象可以发送的消息,大多数情况下用于通知状态变化。

A related concept is the slot:a slot is a function that is used to accept and respond to a signal. The high level APIs used to connect signals and slots are the following
一个相关的概念是 “时隙 ”:时隙是一个用于接收和响应信号的函数。用于连接信号和插槽的高级 API 如下

    static QMetaObject::Connection connect(const QObject *sender, const char *signal,
                        const QObject *receiver, const char *member, Qt::ConnectionType = Qt::AutoConnection);

    static QMetaObject::Connection connect(const QObject *sender, const QMetaMethod &signal,
                        const QObject *receiver, const QMetaMethod &method,
                        Qt::ConnectionType type = Qt::AutoConnection);

    inline QMetaObject::Connection connect(const QObject *sender, const char *signal,
                        const char *member, Qt::ConnectionType type = Qt::AutoConnection) const;

you can navigate yourself the QtObject::connect() implementation or the QtPrivate::FunctionPointer template madness; meanwhile I copy it here for quick reference the relevant part
你可以自己导航 QtObject::connect() 实现 或者 QtPrivate::FunctionPointer 模板的疯狂;同时我把相关部分复制到这里以便快速查阅

// qtbase/src/corelib/kernel/qobjectdefs_impl.h
...
    /*
      The FunctionPointer<Func> struct is a type trait for function pointer.
        - ArgumentCount  is the number of argument, or -1 if it is unknown
        - the Object typedef is the Object of a pointer to member function
        - the Arguments typedef is the list of argument (in a QtPrivate::List)
        - the Function typedef is an alias to the template parameter Func
        - the call<Args, R>(f,o,args) method is used to call that slot
            Args is the list of argument of the signal
            R is the return type of the signal
            f is the function pointer
            o is the receiver object
            and args is the array of pointer to arguments, as used in qt_metacall

       The Functor<Func,N> struct is the helper to call a functor of N argument.
       its call function is the same as the FunctionPointer::call function.
     */
...
    template<class Obj, typename Ret, typename... Args> struct FunctionPointer<Ret (Obj::*) (Args...)>
    {
        typedef Obj Object;
        typedef List<Args...>  Arguments;
        typedef Ret ReturnType;
        typedef Ret (Obj::*Function) (Args...);
        enum {ArgumentCount = sizeof...(Args), IsPointerToMemberFunction = true};
        template <typename SignalArgs, typename R>
        static void call(Function f, Obj *o, void **arg) {
            FunctorCall<typename Indexes<ArgumentCount>::Value, SignalArgs, R, Function>::call(f, o, arg);
        }
    };
...

template <typename Func1, typename Func2>
static inline QMetaObject::Connection connect(
    const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal,
    const typename QtPrivate::FunctionPointer<Func2>::Object *receiver, Func2 slot,
    Qt::ConnectionType type = Qt::AutoConnection)
{
  typedef QtPrivate::FunctionPointer<Func1> SignalType;
  typedef QtPrivate::FunctionPointer<Func2> SlotType;

  //compilation error if the arguments does not match.
  Q_STATIC_ASSERT_X(int(SignalType::ArgumentCount) >= int(SlotType::ArgumentCount),
                    "The slot requires more arguments than the signal provides.");
  Q_STATIC_ASSERT_X((QtPrivate::CheckCompatibleArguments<typename SignalType::Arguments,
                                                         typename SlotType::Arguments>::value),
                    "Signal and slot arguments are not compatible.");
  Q_STATIC_ASSERT_X((QtPrivate::AreArgumentsCompatible<typename SlotType::ReturnType,
                                                       typename SignalType::ReturnType>::value),
                    "Return type of the slot is not compatible with the return type of the signal.");

  const int *types = nullptr;
  if (type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection)
      types = QtPrivate::ConnectionTypes<typename SignalType::Arguments>::types();

  QtPrivate::QSlotObjectBase *slotObj = new QtPrivate::QSlotObject<Func2,
        typename QtPrivate::List_Left<typename SignalType::Arguments, SlotType::ArgumentCount>::Value,
        typename SignalType::ReturnType>(slot);


  return connectImpl(sender, reinterpret_cast<void **>(&signal),
                     receiver, reinterpret_cast<void **>(&slot), slotObj,
                     type, types, &SignalType::Object::staticMetaObject);

What's importat to remember from all of that is the call to connectImpl(): at the end from the decompiler you will see something like the following
从这些中需要记住的是调用 connectImpl():在反编译器末尾你会看到类似这样的内容

  slot = BatteryManager::activity;
  signal = &slot;
  sender = this->batteryManager; <--- this emits the signal
  slotPtr = BatteryManager::standbyEnabledChanged; <--- this is the signal we want to connect
  receiver = this->field56_0xd8;
  _slot = (QSlotObjectBase *)operator.new(0x10);
  _slot->m_ref = 1;
  _slot->m_impl = FUN_000789e8; <--- this is glue code
  // some other values on _slot
  QObject::connectImpl(
    &conn,
    sender,(void **)signal,
    receiver,&slotPtr,_slot,
    0,NULL, (QMetaObject *)&BatteryManager::MetaObject_vtable);

to double check that all makes sense take in mind that the MetaObject_vtable must be of the same type of the sender and remember that since this call returns something "complex" the first argument is the returning object (i.e. QMetaObject::Connection), also, this a static method so no this is required.
为了确认这些内容是否合理,请记住 MetaObject_vtable 必须与发送者类型相同,并且请记住,自此通话以来 返回“复杂”的事物,第一个参数是返回对象(即 QMetaObject::Connection),而且这是一个静态方法,所以不需要这个。

Once that you have obtained the methods of the object is possible to reach their actual implementation via the qt_metacall() function: it's the third entry in the metavtable that resolves everything at runtime, its signature is
一旦你获得了对象的方法,可以通过 qt_metacall() 函数达到它们的实际实现:它是 metavtable,它在运行时解析所有内容,其签名为

<object>::qt_static_metacall(QObject *object, QMetaObject::Call call, int index, void** args);

where object is the object obviously, call is what is requested via the Enum
其中对象对象,显然调用是通过 枚举

    enum Call {
        InvokeMetaMethod,
        ReadProperty,
        WriteProperty,
        ResetProperty,
        QueryPropertyDesignable,
        QueryPropertyScriptable,
        QueryPropertyStored,
        QueryPropertyEditable,
        QueryPropertyUser,
        CreateInstance,
        IndexOfMethod,
        RegisterPropertyMetaType,
        RegisterMethodArgumentMetaType
    };

Meanwhile the meaning of index and args is depending on the context: if call is InvokeMetaMethod then index is the identifier of the signal/method/slot you are trying to invoke and from our perspective can allow to resolve the functions easily.
同时, 索引args 的含义取决于上下文:如果调用InvokeMetaMethod 然后 Index 是你试图调用的信号/方法/槽的标识符, 从我们的角度来看,它可以帮助你轻松解析这些函数

Note however that it's not the only point where the signal are activated, to find them you have to look for all the QMetaObject::activate() filtering with the right metavtable and index. This probably can be automated 😃
不过请注意 ,信号被激活的并不是唯一的点,要找到它们,你需要用合适的 metavtable索引查找所有 QMetaObject::activate() 过滤。这大概率可以实现自动化:)

QML&Resources QML&资源

the Qt QML module provides a framework for developing applications and libraries with the QML language. QML is designed to be easily extensible through C++ code.
Qt QML 模块为使用 QML 语言开发应用程序和库提供了一个框架。QML 设计上可通过 C++ 代码轻松扩展。

The QML is intended to be used to define the UI of the application via json-like syntax
QML 旨在通过以下方式定义应用程序的用户界面json 语法

import QtQuick 2.0

Rectangle {
    width: 100
    height: 100

    gradient: Gradient {
        GradientStop { position: 0.0; color: "yellow" }
        GradientStop { position: 1.0; color: "green" }
    }
}

A lot of elements are already predefined but it's possible to define new types and UI elements from C++, the low-level API to do that is qmlregister() and probably I'll update the post in the future with more about that.
很多元素已经预设,但也可以定义新的类型和 UI 元素 从 C++开始 ,底层 API 是 qmlregister(), 我可能会以后更新帖子,详细介绍。

Another part is the Qt resource system, that is a platform independent mechanism for storing binary files in the application's executable. The resource system is based on tight cooperation between qmake, rcc and QFile.
另一部分是 Qt 资源系统 ,这是一种平台无关的机制,用于将二进制文件存储在应用程序的可执行文件中。该资源系统基于 qmakerccQFile 之间的紧密合作。

The resources associated with an application are specified in a .qrc file that is an XML-based file format that lists files on the disk
与应用程序关联的资源以 .qrc 文件形式指定,该文件格式基于 XML 格式,列出磁盘上的文件

<!DOCTYPE RCC><RCC version="1.0">
<qresource>
    <file>images/copy.png</file>
    <file>images/cut.png</file>
    <file>images/new.png</file>
    <file>images/open.png</file>
    <file>images/paste.png</file>
    <file>images/save.png</file>
</qresource>
</RCC>

Resource data can either be compiled into the binary or a binary resource loadable at runtime (externally).
资源数据可以编译成二进制,也可以在运行时(外部)编译成二进制资源。

For a resource to be compiled into the binary, the .qrc file must be mentioned in the application's .pro file so that qmake knows about it. qmake will produce make rules to generate a file called qrc_application.cpp that is linked into the application.
要将资源编译成二进制文件,必须在应用程序的 .pro 文件中提及 .qrc 文件,这样 qmake 才能知道它的存在。 QMeTle`` 会生成一个名为 qrc_application.cpp 这些都和申请相关联。

The rcc tool is used to embed resources into a Qt application during the build process. It works by generating a C++ source file containing data specified in a .qrc file.
RCC 工具用于在构建过程中将资源嵌入到 Qt 应用中。它的工作原理是生成一个包含 .qrc 文件中指定数据的 C++ 源文件。

But since in this post I'm interested in reverse engineering, here the way all this stuff is implemented: via the function qRegisterResourceData() that has the following signature
但因为我在这篇帖子里对逆向工程感兴趣,所有这些东西的实现方式是:通过函数 qRegisterResourceData() 的签名

bool qRegisterResourceData(int version, const unsigned char *tree,
                                         const unsigned char *name, const
                                         unsigned char *data)

where 其中

  • tree is a filesystem tree, where the leaf nodes are the actual files with contents
    tree 是一种文件系统树,叶节点是带有内容的实际文件
  • name is an array of unicode strings where the names of the elements of the tree are contained
    name 是一个由 Unicode 字符串组成的数组,其中元素的名称 树包含
  • data is the actual content of the files.
    数据是文件的实际内容。

Since the resources are like global data they are initialized at program startup via the __DT_INIT_ARRAY in functions named something like _INIT_<integer>. This allows to find all the global allocated QString.
由于资源类似于全局数据,它们在程序启动时通过函数中的 __DT_INIT_ARRAY 初始化,名称类似类似 _INIT_<integer>.这允许找到所有全局分配的 QString

If you are interested in knowing the format of the argument of qRegisterResourceData() you have to look at the rcc source code and in particular at RCCResourceLibrary::output() method and its internal calls to
如果你想了解 的论证格式,可以 qRegisterResourceData() 你必须查看 rcc 源代码,特别是 RCCResourceLibrary::output() 方法及其内部调用

  • writeDataBlobs() writeDataBlobs()
  • writeDataNames() writeDataNames()
  • writeDataStructure() writeDataStructure()

but since we are here let me explain how the data is organized: the most important is tree that is generated by writeDataStructure() with each entry written via RCCFileInfo::writeDataInfo(); the data structure for each entry is 22 bytes long
既然我们在这里,我来解释一下数据是如何组织的:最重要的是由 writeDataStructure() 生成的,每个条目通过 RCCFileInfo::writeDataInfo() 写入;每个条目的数据结构为 22 字节

offset 偏移 directory 目录 file 文件
0 name 名称 name 名称
4 flags 旗帜
6 # child # 孩子 country 乡村
8 lang 很长
10 1st child offset 第一个孩子错置 data offset 数据偏移
14 last mode 最后模式 last mode 最后模式

Take in mind that is all big endian.
请记住,这都是大端序

Each one of these entries represent a node in a filesystem tree, the file are the leaf nodes, the other type of nodes are the components of the path where the files are. So to retrieve the name of the nodes the name parameter of qRegisterResourceData() is used, to retrieve the data instead the data parameter is used.
这些条目中的每个代表文件系统树中的一个节点,文件是叶节点,其他类型的节点是文件所在路径的组成部分。因此,要获取节点名称 ,需要 qRegisterResourceData() 用于检索数据,而非`` 使用参数。

Take in mind that the data can be compressed via qCompress(), i.e. it prepends 4 bytes with the uncompressed length (encoded big-endian) and then the zlib compressed data.
请注意,数据可以通过 qCompress() 进行压缩,即它在 4 字节前加上未压缩长度(编码大端序),然后是 zlib 压缩数据。

Ghidra 基德拉

Now we are going to do something practical, using ghidra and the information in the previous sections, we'll develop some scripts to automate all the things.
现在我们将做一些实用的操作,利用 ghidra 和前面部分的信息,开发一些脚本来自动化所有工作。

Warm up 热身

First of all some useful information about ghidra scripting: it's possible to write script for ghidra in two languages: Java (the language ghidra is written) and python; my examples use the latter because I prefer that.
首先,关于 ghidra 脚本的实用信息:可以用两种语言编写 ghidra 脚本:Java(语言) Ghidra(书写)和 Python;我的例子用后者是因为我更喜欢。

The most important APIs available are under
最重要的 API 如下

ghidra.program.flatapi.FlatProgramAPI
ghidra.app.script.GhidraScript

To build the decompiler documentation
构建反编译器文档

$ cd ./Ghidra/Features/Decompiler/src/decompile/cpp
$ make doc
$ xdg-open ../doc/html/index.html

If you want to factorize your code in a module of its own and you want to access the globals provided by ghidra you have to add (issue about it)
如果你想把代码分解成独立的模块,并且想访问 ghidra 提供的全局函数,你必须添加( 关于它的问题 )。

from __main__ import *

As a threat, here a table with some important Java classes
作为威胁,这里有一张包含一些重要 Java 类的表

type 类型 description 描述 APIs of interest 关注的 API
Program 项目 object which stores all information related to a single program (API doc) 该对象存储与单一程序(API 文档 )相关的所有信息 currentProgram 当前项目
Address 地址 An address represents a location in a program (API doc) 地址表示程序中的一个位置(API 文档 currentAddress toAddr() 当前收件人 Addr()
MemoryBlock 内存块 Interface that defines a block in memory (API doc) 定义内存块的接口(API 文档 createMemoryBlock() createMemoryBlock()
Namespace 命名空间 Symbol class for namespaces. (API doc) 符号类用于命名空间。(API 文档 currentProgram.getNamespace() NamespaceUtils currentProgram.getNamespace()NamespaceUtils
Symbol 象征 Interface for a symbol, which associates a string value with an address 符号接口,将字符串值与地址关联起来 createLabel() createLabel()
HighSymbol 高象征 A symbol within the decompiler's model of a particular function (API doc) 反编译器模型中特定函数(API 文档 )中的一个符号
ExternalManager 外部管理器 External manager interface. Defines methods for dealing with external programs and locations within those programs API doc 外部管理器接口。定义了处理外部程序的方法及其程序内的位置 API 文档 currentProgram.getExternalManager() currentProgram.getExternalManager()
Reference 参考文献 Base class to hold information about a referring address. Derived classes add what the address is referring to. A basic reference consists of a "from" address, the reference type, the operand index for where the reference is, and whether the reference is user defined (API doc) 基类用于存储关于引用地址的信息。派生类会添加地址所指的内容。基本引用包括“发件人”地址、引用类型、引用所在的操作数索引,以及引用是否为用户定义(API 文档 getReferencesTo() getReferencesTo()
DataType 数据类型 The interface that all datatypes must implement (API doc) 所有数据类型都必须实现的接口(API 文档 getDataTypes() currentProgram.getDataTypeManager() Variable.getDataType() VariableUtilities createData() getDataTypes()currentProgram.getDataTypeManager()Variable.getDataType()VariableUtilitiescreateData()
FunctionManager The manager for functions (API doc) 函数管理器(API 文档 currentProgram.getFunctionManager() currentProgram.getFunctionManager()

An important concept in ghidra is Namespace
德拉中一个重要的概念是命名空间

Note: Function, GhidraClass and Library are implementation of the interface Namespace
注:函数GhidraClassLibrary 是接口命名空间的实现

Calling convention 呼叫惯例

Before starting using ghidra a little note: you must know how the variable are passed around in memory and how function calling is implemented in the architecture of the binary you are disecting otherwise little errors by tools can throw hours of your time in the trashcan.
在开始使用 ghidra 之前,先提醒一下:你必须了解变量如何在内存中传递,以及函数调用在你分析的二进制架构中是如何实现的,否则工具上的一些小错误可能会让你浪费数小时的时间。

Address spaces 地址空间

To refer to "entity" in ghidra an Address is used, you can think of it of something like an offset but it's not enough since an Address is associated to an AddressSpace: there are a few of these
ghidra 中,指代“实体”时会用地址 ,你可以把它想象成偏移量,但这还不够,因为地址是关联到地址空间的:有几种这样的

  • ram: Modelling the main processor address bus
    RAM: 主处理器地址总线的建模
  • register: Modelling a processors registers
    注册: 建模处理器寄存器
  • unique: used as a pool for temporary registers
    唯一性: 用作临时寄存器的池
  • stack: virtual space stack space, implemented by the class SpacebaseSpace in the decompiler; in general is used for a lot of analysis situations it is convenient to extend the notion of an address space to mean bytes that are indexed relative to some base register.
    栈: 虚拟空间栈空间,由类实现 反编译器中的 SpacebaseSpace;一般来说,用于许多分析情境,方便地将地址空间的概念扩展为相对于某个基寄存器被索引的字节。
  • constant: modelling constant values in pcode expressions
    常数: 在 pcode 表达式中建模常数值
  • other: for special/user-defined address spaces
    其他: 用于特殊/用户定义的地址空间

Symbol&Namespace 符号与命名空间

The documentation states that a Symbol is the association of an address with a string, so it's a more specialized version of a Namespace, that I can loosely describe as a "category"; in particular when you have a class
文档中指出, 符号是地址与字符串的关联,因此它是更专业化的命名空间版本,我可以宽泛地称之为“类别”;尤其是当你有一个职业时

>>> ss = getSymbol("ScreenShare", None)
>>> type(ss)
<type 'ghidra.program.database.symbol.ClassSymbol'>
>>> ss1 = getNamespace(None, "ScreenShare")
>>> ss1
ScreenShare (GhidraClass)
>>> type(ss1)
<type 'ghidra.program.database.symbol.GhidraClassDB'>
>>> ss1.getSymbol()
ScreenShare
>>> ss1.getSymbol() == ss
True
GhidraClass`: interface for representing class objects in the program, derives from `Namespace`
`GhidraClass`:用于在程序中表示类对象的接口,源自`命名空间

ClassSymbol: symbols that represent "classes", extends Symbol (but it doesn't have an address associated to it ¯_(ツ)/¯).
ClassSymbol:代表“类”的符号,扩展了 Symbol(但没有关联地址 ̄
(ツ)_/ ̄)。

Note: a label is a Symbol, indeed label doesn't exist as a type in the ghidra's APIs. They exist different "types" associated to a symbol.
注意: 标签是一个符号 ,实际上标签作为类型不存在于 Ghidra 的 API。它们存在于与符号相关的不同“类型”。

Be aware that getSymbol() gets only global defined symbols, if you want to obtain symbol associated with the analysis of a function you have to look to HighSymbol.
注意,getSymbol() 只接收全局定义的符号,如果你愿意的话 获取与函数分析相关的符号,你必须关注 高象征

Selections 选曲

currentSelection` gives you the `ProgramSelection` that can be split into `AddressRange` with an iterator, usually you have a contiguos piece of memory so you are going to use `getFirstRange()`.
`currentSelection` 给你 `ProgramSelection` 可以拆分为 用迭代器`处理 AddressRange`,通常你会有一个连续的内存,所以你会使用 `getFirstRange()

Suppose you are selecting a table of pointers to function and you want the list of it
假设你正在选择一个指向函数的指针表,并且你想要它的列表

>>> startAddress = currentSelection.getFirstRange().getMinAddress()
>>> count = currentSelection.getFirstRange().getLength()/4
>>> [getFunctionAt(toAddr(getDataAt(startAddress.add(_*4)).getInt(0))) for _ in range(count)]
[ScreenShare::metaObject, qt_metacast, FUN_001496cc, FUN_0014d2b0, FUN_0014da88, <EXTERNAL>::QObject::event, <EXTERNAL>::QObject::eventFilter, <EXTERNAL>::QObject::timerEvent, <EXTERNAL>::QObject::childEvent, <EXTERNAL>::QObject::customEvent, <EXTERNAL>::QObject::connectNotify, <EXTERNAL>::QObject::disconnectNotify]

It's also possible to set the selection
也可以设置选择

>>> setCurrentSelection(ProgramSelection(*list(table.getCases()[-2:])))

Memory block 内存块

ghidra main usage is reversing binaries and binaries represent static information about memory organization of running processes: usually you have indication of region of memory (in same cases with names attached), probably with permissions where the data in the binary is going to be loaded at runtime.
Ghidra 的主要用途是反向二进制,二进制代表运行进程内存组织的静态信息:通常你会有内存区域的指示(同一情况中带有名称),可能带有运行时二进制数据将被加载的权限。

ghidra represents these regions using memory blocks, accessible in the GUI via Window > Memory Map.
ghidra 通过 GUI 中的 Window > 内存映射访问内存块来表示这些区域。

Note: there is a particular memory block named EXTERNAL that is used for example for thunked functions and indeed when you look at the name of such functions you see a prefixed EXTERNAL::: take in mind that is not a namespace but the name of the memory map.
注意: 有一个名为 EXTERNAL 的内存块,例如用于 thunked 函数,实际上当你查看这些函数的名称时,会看到前缀 EXTERNAL::,请记住这不是命名空间 ,而是内存映射的名称。

The memory blocks are from where you can read the data from the binary: here a couple of functions
内存块是从二进制读取数据的来源:这里有几个函数

Function 功能
int getInt(Address) int getInt(地址)
byte getByte(Address) byte getByte(地址)
byte[] getBytes(Address, int length) byte[] getBytes(地址,整数长度)

but if you need to read a "large" chunk of data I advice for a function like this
但如果你需要读取“大量”数据,我建议用在这样的函数上

import jarray

def get_bytes_from_binary(address, length):
    v = jarray.zeros(length, 'b')
    currentProgram.getMemory().getBytes(address, v)

    return v.tostring()

Datatypes 数据类型

You can query the datatypes with getDataTypes() that returns a list with data types with a given name, but something overlooked is the fact that data types are organized under "categories", that are the folders you see in the "Data Type manager" panel, if you want to query with respect to the category you can use currentProgram.getDataTypeManager().getDataType(<category>) taking in mind that categories use an explicit path structure
你可以用 getDataTypes() 查询数据类型,返回一个带有指定名称的数据类型列表,但有一点被忽略了,数据类型是被组织在“categories”下,也就是你在“数据类型管理器”面板中看到的文件夹。如果你想针对类别查询,可以用 currentProgram.getDataTypeManager().getDataType(<category>), 但要注意类别使用显式路径结构

>>> list(getDataTypes("QArrayData"))
[/QArrayData
pack(disabled)
Structure QArrayData {
   0   int   4   ref   ""
   4   int   4   size   ""
}
Size = 8   Actual Alignment = 1
, /Demangler/QArrayData
pack(disabled)
Structure QArrayData {
}
Size = 1   Actual Alignment = 1
]
>>> currentProgram.getDataTypeManager().getDataType("/QArrayData")
/QArrayData
pack(disabled)
Structure QArrayData {
   0   int   4   ref   ""
   4   int   4   size   ""
}
Size = 8   Actual Alignment = 1

A little note here: to obtain the actual value (i.e. an integer, an address etc...) you have to call getValue() (yes, it seems obvious but you have to explore the documentation to notice that _).
这里有一点小提示:要获得实际 (比如整数、地址等),你必须调用 getValue()( 是的,这看起来很明显,但你得查阅文档才能发现_)。

A part from the data types you already start with, you can define more complex types from simpler ones, the most common use case is the definition of a struct via the StructureDataType
除了你已经用到的数据类型外,你还可以从更简单的类型中定义更复杂的类型,最常见的用例是通过 StructureDataType 定义结构

>>> from ghidra.program.model.data import StructureDataType
>>> from ghidra.program.model.data import IntegerDataType
>>> from ghidra.program.model.data import DataTypeConflictHandler
>>> structure = StructureDataType("miao", 0)
>>> structure.insertAtOffset(0, IntegerDataType.dataType, 4, "kebab", "")
  0  0  int  4  kebab  ""
>>> structure.insertAtOffset(4, IntegerDataType.dataType, 4, "sauce", "")
  1  4  int  4  sauce  ""
>>> currentProgram.getDataTypeManager().addDataType(structure, DataTypeConflictHandler.REPLACE_HANDLER)
/miao
pack(disabled)
Structure miao {
   0   int   4   kebab   ""
   4   int   4   sauce   ""
}
Size = 8   Actual Alignment = 1

Take in mind that you need to save explicitely a new data type into the database via the DataTypeManager
请记住,你需要通过 DataTypeManager 明确保存一个新的数据类型到数据库中

>>> from ghidra.program.model.data import DataTypeConflictHandler
>>> data_type_manager = currentProgram.getDataTypeManager()
>>> data_type_manager.addDataType(structure, DataTypeConflictHandler.DEFAULT_HANDLER)

use DataTypeConflictHandler.REPLACE_HANDLER if you want a substitution without questions asked, otherwise you could end up with conflict datatypes.
如果你想要无条件替换,可以使用 DataTypeConflictHandler.REPLACE_HANDLER,否则可能会导致数据类型冲突

Obviously is possible to associate an address to some data type via createData() or retrieve the data via getDataAt().
显然,可以通过以下方式将地址关联到某个数据类型 createData() 或通过 getDataAt() 检索数据。

If you want to create data types directly from C
如果你想直接用 C 语言创建数据类型

# from <https://github.com/NationalSecurityAgency/ghidra/issues/1986>
def createDataTypeFromC(declaration):
    from ghidra.app.util.cparser.C import CParser
    from ghidra.program.model.data import DataTypeConflictHandler
    dtm = currentProgram.getDataTypeManager()
    parser = CParser(dtm)
    new_dt = parser.parse(declaration)
    transaction = dtm.startTransaction("Adding new data")
    dtm.addDataType(new_dt, None)
    dtm.endTransaction(transaction, True)

Note: the data type must be requeried after this call.
注意: 该数据类型必须在本次调用后重新查询。

Note2: bad enough it doesn't work for data types that are not "primitive" since it's not capable of using already defined types in other archives, moreover it fucks up packing! so unless you have very basic declaration I advise to defined data types programmatically.
注意 2: 它已经够糟糕了,因为它无法在非“原始”数据类型上使用,因为它无法在其他档案中使用已定义的类型,而且还搞砸了打包!所以除非你有非常基础的声明,否则我建议用程序方式定义数据类型。

Since a lot of data types depend on the actual architecture you are on (I'm looking at you pointers) you can query ghidra and ask for the size of certain data types, for example the integers
由于很多数据类型取决于你所处的实际架构(我说的是你的指针),你可以查询 ghidra 并询问某些数据类型的大小,比如整数

>>> currentProgram.getCompilerSpec().getDataOrganization().getIntegerSize()
4

If you want to set some field to big-endian
如果你想把某个字段设置为大端序

>>> from ghidra.program.model.data import EndianSettingsDefinition
>>> datatype = getDataType("mystruct")
>>> field1 = datatype.components[0]
>>> field1_settings = field1.getDefaultSettings()
>>> field1_settings.setLong('endian', EndianSettingsDefinition.BIG)

Note: it seems that you must save it and then edit the endianess and then requery it.
注意: 看起来你必须保存它,然后编辑日期,再重新查询。

Note: in the struct editor it seems impossible to edit this setting manually.
注意: 在结构编辑器中似乎无法手动编辑这个设置。

References 参考文献

With references you have the possibility to query ghidra about relations about different addresses
有了参考资料,你可以向吉德拉询问不同地址的关系

>>> getReferencesTo(currentAddress)
array(ghidra.program.model.symbol.Reference, [From: 0015a33c To: 0015a358 Type: CONDITIONAL_COMPUTED_JUMP Op: -1 ANALYSIS])

It's also possible to ask for references to data types
也可以请求引用数据类型

>>> print struct
/ScreenShare_vtable_t
pack(disabled)
Structure ScreenShare_vtable_t {
   0   int   4   metaObject   ""
   4   int   4   qt_metacast   ""
   8   int   4   FUN_001496cc   ""
   12   int   4   FUN_0014d2b0   ""
   16   int   4   FUN_0014da88   ""
   20   int   4   event   ""
   24   int   4   eventFilter   ""
   28   int   4   timerEvent   ""
   32   int   4   childEvent   ""
   36   int   4   customEvent   ""
   40   int   4   connectNotify   ""
   44   int   4   disconnectNotify   ""
}
Size = 48   Actual Alignment = 1
>>> from ghidra.util.datastruct import ListAccumulator
>>> from ghidra.app.plugin.core.navigation.locationreferences import ReferenceUtils
>>> lst = ListAccumulator()
>>> ReferenceUtils.findDataTypeReferences(lst, struct, "FUN_0014da88", currentProgram, None)
>>> print type(list(lst)[0])
<type 'ghidra.app.plugin.core.navigation.locationreferences.LocationReference'>
>>> reference = list(lst)[0]
>>> reference.getProgramLocation()
>>> reference.getLocationOfUse()
00233e58
>>> context = list(lst)[0].context
>>> type(context)
<type 'ghidra.app.plugin.core.navigation.locationreferences.LocationReferenceContext'>
>>> context.getPlainText()
u'92: (*(code *)piVar1->_vtable->FUN_0014da88)();'

Functions 职能

Usually code is organized in "blocks of execution" that can be identified as functions, and obvously ghidra as its own way of dealing with that.
通常代码会被组织成“执行块”,这些块可以被识别为函数,显然还有 ghidra 作为处理函数的方式。

This section is more about the "interface" to a function not the analysis of its internal behaviour, that is subject of a section a little below. So here you'll se how to retrieve a function, how to set the signature and calling convention and so on.
本节更多关注函数的“接口”,而非其内部行为的分析,后者将在下文稍作介绍。这里你会看到如何检索函数,如何设置签名和调用约定等等。

An example of the sometime-difficult-to-work-with ghidra is that I have yet to find an easy way to, for example, get a function by name reliably, the code I come up with is the following
举个例子,比如我还没找到一个简单的方法,比如可靠``地按函数名获取函数,我设计的代码如下

def get_function_by_name(name, namespace=None):
    """Little hacky way of finding the function by name since getFunction() by FlatAPI
    doesn't work."""
    candidates = [_ for _ in currentProgram.getFunctionManager().getFunctionsNoStubs(True) if name == _.name]

    if namespace:
        candidates = [_ for _ in candidates if _.getParentNamespace().getName() == namespace]

    if len(candidates) != 1:
        raise ValueError("We expected to find only one of '%s' instead we have %s" % (name, candidates))

    return candidates[0]

Another important piece of code is something that returns the references to a given address
另一个重要的代码是返回给定地址的引用

def getXref(target_addr):
    """return the xrefs defined towards the target_addr as a list
    having as entries couple of the form (call_addr, calling function)
    where the latter is None when is not defined."""

    references = getReferencesTo(target_addr)

    callers = []

    for xref in references:
        call_addr = xref.getFromAddress()
        caller = getFunctionContaining(call_addr)

        if caller is None:
            logger.warning("found reference to undefined at {}".format(call_addr))

        callers.append((call_addr, caller))

    return callers

If you want to define programmatically the signature of a function this is the convoluted way to do that
如果你想用程序定义函数的签名,这就是复杂的方法

>>> from ghidra.program.model.data import FunctionDefinitionDataType
>>> from ghidra.program.model.data import IntegerDataType
>>> from ghidra.program.model.data import ParameterDefinitionImpl
>>> from ghidra.program.model.data import PointerDataType
>>> from ghidra.program.model.data import VoidDataType
>>> from ghidra.program.model.data import GenericCallingConvention
>>> sig = FunctionDefinitionDataType("miao")
>>> param1 = ParameterDefinitionImpl('kebab', IntegerDataType.dataType, 'comment')
>>> param2 = ParameterDefinitionImpl('falafel', PointerDataType(VoidDataType.dataType), 'comment bis')
>>> sig.setArguments([param1, param2])
>>> sig.setGenericCallingConvention(GenericCallingConvention.thiscall)
>>> sig
undefined thiscall miao(int kebab, void * falafel)

Note: PointerDataType() can be used without .dataType (why?)
注意:PointerDataType() 可以不用 .dataType(为什么?)

Now if you want to apply the fucking signature you have to call a fucking command (see this issue)
现在如果你想应用该死的签名,你得调用命令(见本期)。

>>> from ghidra.app.cmd.function import ApplyFunctionSignatureCmd
>>> from ghidra.program.model.symbol import SourceType
>>> f = getFunctionAt(toAddr(0x0036af14))
>>> runCommand(ApplyFunctionSignatureCmd(f.entryPoint, sig, SourceType.USER_DEFINED))

It's instead easy to move a function in a class Namespace:
相反,在类命名空间中移动函数是很简单的:

>>> ht = getNamespace(None, 'HttpManager')
>>> f = getFunctionAt(toAddr(0x0036af14))
>>> f.setParentNamespace(ht)

Note: It's also possible to change the calling convention directly from the function using a string via setCallingConvention(). You can also define a data type from a function definition (vtable anyone?)
注: 也可以通过 setCallingConvention() 的字符串直接从函数中更改调用约定。你也可以从函数定义中定义数据类型(比如 vtable,有人知道吗?)

>>> from ghidra.program.model.data import FunctionDefinitionDataType
>>> from ghidra.program.model.data import PointerDataType
>>> functionDefinitionDatatype = FunctionDefinitionDataType(function, False)
>>> functionDefinitionDatatype
undefined stdcall FUN_001924a0(QObject * param_1, int param_2)
>>> PointerDataType(functionDefinitionDatatype)
FUN_001924a0 *

Sometimes you have thunked functions and you want to retrieve the original mangled name
有时你遇到了 thunk 函数 ,想恢复原始被扭曲的名称

                             **************************************************************
                             *                       THUNK FUNCTION                       *
                             **************************************************************
                             thunk undefined __thiscall operator<<(QDataStream * this
                               Thunked-Function: <EXTERNAL>::QDataStream
                               assume TMode = 0x0
             undefined         r0:1            <RETURN>
             QDataStream *     r0:4 (auto)     this
             longlong          Stack[0x0]:8    param_1
                             <EXTERNAL>::QDataStream::operator<<             XREF[2]:     operator<<:00038620(T), 
                                                                                          operator<<:00038628(c), 
                                                                                          005a4054(*)  
         EXTERNAL:00719da8                               ??                 ??
         EXTERNAL:00719da9                               ??                 ??
         EXTERNAL:00719daa                               ??                 ??
         EXTERNAL:00719dab                               ??                 ??
>>> func = getFunctionAt(currentAddress)
>>> thunk = func.getThunkedFunction(True)
>>> thunk.getSymbol().getSymbolStringData()
u',_ZN11QDataStreamlsEx'
>>> from ghidra.app.util.demangler import DemanglerUtil
>>> DemanglerUtil.demangle(currentProgram, thunk.getSymbol().getSymbolStringData()[1:])
undefined QDataStream::operator<<(long long)

Note: the last command removed the _ prepending the mangled name, otherwise it doesn't demangle ¯_(ツ)
注意: 最后一个命令移除了前置“扭曲”名称前的
,否则不会“纠结”̄_(ツ)_/ ̄

ghidra analysis under the hood 吉德拉分析

This and the following sections are more involved with internals of how ghidra "understands" things and so it's not directly related to the act of reversing but can be pretty useful to wrap your head around and knowing that some information exists somewhere to look for.
本节及后续章节更深入探讨内部结构 吉德拉 “理解”事物,所以这和倒车本身没有直接关系,但理解一些信息存在某处是很有用的。

The main assumption here is that ghidra, when analizes the code, has two different possible interpretation of what is happening: code and data (this is true also in real binaries and code execution); you can see the instructions creating a directed graph between them, where the jump from one node to the other can be data-dependent, but you can also see the instructions as edges that link transition of data from one state to another.
这里的主要假设是,基德拉在分析代码时,对发生的事情有两种不同的解释: 代码数据 (这在实二进制和代码执行中同样成立);你可以看到 指令在它们之间创建有向图,跳跃从 节点到另一个节点可以依赖数据,但你也可以把指令看作连接数据从一个状态到另一个状态的边。

When you are talking about code in ghidra you use the Pcode, when you are talking about data you are using Varnode, probably is a little more complex than that but let things simpler.
当你在 ghidra 里谈论代码时,你用的是 Pcode;谈到数据时,你用的是 Varnode,可能比那复杂一些,但可以让事情更简单一些。

To remeber that relation, take in mind this methods to pass from one to another
为了记住这种关系,请记住这些方法可以从一个传递到另一个

varnode.getDef() -> PcodeOp
pcodeop.getInputs() -> Varnode[]

Take in mind that all of these concepts apply to functions analysis and that to analyze variables you need to commit locally them, like using HighFunctionDBUtil.commitLocalNamesToDatabase(high_func, SourceType.USER_DEFINED).
请记住,所有这些概念都适用于函数分析,并且 要分析变量,你需要在本地提交它们,比如用 HighFunctionDBUtil.commitLocalNamesToDatabase(high_func, SourceType.USER_DEFINED).

Here a table with some definitions
这里有一张带有定义的表格

Entity 实体 Description 描述
Varnode 瓦尔诺德 sequence of bytes in an address space, represented as a triple (address space, offset, size); It's a central concept for the decompiler, it forms the individual nodes in the decompiler's data-flow representation of functions. 地址空间中的字节序列,表示为三元组(地址空间、偏移量、大小);它是反编译器的核心概念,它构成了反编译器函数数据流表示中的各个节点。
VarnodeAST is a node in an AbstractSyntaxTree; it keeps track of its defining PcodeOp (in-edge) (VarnodeAST.getDef()) and PcodeOps which use it (out-edges) (VarnodeAST.getDescendendants()) 是抽象语法树中的一个节点;它跟踪其定义的 PcodeOp(in-edge)(VarnodeAST.getDef())和使用 PcodeOp 的 s(out-edge)(VarnodeAST.getDescendants()
HighVariable 高变 is a set of varnodes that represent the storage of an entire variable in high-level language being output by the decompiler 是一组 varnode,代表由反编译器输出的高级语言中整个变量的存储
HighFunction 高功能 high-level abstraction associated with a low-level function made up of assembly instructions 与由汇编指令组成的低层函数相关的高层抽象

Analyzing opcodes 操作码分析

In the section above I described some code to get the xrefs from a function to another, in particular we are able to get the tuple (address, function) where this reference come from; if we want to extract information about the arguments with which the function is called with can use the Pcode directly like in this example:
在上面的部分,我描述了一些代码,用来将函数的 xrefs 传递到另一个函数,特别是我们能够获得该引用来源的元组(地址函数);如果我们想提取调用该函数的参数信息,可以直接使用 Pcode,如本例所示:

>>> from ghidra.app.decompiler import DecompileOptions
>>> from ghidra.app.decompiler import DecompInterface
>>> from ghidra.util.task import ConsoleTaskMonitor
>>> monitor = ConsoleTaskMonitor()
>>> ifc = DecompInterface()
>>> options = DecompileOptions()
>>> ifc.setOptions(options)
True
>>> ifc.openProgram(currentProgram)
True
>>> func = getFunctionContaining(currentAddress)
>>> func
KeyboardBridgeServer::connectedChanged
>>> res = ifc.decompileFunction(func, 60, monitor)
>>> res
ghidra.app.decompiler.DecompileResults@46c33206
>>> high_func = res.getHighFunction()
>>> high_func.getPcodeOps(toAddr(0x00c8d64))
java.util.AbstractMap$2$1@4511ac6b
>>> pcodeops = high_func.getPcodeOps(toAddr(0x00c8d64))
>>> op = pcodeops.next()
>>> op
 ---  CALL (ram, 0x3b0e0, 8) , (unique, 0x10000009, 4) , (unique, 0x1000000d, 4) , (const, 0x0, 4) , (const, 0x0, 4)
>>> op.getInputs()
array(ghidra.program.model.pcode.Varnode, [(ram, 0x3b0e0, 8), (unique, 0x10000009, 4), (unique, 0x1000000d, 4), (const, 0x0, 4), (const, 0x0, 4)])

moreover, now we have the tool to extract the information about local variables defined in the function by the decompiler
此外,现在我们有了工具,可以提取反编译器在函数中定义的局部变量信息

>>> [_.getName()  for _ in res.getHighFunction().getLocalSymbolMap().getSymbols()]
[u'ret', u'bVar1', u'cVar3', u'pQVar2', u'iVar4', u'pQVar6', u'pQVar5',
  u'type_of_message', u'local_34', u'local_30', u'local_2c', u'local_28',
  u'local_24', u'local_20', u'connection', u'local_1c', u'inStream']

Now, to apply this to something practical, something we talked about before, let me show how to extract the arguments from a call to qRegisterResourceData(): suppose we are "lucky" and the decompiler shows us the following situation
现在,为了应用到我们之前讨论过的实际应用中,我来演示如何从调用 qRegisterResourceData() 中提取参数:假设我们“幸运”,反编译器给我们展示了以下情况

void _INIT_3(void)

{
  qRegisterResourceData(3,"","","");
  __aeabi_atexit(&DAT_005a7ec4,&LAB_000464a8,&DAT_005a7c44);
  return;
}

if I place the cursor at the function name in the decompiler panel I can obtain the operation with its arguments
如果我把光标放在反编译器面板中的函数名上,我就能得到带有参数的操作

>>> currentLocation.getToken().getPcodeOp()
 ---  CALL (ram, 0x3a294, 8) , (const, 0x3, 4) , (unique, 0x1000001e, 4) , (unique, 0x1000001a, 4) , (unique, 0x10000022, 4)

Note: for some strange reason, the address where the Pcode is can be extracted via getSeqnum()
注意: 出于某种奇怪的原因,Pcode 所在地址可以通过 getSeqnum() 提取

>>> call.getSeqnum()
(ram, 0x3e80c, 59, 6)

Remember, this function takes four arguments, the first one is the integer representing the version, the other three are pointers; the integer one is trivial to retrieve (note that the 0th argument of the opcode is the address of the function to call)
请记住,该函数包含四个参数,第一个参数是表示版本的整数,另外三个是指针;整数一的检索非常简单(注意操作码的第0个参数是调用函数的地址)

>>> op = currentLocation.getToken().getPcodeOp()
>>> op.getInput(1)
(const, 0x3, 4)
>>> type(op.getInput(1))
<type 'ghidra.program.model.pcode.VarnodeAST'>
>>> op.getInput(1).getAddress()
const:00000003
>>> op.getInput(1).getOffset()
3L

and here the thing: remember when I told you a Varnode is a triple? this is in the const address space, at address 0x3 and 4bytes wide.
事情是这样的:还记得我告诉你瓦尔诺德是三重吗?它位于 const 地址空间,地址为 0x3,宽度为 4 字节。

Now look for the second argument
现在看看第二个论点

>>> ptr = op.getInput(2)
>>> type(ptr)
<type 'ghidra.program.model.pcode.VarnodeAST'>
>>> ptr.getDef()
(unique, 0x1000001e, 4) COPY (const, 0x44c454, 4)
>>> ptr.getDef().getInput(0)
(const, 0x44c454, 4)

here we don't have a direct constant but a COPY of the constant inside the variable; you can inspect the variable to get more information out of it
这里我们没有直接常量,而是变量内部常数的副本 ;你可以检查变量,获取更多信息

>>> ptr.getHigh()
ghidra.program.model.pcode.HighOther@132988de
>>> ptr.getHigh().getName()
u'UNNAMED'
>>> ptr.getHigh().getDataType()
uchar *

in particular it is a uchar pointer.
特别是它是一个 Uchar 指针。

This seems simple, but not always the decompiler is able to have clean data definition and you can end up in situations like the following where some data defined over the one we are interested in, causes the analysis to mess up the "arithmetics": the second argument is still a pointer but the problem is that the pointer is in the middle of an already defined string, so the decompiler get around doing some casting
这看起来很简单,但并非总能让反编译器做出干净的数据定义,你可能会遇到以下这种情况:某些数据定义在我们感兴趣的数据上,导致分析出错的“算术”:第二个参数仍然是指针,但问题是指针位于已经定义好的字符串中间, 所以反编译器会绕过一些 casting(casting)的处理

void _INIT_2(void)

{
  qRegisterResourceData
            (3,(uchar *)((int)
                         L"<imagine gibberish data here>"
                        + 0x475),"","");
  __aeabi_atexit(&DAT_005a7ec0,FUN_000463d0,&DAT_005a7c44);
  return;
}

If I place the cursor over the function name you can manually disect the arguments passed to it:
如果我把光标放在函数名上,你可以手动剖析传递给它的参数:

>>> currentLocation.getToken().getPcodeOp()
 ---  CALL (ram, 0x3a294, 8) , (const, 0x3, 4) , (unique, 0x10000022, 4) , (unique, 0x1000001a, 4) , (unique, 0x10000026, 4)
>>> call = currentLocation.getToken().getPcodeOp()
>>> call.getInputs()
array(ghidra.program.model.pcode.Varnode, [(ram, 0x3a294, 8), (const, 0x3, 4), (unique, 0x10000022, 4), (unique, 0x1000001a, 4), (unique, 0x10000026, 4)])

I'm interested in the second argument, i.e. the pointer to the string data
我对第二个参数感兴趣,也就是指向字符串数据的指针

>>> ptr = call.getInput(2)
>>> ptr
(unique, 0x10000022, 4)

The first operation is a CAST
第一个操作是 CAST

>>> ptr.getDef()
(unique, 0x10000022, 4) CAST (unique, 0x10000036, 4)

it has only one input
它只有一个输入

>>> ptr.getDef().getInputs()
array(ghidra.program.model.pcode.Varnode, [(unique, 0x10000036, 4)])
>>> ptr.getDef().getInput(0)
(unique, 0x10000036, 4)

we can follow the chain to retrieve the operations that generated it
我们可以沿着链条获取生成该链的操作

>>> ptr.getDef().getInput(0).getDef()
(unique, 0x10000036, 4) INT_ADD (unique, 0x10000032, 4) , (const, 0x475, 4)

it's INT_ADD with two operands, one is another Varnode, the other a costant; for the first we can descend the different operations until we obtain a const representing an address, i.e. 0x404303 that summed to the second argument gives us the right address where the data lives
INT_ADD 两个操作数,一个是另一个变形体 ,另一个是短变数; 对于第一个,我们可以向下递减不同的操作,直到得到 const 表示地址,即 0x404303 与第二个参数相加,给出了数据所在的正确地址

>>> ptr.getDef().getInput(0).getDef().getInput(0)
(unique, 0x10000032, 4)
>>> ptr.getDef().getInput(0).getDef().getInput(0).getDef()
(unique, 0x10000032, 4) CAST (unique, 0x1000001e, 4)
>>> ptr.getDef().getInput(0).getDef().getInput(0).getDef().getInput(0)
(unique, 0x1000001e, 4)
>>> ptr.getDef().getInput(0).getDef().getInput(0).getDef().getInput(0).getDef()
(unique, 0x1000001e, 4) COPY (const, 0x404303, 4)
>>> ptr.getDef().getInput(0).getDef().getInput(0).getDef().getInput(0).getDef().getInput(0)
(const, 0x404303, 4)
>>> ptr.getDef().getInput(0).getDef().getInput(1)
(const, 0x475, 4)
>>> toAddr(0x404303 + 0x475)
00404778

In general it's tricky to generalize a function to extract arguments from a call, in particular because you can have argument passed using variables on the stack (did you notice all the previous example used global defined data?).
一般来说,要将函数从调用中提取参数是比较棘手的,尤其是因为你可以用栈上的变量传递参数(你注意到之前的例子都用全局定义数据了吗?)。

Suppose I want to extract the string pointed from the field uri in the following function call
假设我想在以下函数调用中提取指向字段 uri 的字符串

struct {
    int versions;
    char* uri;

} kebab;

void somefunction() {
    /* something happening
      before */
    struct kebab api {
        .version = 4;
        .uri = "miao";
    };

    if (<some condition>) {
        /* stuff I don't care about */
    }

    QQmlPrivate::qmlregister(SingletonRegistration,&api);
}

Let's start with the obvious
我们先从显而易见的说起

>>> op.getInput(2)
(register, 0x2c, 4)

but we are not lucky trying to find something useful directly from this Varnode
但我们很难直接从中找到有用的信息 瓦尔诺德

>>> type(op.getInput(2))
<type 'ghidra.program.model.pcode.VarnodeAST'>
>>> op.getInput(2)
(register, 0x2c, 4)
>>> op.getInput(2).getHigh()
ghidra.program.model.pcode.HighOther@15347f34
>>> op.getInput(2).getHigh().getDataType()
RegisterSingletonType *
>>> op.getInput(2).getHigh().getName()
u'UNNAMED'
>>> op.getInput(2).getHigh().getInstances()
array(ghidra.program.model.pcode.Varnode, [(register, 0x2c, 4)])

We can obtain the actual register used
我们可以获得实际使用的寄存器

>>> addr = op.getInput(2).getAddress()
>>> currentProgram.getRegister(addr)
r3

it should be r1 but there are some operations that probably are hidden under the rug; if we try with the operation that put the address in r3 we have
应该是 R1,但有些操作可能被掩盖了;如果我们尝试将地址放入 R3 的操作,我们有

>>> op.getInput(2).getDef()
(register, 0x2c, 4) PTRSUB (register, 0x54, 4) , (const, 0xffffff10, 4)
>>> currentProgram.getRegister(op.getInput(2).getDef().getInput(0))
sp

that is something pointing at the stack (register 0x54)! in particular at offset -0xf0 (the signed encoding of 0xffffff10). How we retrieve the corresponding variable in the function?
那是指向堆栈(寄存器 0x54)的东西!特别是在偏移-0xf00xffffff10 的带符号编码)处。我们如何检索函数中对应的变量?

>>> res.getHighFunction().getFunction().getAllVariables()
array(ghidra.program.model.listing.Variable, [[int * param_1@r0:4], [RegisterSingletonType api@Stack[-0xf0]:44], [undefined1
local_f4@Stack[-0xf4]:1], [undefined4 local_f8@Stack[-0xf8]:4], [undefined4
local_fc@Stack[-0xfc]:4], [undefined1 local_100@Stack[-0x100]:1], [undefined1 bVar1@HASH:4a0331695fe:1]])

Note: here we are asking the variables via Function, not HighFunction since the former are the ones saved in the database of ghidra
注意: 这里我们是通过函数来询问变量,而不是 HighFunction,因为前者是保存在 吉德拉

>>> variable = res.getHighFunction().getFunction().getAllVariables()[1]
>>> variable
[RegisterSingletonType api@Stack[-0xf0]:44]
>>> hex(variable.getMinAddress().getUnsignedOffset())
'0xffffff10L'
>>> variable = res.getHighFunction().getFunction().getStackFrame().getVariableContaining(-0xf0)
>>> variable
[RegisterSingletonType api@Stack[-0xf0]:44]
>>> ref_mgr = currentProgram.getReferenceManager()
>>> offset_field = variable.getStackOffset() + variable.getDataType().getComponent(4).getOffset()
>>> [_ for _ in ref_mgr.getReferencesTo(variable) if _.getFromAddress() < currentAddress and _.getStackOffset() == offset_field]
[From: 00070a20 To: Stack[-0xe0] Type: WRITE Op: 1 ANALYSIS]
>>> ref = [_ for _ in ref_mgr.getReferencesTo(variable) if _.getFromAddress() < currentAddress and _.getStackOffset() == offset_field][0]
>>> hex(ref.getStackOffset())
'-0xe0'
>>> ref.getFromAddress()
00070a20

but here we are encounter a blocking problem: if we try to recover a Pcode from the decompiler here, we have no luck, no instructions defined there.
但这里我们遇到了一个阻塞问题:如果我们尝试恢复一个 Pcode。 从这里的反编译器来看,我们没有运气,也没有定义指令。

It's the same problem described in this post about reversing Go binaries' strings.
这和这篇帖子中关于反转围棋二进制字符串的问题是一样的。

Note: the Pcode from the listing panel and from the decompiler are related but are not 1-to-1 because the decompiler is "synthetizing" the former; let me open a parentesis to explain a little better: take this code as an example
注意: 列表面板中的 Pcode 和反编译器中的 Pcode 是相关的,但不是一一对应,因为反编译器是在“综合”前者;让我开个例证来更好地解释:以这段代码为例

int constant() {
    int x = 27;

    char y = getchar();

    int z = 2 * x + y;

    if (x < 0) {
        y = z - 3;
    } else {
        y = 12;
    }

    return y;
}

(this is from the book "Static program analysis"), compile it with -O0 to avoid the optimization (x is a constant and the compiler rightly removes all the code and return the value 12); ghidra shows you simply this piece of code in the decompiler
(摘自《静态程序分析》一书),编译为 -O0 为了避免优化(x 是常数,编译器理所当然地删除所有代码并返回值 12);Ghidra 在反编译器中只展示了这段代码

/* WARNING: Removing unreachable block (ram,0x0010002c) */

undefined4 constant(void)

{
  getchar();
  return 0xc;
}

but the instructions in the listing are all there. A simple class to handle the different type of "instructions" is the following
但房源里的说明都写在里面。处理不同类型“指令”的简单类如下

class BasicBlock:
    """Wrap the ghidra's basic block representation."""
    def __init__(self, pcodeblock):
        self.start = pcodeblock.getStart()
        self.end = pcodeblock.getStop()
        self._block = pcodeblock
    def instructions(self):
        inst = getInstructionAt(self.start)
        while inst and inst.getAddress() <= self.end:
            yield inst
            inst = inst.getNext()
    def _raw_pcodes(self):
        for inst in self.instructions():
            for op in inst.getPcode():
                yield op
    def _pcodes(self):
        it = self._block.getIterator()
        for op in it:
            yield op

the wrap a basic block resulting from the decompilation and provides with the methods to obtain the right data:
将反编译后 Pack 一个基本块包裹起来,并提供了获得正确数据的方法:

>>> bb = BasicBlock(high_func.getBasicBlocks()[0])
>>> list(bb.instructions())
[PUSH RBP, MOV RBP,RSP, MOV qword ptr [RBP + -0x18],RDI, MOV qword ptr [RBP + -0x20],RSI, MOV qword ptr [RBP + -0x8],0x1, JMP 0x001011f3]
>>> list(bb._pcodes())
[(stack, 0xfffffffffffffff0, 8) COPY (const, 0x1, 8),  ---  BRANCH (ram, 0x1011f3, 1)]
>>> list(bb._raw_pcodes())
[
  (unique, 0xea00, 8) COPY (register, 0x28, 8),
  (register, 0x20, 8) INT_SUB (register, 0x20, 8) , (const, 0x8, 8),
   ---  STORE (const, 0x1b1, 8) , (register, 0x20, 8) , (unique, 0xea00, 8),
  (register, 0x28, 8) COPY (register, 0x20, 8),
  (unique, 0x3100, 8) INT_ADD (register, 0x28, 8) , (const, 0xffffffffffffffe8, 8),
  (unique, 0xc000, 8) COPY (register, 0x38, 8),
   --- STORE (const, 0x1b1, 4) , (unique, 0x3100, 8) , (unique, 0xc000, 8),
  (unique, 0x3100, 8) INT_ADD (register, 0x28, 8) , (const, 0xffffffffffffffe0, 8),
  (unique, 0xc000, 8) COPY (register, 0x30, 8),
   ---  STORE (const, 0x1b1, 4) , (unique, 0x3100, 8) , (unique, 0xc000, 8),
  (unique, 0x3100, 8) INT_ADD (register, 0x28, 8) , (const, 0xfffffffffffffff8, 8),
  (unique, 0xc080, 8) COPY (const, 0x1, 8),
   ---  STORE (const, 0x1b1, 4) , (unique, 0x3100, 8) , (unique, 0xc080, 8),
   ---  BRANCH (ram, 0x1011f3, 8)
]

If you want to traverse all the function you have to implement a path traversal algorithm that avoids infinite loop.
如果你想遍历所有函数,就必须实现一个路径遍历算法来避免无限循环。

Now, to return to our initial endevour, we can try to extract the fucking information that is present in the listing panel
现在,回到最初的尝试,我们可以尝试提取列表面板里的那些该死的信息

>>> from ghidra.program.model.listing import CodeUnitFormat, CodeUnitFormatOptions
>>> codeUnitFormat = CodeUnitFormat(
    CodeUnitFormatOptions(
        CodeUnitFormatOptions.ShowBlockName.ALWAYS,
        CodeUnitFormatOptions.ShowNamespace.ALWAYS,
        "",
        True, True, True, True, True, True, True
    )
)
>>> instr = getInstructionAt(ref.getFromAddress())
>>> instr
str r3,[sp,#0x58]
>>> codeUnitFormat.getRepresentationString(instr)
u'str r3=>.rodata:s_DeviceApp_004fa30c,[sp,#api.typeName+0x138]'

Note: this is what's called a pro move, it's not known to humanity if it's reliable...
注意: 这就是所谓的职业招式 ,人类不知道它是否可靠......

A similar situation can be observed about "blocks": usually it's useful to study the code of a function using the contiguous blocks of instructions, connected via control flow instructions.
类似的情况也适用于“块”:通常研究函数代码是有用的,利用连续的指令块,通过控制流指令连接。

In order to make an example, I'll try to analyze a switch: here the magic incantation that allows to obtain information about it
为了举个例子,我试着分析一个开关 :这里是允许获取相关信息的魔法咒语

>>> tables = res.getHighFunction().getJumpTables()
>>> len(tables)
1
>>> table = tables[0]
>>> table.getSwitchAddress()
0015a33c
>>> table.getLabelValues()
array(java.lang.Integer, [0, 1, 2, 3, 4, 5, 6])

from it we can obtain the basic blocks (of Pcode) that starts from the switch itself and from each case
由此我们可以得到从开关本身及每个情况开始的基本区块(Pcode)。

>>> ops = list(res.getHighFunction().getPcodeOps(table.getSwitchAddress()))
>>> [_.getParent() for _ in ops]
[basic@0015a334, basic@0015a334, basic@0015a33c, basic@0015a33c, basic@0015a33c]
>>> set([_.getParent() for _ in ops])
set([basic@0015a33c, basic@0015a334])
>>> get_block_at = lambda addr: [_ for _ in res.getHighFunction().getBasicBlocks() if _.contains(addr)][0]
>>> [get_block_at(_) for _ in table.getCases()]
[basic@0015a344, basic@0015a348, basic@0015a34c, basic@0015a350, basic@0015a354, basic@0015a358, basic@0015a35c]
>>> describe_block = lambda _block: (_block, (_block.getStart(), _block.getStop()),[_block.getIn(_in) for _in in range(_block.getInSize())], [_block.getOut(_out) for _out in range(_block.getOutSize())], list(_block.getIterator()))

But it exists an alternative way of obtaining the flow
但它存在另一种获取流动的方式

>>> from ghidra.util.task import TaskMonitor
>>> from ghidra.program.model.block import SimpleBlockModel
>>> bm = SimpleBlockModel(currentProgram)
>>> bm.getCodeBlocksContaining(currentAddress, TaskMonitor.DUMMY)
>>> [bm.getCodeBlocksContaining(_, TaskMonitor.DUMMY) for _ in table.getCases()]
[array(ghidra.program.model.block.CodeBlock, [caseD_0  src:[0015a33c] dst:[0015a43c]]), array(ghidra.program.model.block.CodeBlock, [caseD_1 src:[0015a33c]  dst:[0015a4a0]]), array(ghidra.program.model.block.CodeBlock, [caseD_2  src:[0015a33c]  dst:[0015a44c]]),
array(ghidra.program.model.block.CodeBlock, [caseD_3  src:[0015a33c] dst:[0015a45c]]), array(ghidra.program.model.block.CodeBlock, [caseD_4 src:[0015a33c]  dst:[0015a46c]]), array(ghidra.program.model.block.CodeBlock, [caseD_5  src:[0015a33c]  dst:[0015a478]]),
array(ghidra.program.model.block.CodeBlock, [caseD_6  src:[0015a33c] dst:[0015a484]])]

This is the difficult to explain well in a post but the two different representations of blocks are similar but not equals since the basic blocks are generated from the decompiler Pcode, meanwhile the other is generated from the "raw" Pcode and are the same blocks (I think) used for the "Function graph" window.
这在帖子里很难解释清楚,但这两个不同的块表示方式相似但不等同,因为基本块是从反编译器的 Pcode 生成的,而另一个是从“原始” 的 Pcode 生成的,并且(我认为)是用于“函数图”窗口的块。

Last information: if you want to get the Varnode at the cursor location in the decompiler panel this is the code for you
最后一条信息:如果你想在反编译器面板的光标位置获取 Varnode,这个代码就是你的

>>> from ghidra.app.decompiler.component import DecompilerUtils
>>> tokenAtCursor = currentLocation.getToken()
>>> var = DecompilerUtils.getVarnodeRef(tokenAtCursor)

Visualizing table 可视化表

Sometime is useful to show a table with an entry for address with some related information, to do that createTableChooserDialog() exists; suppose we want to have all the calls to qmlregister() with the name of the class the is going to register at runtime and I want to have shown in order to find the constructor and stuff like that;
有时显示一个带有地址条目与相关 的表很有用 信息,要做到这一点 createTableChooserDialog() 存在;假设我们想让所有调用 qmlregister() 的调用都包含 the will will register 的类名,并且 I 想要 ,以便找到构造函数等;

from ghidra.app.tablechooser import TableChooserExecutor, AddressableRowObject, StringColumnDisplay

class ArgumentsExecutor(TableChooserExecutor):
    def execute(self, rowObject):
        # here some code to execute on the selected row
        return True

    def getButtonName(self):
        return "I'm late!"


class Argument(AddressableRowObject):
    def __init__(self, row):
        # using "address" raises "AttributeError: read-only attr: address"
        self.row = row

    def getAddress(self):
        return self.row[0]


class TypeColumn(StringColumnDisplay):
    def getColumnName(self):
        return u"Type"

    def getColumnValue(self, row):
        return row.row[1]


class ClassNameColumn(StringColumnDisplay):
    def getColumnName(self):
        return u"Class"

    def getColumnValue(self, row):
        return row.row[2]


tableDialog = createTableChooserDialog("qmlregister() calls", ArgumentsExecutor(), False)
tableDialog.addCustomColumn(TypeColumn())
tableDialog.addCustomColumn(ClassNameColumn())

for result in results:
    tableDialog.add(Argument(result))

tableDialog.show()

Scripting 脚本

Now that we have some ghidra scripting under our feet, let's try to implement something useful; what I'll show you is probably improved in my repository.
既然我们已经掌握了一些 ghidra 脚本,让我们尝试实现一些有用的东西;我现在展示的内容可能已经在我的仓库里有所改进。

I'm not sure that they work out the box for now since some data types are assumed to exist at runtime, probably I'll improve them in the future.
我不确定他们目前是否能满足这个框架,因为有些数据类型默认运行时存在,可能我以后会改进。

Note: if you want to associate a key binding to a script you need to add a line like
注意: 如果你想给脚本关联按键绑定,你需要添加一行,比如

#@keybinding SHIFT-V

in your script and then tick the checkbox in the script listing.
然后在脚本列表中勾选复选框。

Vtable Vtable(虚拟表格)

Since there isn't a standard way of building virtual tables in ghidra I thought about creating a simple script that from a selection of region containing function pointers, creates a new struct using the metadata from the symbol you labeled the starting address with: the convention I used is that the label will be of the form <class name>::vtable.
由于 ghidra 中没有标准的虚拟表构建方法,我想创建一个简单的脚本,从包含函数指针的区域中选取,利用你标记起始地址的符号中的元数据创建新结构 :我使用的惯例是标签格式为 <class name>::vtable

First of all the function that creates the data type: from the name of the class creates a StructureDataType with a path strictly dependent from the class name (for now the dimension is zero, we are going to append its components one by one)
首先是创建数据类型的函数:从类名创建一个 StructureDataType,其路径严格依赖于类名(目前维度为零,我们将逐个附加其组件)

def build_structure(class_name, startAddress, count):
    path = "{}_vtable_t".format(class_name)
    logger.info("building struct named {}".format(path))
    structure = StructureDataType(path, 0)

The code is going to loop for how many function pointers we have told it via the count parameter: each iteration is going to retrieve the function pointer at a given offset, dereference the function itself if it exists or creates it.
代码会循环显示我们通过 计数参数:每次迭代都会在给定偏移量处检索函数指针,如果函数存在则撤参照该函数本身,或创建该函数。

    for index in range(count):
        logger.debug(" index: {}".format(index))
        address = startAddress.add(index * 4)
        addr_func = toAddr(getDataAt(address).getInt(0))
        function = getFunctionAt(addr_func)

        if function is None:
            logger.info("no function at {}, creating right now!".format(address))
            function = createFunction(addr_func, None)  # use default name

if the function is not already in a namespace set to be the same of the class and if it has the default name set the calling convention to __thiscall
如果函数不在某个命名空间中,则设置为与类相同的命名空间,且它有默认名称,则将调用约定设置为 __thiscall

        function_name = function.getName()

        # if it's a function with an already defined Namespace don't change that
        if function.getParentNamespace().isGlobal():
            # set the right Namespace
            namespace = getNamespace(None, class_name)
            function.setParentNamespace(namespace)

        # if is a function not touched from human try to set the calling convention
        # NOTE: for sure is __thiscall since it's a vtable but if the return
        #       value is an object this must go before the "this" pointer
        if function.getName().startswith("FUN_"):
            function.setCallingConvention('__thiscall')

obtain the function definition, obtain its datatype and in case update it
获取函数定义,获取其数据类型,并在

        funcDefinition = FunctionDefinitionDataType(function, False)

        logger.debug(" with signature: {}".format(funcDefinition))

        ptr_func_definition_data_type = PointerDataType(funcDefinition)

        # we are going to save definition and all
        # but probably we should clean the old definitions
        # of data types?
        data_type_manager = currentProgram.getDataTypeManager()
        logger.debug("Replacing {}".format(funcDefinition))
        # we replace all the things since they are generated automagically anyway
        data_type_manager.addDataType(funcDefinition, DataTypeConflictHandler.REPLACE_HANDLER)
        data_type_manager.addDataType(ptr_func_definition_data_type, DataTypeConflictHandler.REPLACE_HANDLER)

and finally insert it in the virtual table and return it
最后将其插入虚拟表并返回

        structure.insertAtOffset(  # FIXME: in general 4 is not the right size
            index * 4,
            ptr_func_definition_data_type,
            4,
            function_name,
            "",
        )

    return structure

This routine is simpler: retrieve the class's struct doing some check that it exists
这个例程更简单:检索类的结构体,并进行某种检查

def set_vtable_datatype(class_name, structure):
    path = "/{}".format(class_name)
    class_type = currentProgram.getDataTypeManager().getDataType(path)

    if class_type is None or class_type.isZeroLength():
        raise ValueError("You must define the class '{}' with '_vtable' before".format(class_name))

or that its first field is named _vtable
或者它的第一个字段被命名为 _vtable

    field = class_type.getComponent(0)
    field_name = field.getFieldName()

    if field_name != "_vtable":
        raise ValueError("I was expecting the first field to be named '_vtable'")

and then set the first field with the virtual table structure
然后用虚拟表结构设置第一个字段

    logger.info("set vtable as a pointer to {}".format(structure.getName()))
    field.setDataType(PointerDataType(structure))

In the main body of the script we are taking the start address and the number of the function pointers (note that this is not portable since we are assuming that a pointer is four bytes wide)
在脚本的主体中,我们取用起始地址和函数指针的数量(注意,这不是可移植的,因为我们假设指针宽度为四字节)

def main():
    startAddress = currentSelection.getFirstRange().getMinAddress()
    count = currentSelection.getFirstRange().getLength() / 4

then we take the symbol defined at the start of the selection checking that is following the previously indicated convention
然后取选择检查开始时定义的符号,遵循上述约定

    sym = getSymbolAt(startAddress)

    if sym is None or sym.getName() != "vtable" or sym.isGlobal():
        raise ValueError(
            "I was expecting a label here indicating the class Namespace, something like 'ClassName::vtable'")

    # FIXME: nested namespaces are not handled correctly
    class_name = sym.getParentNamespace().getName()
    if "::" in class_name:
        raise ValueError("Probably you want to handle manually this one: namespace '{}'".format(class_name))

then we use the two previous functions to do the thing
然后我们用前面的两个函数来完成这个任务

    structure = build_structure(class_name, startAddress, count)

    data_type_manager = currentProgram.getDataTypeManager()
    logger.info("Replacing {}".format(structure.getName()))
    data_type_manager.addDataType(structure, DataTypeConflictHandler.REPLACE_HANDLER)

    set_vtable_datatype(class_name, structure)

Since the virtual table is automagically generated, the script everytime overwrites it without any check.
由于虚拟表是自动生成的,脚本每次都会覆盖它,无需检查。

QString

The first thing that I explained, related to Qt was the QString data type, so let write some code that creates something that can be used like normal strings: first of all, let define a class with some constants that we are going to use later
我首先解释的,与 Qt 相关的是 QString 数据类型,所以我们写一些代码来创建可以像这样 正规字符串:首先,设定义一个类,其中某些常量使得我们为 稍后会用

class QString:
    INDEX_QARRAYDATA_OFFSET = 3
    INDEX_QARRAYDATA_LENGTH = 1

then the initialization function, where we pass the address where we want to create the QString: save the address for later use
然后是初始化函数,我们传递想要创建 QString 的地址:保存地址以备后用

    def __init__(self, address):
        self.address = address

try to get the QArrayData data type (this part needs improvement for sure, since it's tricky to not step on work already done, for example consider someone having already defined this data type but for some reason they decided to used a different layout, if I was going to overwrite QArrayData in their project probably their analysis would end in the garbage)
尽量获取 QArrayData 的数据类型(这部分确实需要改进,因为很难不踩到已经完成的工作,比如有人已经定义了这个数据类型,但不知为何他们选择了不同的布局,如果我要覆盖他们的项目中的 QArrayData,他们的分析可能就会变成垃圾)。

        dataType = getDataTypes("QArrayData")

        if len(dataType) < 1:  # TODO: more check that the datatype is right
            raise ValueError("You must define the QArrayData type")

        self.dataType = dataType[0]

double check that the ref field makes sense for something static
仔细检查ref字段是否适合静态内容

        # sanity check (probably some more TODO)
        if getInt(address) != -1:
            raise ValueError("We are expecting -1 for the 'ref' field")

and create the data accordingly (if it's already created it simply returns the data)
并据此创建数据(如果已经创建,它只需返回数据)

        # create data at the wanted position
        self._d = createData(address, self.dataType)

create the reference between the address of the QString and the string itself (I don't like that creates the reference inside the ref field but for now I can live with that)
创建 QString 地址和字符串本身之间的引用(我不喜欢在 ref 字段内创建引用,但目前还能接受)。

        # create reference
        rm = currentProgram.getReferenceManager()

        to_address = address.add(self.offset)

        rm.addOffsetMemReference(
            address,
            to_address,
            self.offset,
            RefType.DATA,
            SourceType.USER_DEFINED,
            0,
        )

Now it's time to create, or at least retrieve, the string associated with this object (QString stores internally data as utf16 little endian)
现在是时候创建,或者至少检索与该对象关联的字符串(QString 内部存储数据为 utf16 小端序)

        self.data = getDataAt(to_address)

        # we try to define a unicode string but maybe
        # some others data was defined before so we simply
        # get the string and whatever
        if self.data is None:
            try:
                self.data = createUnicodeString(to_address)
                str_ = self.data.value
            except CodeUnitInsertionException as e:
                logger.warning("--- code conflict below ---")
                logger.exception(e)
                logger.warning("---------------------------")
                # we haven't any data defined, use unicode
                self.data = common.get_bytes_from_binary(to_address, self.size * 2)
                str_ = self.data.decode('utf-16le')
        else:
            str_ = self.data.value

and set a label mimicking the string itself
并设置一个模仿琴弦本身的标签

        createLabel(address, 'QARRAYDATA_%s' % slugify(str_), True)

What remains are a couple of accessory methods used to retrieve the fields in the QArrayData structure
剩下的是几种辅助方法,用于检索 QArrayData 结构中的字段

    @property
    def offset(self):
        return self._d.getComponent(self.INDEX_QARRAYDATA_OFFSET).value.getValue()

    @property
    def size(self):
        """This is the value as is, if you need the length of the unicode encoded
        data you need to multiply this by 2."""
        return self._d.getComponent(self.INDEX_QARRAYDATA_LENGTH).value.getValue()

    @property
    def end(self):
        """Return the address where the data pointed by this ends"""
        return self.address.add(self.offset + self.size * 2)

    @property
    def end_aligned(self):
        """Return the address where the data pointed by this end but aligned"""
        return self.address.add((self.offset + (self.size + 1) * 2 + 3) & 0xfffffc)  # FIXME: generate mask

Now it's possible to use it on a script with the following lines
现在可以用以下几行来对付脚本

def main(address):

    string = QString(address)
    # move the cursor at the adjacent location
    goTo(string.end_aligned)

main(currentAddress)

and you have a procedure to create a QString at the address where you have the cursor and have it advancing at the end of the string when done.
你有一个过程可以在你有光标的地址创建一个 QString,完成后它会在字符串末尾前进。

QML

As described in the section about QML, Qt based binaries contain resources inside them that the application loads at runtime by the use of the function qRegisterResourceData(); creating a script that from the address of the function finds all the points where is called extracting its arguments it's possible to dump all the resources.
QML 部分所述,基于 Qt 的二进制文件内部包含资源,应用程序通过函数 qRegisterResourceData() 在运行时加载这些资源;创建一个脚本,从函数地址找到所有 的点,提取其参数后,可以倾倒所有资源。

I don't waste space in explaining it here line by line since it's pratically explained in the section about the ghidra's APIs and the rest is matter of extracting filesystem nodes, however you can find it on github.
我在这里一行一行解释就不浪费篇幅,因为大致上在关于 ghidra 的 API 的部分已经解释清楚了,剩下的就是解压文件系统节点的内容,不过你可以在 GitHub 上找到。

QObject

Same for a script regarding the extraction of metadata of a class that inherits from QObject: it's on github, it needs improvements, for example it's not yet able to retrieve the type of the arguments of a function.
同样,关于从 QObject 继承的类元数据提取脚本也是如此:它在 GitHub 上,需要改进,比如还没能检索函数参数的类型。


其它

OSUSecLab/QtRE: A Ghidra headless analyzer tailored for Qt binary analysis

posted @ 2026-05-23 17:53  DirWangK  阅读(67)  评论(0)    收藏  举报