<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    C++異常處理控制流下的OLLVM混淆

    VSole2022-08-09 16:54:42

    0x00 日常查殼

    (感覺好久沒寫wp了)

    無殼64位

    0x01 CFG

    GETC

    在講這題ollvm與異常處理之前,有必要先搞懂我們到底是怎么輸入的。

    一共有三處getc處理我們第一段輸入的地方。

    40762940553A(專門用來處理箭頭)405676(專門用來處理箭頭)
    

    程序最先開始運行的是 407629,這里我們可以輸入上下左右箭頭與特定的數字。

    • 如果是數字,程序讀取加密進行存放
    • 如果是箭頭,會繼續進行處理

    (同時我們的輸入還會決定異常類型)

    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.

    那么異常處理先不深究,繼續回來箭頭如何處理這個問題。那么箭頭其實為三字節碼,上下左右箭頭分別對應 ^[[A ^[[B ^[[C ^[[D。此時開始動調,我第一次輸入為上箭頭,同時注意RAX。

    那么在 407629 第一次處理箭頭會讀取為1B。

    隨后到 40553A 讀取為5B。

    最后到達 405676 可以發現我們的上箭頭代碼所對應的字符為A。

    以上就解釋了第一段輸入的處理,等到最后解密第一段輸入就會用到此。

    OLLVM

    引用這張圖,想要去掉ollvm最基本的是要認識這幾個塊。

    https://security.tencent.com/index.php/blog/msg/112

    先拋去原題,來認識一下這些名詞:

    1. 函數的開始地址為序言(Prologue)的地址
    2. 序言的后繼為主分發器(Main dispatcher)
    3. 后繼為主分發器的塊為預處理器(Predispatcher)
    4. 后繼為預處理器的塊為真實塊(Relevant blocks)
    5. 無后繼的塊為retn塊
    6. 剩下的為無用塊與子分發器(Sub dispatchers)

    那參考文章,總結來說,利用angr符號執行去除控制流平坦化的步驟可以歸結為三個步驟:

    1. 靜態分析CFG得到序言/入口塊(Prologue)、主分發器(Main dis。
    2. patcher)、子分發器/無用塊(Sub dispatchers)、真實塊(Relevant blocks)、預分發器(Predispatcher)和返回塊(Return)。
    3. 利用符號執行恢復真實塊的前后關系,重建控制流。
    4. 根據第二步重建的控制流Patch程序,輸出恢復后的可執行文件。

    簡單來說就是獲取所有的塊,利用angr符號執行我們的真實塊,查看真實塊之間的流程,再拋去我們不要的塊,patch程序,完成!

    (那么具體的實現看文章)

    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混淆過的cfg那么漂亮。

    Exception

    為了搞懂CFG為什么成這樣了,得先了解下異常的原理,參考原文:

    https://www.cnblogs.com/catch/p/3604516.html

    對于最基本的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

    異常拋出后,發生了什么事情?

    1、如果當前函數沒有catch,就沿著函數的調用鏈繼續往上拋,然后出現兩種情況:

    在某個函數中找到相應的catch;

    沒找到相應的catch,調用 std::terminate() (這個函數是把程序abort)。

    2、如果想找到了相應的catch,執行相應的操作。

    程序中catch的代碼塊有個專有名詞:Landing pad

    3、從拋異常到開始 -> 執行Landing pad代碼 這整個過程叫作Stack unwind。

    Stack unwind

    從拋異常函數開始,對調用鏈上的函數逐個往前查找Landing pad。

    如果沒有找到Landing pad則把程序abort,如果找到則記下Landing pad的位置,再重新回到拋異常的函數那里開始,一幀一幀地清理調用鏈上各個函數內部的局部變量,直到 landing pad 所在的函數為止。

    void func1(){  cs a; // stack unwind時被析構。  throw 3;}
    void func2(){  cs b;  func1();}
    void func3(){  cs c;  try  {    func2();  }  catch (int)  {    //進入這里之前, func1, func2已經被unwind.  }}
    

    stack unwind的過程可以簡單看成函數調用的逆過程,這個過程在實現上由一個專門的stack unwind庫來實現。

    • stack unwind庫在intel平臺上
    • 屬于Itanium ABI 接口中的一部分
    • 與具體的語言無關,由系統實現
    • 任何上層語言都可以通過這個接口的基礎實現各自的異常處理
    • GCC就是通過這個接口實現C++的異常處理

    Itanium C++ ABI

    ltanium C++ ABI定義了一系列函數以及數據結構來建立整個異常處理的流程及框架,主要函數包括以下列:

    _Unwind_RaiseException,_Unwind_Resume,_Unwind_DeleteException,_Unwind_GetGR,_Unwind_SetGR,_Unwind_GetIP,_Unwind_SetIP,_Unwind_GetRegionStart,_Unwind_GetLanguageSpecificData,_Unwind_ForcedUnwind
    

    其中 _Unwind_RaiseException() 函數進行stack unwind,它在用戶執行throw的時被調用。

    主要功能:

    從當前函數開始,對調用鏈上的每一個函數都調用一個叫做 personality routine 的函數(__gxx_personality_v0)。

    personality routine 該函數由上層的語言定義及提供實現。

    _Unwind_RaiseException() 會在內部把函數棧調用現場重現,然后傳給 personality routine,該函數主要做兩件事情:

    1、檢查當前函數是否有相對應的catch;

    2、清理調用棧上的局部變量。

    那么稍稍總結一下,就是當程序拋出異常就要進行 stack unwind 操作。

    而這個操作具體是 _Unwind_RaiseException() 中的 personality routine() 實現了檢查catch和清理棧上的局部變量。

    C++ ABI

    基于前面介紹的 ltanium ABI,編譯器層面也定義了一系列 ABI 與之交互。

    當我們在代碼中寫下 throw xxx,編譯器會分配一個數據結構 __cxa_exception 來表示該異常,該異常也有一個頭部,定義如下:

    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;};
    

    當用戶 throw 一個異常時,編譯器會幫我們調用相應的函數分配出如下的結構:

    其中 __cxa_exception 就是頭部,exception_obj 則是 "throw xxx" 中的 xxx,這兩部分在內存中是連續的。

    • 異常對象由函數 __cxa_allocate_exception() 進行創建
    • 最后由 __cxa_free_exception() 進行銷毀

    當我們在程序里執行了拋出異常的操作,編譯器為我們做了如下的事情:

    1、調用 cxa_allocate_exception 函數,分配一個異常對象(cxa_exception,數據結構如上)。

    2、調用 __cxa_throw 函數,這個函數會將異常對象做一些初始化。

    3、__cxa_throw() 調用 Itanium ABI 里的 _Unwind_RaiseException() 從而開始 unwind。

    4、_Unwind_RaiseException() 對調用鏈上的函數進行 unwind 時,調用 personality routine()。

    5、該異常如能被處理(有相應的 catch),則 personality routine 會依次對調用鏈上的函數進行清理。

    6、_Unwind_RaiseException() 將控制權轉到相應的catch代碼。

    7、unwind 完成,用戶代碼繼續執行。

    總結太Bravo了!

    再看異常處理

    有了這些前置知識,再看題目中的異常,由前面描述可知實現 unwind stack 的具體過程是通過 __gxx_personality_v0(即personality routine)實現。

    這時候我們再去IDA里調整此函數。

    _Unwind_Reason_Code __fastcall _gxx_personality_v0(        int Version,        _Unwind_Action actions,        __int64 exceptionClass,        _Unwind_Exception *exceptionObject,        _Unwind_Context *context)
    

    光標在函數,按Y修改類型。

    scan_eh_tab

    回憶__gxx_personality_v0函數功能:

    1. 檢查當前函數是否有相應的 catch 語句。
    2. 清理當前函數中的局部變量。

    在personality routine()下的 scan_eh_tab() 該函數有我們最關心的兩個值,同時也是魔改處。

    與源碼對比:https://code.woboq.org/llvm/libcxxabi/src/cxa_personality.cpp.html#__cxxabiv1::scan_eh_tab

    Shfit + F1 -> INS 導入結構體。

    struct scan_results{int64_t ttypeIndex;const uint8_t* actionRecord;const uint8_t* languageSpecificData;uintptr_t landingPad;void* adjustedPtr;_Unwind_Reason_Code reason;};
    

    光標在scan_eh_tab函數上按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塊的分發處,只單單拿到landing pad還不夠,這時候還缺少一個對應異常類型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.

    那么這兩個值到底具體指的是什么??

    其實上面已經給出了答案,反復調試可知,可以發現我們的第一段輸入設置了父類StdObfException。

    the first field of the thrown StdObfException object 指的就是我們的輸入。

    the lptinfo passed to __cxa_throw 指的就是當 ___cxa_allocate_exception 創建的異常,也就是固定的。

    現在知道了魔改后的流程是從哪里來到哪里去,人工方式就是跳到landing pad再設置rdx為ttypeIndex就可以到達我們所對應的catch塊。

    什么叫CFG!

    那么現在知道了routine personality 中的 scan_eh_tab被修改了,而IDA平常能識別throw catch這些塊的原因就是這些正常的源碼。

    然而landingpad與ttypeIndex都被修改了,所以導致了IDA識別的CFG成了這個樣子。

    我們根本沒法用肉眼知道throw的塊在哪,只有通過動調才能確定,然而這就導致了原先的deflat腳本都不不行了。

    原因主要為兩點:

    1、無法確定throw后的塊;

    2、throw可能對著多個catch塊,這時候就通過rdi(ttypeIndex)進行catch塊分發(landingPad)。

    原因還有種種就不一一舉例,就無法正常原先deflat所需要的CFG塊。

    以下開始就是跟著官方腳本復現。我們再回憶一下正常的ollvm的執行流程:

    Prologue(入口塊)-> Main dispatcher(主分發器)-> Sub dispathers(子分發器)-> Relevant blocks(真實塊)-> Predispather(預分發器)-> Main dispatcher(主分發器)...

    總結一下這道題的CFG。

    我們的下一個真實塊取決于系統生產的lptinfo和我們的第一段輸入所導致的StdObfException,在每個真實塊的結束,我們不只是跳往與預分發器,而是調用 __cxa_throw 進行第二次調度,我們稱二次調用為 second dispatch。

    所以我們的執行流就是:

    ... -> main dispatcher -> sub dispatchers -> relevant block -> throw StdObfException exception -> Secondary dispatchers -> pre-dispatcher -> main dispatcher -> ...

    除此之外,程序還拋出了一些真正的異常,對于這些異常,第二次調用發生于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

    去該平坦化控制流,有兩個步驟:

    1. 找到所有的真實塊
    2. 找到真實塊之間的關系

    Find all relevant blocks

    我們可以從主分發器開始尋找,找到所有子分發器的后繼者,這些后繼者本身不是子分發器。

    官方WP中一眼丁真發現子分發器由該指令格式組成。

    sub dispathers such as:cmpjx
    

    于是由此區別出來:

    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])
    

    首先判斷是否為子分發器,然后排除法找到所有真實塊。

    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:0x404a80relevant_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

    官網WP指出抽象出來,留個坑,以后熟了試試。

    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 利用符號執行去除控制流平坦化(https://security.tencent.com/index.php/blog/msg/112) for more information).

    于是官網WP又給了個更有趣的方法,GDB腳本!

    為了找到真實塊之間的流程,通過普通的執行然后打印真實塊需要的信息!

    但是我們不一樣能得到所有的流程因為部分可能沒執行到,但是我們依然可以利用提取出來的信息去恢復部分控制流,并弄清楚如何輸入可以恢復更多流程。(怎么好像夢到過我在這寫wp...)

    生成GDB的腳本如下:

    • 40A3D4為我們catch塊地址
    • _ZN18StdSubObfExceptionC2Ec為了打印異常類型
    cmds = """\set pagination off b *0x40A3D4commands  silent  printf "landingPad: %x\\n", $rdx  continueend b _ZN18StdSubObfExceptionC2Eccommands  silent  printf "selector: %x\\n", $rsi  continueend define mytrace  break $arg0  commands    silent    printf "%x\\n", $pc    python gdb.execute('continue')  endend"""for bb in relevant_blocks:    cmds += (f"mytrace *{hex(bb.address)} \n")cmds += "run\n"with open("test.gdb", "w") as f:    f.write(cmds)
    cat teatin0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef gdb inflated -x test.gdb --batch < testin > testout
    

    于是可以獲取真實塊接下來的landing pad與異常類型。

    Breakpoint 1 at 0x40a3d4......Breakpoint 88 at 0x404ca44075f9selector: 0landingPad: 4089bf4072b4selector: 0landingPad: 4085034075f9selector: 2landingPad: 4089bf4060e7selector: 0......40617cselector: 0landingPad: 409100409437[Inferior 1 (process 13732) exited normally]
    

    然后就寫個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

    修復程序環節!當我們已經確定了執行流程,像拋異常 子分發器什么都是多余的了,統統patch掉。

    對于后繼塊只有一個的真實塊,只需要jmp過去。

    對于有多個后繼塊的,需要通過esi(也就是異常類型)來改成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('<i', self.func_terminate-va_start-6)      # cj_instr = asm(f"je {hex(self.func_terminate)}", vma=va_start)    else:      cj_instr += struct.pack('<i', t-va_start-6)      # cj_instr = asm(f"je {hex(t)}", vma=va_start)    self.do_patch(va_start, cj_instr)    va_start += len(cj_instr)  va_start += self.patch_jmp(va_start, va_targets[-1])  if va_start > org_start+size:    print("[Warning] patches at (%x, %x) overlaps next blk. "%(org_start, va_start))
    

    官方完整腳本:

    ## filename: deflat.pyfrom ast import Tuplefrom xmlrpc.client import Booleanfrom barf.barf import BARFimport angrimport structimport sysfrom pwnlib import elffrom 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('<i', offset)    self.do_patch(va_start, jmp)    return PatchHelper.JMP_SIZE   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 = (3+6) * 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("\033[31m[Error]\033[0m `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('<i', self.func_terminate-va_start-6)        # cj_instr = asm(f"je {hex(self.func_terminate)}", vma=va_start)      else:        cj_instr += struct.pack('<i', t-va_start-6)        # cj_instr = asm(f"je {hex(t)}", vma=va_start)      self.do_patch(va_start, cj_instr)      va_start += len(cj_instr)    va_start += self.patch_jmp(va_start, va_targets[-1])    if va_start > 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 *0x40A3D4commands  silent  printf "landingPad: %x\n", $rdx  continueend b _ZN18StdSubObfExceptionC2Eccommands  silent  printf "selector: %x\n", $rsi  continueend define mytrace  break $arg0  commands    silent    printf "%x\\n", $pc    python gdb.execute('continue')  endend"""  for bb in relevant_blocks:    cmds += (f"mytrace *{hex(bb.address)} \n")  cmds += "run\n"  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可能會報個錯,程序把本身有個\n是腳本中需要打印的,但直接轉義成真換行了需要手動恢復。

    觀看修復后的流程:

    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::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::_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::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::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

    之前也提到過,由于我們的輸入部分流可能執行不到,很明顯我們剛剛根本沒有輸入上下左右箭頭啥的。

    所以關于處理上下左右箭頭的代碼無了。

    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 );
    

    這個時候就可以更改我們的輸入(指的是輸入箭頭再輸入字符)再來一遍。

    成功解析出我們的第一段輸入。

     

    由于兩個文件分析過程不貼了,可以直接看官方WP給出的源碼。

    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 Partif (... && state == 0xb3e659480) {  std::cout << LIT("Congratulation! \n") << LIT("Your flag is ACTF{") << flag << LIT("_amazing!}") << std::endl;}
    

    PART TWO

    這個部分完全跟著lchild的分析來了。

    接著就是第二段輸入。首先是經過一段Base64解碼操作,再經過取模除十操作得到一個數組。

      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::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::_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;      *(&copy_input1 + v15) = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::find(                                v9,                                (unsigned int)*(&copy_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    {                                         // 對輸入進行操作分值操作      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 );                               // 以上是對input進行了base64解碼
    

    之后計算了九個數值,和一堆pang臭的代碼,不過干的事情不是很復雜。

    第一個循環是復制,后兩個循環判斷行列,不難發現這是個數獨,拿網站一把梭了。

    具體參考lchild師傅的Write up 
    # https://sudoku.vip/sudoku-x-solver/

    0x04 GetFlag!!

    第一個解密就直接移回去即可。

    第二個解密出數獨的值,列移動,取出值恢復原權位值,最后Base64即可!

    s = []t = 0xB3E659480# 每3個字節為一次輸入for i in range(12):    s.append(t & 0x7)    t >>= 3 assert t == 0key = ''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]] # 數獨列右移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!!

    instrinstr函數
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    數據庫注入提權總結
    2022-08-09 16:49:49
    select * from test where id=1 and ;布爾盲注常見的布爾盲注場景有兩種,一是返回值只有True或False的類型,二是Order by盲注。查詢結果正確,則延遲3秒,錯誤則無延時。笛卡爾積延時大約也是3秒HTTP頭注入注入手法和上述相差不多,就是注入點發生了變化HTTP分割注入常見場景,登錄處SQL語句如下
    0x00 日常查殼無殼64位0x01 CFGGETC在講這題ollvm與異常處理之前,有必要先搞懂我們到底是怎么輸入的。一共有三處getc處理我們第一段輸入的地方。程序最先開始運行的是 407629,這里我們可以輸入上下左右箭頭與特定的數字。隨后到 40553A 讀取為5B。
    Oracle Database,又名Oracle RDBMS,或簡稱Oracle。是甲骨文公司的一款關系數據庫管理系統。它是在數據庫領域一直處于領先地位的產品。可以說Oracle數據庫系統是世界上流行的關系數據庫管理系統,系統可移植性好、使用方便、功能強,適用于各類大、中、小微機環境。它是一種高效率的、可靠性好的、適應高吞吐量的數據庫方案。
    Oracle數據庫的基本知識Oracle數據庫介紹Oracle Database,又名Oracle RDBMS,或簡稱Oracle。
    前言筆者于五月份時遇到幾個經控制流平坦化的樣本,由于之前沒有接觸過這方面知識,未深入分析。七月初看到一篇
    oracle注入繞狗
    2021-10-14 15:03:05
    0x00 前言最近學習了oracle注入,和mysql比語法差異還是有的,做下小記錄,后面是嘗試繞狗。0x01 簡單fuzz空白符%09 %0A %0B %0C %0D. 當注入類型為數字型即id=1union select 全字符url編碼fuzz一遍后,發現%2E %44 %46 %64 %66這些字符添加不影響SQL語句運行
    大廠基本為了程序的安全,會使用大量內聯SVC去調用系統函數,以此來保護程序的安全。如何實現SVC指令的IO重定向,成為最大的問題。內核態是當Linux需要處理文件,或者進行中斷IO等操作的時候就會進入內核態。當arm系列cpu發現svc指令的時候,就會陷入中斷,簡稱0x80中斷。
    Dobby一共兩個功能,其一是inlinehook,其二是指令插樁,兩者原理差不多,主要介紹指令插樁。所謂指令插樁,就是在任意一條指令,進行插樁,執行到這條指令的時候,會去執行我們定義的回調函數
    STL容器逆向與實戰
    2023-02-08 09:53:04
    當然可能還存在許許多多的STL容器,但是大體的分析思路是類似的。
    AFL源碼淺析
    2022-10-26 09:54:13
    前言AFL是一款著名的模糊測試的工具,最近在閱讀AFL源碼,記錄一下,方便以后查閱。編譯項目:將編譯的優化選項關閉,即改寫成-O01afl-gcc.c使用gdb加載afl-gcc,并使用set arg -o test test.c設置參數2find_as函數?find_as函數首先會通過AFL_PATH環境變量的值從而獲得AFL對應的路徑?若上述環境變量不存在則獲取當前afl-gcc所在的文件路徑?判斷該路徑下的as文件是否具有可執行權限u8?//函數用來判斷指定的文件或目錄是否有可執行權限,若指定方式有效則返回0,否則返回-1
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类