C++異?;幚?/p>
OLLVM-控制流平坦化
Two Puzzles
Exception
一般碰到C++異常逆向,確定了異常分發(fā)、處理部分,直接把call throw改為jmp catch塊,再F5即可。 ? PS: 多個(gè)catch塊根據(jù)rdx來(lái)當(dāng)為異常處理數(shù)值決定哪個(gè)為對(duì)應(yīng)的catch塊。 ? 關(guān)于以上,這篇講的很詳細(xì): https://4nsw3r.top/2022/02/03/SCTF-REVERSE-CplusExceptionEncrypt-%E8%B5%9B%E5%90%8E%E5%A4%8D%E7%8E%B0/#Clang-x64 ? 然而,這題沒這么簡(jiǎn)單,套了個(gè)ollvm?。炕诋惓L幚淼膐llvm,無(wú)論從哪個(gè)角度都沒法使用之前的老套路。 ? 耐心看完這兩篇文章就會(huì)有所收獲,對(duì)于此題的被異常處理搞亂掉的cfg就會(huì)有所理解。 https://www.cnblogs.com/catch/p/3604516.html https://www.cnblogs.com/catch/p/3619379.html ?
OLLVM
要是平常的ollvm都可以按照這篇來(lái)解決: https://bluesadi.github.io/0x401RevTrain-Tools/angr/10_%E5%88%A9%E7%94%A8angr%E7%AC%A6%E5%8F%B7%E6%89%A7%E8%A1%8C%E5%8E%BB%E9%99%A4%E6%8E%A7%E5%88%B6%E6%B5%81%E5%B9%B3%E5%9D%A6%E5%8C%96/ ? 其他的原理講的非常好,問(wèn)題是這題并不是那么簡(jiǎn)單,但為了去ollvm我們的思路也是一樣的,所以要對(duì)ollvm的cfg熟悉,并懂得我們?cè)撊绾位謴?fù)一個(gè)被ollvm混淆后的代碼。 ? 現(xiàn)在就開始寫我對(duì)這題的看法! ? 參考Write up: ? https://github.com/Lnkvct/CTF-for-Fun/blob/main/Challenges/Inflated-ACTF2022/writeup.md https://www.cnblogs.com/FW-ltlly/p/16472171.html ? lchild師傅的Write up(pdf所以沒法給鏈接)
0x00 日常查殼
(感覺好久沒寫wp了) ? 無(wú)殼64位 ?

0x01 CFG
GETC
在講這題ollvm與異常處理之前,有必要先搞懂我們到底是怎么輸入的。 ? 一共有三處getc處理我們第一段輸入的地方。
407629 40553A(專門用來(lái)處理箭頭) 405676(專門用來(lái)處理箭頭)? 程序最先開始運(yùn)行的是 407629,這里我們可以輸入上下左右箭頭與特定的數(shù)字。
如果是數(shù)字,程序讀取加密進(jìn)行存放
如果是箭頭,會(huì)繼續(xù)進(jìn)行處理
(同時(shí)我們的輸入還會(huì)決定異常類型)
Official?Write?up:?The?value?of?the?first?field?of?the?thrown?StdObfException?object?comes?from?the?second?input?passed?to?the?construct?of?StdObfException.

? 那么異常處理先不深究,繼續(xù)回來(lái)箭頭如何處理這個(gè)問(wèn)題。那么箭頭其實(shí)為三字節(jié)碼,上下左右箭頭分別對(duì)應(yīng) ^[[A ^[[B ^[[C ^[[D。此時(shí)開始動(dòng)調(diào),我第一次輸入為上箭頭,同時(shí)注意RAX。 ? 那么在 407629 第一次處理箭頭會(huì)讀取為1B。

隨后到 40553A 讀取為5B。

最后到達(dá) 405676 可以發(fā)現(xiàn)我們的上箭頭代碼所對(duì)應(yīng)的字符為A。

以上就解釋了第一段輸入的處理,等到最后解密第一段輸入就會(huì)用到此。
OLLVM
引用這張圖,想要去掉ollvm最基本的是要認(rèn)識(shí)這幾個(gè)塊。 https://security.tencent.com/index.php/blog/msg/112 ? ?

? 先拋去原題,來(lái)認(rèn)識(shí)一下這些名詞:
函數(shù)的開始地址為序言(Prologue)的地址
序言的后繼為主分發(fā)器(Main dispatcher)
后繼為主分發(fā)器的塊為預(yù)處理器(Predispatcher)
后繼為預(yù)處理器的塊為真實(shí)塊(Relevant blocks)
無(wú)后繼的塊為retn塊
剩下的為無(wú)用塊與子分發(fā)器(Sub dispatchers)
那參考文章,總結(jié)來(lái)說(shuō),利用angr符號(hào)執(zhí)行去除控制流平坦化的步驟可以歸結(jié)為三個(gè)步驟:
靜態(tài)分析CFG得到序言/入口塊(Prologue)、主分發(fā)器(Main dis。
patcher)、子分發(fā)器/無(wú)用塊(Sub dispatchers)、真實(shí)塊(Relevant blocks)、預(yù)分發(fā)器(Predispatcher)和返回塊(Return)。
利用符號(hào)執(zhí)行恢復(fù)真實(shí)塊的前后關(guān)系,重建控制流。
根據(jù)第二步重建的控制流Patch程序,輸出恢復(fù)后的可執(zhí)行文件。
簡(jiǎn)單來(lái)說(shuō)就是獲取所有的塊,利用angr符號(hào)執(zhí)行我們的真實(shí)塊,查看真實(shí)塊之間的流程,再拋去我們不要的塊,patch程序,完成! ? (那么具體的實(shí)現(xiàn)看文章) https://bluesadi.github.io/0x401RevTrain-Tools/angr/10_%E5%88%A9%E7%94%A8angr%E7%AC%A6%E5%8F%B7%E6%89%A7%E8%A1%8C%E5%8E%BB%E9%99%A4%E6%8E%A7%E5%88%B6%E6%B5%81%E5%B9%B3%E5%9D%A6%E5%8C%96/ ? 然而這題根本不像??!可以看出這題的CFG根本看不懂,不像單單ollvm混淆過(guò)的cfg那么漂亮。 ?

Exception
為了搞懂CFG為什么成這樣了,得先了解下異常的原理,參考原文: https://www.cnblogs.com/catch/p/3604516.html ? 對(duì)于最基本的thown catch不再贅述,這篇講到很清楚: https://4nsw3r.top/2022/02/03/SCTF-REVERSE-CplusExceptionEncrypt-%E8%B5%9B%E5%90%8E%E5%A4%8D%E7%8E%B0/#Clang-x64 ?
異常拋出后,發(fā)生了什么事情?
1、如果當(dāng)前函數(shù)沒有catch,就沿著函數(shù)的調(diào)用鏈繼續(xù)往上拋,然后出現(xiàn)兩種情況:
在某個(gè)函數(shù)中找到相應(yīng)的catch;
沒找到相應(yīng)的catch,調(diào)用 std::terminate() (這個(gè)函數(shù)是把程序abort)。
2、如果想找到了相應(yīng)的catch,執(zhí)行相應(yīng)的操作。
程序中catch的代碼塊有個(gè)專有名詞:Landing pad
3、從拋異常到開始 -> 執(zhí)行Landing pad代碼 這整個(gè)過(guò)程叫作Stack unwind。
Stack unwind 從拋異常函數(shù)開始,對(duì)調(diào)用鏈上的函數(shù)逐個(gè)往前查找Landing pad。 ? 如果沒有找到Landing pad則把程序abort,如果找到則記下Landing pad的位置,再重新回到拋異常的函數(shù)那里開始,一幀一幀地清理調(diào)用鏈上各個(gè)函數(shù)內(nèi)部的局部變量,直到 landing pad 所在的函數(shù)為止。
void func1()
{
cs a; // stack unwind時(shí)被析構(gòu)。
throw 3;
}
void func2()
{
cs b;
func1();
}
void func3()
{
cs c;
try
{
func2();
}
catch (int)
{
//進(jìn)入這里之前, func1, func2已經(jīng)被unwind.
}
}
? stack unwind的過(guò)程可以簡(jiǎn)單看成函數(shù)調(diào)用的逆過(guò)程,這個(gè)過(guò)程在實(shí)現(xiàn)上由一個(gè)專門的stack unwind庫(kù)來(lái)實(shí)現(xiàn)。
stack unwind庫(kù)在intel平臺(tái)上
屬于Itanium ABI 接口中的一部分
與具體的語(yǔ)言無(wú)關(guān),由系統(tǒng)實(shí)現(xiàn)
任何上層語(yǔ)言都可以通過(guò)這個(gè)接口的基礎(chǔ)實(shí)現(xiàn)各自的異常處理
GCC就是通過(guò)這個(gè)接口實(shí)現(xiàn)C++的異常處理
Itanium C++ ABI
ltanium C++ ABI定義了一系列函數(shù)以及數(shù)據(jù)結(jié)構(gòu)來(lái)建立整個(gè)異常處理的流程及框架,主要函數(shù)包括以下列:
_Unwind_RaiseException, _Unwind_Resume, _Unwind_DeleteException, _Unwind_GetGR, _Unwind_SetGR, _Unwind_GetIP, _Unwind_SetIP, _Unwind_GetRegionStart, _Unwind_GetLanguageSpecificData, _Unwind_ForcedUnwind? 其中 _Unwind_RaiseException() 函數(shù)進(jìn)行stack unwind,它在用戶執(zhí)行throw的時(shí)被調(diào)用。 ? 主要功能: 從當(dāng)前函數(shù)開始,對(duì)調(diào)用鏈上的每一個(gè)函數(shù)都調(diào)用一個(gè)叫做 personality routine 的函數(shù)(__gxx_personality_v0)。 personality routine 該函數(shù)由上層的語(yǔ)言定義及提供實(shí)現(xiàn)。 ? _Unwind_RaiseException() 會(huì)在內(nèi)部把函數(shù)棧調(diào)用現(xiàn)場(chǎng)重現(xiàn),然后傳給 personality routine,該函數(shù)主要做兩件事情: 1、檢查當(dāng)前函數(shù)是否有相對(duì)應(yīng)的catch; 2、清理調(diào)用棧上的局部變量。 ? 那么稍稍總結(jié)一下,就是當(dāng)程序拋出異常就要進(jìn)行 stack unwind 操作。 ? 而這個(gè)操作具體是 _Unwind_RaiseException() 中的 personality routine() 實(shí)現(xiàn)了檢查catch和清理?xiàng)I系木植孔兞俊??
C++ ABI
基于前面介紹的 ltanium ABI,編譯器層面也定義了一系列 ABI 與之交互。 ? 當(dāng)我們?cè)诖a中寫下 throw xxx,編譯器會(huì)分配一個(gè)數(shù)據(jù)結(jié)構(gòu)?__cxa_exception 來(lái)表示該異常,該異常也有一個(gè)頭部,定義如下:
struct __cxa_exception
{
std::type_info * exceptionType;
void (*exceptionDestructor) (void *);
unexpected_handler unexpectedHandler;
terminate_handler terminateHandler;
__cxa_exception * nextException;
int handlerCount;
int handlerSwitchValue;
const char * actionRecord;
const char * languageSpecificData;
void * catchTemp;
void * adjustedPtr;
_Unwind_Exception unwindHeader;
};
? 當(dāng)用戶 throw 一個(gè)異常時(shí),編譯器會(huì)幫我們調(diào)用相應(yīng)的函數(shù)分配出如下的結(jié)構(gòu):

異常對(duì)象由函數(shù) __cxa_allocate_exception() 進(jìn)行創(chuàng)建
最后由 __cxa_free_exception() 進(jìn)行銷毀
當(dāng)我們?cè)诔绦蚶飯?zhí)行了拋出異常的操作,編譯器為我們做了如下的事情: ? 1、調(diào)用 cxa_allocate_exception 函數(shù),分配一個(gè)異常對(duì)象(cxa_exception,數(shù)據(jù)結(jié)構(gòu)如上)。 ? 2、調(diào)用 __cxa_throw 函數(shù),這個(gè)函數(shù)會(huì)將異常對(duì)象做一些初始化。 ? 3、__cxa_throw() 調(diào)用 Itanium ABI 里的 _Unwind_RaiseException() 從而開始 unwind。 ? 4、_Unwind_RaiseException() 對(duì)調(diào)用鏈上的函數(shù)進(jìn)行 unwind 時(shí),調(diào)用 personality routine()。 ? 5、該異常如能被處理(有相應(yīng)的 catch),則 personality routine 會(huì)依次對(duì)調(diào)用鏈上的函數(shù)進(jìn)行清理。 ? 6、_Unwind_RaiseException() 將控制權(quán)轉(zhuǎn)到相應(yīng)的catch代碼。 ? 7、unwind 完成,用戶代碼繼續(xù)執(zhí)行。 ? 總結(jié)太Bravo了!
再看異常處理
有了這些前置知識(shí),再看題目中的異常,由前面描述可知實(shí)現(xiàn) unwind stack 的具體過(guò)程是通過(guò) __gxx_personality_v0(即personality routine)實(shí)現(xiàn)。 ? 這時(shí)候我們?cè)偃DA里調(diào)整此函數(shù)。
_Unwind_Reason_Code __fastcall _gxx_personality_v0(
int Version,
_Unwind_Action actions,
__int64 exceptionClass,
_Unwind_Exception *exceptionObject,
_Unwind_Context *context)
? 光標(biāo)在函數(shù),按Y修改類型。 ? ?

檢查當(dāng)前函數(shù)是否有相應(yīng)的 catch 語(yǔ)句。
清理當(dāng)前函數(shù)中的局部變量。
在personality routine()下的 scan_eh_tab() 該函數(shù)有我們最關(guān)心的兩個(gè)值,同時(shí)也是魔改處。
與源碼對(duì)比:https://code.woboq.org/llvm/libcxxabi/src/cxa_personality.cpp.html#__cxxabiv1::scan_eh_tab
Shfit + F1 -> INS 導(dǎo)入結(jié)構(gòu)體。
struct scan_results
{
int64_t ttypeIndex;
const uint8_t* actionRecord;
const uint8_t* languageSpecificData;
uintptr_t landingPad;
void* adjustedPtr;
_Unwind_Reason_Code reason;
};
? 光標(biāo)在scan_eh_tab函數(shù)上按Y修改。
void scan_eh_tab(scan_results *results, _Unwind_Action actions, bool native_exception, _Unwind_Exception *unwind_exception, _Unwind_Context *context)? Landing pad Landing pad(指向catch塊的分發(fā)處,只單單拿到landing pad還不夠,這時(shí)候還缺少一個(gè)對(duì)應(yīng)異常類型ttypeIndex)。 ?

?
?
ttypeIndex
首先要求父類為StdObfException的異常。 ? 最后的ttypeIndex由 thrown_object_ptr(由我們的第一段輸入所決定的thrown_object_ptr) 和 原始固定固定typeIndex 決定。 ?

? Official Write up: And we have figured out that the?ttypeIndex?is determined by the first field of the thrown?StdObfException?object and the?lptinfo?passed to?__cxa_throw. The value of the first field of the thrown?StdObfException?object comes from the second input passed to the construct of?StdObfException. ? 那么這兩個(gè)值到底具體指的是什么?? ? 其實(shí)上面已經(jīng)給出了答案,反復(fù)調(diào)試可知,可以發(fā)現(xiàn)我們的第一段輸入設(shè)置了父類StdObfException。 ? the first field of the thrown StdObfException object 指的就是我們的輸入。 ? the lptinfo passed to __cxa_throw 指的就是當(dāng) ___cxa_allocate_exception 創(chuàng)建的異常,也就是固定的。

現(xiàn)在知道了魔改后的流程是從哪里來(lái)到哪里去,人工方式就是跳到landing pad再設(shè)置rdx為ttypeIndex就可以到達(dá)我們所對(duì)應(yīng)的catch塊。 ?
什么叫CFG!
那么現(xiàn)在知道了routine personality 中的 scan_eh_tab被修改了,而IDA平常能識(shí)別throw catch這些塊的原因就是這些正常的源碼。 ? 然而landingpad與ttypeIndex都被修改了,所以導(dǎo)致了IDA識(shí)別的CFG成了這個(gè)樣子。 ? 我們根本沒法用肉眼知道throw的塊在哪,只有通過(guò)動(dòng)調(diào)才能確定,然而這就導(dǎo)致了原先的deflat腳本都不不行了。 ? 原因主要為兩點(diǎn): 1、無(wú)法確定throw后的塊; 2、throw可能對(duì)著多個(gè)catch塊,這時(shí)候就通過(guò)rdi(ttypeIndex)進(jìn)行catch塊分發(fā)(landingPad)。 ? 原因還有種種就不一一舉例,就無(wú)法正常原先deflat所需要的CFG塊。

? 以下開始就是跟著官方腳本復(fù)現(xiàn)。我們?cè)倩貞浺幌抡5膐llvm的執(zhí)行流程: ? Prologue(入口塊)-> Main dispatcher(主分發(fā)器)-> Sub dispathers(子分發(fā)器)-> Relevant blocks(真實(shí)塊)-> Predispather(預(yù)分發(fā)器)-> Main dispatcher(主分發(fā)器)... ? 總結(jié)一下這道題的CFG。 ? 我們的下一個(gè)真實(shí)塊取決于系統(tǒng)生產(chǎn)的lptinfo和我們的第一段輸入所導(dǎo)致的StdObfException,在每個(gè)真實(shí)塊的結(jié)束,我們不只是跳往與預(yù)分發(fā)器,而是調(diào)用 __cxa_throw 進(jìn)行第二次調(diào)度,我們稱二次調(diào)用為 second dispatch。 ? 所以我們的執(zhí)行流就是: ... -> main dispatcher -> sub dispatchers -> relevant block -> throw StdObfException exception -> Secondary dispatchers -> pre-dispatcher -> main dispatcher -> ... ? 除此之外,程序還拋出了一些真正的異常,對(duì)于這些異常,第二次調(diào)用發(fā)生于Landing pad末尾。 ? ... -> main dispatcher -> sub dispatchers -> relevant block that throws real exceptions -> the according real LandingPad block -> throw StdObfException exception -> Secondary dispatchers -> pre-dispatcher -> main dispatcher -> ... ?
0x02 Deflat Solution
去該平坦化控制流,有兩個(gè)步驟:
找到所有的真實(shí)塊
找到真實(shí)塊之間的關(guān)系
Find all relevant blocks
我們可以從主分發(fā)器開始尋找,找到所有子分發(fā)器的后繼者,這些后繼者本身不是子分發(fā)器。 ? 官方WP中一眼丁真發(fā)現(xiàn)子分發(fā)器由該指令格式組成。
sub dispathers such as: cmp jx? 于是由此區(qū)別出來(lái):
isCmpRI = lambda instr: instr.mnemonic == "cmp" and
hasattr(instr.operands[0], "_X86RegisterOperand__key") and
hasattr(instr.operands[1], "_X86ImmediateOperand__key")
isCJmp = lambda instr: instr.mnemonic.startswith("j") and
instr.mnemonic != "jmp"
isSubDispatcher = lambda bb: (len(bb.instrs) == 2) and
isCmpRI(bb.instrs[0]) and isCJmp(bb.instrs[1])
? 首先判斷是否為子分發(fā)器,然后排除法找到所有真實(shí)塊。
class PatchHelper:
## ......
# To get all cfgs
def block(self, addr):
bb = self.cfg.find_basic_block(addr)
if bb is None:
bb = barf.bb_builder.strategy._disassemble_bb(addr, barf.binary.ea_end, {})
return bb
def get_relevant_blocks(cfg, patch_helper, main_dispatcher):
isCmpRI = lambda instr: instr.mnemonic == "cmp" and
hasattr(instr.operands[0], "_X86RegisterOperand__key") and
hasattr(instr.operands[1], "_X86ImmediateOperand__key")
isCJmp = lambda instr: instr.mnemonic.startswith("j") and
instr.mnemonic != "jmp"
isSubDispatcher = lambda bb: (len(bb.instrs) == 2) and
isCmpRI(bb.instrs[0]) and isCJmp(bb.instrs[1])
relevant_blocks = []
visited = set()
q = SimpleQueue()
q.put(patch_helper.block(main_dispatcher))
while not q.empty():
bb = q.get()
# Either Sub Patchers or Relevant blocks?
if isSubDispatcher(bb):
for succ, cond in bb.branches:
if succ in visited:
continue
q.put(patch_helper.block(succ))
visited.add(succ)
else:
relevant_blocks.append(bb)
return relevant_blocks
? Relevant blocks:
*******************relevant blocks************************ main_dispatcher:0x404a80 relevant_blocks: ['0x409437', '0x406443', '0x404ab8', '0x408031', '0x407842', '0x407d31', '0x407437', '0x407f4f', '0x4076bd', '0x407a6b', '0x40723e', '0x407fc4', '0x409458', '0x407bc7', '0x40732f', '0x407ebc', '0x407566', '0x407960', '0x4070fa', '0x405e7a', '0x4078e3', '0x407e5a', '0x4074ca', '0x405c87', '0x407741', '0x407af5', '0x4072b4', '0x405ded', '0x4077b6', '0x407c6b', '0x4073a4', '0x405b29', '0x4075f9', '0x407a06', '0x4071aa', '0x406cfe', '0x406c94', '0x406ef0', '0x406859', '0x40707d', '0x406b62', '0x406f5f', '0x4065c9', '0x406e5d', '0x406a72', '0x406d7b', '0x406704', '0x406def', '0x406964', '0x40944b', '0x4064a5', '0x405469', '0x405a5f', '0x404fae', '0x40532c', '0x40589c', '0x404d58', '0x4053d3', '0x405923', '0x404ec5', '0x40529a', '0x4057b8', '0x404bc4', '0x405f2a', '0x4056f0', '0x406299', '0x4068f0', '0x4063b0', '0x406bf9', '0x406323', '0x406646', '0x40620f', '0x406b00', '0x4060e7', '0x4067bb', '0x40617c', '0x4069e3', '0x40606d', '0x406521', '0x4051fe', '0x405647', '0x404e14', '0x4055b5', '0x4050cc', '0x40550b', '0x404ca4']
Find the flow
官網(wǎng)WP指出抽象出來(lái),留個(gè)坑,以后熟了試試。 ? Official Write up: A good idea is to abstract the?throw StdObfException -> catch?process and do the?one basic block symbolic execution?(You can refer to?Deobfuscation: recovering an OLLVM-protected program(https://blog.quarkslab.com/deobfuscation-recovering-an-ollvm-protected-program.html)?or?利用符號(hào)執(zhí)行去除控制流平坦化(https://security.tencent.com/index.php/blog/msg/112)?for more information). ? 于是官網(wǎng)WP又給了個(gè)更有趣的方法,GDB腳本! ? 為了找到真實(shí)塊之間的流程,通過(guò)普通的執(zhí)行然后打印真實(shí)塊需要的信息! ? 但是我們不一樣能得到所有的流程因?yàn)椴糠挚赡軟]執(zhí)行到,但是我們依然可以利用提取出來(lái)的信息去恢復(fù)部分控制流,并弄清楚如何輸入可以恢復(fù)更多流程。(怎么好像夢(mèng)到過(guò)我在這寫wp...) ? 生成GDB的腳本如下:
40A3D4為我們catch塊地址
_ZN18StdSubObfExceptionC2Ec為了打印異常類型
cmds = """
set pagination off
b *0x40A3D4
commands
silent
printf "landingPad: %x\n", $rdx
continue
end
b _ZN18StdSubObfExceptionC2Ec
commands
silent
printf "selector: %x\n", $rsi
continue
end
define mytrace
break $arg0
commands
silent
printf "%x\n", $pc
python gdb.execute('continue')
end
end
"""
for bb in relevant_blocks:
cmds += (f"mytrace *{hex(bb.address)}
")
cmds += "run
"
with open("test.gdb", "w") as f:
f.write(cmds)
cat teatin 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef gdb inflated -x test.gdb --batch < testin > testout? 于是可以獲取真實(shí)塊接下來(lái)的landing pad與異常類型。
Breakpoint 1 at 0x40a3d4 ...... Breakpoint 88 at 0x404ca4 4075f9 selector: 0 landingPad: 4089bf 4072b4 selector: 0 landingPad: 408503 4075f9 selector: 2 landingPad: 4089bf 4060e7 selector: 0 ...... 40617c selector: 0 landingPad: 409100 409437 [Inferior 1 (process 13732) exited normally]? 然后就寫個(gè)PARSER分析。
def parse_logs(logfn, prologue, patch_helper):
with open(logfn, "r") as f:
t = f.readlines()
i = 0
selector_s = "selector: "
landingpad_s = "landingPad: "
relations = set()
laddr = prologue
lselector = 0
landingpad = 0
while i < len(t):
try:
addr = int(t[i], 16)
except:
i += 1
continue
if not laddr is None:
relations.add((laddr, lselector, addr))
if t[i+1].startswith(selector_s):
selector = int(t[i+1][len(selector_s):], 16)
i += 2
elif t[i+1].startswith(landingpad_s):
landingpad = int(t[i+1][len(landingpad_s):], 16)
relations.add((addr, -1, landingpad))
addr = landingpad
while not patch_helper.is_unreachable(patch_helper.block(addr).direct_branch):
addr = patch_helper.block(addr).direct_branch
if t[i+2].startswith(selector_s):
selector = int(t[i+2][len(selector_s):], 16)
i += 3
elif t[i+1].startswith("[Inferior "):
i += 1
else:
print("Warning: %x doesn't have selector. "%addr)
exit(0)
laddr = addr
lselector = selector
return list(relations)
print('************************flow******************************')
relations = parse_logs(sys.argv[3], prologue, patch_helper)
relations.sort(key = lambda x:x)
flow = {}
for bb, selector, child in relations:
if bb in flow:
while len(flow[bb]) < selector:
flow[bb].append(-1)
flow[bb].append(child)
assert(len(flow[bb]) == selector+1)
else:
flow[bb] = [child]
for (k, v) in list(flow.items()):
print('%#x:' % k, [hex(child) for child in v])
? Flows:
************************flow****************************** 0x404820: ['0x4075f9'] 0x404ab8: ['0x404ab8', '0x406c94'] 0x404bc4: ['0x407bc7'] 0x404ca4: ['0x406bf9'] 0x404ec5: ['0x4053d3'] 0x404fae: ['0x406b00'] 0x4051fe: ['0x40707d'] 0x4053d3: ['0x406521'] 0x405469: ['0x407d31'] 0x4056f0: ['0x405a5f', '0x4056f0'] 0x4057b8: ['0x404ab8'] 0x405923: ['0x405923', '0x406e5d'] 0x405a5f: ['0x4067bb'] 0x405b29: ['0x406964', '0x406646'] 0x405c87: ['0x405c87', '0x407437'] 0x405f2a: ['0x405f2a', '0x4063b0'] 0x4060e7: ['0x40723e'] 0x40617c: ['0x409437'] 0x40620f: ['0x405f2a'] 0x406299: ['0x404bc4', '0x4057b8'] 0x4063b0: ['0x4063b0', '0x405469'] 0x4064a5: ['0x406704', '0x40620f'] 0x406521: ['0x4074ca', '0x404bc4'] 0x4065c9: ['0x40723e'] 0x406646: ['0x406964'] 0x406704: ['0x405c87'] 0x4067bb: ['0x4082b6'] 0x406964: ['0x405b29', '0x404ca4'] 0x4069e3: ['0x408281'] 0x406a72: ['0x404fae'] 0x406b00: ['0x406299'] 0x406bf9: ['0x405923'] 0x406c94: ['0x4074ca'] 0x406cfe: ['0x40723e'] 0x406e5d: ['0x406e5d', '0x4077b6'] 0x406f5f: ['0x406f5f', '0x407566'] 0x40707d: ['0x40707d', '0x407960'] 0x4070fa: ['0x406f5f'] 0x4071aa: ['0x4056f0'] 0x40723e: ['0x4072b4'] 0x4072b4: ['0x4075f9', '0x4071aa'] 0x407437: ['0x407437', '0x4064a5'] 0x4074ca: ['0x404ec5', '0x407c6b'] 0x407566: ['0x407566', '0x407a6b'] 0x4075f9: ['0x4072b4', '-0x1', '0x4060e7', '0x406cfe', '0x4078e3', '0x4065c9'] 0x4076bd: ['0x404ec5'] 0x4077b6: ['0x406bf9', '0x4070fa'] 0x4078e3: ['0x40723e'] 0x407960: ['0x4081f5'] 0x407a6b: ['0x4070fa', '0x406704'] 0x407bc7: ['0x406a72', '0x407bc7'] 0x407c6b: ['0x4069e3'] 0x407d31: ['0x407d31', '0x407ebc'] 0x407ebc: ['0x407ebc', '0x40617c'] 0x4081f5: ['0x405b29'] 0x408281: ['0x4051fe'] 0x4082b6: ['0x4076bd']
Patch
修復(fù)程序環(huán)節(jié)!當(dāng)我們已經(jīng)確定了執(zhí)行流程,像拋異常 子分發(fā)器什么都是多余的了,統(tǒng)統(tǒng)patch掉。 ? 對(duì)于后繼塊只有一個(gè)的真實(shí)塊,只需要jmp過(guò)去。 ? 對(duì)于有多個(gè)后繼塊的,需要通過(guò)esi(也就是異常類型)來(lái)改成cmp esi, ... jz即可。
def patch_branches(self, bb, va_targets):
va_start, size = self.get_patchable_from_relblk(bb)
if size < PatchHelper.JMP_SIZE:
print("[Warning] patch_jmp at block %x may fail. size: %d."%(bb.address, size))
org_start = va_start
print(f"va_start: {hex(va_start)}, bb addr: {hex(bb.address)}, size: {size}")
## `cmp esi, v` instr takes 3 bytes while `je xxx` takes 6 bytes
## And the last jmp instr takes 5 bytes.
total_size = 9 * len(va_targets) - 4
if size < total_size:
## If the nop block at the end of current block is not large enough,
## try to find another nop block and then jump to it.
nx_va_start, nx_size = self.get_nop_by_size(total_size)
if nx_size == 0:
print("[Error] `patch_branches` needs a nop block with size larger than %d."%(total_size))
self.patch_jmp(va_start, nx_va_start)
va_start, size = nx_va_start, nx_size
for i, t in enumerate(va_targets[:-1]):
cmp_instr = bytes([0x83,0xfe,i])
self.do_patch(va_start, cmp_instr)
va_start += len(cmp_instr)
cj_instr = bytes([PatchHelper.opcode['j'],PatchHelper.opcode['e']])
if t == -1:
## -1 represent that we do not know the flow for this selector value for now.
cj_instr += struct.pack(' org_start+size:
print("[Warning] patches at (%x, %x) overlaps next blk. "%(org_start, va_start))
? 官方完整腳本:
## filename: deflat.py
from ast import Tuple
from xmlrpc.client import Boolean
from barf.barf import BARF
import angr
import struct
import sys
from pwnlib import elf
from queue import SimpleQueue
# from pwn import *
class PatchHelper:
opcode = {'a' :0x87, 'ae':0x83, 'b' :0x82, 'be':0x86, 'c' :0x82, 'e' :0x84, 'z' :0x84, 'g' :0x8F,
'ge':0x8D, 'l' :0x8C, 'le':0x8E, 'na':0x86, 'nae':0x82,'nb':0x83, 'nbe':0x87,'nc':0x83,
'ne':0x85, 'ng':0x8E, 'nge':0x8C,'nl':0x8D, 'nle':0x8F,'no':0x81, 'np':0x8B, 'ns':0x89,
'nz':0x85, 'o' :0x80, 'p' :0x8A, 'pe':0x8A, 'po':0x8B, 's' :0x88, 'nop':0x90,'jmp':0xE9, 'j':0x0F}
JMP_SIZE = 5
def is_unreachable(self, bb):
if isinstance(bb, int):
bb = self.block(bb)
for i in range(len(bb.instrs)):
if bb.instrs[i].mnemonic != "call":
continue
target = bb.instrs[i].operands[0].immediate
if target == self.func_terminate:
return True
def block(self, addr):
bb = self.cfg.find_basic_block(addr)
if bb is None:
bb = barf.bb_builder.strategy._disassemble_bb(addr, barf.binary.ea_end, {})
return bb
@staticmethod
def is_imm(operand):
return (hasattr(operand, "_X86ImmediateOperand__key"))
@staticmethod
def is_reg(operand):
return (hasattr(operand, "_X86RegisterOperand__key"))
def is_call_throw(self, instr):
return instr.mnemonic == "call" and
self.is_imm(instr.operands[0]) and
instr.operands[0].immediate == self.func_throw
def is_call_allocate_exception(self, instr):
return instr.mnemonic == "call" and
self.is_imm(instr.operands[0]) and
instr.operands[0].immediate == self.func_allocate_exception
def is_call_obf_exception(self, instr):
return instr.mnemonic == "call" and
self.is_imm(instr.operands[0]) and
instr.operands[0].immediate == self.func_obf_exception
def skip_call_args(self, bb, i):
while ((bb.instrs[i].mnemonic in ["xor","mov","lea"]) and
(len(bb.instrs[i].operands) > 0) and (self.is_reg(bb.instrs[i].operands[0])) and
(bb.instrs[i].operands[0].name in ["edx", "rdx", "esi", "rsi", "edi", "rdi"])) or
bb.instrs[i].mnemonic == "nop":
i -= 1
return i
def get_patchable_from_relblk(self, bb):
i = 0
end = bb.start_address + bb.size
while i < len(bb.instrs) and not self.is_call_throw(bb.instrs[i]):
i += 1
i = self.skip_call_args(bb, i-1)
if i == len(bb.instrs) - 1:
start = end
else:
start = bb.instrs[i+1].address
self.fill_nops(start, end)
return (start, end-start)
def __init__(self, proj, elf, barf, cfg) -> None:
self.p = proj
obj = proj.loader.main_object
self.func_terminate = obj.symbols_by_name["__clang_call_terminate"].rebased_addr
self.func_throw = obj.plt["__cxa_throw"]
self.func_allocate_exception = obj.plt["__cxa_allocate_exception"]
self.func_obf_exception = obj.symbols_by_name["_ZN18StdSubObfExceptionC2Ec"].rebased_addr
self.elf = elf
self.elfData = bytearray(self.elf.data)
self.barf = barf
self.cfg = cfg
self.nops = []
def append_nop(self, nopblk):
if nopblk[1] > 0:
self.nops.append(nopblk)
def finalize(self):
self.nops.sort()
idx = 0
while idx < len(self.nops) - 1:
if self.nops[idx][0] + self.nops[idx][1] != self.nops[idx+1][0]:
idx += 1
continue
self.nops[idx]=(self.nops[idx][0], self.nops[idx][1]+self.nops[idx+1][1])
del self.nops[idx+1]
def fill_nops(self, va_start, va_end):
assert not self.elf is None
start = self.elf.vaddr_to_offset(va_start)
end = self.elf.vaddr_to_offset(va_end)
for i in range(start, end):
self.elfData[i] = PatchHelper.opcode['nop']
def get_nop_by_size(self, min_size):
for idx, nop in enumerate(self.nops):
if nop[1] > min_size:
del self.nops[idx]
return nop
return (-1, 0)
def do_patch(self, va_start, codes):
start = self.elf.vaddr_to_offset(va_start)
for i in range(len(codes)):
self.elfData[start+i] = codes[i]
def patch_jmp(self, va_start, va_target):
offset = va_target - va_start - PatchHelper.JMP_SIZE
jmp = bytes([PatchHelper.opcode['jmp']])+struct.pack(' org_start+size:
print("[Warning] patches at (%x, %x) overlaps next blk. "%(org_start, va_start))
def get_relevant_blocks(cfg, patch_helper, main_dispatcher):
isCmpRI = lambda instr: instr.mnemonic == "cmp" and
hasattr(instr.operands[0], "_X86RegisterOperand__key") and
hasattr(instr.operands[1], "_X86ImmediateOperand__key")
isCJmp = lambda instr: instr.mnemonic.startswith("j") and
instr.mnemonic != "jmp"
isSubDispatcher = lambda bb: (len(bb.instrs) == 2) and
isCmpRI(bb.instrs[0]) and isCJmp(bb.instrs[1])
relevant_blocks = []
visited = set()
q = SimpleQueue()
q.put(patch_helper.block(main_dispatcher))
while not q.empty():
bb = q.get()
if isSubDispatcher(bb):
patch_helper.append_nop((bb.start_address, bb.size))
for succ, cond in bb.branches:
if succ in visited:
continue
q.put(patch_helper.block(succ))
visited.add(succ)
else:
relevant_blocks.append(bb)
return relevant_blocks
def parse_logs(logfn, prologue, patch_helper):
with open(logfn, "r") as f:
t = f.readlines()
i = 0
selector_s = "selector: "
landingpad_s = "landingPad: "
relations = set()
laddr = prologue
lselector = 0
landingpad = 0
while i < len(t):
try:
addr = int(t[i], 16)
except:
i += 1
continue
if not laddr is None:
relations.add((laddr, lselector, addr))
if t[i+1].startswith(selector_s):
selector = int(t[i+1][len(selector_s):], 16)
i += 2
elif t[i+1].startswith(landingpad_s):
landingpad = int(t[i+1][len(landingpad_s):], 16)
relations.add((addr, -1, landingpad))
addr = landingpad
while not patch_helper.is_unreachable(patch_helper.block(addr).direct_branch):
addr = patch_helper.block(addr).direct_branch
if t[i+2].startswith(selector_s):
selector = int(t[i+2][len(selector_s):], 16)
i += 3
elif t[i+1].startswith("[Inferior "):
i += 1
else:
print("Warning: %x doesn't have selector. "%addr)
exit(0)
laddr = addr
lselector = selector
return list(relations)
def generate_gdb_script(relevant_blocks):
cmds = """
set pagination off
b *0x40A3D4
commands
silent
printf "landingPad: %x
", $rdx
continue
end
b _ZN18StdSubObfExceptionC2Ec
commands
silent
printf "selector: %x
", $rsi
continue
end
define mytrace
break $arg0
commands
silent
printf "%x\n", $pc
python gdb.execute('continue')
end
end
"""
for bb in relevant_blocks:
cmds += (f"mytrace *{hex(bb.address)}
")
cmds += "run
"
with open("test.gdb", "w") as f:
f.write(cmds)
if __name__ == '__main__':
if len(sys.argv) < 3:
print('Usage: python deflat.py filename function_address(hex) [logfile]')
exit(0)
# context.arch = "amd64"
# context.os = "linux"
# context.endian = "little"
filename = sys.argv[1]
start = int(sys.argv[2], 16)
origin = elf.ELF(filename)
b = angr.Project(filename, load_options={'auto_load_libs': False, 'main_opts':{'custom_base_addr': 0}})
barf = BARF(filename)
cfg = barf.recover_cfg(start=start)
patch_helper = PatchHelper(b, origin, barf, cfg)
blocks = cfg.basic_blocks
prologue = start
main_dispatcher = patch_helper.block(prologue).direct_branch
relevant_blocks = get_relevant_blocks(cfg, patch_helper, main_dispatcher)
nop = patch_helper.get_patchable_from_relblk(patch_helper.block(prologue))
patch_helper.append_nop(nop)
print('*******************relevant blocks************************')
print('main_dispatcher:%#x' % main_dispatcher)
print('relevant_blocks:', [hex(bb.address) for bb in relevant_blocks])
if len(sys.argv) < 4:
generate_gdb_script(relevant_blocks)
exit(0)
print('************************flow******************************')
relations = parse_logs(sys.argv[3], prologue, patch_helper)
relations.sort(key = lambda x:x)
flow = {}
for bb, selector, child in relations:
if bb in flow:
while len(flow[bb]) < selector:
flow[bb].append(-1)
flow[bb].append(child)
assert(len(flow[bb]) == selector+1)
else:
flow[bb] = [child]
for (k, v) in list(flow.items()):
print('%#x:' % k, [hex(child) for child in v])
print('************************patch*****************************')
patch_helper.finalize()
for (parent, childs) in list(flow.items()):
## Patch jmps
blk = patch_helper.block(parent)
patch_helper.patch_branches(blk, childs)
## Nop call allocate_exception and call obf_exception
for idx, instr in enumerate(blk.instrs):
if patch_helper.is_call_allocate_exception(instr) or
patch_helper.is_call_obf_exception(instr):
# si = patch_helper.skip_call_args(blk, idx-1)+1
# start = blk.instrs[si].address
start = instr.address
end = instr.address + instr.size
patch_helper.fill_nops(start, end)
with open(filename + '.recovered', 'wb') as f:
f.write(bytes(patch_helper.elfData))
print('Successful! The recovered file: %s' % (filename + '.recovered'))
? Work flow:
$ python deflat.py inflated 0x404820 $ gdb inflated -x test.gdb --batch < testin > testout $ python deflat.py inflated 0x404820 testout? 按照以上流程,test.gdb可能會(huì)報(bào)個(gè)錯(cuò),程序把本身有個(gè) 是腳本中需要打印的,但直接轉(zhuǎn)義成真換行了需要手動(dòng)恢復(fù)。 ? 觀看修復(fù)后的流程:
int __cdecl main(int argc, const char **argv, const char **envp)
{
......
v3 = fileno(stdin);
tcgetattr(v3, &intermiosBufBackup);
cfmakeraw(&intermiosBuf);
tcsetattr(v3, 0, &intermiosBuf);
*(_OWORD *)v196 = 0LL;
v195 = 0LL;
*(_OWORD *)s = 0LL;
*(_QWORD *)&v196[13] = 0LL;
v124 = &v168;
v123 = &v167;
v164 = v199;
v187 = &v198;
v186 = &v96;
v185 = &v97;
v184 = &v100;
v122 = &s[12];
v108 = v103;
v163 = &v197;
v183 = &v99;
v162 = &v166;
......
v5 = 0LL;
do
{
v72 = v4;
v98 = getc(stdin);
v73 = v98 << 24;
v74 = v98 << 24 == 0x1B000000;
if ( v98 << 24 == 0x31000000 )
v74 = 2;
if ( v73 == 0x37000000 )
v74 = 3;
if ( v73 == 0x33000000 )
v74 = 4;
if ( v73 == 0x34000000 )
v74 = 5;
v101 = v5;
v102 = v72;
v119 = v72;
if ( v74 )
{
if ( v74 == 1 )
_clang_call_terminate(5LL);
if ( v74 == 2 )
{
v107 = v102 + (4LL << (3 * (unsigned __int8)v101));
v85 = v98;
}
else if ( v74 == 3 )
{
v107 = v102 + (5LL << (3 * (unsigned __int8)v101));
v85 = v98;
}
else
{
if ( v74 == 4 )
v107 = v102 + (6LL << (3 * (unsigned __int8)v101));
else
v107 = v102 + (7LL << (3 * (unsigned __int8)v101));
v85 = v98;
}
s[v101] = v85;
v119 = v107;
}
v5 = v101 + 1;
v174 = v119;
}
while ( v101 != 11 );
s[12] = 0;
v69 = fileno(stdin);
tcsetattr(v69, 0, &intermiosBufBackup);
for ( i = 0LL; i < 5; ++i )
*((_BYTE *)v136 + i) = byte_40E0F3[i] - byte_40E0F8[i];
v188 = &v190;
v190 = v136[0];
v189 = 4LL;
v191 = 0;
__isoc99_scanf(&v190, v122);
v26 = v188;
v175 = v188;
*(_OWORD *)v188 = xmmword_40E040;
v26[4] = 639210836;
*((_BYTE *)v26 + 20) = 16;
*(_QWORD *)((char *)v26 + 34) = 0x1005E763241AA6B1LL;
*(_OWORD *)((char *)v26 + 21) = xmmword_40E148;
__cxa_begin_catch(v26);
v155 = strlen(v122);
v128 = 0LL;
v113 = 0;
v125 = v155;
v147 = 0LL;
do
{
v133 = v125 - 1;
v86 = v122[v147];
v160 = v128;
v110 = v113;
v176 = v147;
isalnum(v86);
v50 = (unsigned int)(v160 + 1);
*(&v95 + (int)v160) = v86;
v181 = v176 + 1;
v130 = v50;
v112 = v110;
v146 = 0LL;
if ( (_DWORD)v50 == 4 )
{
do
{
v106 = 0LL;
v149 = v146;
do
{
v199[v106 + 16] = byte_40E071[v106] - byte_40E0B2[v106];
++v106;
}
while ( v106 < 0x41 );
v56 = v163;
*(_QWORD *)v163 = v164;
v165 = 64LL;
v169 = (_OWORD *)std::basic_string,std::allocator>::_M_create(
v56,
&v165,
0LL);
v9 = (void **)v163;
v10 = v169;
*(_QWORD *)v163 = v169;
v11 = v165;
*(_QWORD *)v164 = v165;
v12 = MEMORY[5];
v13 = MEMORY[0x15];
v14 = MEMORY[0x25];
v10[3] = MEMORY[0x35];
v10[2] = v14;
v10[1] = v13;
*v10 = v12;
*(_QWORD *)v187 = v11;
*((_BYTE *)v10 + v11) = 0;
v15 = v149;
*(&v95 + v15) = std::basic_string,std::allocator>::find(
v9,
(unsigned int)*(&v95 + v149),
0LL);
v177 = *v9;
operator delete(v177);
v146 = v149 + 1;
}
while ( v149 != 3 );
v17 = *v186;
*v183 = (4 * *v57) | ((unsigned __int8)*v186 >> 4) & 3;
v18 = *v185;
*v59 = (16 * v17) | ((unsigned __int8)*v185 >> 2) & 0xF;
*v184 = *v58 + (v18 << 6);
v152 = v110;
v151 = 0LL;
do
{
v6 = v151;
v7 = (unsigned __int8)*(&v99 + v151) / 0xAu;
v8 = v152;
v199[v152 + 96] = (unsigned __int8)*(&v99 + v151) % 0xAu;
v199[v8 + 97] = v7;
v151 = v6 + 1;
v152 = v8 + 2;
v182 = v8 + 2;
}
while ( v6 != 2 );
v130 = 0LL;
v112 = v182;
}
v128 = v130;
v113 = v112;
v125 = v133;
v147 = v181;
}
while ( v133 );
__cxa_end_catch();
v193 = 152788034LL;
v192[3] = xmmword_40E130;
v192[2] = xmmword_40E120;
v192[1] = xmmword_40E110;
v192[0] = xmmword_40E100;
v138 = 152788034LL;
cipher_helper<12037464u,StList<0ul,1ul,2ul,3ul,4ul,5ul,6ul,7ul,8ul,9ul,10ul,11ul,12ul,13ul,14ul,15ul,16ul,17ul,18ul,19ul,20ul,21ul,
22ul,23ul,24ul,25ul,26ul,27ul,28ul,29ul,30ul,31ul,32ul,33ul,34ul,35ul,36ul,37ul,38ul,39ul>>::get_array(
152788034LL,
"Knows the futility yet does it anyway. ");
v55 = v138;
*(_OWORD *)(v138 + 56) = xmmword_40E16D;
*(_OWORD *)(v55 + 40) = xmmword_40E15D;
*(_QWORD *)(v55 + 72) = 0x6FF0E70B5B3F60A4LL;
v137 = (void *)0x6FF0E70B5B3F60A4LL;
__cxa_begin_catch((void *)0x6FF0E70B5B3F60A4LL);
v145 = 0LL;
do
{
v67 = v145;
*((_DWORD *)v192 + 2 * v145) ^= 0x9005408u;
v145 = v67 + 1;
}
while ( v67 != 8 );
__cxa_end_catch();
*(_OWORD *)v75 = xmmword_40E030;
*((_QWORD *)v75 + 2) = 0x48D1556A814FF991LL;
*((_QWORD *)v75 + 5) = 0x48B0E10161EA8322LL;
v25 = -2.526699287193993e95;
*(_OWORD *)(v75 + 24) = xmmword_40E185;
__cxa_begin_catch(v75);
v121 = 0LL;
v109 = 0;
do
{
v27 = v121;
v179 = (unsigned __int64 *)v192 + (unsigned int)v121 / 9uLL;
v28 = *v179;
v29 = (unsigned int)v121 % 9;
v30 = pow(v25, (double)(int)((unsigned int)v121 % 9 + 1));
v178 = v28;
v31 = v28 % (unsigned int)(int)(v30 + 0.5);
y = (double)v29;
v32 = pow(11.0, (double)v29) + 0.5;
v33 = (unsigned int)(int)v32;
v25 = v32 - 9.223372036854776e18;
v158 = v27;
v157 = v109;
v111 = v109;
if ( v31 < v33 )
{
v111 = v157 + 1;
v51 = v199[(int)v157 + 96];
v52 = pow(v25, y) + 0.5;
v53 = (unsigned int)(int)v52;
v25 = v52 - 9.223372036854776e18;
*v179 = v178 + v51 * v53;
}
v121 = (unsigned int)(v158 + 1);
v109 = v111;
}
while ( (_DWORD)v158 != 80 );
__cxa_end_catch();
v88 = 1;
v140 = 0LL;
do
{
v60 = v108;
v108[8] = 0;
*(_QWORD *)v60 = 0LL;
v171 = *((_QWORD *)v192 + v140);
v126 = 0LL;
v170 = v140;
do
{
v19 = v126;
v20 = v126 + 1;
v21 = pow(v25, (double)((int)v126 + 1));
v22 = v171 % (unsigned int)(int)(v21 + 0.5);
v23 = pow(11.0, (double)v19) + 0.5;
v24 = (unsigned int)(int)v23;
v25 = v23 - 9.223372036854776e18;
v103[v22 / v24] = 1;
v141 = 1LL;
v89 = v88;
v126 = v20;
}
while ( v20 != 9 );
do
{
v61 = v89;
if ( !v103[v141] )
v61 = 0;
++v141;
v115 = v61;
v89 = v61;
}
while ( v141 != 10 );
v140 = v170 + 1;
v131 = 0LL;
v87 = v115;
v88 = v115;
}
while ( v170 != 8 );
do
{
v68 = v108;
v108[8] = 0;
*(_QWORD *)v68 = 0LL;
v172 = (double)((int)v131 + 1);
v40 = (double)(int)v131;
v173 = (double)(int)v131;
v161 = (unsigned int)v131;
v142 = 0LL;
do
{
v62 = v142;
v63 = *((_QWORD *)v192 + v142);
v64 = v63 % (unsigned int)(int)(pow(v40, v172) + 0.5);
v65 = pow(11.0, v173) + 0.5;
v66 = (unsigned int)(int)v65;
v40 = v65 - 9.223372036854776e18;
v103[v64 / v66] = 1;
v142 = v62 + 1;
v144 = 1LL;
v90 = v87;
}
while ( v62 != 8 );
do
{
v71 = v90;
if ( !v103[v144] )
v71 = 0;
++v144;
v116 = v71;
v90 = v71;
}
while ( v144 != 10 );
v131 = (unsigned int)(v161 + 1);
v132 = 0LL;
v92 = v116;
v87 = v116;
}
while ( (_DWORD)v131 != 9 );
do
{
v54 = v108;
v108[8] = 0;
*(_QWORD *)v54 = 0LL;
v135 = 3 * ((unsigned int)v132 / 3);
v134 = 3 * ((unsigned int)v132 % 3) + 1;
v129 = 0LL;
v159 = (unsigned int)v132;
do
{
v34 = v129;
v35 = *((_QWORD *)v192 + (int)(v135 + (unsigned int)v129 / 3));
v36 = (v134 + (unsigned int)v129 % 3) % 9;
v37 = v35 % (unsigned int)(int)(pow(v40, (double)(v36 + 1)) + 0.5);
v38 = pow(11.0, (double)v36) + 0.5;
v39 = (unsigned int)(int)v38;
v40 = v38 - 9.223372036854776e18;
v103[v37 / v39] = 1;
v129 = (unsigned int)(v34 + 1);
v150 = 1LL;
v94 = v92;
}
while ( v34 != 8 );
do
{
v70 = v94;
if ( !v103[v150] )
v70 = 0;
++v150;
v104 = v70;
v94 = v70;
}
while ( v150 != 10 );
v132 = (unsigned int)(v159 + 1);
v92 = v104;
}
while ( (_DWORD)v159 != 8 );
v48 = v108;
v108[8] = 0;
*(_QWORD *)v48 = 0LL;
v127 = 0LL;
do
{
v41 = v127;
v42 = 9 - v127;
if ( !(_DWORD)v127 )
v42 = 0;
v43 = *((_QWORD *)v192 + v42);
v44 = v127 + 1;
v45 = v43 % (unsigned int)(int)(pow(v40, (double)((int)v127 + 1)) + 0.5);
v46 = pow(11.0, (double)v41) + 0.5;
v47 = (unsigned int)(int)v46;
v40 = v46 - 9.223372036854776e18;
v103[v45 / v47] = 1;
v143 = 1LL;
v91 = v104;
v127 = v44;
}
while ( v44 != 9 );
do
{
v49 = v91;
if ( !v103[v143] )
v49 = 0;
++v143;
v117 = v49;
v91 = v49;
}
while ( v143 != 10 );
v16 = v108;
v108[8] = 0;
*(_QWORD *)v16 = 0LL;
v139 = 0LL;
do
{
v76 = v139 + 1;
v77 = v139 == 8;
v78 = v139 + 1;
if ( v139 == 8 )
v78 = 0;
v79 = *((_QWORD *)v192 + v139);
v80 = v79 % (unsigned int)(int)(pow(v40, (double)(v78 + 1)) + 0.5);
v81 = pow(11.0, (double)v78) + 0.5;
v82 = (unsigned int)(int)v81;
v40 = v81 - 9.223372036854776e18;
v103[v80 / v82] = 1;
v148 = 1LL;
v93 = v117;
v139 = v76;
}
while ( !v77 );
do
{
v83 = v93;
if ( !v103[v148] )
v83 = 0;
++v148;
v118 = v83;
v93 = v83;
}
while ( v148 != 10 );
return 0;
}
0x03 Solve the Puzzles
PART ONE
之前也提到過(guò),由于我們的輸入部分流可能執(zhí)行不到,很明顯我們剛剛根本沒有輸入上下左右箭頭啥的。 ? 所以關(guān)于處理上下左右箭頭的代碼無(wú)了。
do
{
v72 = v4;
input1 = getc(stdin);
v73 = input1 << 24;
shift_input1 = input1 << 24 == 0x1B000000;
if ( input1 << 24 == 0x31000000 )
shift_input1 = 2;
if ( v73 == 0x37000000 )
shift_input1 = 3;
if ( v73 == 0x33000000 )
shift_input1 = 4;
if ( v73 == 0x34000000 )
shift_input1 = 5;
count = v5;
v102 = v72;
v119 = v72;
if ( shift_input1 )
{
if ( shift_input1 == 1 )
_clang_call_terminate((void *)5);
if ( shift_input1 == 2 )
{
v107 = v102 + (4LL << (3 * (unsigned __int8)count));
org_input = input1;
}
else if ( shift_input1 == 3 )
{
v107 = v102 + (5LL << (3 * (unsigned __int8)count));
org_input = input1;
}
else
{
if ( shift_input1 == 4 )
v107 = v102 + (6LL << (3 * (unsigned __int8)count));
else
v107 = v102 + (7LL << (3 * (unsigned __int8)count));
org_input = input1;
}
s[count] = org_input;
v119 = v107;
}
v5 = count + 1;
v174 = v119;
}
while ( count != 11 );
? 這個(gè)時(shí)候就可以更改我們的輸入(指的是輸入箭頭再輸入字符)再來(lái)一遍。 ? 成功解析出我們的第一段輸入。 ?

int part1_size = 12;
while(count < part1_size) {
char a = getchar();
if (a == 27) {
if (getchar() == 91) {
char c = getchar();
try {
rmCjJ0(true, c);
} catch(Le3KW5 &cc) {
char c = cc.state;
if (c == 65) {
state += 0ull << (3 * count);
} else if (c==66) {
state += 2ull << (3 * count);
} else if (c==67) {
state += 1ull << (3 * count);
} else if (c==68) {
state += 3ull << (3 * count);
}
}
flag[count] = c;
}
} else if (a=='1') {
state += 4ull << (3 * count);
flag[count] = a;
} else if (a=='7') {
state += 5ull << (3 * count);
flag[count] = a;
} else if (a=='3') {
state += 6ull << (3 * count);
flag[count] = a;
} else if (a=='4') {
state += 7ull << (3 * count);
flag[count] = a;
}
count += 1;
}
// ... Second Part ...
// Check Part
if (... && state == 0xb3e659480) {
std::cout << LIT("Congratulation!
") << LIT("Your flag is ACTF{") << flag << LIT("_amazing!}") << std::endl;
}
?
?
PART TWO
這個(gè)部分完全跟著lchild的分析來(lái)了。 ? 接著就是第二段輸入。首先是經(jīng)過(guò)一段Base64解碼操作,再經(jīng)過(guò)取模除十操作得到一個(gè)數(shù)組。
?
if ( (_DWORD)v50 == 4 )
{
do
{
v106 = 0LL;
v149 = v146;
do
{
baseTable[v106 + 16] = byte_40E071[v106] - byte_40E0B2[v106];// baseTable
++v106;
}
while ( v106 < 0x41 );
v56 = (__int64)v163;
*(_QWORD *)v163 = v164;
v165 = 64LL;
v169 = (_OWORD *)std::basic_string,std::allocator>::_M_create(
v56,
&v165,
0LL);
v9 = (void **)v163;
v10 = v169;
*(_QWORD *)v163 = v169;
v11 = v165;
*(_QWORD *)v164 = v165;
v12 = MEMORY[5];
v13 = MEMORY[0x15];
v14 = MEMORY[0x25];
v10[3] = MEMORY[0x35];
v10[2] = v14;
v10[1] = v13;
*v10 = v12;
*(_QWORD *)v187 = v11;
*((_BYTE *)v10 + v11) = 0;
v15 = v149;
*(©_input1 + v15) = std::basic_string,std::allocator>::find(
v9,
(unsigned int)*(©_input1 + v149),
0LL);
v177 = *v9;
operator delete(v177);
v146 = v149 + 1;
}
while ( v149 != 3 );
v17 = *v186;
*v183 = (4 * *v57) | ((unsigned __int8)*v186 >> 4) & 3;
v18 = *v185;
*v59 = (16 * v17) | ((unsigned __int8)*v185 >> 2) & 0xF;
*v184 = *v58 + (v18 << 6);
v152 = v110;
v151 = 0LL;
do
{ // 對(duì)輸入進(jìn)行操作分值操作
v6 = v151;
v7 = (unsigned __int8)*(&v99 + v151) / 0xAu;
v8 = v152;
baseTable[v152 + 96] = (unsigned __int8)*(&v99 + v151) % 0xAu;
baseTable[v8 + 97] = v7;
v151 = v6 + 1;
v152 = v8 + 2;
v182 = v8 + 2;
}
while ( v6 != 2 );
v130 = 0LL;
v112 = v182;
}
v128 = v130;
v113 = v112;
copy_len = v133;
v147 = v181;
}
while ( v133 ); // 以上是對(duì)input進(jìn)行了base64解碼
? 之后計(jì)算了九個(gè)數(shù)值,和一堆pang臭的代碼,不過(guò)干的事情不是很復(fù)雜。

具體參考lchild師傅的Write?up?
#?https://sudoku.vip/sudoku-x-solver/
0x04 GetFlag!!
第一個(gè)解密就直接移回去即可。 ? 第二個(gè)解密出數(shù)獨(dú)的值,列移動(dòng),取出值恢復(fù)原權(quán)位值,最后Base64即可!
s = []
t = 0xB3E659480
# 每3個(gè)字節(jié)為一次輸入
for i in range(12):
s.append(t & 0x7)
t >>= 3
assert t == 0
key = ''
for i in s:
if i == 0: key += '↑'
elif i == 1: key += '→'
elif i == 2: key += '↓'
elif i == 3: key += '←'
elif i == 4: key += '1'
elif i == 5: key += '7'
elif i == 6: key += '3'
elif i == 7: key += '4'
print(key) # ??↓↓→←→←3417
values = [0x00000000331b6d84, 0x0000000054cab29a, 0x000000000cd0afcd,
0x000000006636db08, 0x0000000000021528, 0x0000000005d62020, 0x00000000070bc7c1,
0x00000000006739bd, 0x00000000001b084a]
table = []
for i in values:
table.append([])
s = ''
value = i
for j in range(9):
table[-1].append(int(value % 11))
s += "%2d" % (value % 11)
value /= 11
# print(s[2: ] + s[: 2])
'''
0 0 0 0 0 0 0 4 0
0 0 5 0 0 0 7 6 0
0 0 0 0 4 0 0 1 0
0 0 0 0 0 0 0 8 0
0 6 3 9 0 0 0 0 0
0 0 0 0 3 0 5 0 0
2 9 0 0 8 0 6 0 0
0 7 0 0 9 3 0 0 0
3 0 0 0 0 1 0 0 0
'''
# print(sum(table, []).count(0))
# https://sudoku.vip/sudoku-x-solver/
solves = [
[8, 1, 6, 7, 5, 2, 3, 4, 9],
[4, 3, 5, 8, 1, 9, 7, 6, 2],
[7, 2, 9, 3, 4, 6, 8, 1, 5],
[9, 4, 7, 1, 6, 5, 2, 8, 3],
[5, 6, 3, 9, 2, 8, 4, 7, 1],
[1, 8, 2, 4, 3, 7, 5, 9, 6],
[2, 9, 1, 5, 8, 4, 6, 3, 7],
[6, 7, 4, 2, 9, 3, 1, 5, 8],
[3, 5, 8, 6, 7, 1, 9, 2, 4]
]
# 數(shù)獨(dú)列右移
for i in range(9):
solves[i] = [solves[i][-1]] + solves[i][: -1]
# print(solves[i])
numbers = []
for y in range(9):
for x in range(9):
if table[y][x] == 0:
# print(table[y][x])
numbers.append(solves[y][x])
assert len(numbers) % 2 == 0
flag = ''
for i in range(0, len(numbers), 2):
flag += chr(numbers[i] + 10 * numbers[i + 1])
import base64
# print(flag)
print(base64.b64encode(str.encode(flag)))
# ↑↑↓↓→←→←3417
# WT05ICpTW0tcPyYxETgMGTBDUSphES1TLgwtVUwd
? 最后輸入上上下下右左右左3417再二段。 ? GetFlag!! ?

?
?
?
電子發(fā)燒友App
















評(píng)論