初探CTF中特殊框架逆向
最近做題,逐漸遇到一些非 IAT32 ASR arm32,64(Linux)框架之外的 各種框架的re程序逆向分析。雖然題目難度不大,但著實令人耳目一新。而且這會不會是出題人釋放的信號,代表著未來re出題的新方向呢?本著認(rèn)真嚴(yán)謹(jǐn)?shù)膽B(tài)度,我想應(yīng)該把它們做一個總結(jié)。
QT框架程序逆向
QT框架簡介
Qt 是一個1991年由Qt Company開發(fā)的跨平臺C++圖形用戶界面應(yīng)用程序開發(fā)框架。它既可以開發(fā)GUI程序,也可用于開發(fā)非GUI程序,比如控制臺工具和服務(wù)器。Qt是面向?qū)ο蟮目蚣?,使用特殊的代碼生成擴展(稱為元對象編譯器(Meta Object Compiler, moc))以及一些宏,Qt很容易擴展,并且允許真正地組件編程。另一方面,QT實現(xiàn)了全面支持iOS、Android、WP,它提供給應(yīng)用程序開發(fā)者建立藝術(shù)級的圖形用戶界面所需的所有功能。
Qt框架應(yīng)用還是很廣泛的,為了大家有一個直觀的概念,我用Everything搜索了下我電腦上的qt5core.dll,部分結(jié)果如下圖所示:
QT對象樹
Qt提供了一種機制,能夠自動、有效的組織和管理繼承自QObject的Qt對象,這種機制就是對象樹。
如圖所示,QObject 對象有且僅有一個父對象,但可以有很多個子對象。按照這種形式排列就會形成一個對象樹的結(jié)構(gòu),最上層是父對象,下面是子對象,在再下面是孫子對象,以此類推。當(dāng)父對象析構(gòu)的時候,這個子對象列表中的所有對象都會被析構(gòu),當(dāng)析構(gòu)子對象的時候,會自動從父對象的子對象列表中刪除。
那么Qt為什么要這樣設(shè)計呢?很簡單,方便內(nèi)存管理我們在創(chuàng)建 QObject 對象時,提供一個父對象,那么我們創(chuàng)建的這個 QObject 對象會自動添加到其父對象的 children() 列表。這種機制在GUI程序開發(fā)過程中是相當(dāng)實用的。值得注意的是,如果在構(gòu)造時設(shè)置父對象為 NULL,那么當(dāng)前實例不會有父對象存在,Qt 也不會自動析構(gòu)該實例。
tips:QWidget的操作流程
QWidget 也是 QObject 的子類,所以在 parent 機制上是沒有區(qū)別的。然而實際使用時,對于 QWidget 和其派生類來說,在內(nèi)存管理上要稍微復(fù)雜一些。例如QWidget的關(guān)閉流程,首先用戶點擊關(guān)閉按鈕觸發(fā) close()槽,默認(rèn)的 QCloseEvent 會將 widget隱藏起來,也就是觸發(fā)hide()槽。也就是說widget的關(guān)閉實際是將其隱藏,而沒有釋放內(nèi)存,雖然我們有時會重寫 closeEvent 但也不會手動釋放 widget。
解決方案:需要設(shè)置 Qt::WA_DeleteOnClose 屬性,那么會在 close 之后接著調(diào)用 widget 的析構(gòu)函數(shù)。
tips2:對象樹模型存在的問題
俗話說,百密終有一疏。對象樹模型的特殊析構(gòu)規(guī)則,大概注定著它會爆出邏輯性漏洞。
來看下面這段程序:
#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();
}
運行結(jié)果:
關(guān)閉 widget 后程序崩潰,沒有正常結(jié)束
為什么程序會崩潰呢?通過上面的學(xué)習(xí)我們知道,C++規(guī)定了析構(gòu)順序應(yīng)該按照其創(chuàng)建順序的相反過程。而當(dāng)我們先創(chuàng)建子對象,再創(chuàng)建父對象時,根據(jù)自動析構(gòu)原理,我們析構(gòu)父對象的時候會自動析構(gòu)父對象,此時,子對象已經(jīng)被析構(gòu)了,然而代碼繼續(xù)執(zhí)行,按照順序還要再析構(gòu)一次子對象,這時候已經(jīng)是第二次調(diào)用 子對象的析構(gòu)函數(shù)了。C++中不允許調(diào)用兩次析構(gòu)函數(shù),最終導(dǎo)致程序崩潰。
避免問題的方案:
先創(chuàng)建父對象再創(chuàng)建子類對象,并且在創(chuàng)建子對象時就指定父對象;
盡量在堆上創(chuàng)建子對象;
QT程序的特殊機制--信號/槽
先來看一下官方介紹:
信號/槽是 Qt 框架引以為豪的機制之一。所謂信號槽,實際就是觀察者模式。當(dāng)某個事件發(fā)生之后,比如,按鈕檢測到自己被點擊了一下,它就會發(fā)出一個信號(signal)。這種發(fā)出是沒有目的的,類似廣播。如果有對象對這個信號感興趣,它就會使用連接(connect)函數(shù),意思是,將想要處理的信號和自己的一個函數(shù)(稱為槽(slot))綁定來處理這個信號。也就是說,當(dāng)信號發(fā)出時,被連接的槽函數(shù)會自動被回調(diào)。這就類似觀察者模式:當(dāng)發(fā)生了感興趣的事件,某一個操作就會被自動觸發(fā)。(這里提一句,Qt 的信號槽使用了額外的處理來實現(xiàn),并不是 GoF 經(jīng)典的觀察者模式的實現(xiàn)方式。)
Qt使用信號(Signal)和槽(Slot)機制用于對象間的通信??梢詫⑿盘柡筒弁ㄟ^QObject對象的connet函數(shù)關(guān)聯(lián)起來。我們可以使用emit(Qt定義的語句)發(fā)出某個信號,與該信號關(guān)聯(lián)的槽就會接受到信號進行處理。
信號槽編寫
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();
}
關(guān)鍵函數(shù) connect
connect()函數(shù)最常用的一般形式:
connect(sender, signal, receiver, slot);
參數(shù):
sender:發(fā)出信號的對象
signal:發(fā)送對象發(fā)出的信號
receiver:接收信號的對象
slot:接收對象在接收到信號之后所需要調(diào)用的函數(shù)
信號/槽的特殊性質(zhì)
1.一個信號可以和多個槽相連
如果是這種情況,這些槽會一個接一個的被調(diào)用,但是它們的調(diào)用順序是不確定的。
2.多個信號可以連接到一個槽
只要任意一個信號發(fā)出,這個槽就會被調(diào)用。
3.一個信號可以連接到另外的一個信號
當(dāng)?shù)谝粋€信號發(fā)出時,第二個信號被發(fā)出。除此之外,這種信號-信號的形式和信號-槽的形式?jīng)]有什么區(qū)別。
4.槽可以被取消鏈接
這種情況并不經(jīng)常出現(xiàn),因為當(dāng)一個對象delete之后,Qt自動取消所有連接到這個對象上面的槽。
5.使用Lambda 表達式
在使用 Qt 5 的時候,能夠支持 Qt 5 的編譯器都是支持 Lambda 表達式的。
我們的代碼可以寫成下面這樣:
QObject::connect(&newspaper, static_cast
(const QString &)>(&Newspaper::newPaper),
[=](const QString &name)
{ /* Your code here. */ }
);
在連接信號和槽的時候,槽函數(shù)可以使用Lambda表達式的方式進行處理。
6.信號槽要求信號和槽的參數(shù)一致
所謂一致,是參數(shù)類型一致。如果不一致,允許槽函數(shù)的參數(shù)可以比信號的少,即便如此,槽函數(shù)存在的那些參數(shù)的順序也必須和信號的前面幾個一致起來。這是因為,你可以在槽函數(shù)中選擇忽略信號傳來的數(shù)據(jù)(也就是槽函數(shù)的參數(shù)比信號的少),但是信號根本沒有這個數(shù)據(jù),槽函數(shù)中肯定無法使用。
[GKCTF 2021]QQQQT
virtualbox打包的QT程序。
奇怪的main函數(shù)....奇怪的關(guān)鍵函數(shù)。正向引用分析不了,main函數(shù)代碼非常奇怪...找不到關(guān)鍵函數(shù)調(diào)用。只能曲線救國一波,通過字符串搜索搜索到密文,再通過交叉引用定位到關(guān)鍵函數(shù)。
一個base58加密,解密即可:
mfc框架逆向
mfc框架簡介
老規(guī)矩,先來看官方解釋:
MFC庫是開發(fā)Windows應(yīng)用程序的C++接口。MFC提供了面向?qū)ο蟮目蚣?,采用面向?qū)ο蠹夹g(shù),將大部分的Windows API 封裝到C++類中,以類成員函數(shù)的形式提供給程序開發(fā)人員調(diào)用。
簡單來說,MFC是一種面向?qū)ο螅糜陂_發(fā)windows應(yīng)用程序的框架,突出特點是封裝了大部分windows API,便于開發(fā)人員使用。
MFC程序的運行過程分為以下四步:
- 利用全局應(yīng)用程序?qū)ο髏heApp啟動應(yīng)用程序。
- 調(diào)用全局應(yīng)用程序?qū)ο蟮臉?gòu)造函數(shù),從而調(diào)用基類(CWinApp)的構(gòu)造函數(shù),完成應(yīng)用程序的一些初始化工作,并將應(yīng)用程序?qū)ο蟮闹羔槺4嫫饋怼?/li>
- 進入WinMain函數(shù)。在AfxWinMain函數(shù)中獲取子類的指針,利用指針實現(xiàn)上述的三個函數(shù),從而完成窗口的創(chuàng)建注冊等工作。
- 進入消息循環(huán),一直到WM_QUIT。
怎樣逆向mfc程序
怎樣逆向mfc程序?一個非常樸實無華的問題(大霧)。這里介紹常用的兩種方法。
尋找程序初始化函數(shù)下斷點
AfxOleInit (COM初始化)
AfxEnableControlContainer (Ole初始化)
AfxGetModuleState (獲取模塊狀態(tài))
CoInitialize (COM初始化 API)
GdiplusStartup (GDI+初始化)
SetUnhandledExceptionFilter (截獲異常處理)
Enable3dControls()
CWinApp()
使用工具
針對mfc程序逆向分析,前輩們已經(jīng)開發(fā)了一些非常好用的小工具,站在巨人肩膀上的我們,掌握了工具的使用方法,便可大大提高mfc程序的分析效率。
常用工具:
- 彗星小助手
- xspy
- ResourceHacker
[HDCTF2019]MFC
首先通過彗星小助手獲取窗口信息(關(guān)鍵是句柄)。
得到:
944c8d100f82f0c18b682f63e4dbaa207a2f1e72581c2f1b
一大串密文:
接著使用xspy掃描一下mfc窗口的各個框架。
掃到一個特殊的onMsg,0464,為什么說它特殊呢,因為他是唯一 一個沒有系統(tǒng)函數(shù)包裹的參數(shù)。
這里多提一嘴,xspy的使用方法是,拖拽小放大鏡到mfc窗口中(因為無知擱那雙擊了半天小放大鏡沒反應(yīng)急得跳腳)。
接下來我們利用彗星小助手傳遞0464(記得轉(zhuǎn)換為10進制為1124)給mfc框。
這樣就得到了des key 找個des解密網(wǎng)站解密即可得到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??