freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

初探CTF中特殊框架逆向
2022-04-08 18:53:32
所属地 山东省

最近做题,逐渐遇到一些非 IAT32 ASR arm32,64(Linux)框架之外的 各种框架的re程序逆向分析。虽然题目难度不大,但着实令人耳目一新。而且这会不会是出题人释放的信号,代表着未来re出题的新方向呢?本着认真严谨的态度,我想应该把它们做一个总结。

QT框架程序逆向

QT框架简介

Qt 是一个1991年由Qt Company开发的跨平台C++图形用户界面应用程序开发框架。它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器。Qt是面向对象的框架,使用特殊的代码生成扩展(称为元对象编译器(Meta Object Compiler, moc))以及一些,Qt很容易扩展,并且允许真正地组件编程。另一方面,QT实现了全面支持iOS、Android、WP,它提供给应用程序开发者建立艺术级的图形用户界面所需的所有功能。

Qt框架应用还是很广泛的,为了大家有一个直观的概念,我用Everything搜索了下我电脑上的qt5core.dll,部分结果如下图所示:

image-20220406194855852

QT对象树

Qt提供了一种机制,能够自动、有效的组织和管理继承自QObject的Qt对象,这种机制就是对象树。

image-20220406205810726

如图所示,QObject 对象有且仅有一个父对象,但可以有很多个子对象。按照这种形式排列就会形成一个对象树的结构,最上层是父对象,下面是子对象,在再下面是孙子对象,以此类推。当父对象析构的时候,这个子对象列表中的所有对象都会被析构,当析构子对象的时候,会自动从父对象的子对象列表中删除。

那么Qt为什么要这样设计呢?很简单,方便内存管理我们在创建 QObject 对象时,提供一个父对象,那么我们创建的这个 QObject 对象会自动添加到其父对象的 children() 列表。这种机制在GUI程序开发过程中是相当实用的。值得注意的是,如果在构造时设置父对象为 NULL,那么当前实例不会有父对象存在,Qt 也不会自动析构该实例。

tips:QWidget的操作流程

QWidget 也是 QObject 的子类,所以在 parent 机制上是没有区别的。然而实际使用时,对于 QWidget 和其派生类来说,在内存管理上要稍微复杂一些。例如QWidget的关闭流程,首先用户点击关闭按钮触发 close()槽,默认的 QCloseEvent 会将 widget隐藏起来,也就是触发hide()槽。也就是说widget的关闭实际是将其隐藏,而没有释放内存,虽然我们有时会重写 closeEvent 但也不会手动释放 widget。

解决方案:需要设置 Qt::WA_DeleteOnClose 属性,那么会在 close 之后接着调用 widget 的析构函数。

tips2:对象树模型存在的问题

俗话说,百密终有一疏。对象树模型的特殊析构规则,大概注定着它会爆出逻辑性漏洞。

来看下面这段程序:

#include <QApplication>
#include <QPushButton>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QPushButton btn("button");
    QWidget widget;
    btn.setParent(&widget);
    widget.show();

    return a.exec();
}

运行结果:
关闭 widget 后程序崩溃,没有正常结束

为什么程序会崩溃呢?通过上面的学习我们知道,C++规定了析构顺序应该按照其创建顺序的相反过程。而当我们先创建子对象,再创建父对象时,根据自动析构原理,我们析构父对象的时候会自动析构父对象,此时,子对象已经被析构了,然而代码继续执行,按照顺序还要再析构一次子对象,这时候已经是第二次调用 子对象的析构函数了。C++中不允许调用两次析构函数,最终导致程序崩溃。

避免问题的方案:

  1. 先创建父对象再创建子类对象,并且在创建子对象时就指定父对象;

  2. 尽量在堆上创建子对象;

QT程序的特殊机制--信号/槽

先来看一下官方介绍

信号/槽是 Qt 框架引以为豪的机制之一。所谓信号槽,实际就是观察者模式。当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个函数(称为槽(slot))绑定来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。(这里提一句,Qt 的信号槽使用了额外的处理来实现,并不是 GoF 经典的观察者模式的实现方式。)

Qt使用信号(Signal)和槽(Slot)机制用于对象间的通信。可以将信号和槽通过QObject对象的connet函数关联起来。我们可以使用emit(Qt定义的语句)发出某个信号,与该信号关联的槽就会接受到信号进行处理。

信号槽编写

QT5的书写方式
#include <QApplication>
#include <QPushButton>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QPushButton button("Quit");
QObject::connect(&button, &QPushButton::clicked,
&app, &QApplication::quit);
    button.show();
    return app.exec();
}
QT4的书写方式
int main(int argc, char *argv[]) 
{ 
        QApplication a(argc, argv); 
        QPushButton *button = new QPushButton("Quit"); 
        connect(button, SIGNAL(clicked()), &a, SLOT(quit())); 
        button->show(); 
        return a.exec(); 
}
关键函数 connect

connect()函数最常用的一般形式:

connect(sender, signal, receiver, slot);

参数:

sender:发出信号的对象

signal:发送对象发出的信号

receiver:接收信号的对象

slot:接收对象在接收到信号之后所需要调用的函数

信号/槽的特殊性质

1.一个信号可以和多个槽相连

如果是这种情况,这些槽会一个接一个的被调用,但是它们的调用顺序是不确定的。

2.多个信号可以连接到一个槽

只要任意一个信号发出,这个槽就会被调用。

3.一个信号可以连接到另外的一个信号

当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。

4.槽可以被取消链接

这种情况并不经常出现,因为当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽。

5.使用Lambda 表达式

在使用 Qt 5 的时候,能够支持 Qt 5 的编译器都是支持 Lambda 表达式的。

我们的代码可以写成下面这样:

QObject::connect(&newspaper, static_cast<void (Newspaper:: *)
(const QString &)>(&Newspaper::newPaper),
[=](const QString &name) 
{ /* Your code here. */ }
);

在连接信号和槽的时候,槽函数可以使用Lambda表达式的方式进行处理。

6.信号槽要求信号和槽的参数一致

所谓一致,是参数类型一致。如果不一致,允许槽函数的参数可以比信号的少,即便如此,槽函数存在的那些参数的顺序也必须和信号的前面几个一致起来。这是因为,你可以在槽函数中选择忽略信号传来的数据(也就是槽函数的参数比信号的少),但是信号根本没有这个数据,槽函数中肯定无法使用。

[GKCTF 2021]QQQQT

virtualbox打包的QT程序。

image-20220406214901488

奇怪的main函数....奇怪的关键函数。正向引用分析不了,main函数代码非常奇怪...找不到关键函数调用。只能曲线救国一波,通过字符串搜索搜索到密文,再通过交叉引用定位到关键函数。

image-20220406215511840

一个base58加密,解密即可

image-20220406215658757

image-20220406215719506

mfc框架逆向

mfc框架简介

老规矩,先来看官方解释:

MFC库是开发Windows应用程序的C++接口。MFC提供了面向对象的框架,采用面向对象技术,将大部分的Windows API 封装到C++类中,以类成员函数的形式提供给程序开发人员调用。

image-20220407083858771

简单来说,MFC是一种面向对象,用于开发windows应用程序的框架,突出特点是封装了大部分windows API,便于开发人员使用。

MFC程序的运行过程分为以下四步:

1 利用全局应用程序对象theApp启动应用程序。

2 调用全局应用程序对象的构造函数,从而调用基类(CWinApp)的构造函数,完成应用程序的一些初始化工作,并将应用程序对象的指针保存起来。

3 进入WinMain函数。在AfxWinMain函数中获取子类的指针,利用指针实现上述的三个函数,从而完成窗口的创建注册等工作。

4 进入消息循环,一直到WM_QUIT。

怎样逆向mfc程序

怎样逆向mfc程序?一个非常朴实无华的问题(大雾)。这里介绍常用的两种方法。

寻找程序初始化函数下断点

AfxOleInit (COM初始化)
AfxEnableControlContainer  (Ole初始化)
AfxGetModuleState  (获取模块状态)
CoInitialize  (COM初始化 API)
GdiplusStartup (GDI+初始化)
SetUnhandledExceptionFilter  (截获异常处理)
Enable3dControls()
CWinApp()

使用工具

针对mfc程序逆向分析,前辈们已经开发了一些非常好用的小工具,站在巨人肩膀上的我们,掌握了工具的使用方法,便可大大提高mfc程序的分析效率。

常用工具:
彗星小助手
xspy
ResourceHacker

[HDCTF2019]MFC

首先通过彗星小助手获取窗口信息(关键是句柄)。

image-20220407092108451

得到

944c8d100f82f0c18b682f63e4dbaa207a2f1e72581c2f1b

一大串密文

接着使用xspy扫描一下mfc窗口的各个框架。

image-20220407093455978

扫到一个特殊的onMsg,0464,为什么说它特殊呢,因为他是唯一 一个没有系统函数包裹的参数。

这里多提一嘴,xspy的使用方法是,拖拽小放大镜到mfc窗口中(因为无知搁那双击了半天小放大镜没反应急得跳脚)。

接下来我们利用彗星小助手传递0464(记得转换为10进制为1124)给mfc框。

image-20220407094608595

这样就得到了des key 找个des解密网站解密即可得到flag。

参考链接

https://www.52pojie.cn/thread-497018-1-1.html

https://blog.csdn.net/liujiandu101/article/details/84390269

https://blog.csdn.net/baidu_41388533/article/details/111292441

https://blog.csdn.net/qq_34139994/article/details/105391611

https://www.52pojie.cn/thread-65091-1-1.html

# CTF
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录