Simple Reverse - 0x30(2023 HW - Evil FlagChecker)

Simple Reverse - 0x30(2023 HW - Evil FlagChecker)

Background

Anti Disassembly - 這一部分可以看一下碩一修的malware reverse的anti disassembly的修復(就是d和c的交錯使用) Anti Debugging - 首推scylla hide

Source code

  • IDA main
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    int __cdecl main(int argc, const char **argv, const char **envp)
    {
      DWORD TickCount; // [esp+0h] [ebp-14h]
      unsigned int v5; // [esp+8h] [ebp-Ch]
    
      TickCount = GetTickCount();
      Sleep(120000u);
      v5 = GetTickCount() - TickCount;
      if ( v5 < 119950 || v5 > 120050 )
        ExitProcess(0);
      ((void (*)(void))loc_401AE0)();
      return 0;
    }
    
  • IDA loc_401AE0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    .text:00401AE0 loc_401AE0:                             ; CODE XREF: _main:loc_4014AB↑p
    .text:00401AE0 push    ebp
    .text:00401AE1 mov     ebp, esp
    .text:00401AE3 lea     esi, aHelloHacker               ; "Hello Hacker"
    .text:00401AE9 mov     al, 48h ; 'H'
    .text:00401AEB cmp     [esi], al
    .text:00401AED jz      short loc_401AF0
    .text:00401AED
    .text:00401AED ; ---------------------------------------------------------------------------
    .text:00401AEF db 0E8h
    .text:00401AF0 ; ---------------------------------------------------------------------------
    .text:00401AF0
    .text:00401AF0 loc_401AF0:                             ; CODE XREF: .text:00401AED↑j
    .text:00401AF0 nop     word ptr [eax+eax+00000000h]
    .text:00401AF9 jmp     short loc_401B01
    .text:00401AF9
    .text:00401AF9 ; ---------------------------------------------------------------------------
    .text:00401AFB db  48h ; H
    .text:00401AFC db  65h ; e
    .text:00401AFD db  6Ch ; l
    .text:00401AFE db  6Ch ; l
    .text:00401AFF db  6Fh ; o
    .text:00401B00 db    0
    .text:00401B01 ; ---------------------------------------------------------------------------
    .text:00401B01
    .text:00401B01 loc_401B01:                             ; CODE XREF: .text:00401AF9↑j
    .text:00401B01 jmp     short loc_401B0E
    .text:00401B01
    .text:00401B01 ; ---------------------------------------------------------------------------
    .text:00401B03 db 0E8h
    .text:00401B04 db  66h ; f
    .text:00401B05 db  0Fh
    .text:00401B06 db  1Fh
    .text:00401B07 db  84h
    .text:00401B08 db    0
    .text:00401B09 db    0
    .text:00401B0A db    0
    .text:00401B0B db    0
    .text:00401B0C byte_401B0C db 0
    .text:00401B0D db 0E8h
    .text:00401B0E ; ---------------------------------------------------------------------------
    .text:00401B0E
    .text:00401B0E loc_401B0E:                             ; CODE XREF: .text:loc_401B01↑j
    .text:00401B0E jz      short loc_401B13
    .text:00401B0E
    .text:00401B10 jnz     short loc_401B13
    .text:00401B10
    .text:00401B10 ; ---------------------------------------------------------------------------
    .text:00401B12 db 0E8h
    .text:00401B13 ; ---------------------------------------------------------------------------
    .text:00401B13
    .text:00401B13 loc_401B13:                             ; CODE XREF: .text:loc_401B0E↑j
    .text:00401B13                                         ; .text:00401B10↑j
    .text:00401B13 push    1
    .text:00401B15 jmp     sub_401220
    
  • IDA notify_debugger
    1
    2
    3
    4
    5
    6
    void __cdecl __noreturn notify_debugger()
    {
      if ( IsDebuggerPresent() )
        ExitProcess(1u);
      __debugbreak();
    }
    
  • IDA sub_401220
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    .text:00401220 sub_401220 proc near                    ; CODE XREF: .text:00401B15↓j
    .text:00401220
    .text:00401220 ms_exc= CPPEH_RECORD ptr -18h
    .text:00401220
    .text:00401220 push    ebp
    .text:00401221 mov     ebp, esp
    .text:00401223 push    0FFFFFFFEh
    .text:00401225 push    offset stru_403B40
    .text:0040122A push    offset ?notify_debugger@@YAXABUtagEXCEPTION_VISUALCPP_DEBUG_INFO@@@Z_SEH
    .text:0040122F mov     eax, large fs:0
    .text:00401235 push    eax
    .text:00401236 sub     esp, 8
    .text:00401239 push    ebx
    .text:0040123A push    esi
    .text:0040123B push    edi
    .text:0040123C mov     eax, ___security_cookie
    .text:00401241 xor     [ebp+ms_exc.registration.ScopeTable], eax
    .text:00401244 xor     eax, ebp
    .text:00401246 push    eax
    .text:00401247 lea     eax, [ebp+ms_exc.registration]
    .text:0040124A mov     large fs:0, eax
    .text:00401250 mov     [ebp+ms_exc.old_esp], esp
    .text:00401253 mov     [ebp+ms_exc.registration.TryLevel], 0
    .text:0040125A call    sub_401170
    .text:0040125A
    .text:0040125F ; ---------------------------------------------------------------------------
    .text:0040125F test    al, al
    .text:00401261 jz      short loc_40126B
    .text:00401261
    .text:00401263 push    1                               ; uExitCode
    .text:00401265 call    ds:ExitProcess
    .text:00401265
    .text:0040126B ; ---------------------------------------------------------------------------
    .text:0040126B
    .text:0040126B loc_40126B:                             ; CODE XREF: sub_401220+41↑j
    .text:0040126B int     3                               ; Trap to Debugger
    .text:0040126B
    .text:0040126C ; ---------------------------------------------------------------------------
    .text:0040126C jmp     short loc_40127C
    .text:0040126C
    .text:0040126E ; ---------------------------------------------------------------------------
    .text:0040126E
    .text:0040126E loc_40126E:                             ; DATA XREF: .rdata:stru_403B40↓o
    .text:0040126E mov     eax, 1
    .text:00401273 retn
    .text:00401273
    .text:00401274 ; ---------------------------------------------------------------------------
    .text:00401274
    .text:00401274 loc_401274:                             ; DATA XREF: .rdata:stru_403B40↓o
    .text:00401274 mov     esp, [ebp+ms_exc.old_esp]
    .text:00401277 call    ?notify_debugger@@YAXABUtagEXCEPTION_VISUALCPP_DEBUG_INFO@@@Z ; notify_debugger(tagEXCEPTION_VISUALCPP_DEBUG_INFO const &)
    .text:00401277
    .text:0040127C ; ---------------------------------------------------------------------------
    .text:0040127C
    .text:0040127C loc_40127C:                             ; CODE XREF: sub_401220+4C↑j
    .text:0040127C mov     [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
    .text:00401283 mov     ecx, [ebp+ms_exc.registration.Next]
    .text:00401286 mov     large fs:0, ecx
    .text:0040128D pop     ecx
    .text:0040128E pop     edi
    .text:0040128F pop     esi
    .text:00401290 pop     ebx
    .text:00401291 mov     esp, ebp
    .text:00401293 pop     ebp
    .text:00401294 retn
    .text:00401294
    .text:00401294 sub_401220 endp
    
  • IDA sub_401170 → 在做的事情就是利用SEH(Structured Exception Handling)判斷有沒有人使用debugger
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    .text:00401170 sub_401170 proc near                    ; CODE XREF: sub_401220+3A↓p
    .text:00401170
    .text:00401170 var_1= byte ptr -1
    .text:00401170
    .text:00401170 push    ebp
    .text:00401171 mov     ebp, esp
    .text:00401173 push    ecx
    .text:00401174 mov     [ebp+var_1], 1
    .text:00401178 push    offset TopLevelExceptionFilter  ; lpTopLevelExceptionFilter
    .text:0040117D call    ds:SetUnhandledExceptionFilter
    .text:0040117D
    .text:00401183 ; ---------------------------------------------------------------------------
    .text:00401183 int     3                               ; Trap to Debugger
    .text:00401183
    .text:00401183 sub_401170 endp
    .text:00401183
    .text:00401184 ; ---------------------------------------------------------------------------
    .text:00401184 jmp     short loc_40118A
    .text:00401184
    .text:00401186 ; ---------------------------------------------------------------------------
    .text:00401186 mov     byte ptr [ebp-1], 0
    .text:00401186
    .text:0040118A
    .text:0040118A loc_40118A:                             ; CODE XREF: .text:00401184↑j
    .text:0040118A mov     al, [ebp-1]
    .text:0040118D mov     esp, ebp
    .text:0040118F pop     ebp
    .text:00401190 retn
    .text:00401190
    .text:00401190 ; ---------------------------------------------------------------------------
    .text:00401191 align 10h
    .text:004011A0
    .text:004011A0 ; =============== S U B R O U T I N E =======================================
    .text:004011A0
    .text:004011A0 ; Attributes: library function noreturn static bp-based frame
    .text:004011A0
    .text:004011A0 ; void __cdecl __noreturn notify_debugger()
    .text:004011A0 ?notify_debugger@@YAXABUtagEXCEPTION_VISUALCPP_DEBUG_INFO@@@Z proc near
    .text:004011A0                                         ; CODE XREF: sub_401220+57↓p
    .text:004011A0
    .text:004011A0 ms_exc= CPPEH_RECORD ptr -18h
    .text:004011A0
    .text:004011A0 ; FUNCTION CHUNK AT .text:00401206 SIZE 0000000E BYTES
    .text:004011A0
    .text:004011A0 push    ebp
    .text:004011A1 mov     ebp, esp
    .text:004011A3 push    0FFFFFFFEh
    .text:004011A5 push    offset stru_403B20
    .text:004011AA push    offset ?notify_debugger@@YAXABUtagEXCEPTION_VISUALCPP_DEBUG_INFO@@@Z_SEH
    .text:004011AF mov     eax, large fs:0
    .text:004011B5 push    eax
    .text:004011B6 sub     esp, 8
    .text:004011B9 push    ebx
    .text:004011BA push    esi
    .text:004011BB push    edi
    .text:004011BC mov     eax, ___security_cookie
    .text:004011C1 xor     [ebp+ms_exc.registration.ScopeTable], eax
    .text:004011C4 xor     eax, ebp
    .text:004011C6 push    eax
    .text:004011C7 lea     eax, [ebp+ms_exc.registration]
    .text:004011CA mov     large fs:0, eax
    .text:004011D0 mov     [ebp+ms_exc.old_esp], esp
    .text:004011D3 mov     [ebp+ms_exc.registration.TryLevel], 0
    .text:004011DA call    ds:IsDebuggerPresent
    .text:004011DA
    .text:004011E0 test    eax, eax
    .text:004011E2 jz      short loc_4011EC
    .text:004011E2
    .text:004011E4 push    1                               ; uExitCode
    .text:004011E6 call    ds:ExitProcess
    .text:004011E6
    .text:004011EC ; ---------------------------------------------------------------------------
    .text:004011EC
    .text:004011EC loc_4011EC:                             ; CODE XREF: notify_debugger(tagEXCEPTION_VISUALCPP_DEBUG_INFO const &)+42↑j
    .text:004011EC int     3                               ; Trap to Debugger
    .text:004011EC
    .text:004011EC ?notify_debugger@@YAXABUtagEXCEPTION_VISUALCPP_DEBUG_INFO@@@Z endp
    .text:004011EC
    .text:004011ED ; ---------------------------------------------------------------------------
    .text:004011ED mov     dword ptr [ebp-4], 0FFFFFFFEh
    .text:004011F4 mov     ecx, [ebp-10h]
    .text:004011F7 mov     large fs:0, ecx
    .text:004011FE pop     ecx
    .text:004011FF pop     edi
    .text:00401200 pop     esi
    .text:00401201 pop     ebx
    .text:00401202 mov     esp, ebp
    .text:00401204 pop     ebp
    .text:00401205 retn
    .text:00401205
    .text:00401206 ; ---------------------------------------------------------------------------
    .text:00401206 ; START OF FUNCTION CHUNK FOR notify_debugger(tagEXCEPTION_VISUALCPP_DEBUG_INFO const &)
    .text:00401206
    .text:00401206 loc_401206:                             ; DATA XREF: .rdata:stru_403B20↓o
    .text:00401206 mov     eax, 1
    .text:0040120B retn
    .text:0040120B
    .text:0040120C ; ---------------------------------------------------------------------------
    .text:0040120C
    .text:0040120C loc_40120C:                             ; DATA XREF: .rdata:stru_403B20↓o
    .text:0040120C mov     esp, [ebp+ms_exc.old_esp]
    .text:0040120F call    InputFlag_Check
    
  • IDA InputFlag_Check
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void __noreturn InputFlag_Check()
    {
      flag_info flag_info; // [esp+0h] [ebp-408h] BYREF
    
      printf(flag_info.Hello, flag_info.nonono);
      memset(&flag_info, 0, sizeof(flag_info));
      scanf(std::cin, (int)&flag_info);
      check((int)&flag_info, strlen((const char *)&flag_info));
      printf(flag_info.Hello, flag_info.nonono);
      ExitProcess(0);
    }
    
  • IDA check
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    void __fastcall check(char *input, unsigned int len)
    {
      unsigned int iv; // ebx
      unsigned int block; // edi
      int mem_addr_gap; // ecx
      unsigned __int8 cipher; // cl
      char *input_cipher_cp; // ecx
      char *cipher_flag_cp; // edx
      bool v9; // cf
      unsigned int i; // esi
      int dot; // [esp+0h] [ebp-41Ch]
      int new_line; // [esp+4h] [ebp-418h]
      int mem_addr_gap_cp; // [esp+Ch] [ebp-410h]
      char output[1028]; // [esp+10h] [ebp-40Ch] BYREF
    
      iv = 0xE0C92EAB;
      memset(output, 0, 0x400u);
      block = 0;
      if ( len )
      {
        mem_addr_gap = input - output;              // v5代表我們輸入的flag的位址和他memset的位址的差距,從這支檔案為例就是0x418
        mem_addr_gap_cp = input - output;
        do
        {
          cipher = iv ^ output[block + mem_addr_gap];
          output[block] = cipher;
          iv = len + (cipher ^ __ROR4__(iv, 3)) - block;
          Sleep(1000u);
          printf(dot, new_line);
          mem_addr_gap = mem_addr_gap_cp;
          ++block;
        }
        while ( block < len );
      }
      printf(dot, new_line);
      input_cipher_cp = output;
      cipher_flag_cp = cipher_flag;
      v9 = len < 4;
      for ( i = len - 4; !v9; i -= 4 )
      {
        if ( *(_DWORD *)input_cipher_cp != *(_DWORD *)cipher_flag_cp )
          break;
        input_cipher_cp += 4;
        cipher_flag_cp += 4;
        v9 = i < 4;
      }
    }
    

Recon

這一題沒有那麼難,難的是怎麼用工具寫出來,本來想要直接用z3或angr直接噴出來,但是不知道為啥就完全沒有奇蹟發生,所以還是硬幹

首先,先用ida看主要的流程,會發現有很多jmp系列的位址都跑掉了,此時就要修復,就是data(d)和code( c)之間交錯使用,並且把那些奇怪的data byte換成nop,修把patch好的部分,就會呈現上面的source code這樣

  1. 一樣由上而下,首先會先進到sleep睡眠兩分鐘,並且判斷進到下一行的時候,時間是否在範圍內,這也是time based的anti debugging手法,這部分可以動態直接patch掉

    Patch Sleep Function Result 圖片 圖片

  2. 接著會進到loc_401AE0,這部分應該是一個function但不知道為甚麼IDA翻譯不出來,不過看了一下source code也是蠻簡單的,就是一直跳到sub_401220,這個在動態也可以patch

    Patch Anti-Debug Result 圖片

  3. sub_401220主要是在其他anti debug的部分,具體怎麼做不是很清楚,只知道大概是和exception handler有關係,不過我在開了scylla hide之後沒有出現甚麼特別的事情 圖片
  4. 接著會進到sub_401170,這一段蠻重要的,就是處理一些Exception Handler的事情,然後莫名其妙的會進到0x40120F中的InputFlag_Check,中間的一些操作可能是被scylla hide擋掉了,不過中間也確實有檢察IsDebuggerPresent這東西
  5. 到了這邊就可以大膽猜測一些常見的操作,諸如scanf或是printf的function,接著我們會進到check這個function,也就是實際把我們的輸入,進行cipher操作後和內部的data bytes進行對比的過程
  6. 所以到了這邊一切都很明瞭了,主要的code如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
       iv = 0xE0C92EAB;
       memset(output, 0, 0x400u);
       block = 0;
       if ( len )
       {
         mem_addr_gap = input - output;              // v5代表我們輸入的flag的位址和他memset的位址的差距,從這支檔案為例就是0x418
         mem_addr_gap_cp = input - output;
         do
         {
           cipher = iv ^ output[block + mem_addr_gap];
           output[block] = cipher;
           iv = len + (cipher ^ __ROR4__(iv, 3)) - block;
           Sleep(1000u);
           printf(dot, new_line);
           mem_addr_gap = mem_addr_gap_cp;
           ++block;
         }
         while ( block < len );
       }
    

    其中,output[block + mem_addr_gap]其實就是我們的input,所以exploit的邏輯就是用brute force,把所有可能都丟一遍,然後嘗試去對比有沒有和built-in cipher bytes一樣,BTW,len代表我們輸入的長度,合理猜測和built-in cipher bytes的長度一樣,也就是23個char,中間的sleep在動態也可以patch掉,就看自己方便

在寫ROR的實作時有一個非常重要的重點要注意,也就是最後一個右旋的bit如果是0,在下一次右旋時會被忽略,也就是那個bit會消失,被當成0x的一部分,舉例來說,0x111001,右旋兩次後變成0x011110,但是最左邊的0會被當成0x的一部分,所以下一次再右旋兩次的結果會變成0x10111而不是0x100111,所以我的作法是在每次右旋之前都檢查bit length是不是都是32 bits,如果有少就padding 0在最左邊

Exploit

另外說明一下,z3或angr的解法都沒辦法實作出來,不確定是甚麼原因,但有機會還是會想解看看,所以先放著看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from string import *
from tqdm import trange


def ror(n, rotations, width):
    if rotations.bit_length() < 32:
        rotations = '0' * (32 - rotations.bit_length()) + bin(rotations)[2:]
        tmp = rotations[-width:] + rotations[:-width]
        return int(tmp, 2)
    tmp = int(bin(rotations << (n-width))[-n:-n+width] + bin(rotations >> width)[2:], 2)
    return tmp


candidates = printable
targets = [0xED, 0x03, 0x81, 0x69, 0x7B, 0x84, 0xA6, 0xA0, 0x5B, 0x2B, 0xB6, 0xE6, 0x5C, 0x57, 0xC9, 0x99, 0xE8, 0xB2, 0x20, 0x72, 0x38, 0xF1, 0x58]
len = len(targets)
iv = 0xE0C92EAB
flag = ''
for byte in trange(len):
    iv_xor = int(hex(iv)[2:][-2:], 16)
    for candidate in candidates:
        cipher = iv_xor ^ ord(candidate)
        if cipher == targets[byte]:
            flag += candidate
            iv = ror(32, iv, 3)
            iv = len + (cipher ^ iv) - byte
            break
    # print(flag)

print(flag)
  • z3 solver
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    from z3 import *
    
    target = [0xED, 0x03, 0x81, 0x69, 0x7B, 0x84, 0xA6, 0xA0, 0x5B, 0x2B, 0xB6, 0xE6, 0x5C, 0x57, 0xC9, 0x99, 0xE8, 0xB2, 0x20, 0x72, 0x38, 0xF1, 0x58]
    len = len(target)
    iv = 0xE0C92EAB
    # 起手式 - 開一個Solver
    s = Solver()
    
    # 建立符號 - 以此HW來說就是建立23個符號對應每一個flag字元
    bvs = [BitVec(f'bt_{i}', 8) for i in range(len)]
    
    # 加上constraint - 以此lab來說每一個flag字元都應該限制在空白到0x7f之間
    for bv in bvs:
        s.add(And(bv >= 0x20, bv <= 0x7f))
    
    for i in range(len):
        iv = f'int(hex(iv)[2:][-2:], 16)'
        bvs_formula = f'(({eval(iv)}) ^ bvs[{i}])'
        s.add(eval(bvs_formula) == target[i])
        RotateRight = f'int(bin({iv_formula} << (32-3))[-32:-29] + bin({iv_formula} >> 3)[2:], 2)'
        iv_formula = f'{int(hex(iv)[2:][-2:], 16)}'
        iv_formula = f'{len} + ({iv_formula} ^ {iv}) - {i}'
        print(f'iv_formula = {iv_formula}')
    
    # 如果有解的話就會做以下操作
    if s.check() == sat:
        print('Find ~~~')
        print(s.model())
    
        flag = ""
        for bv in bvs:
            flag += chr(s.model()[bv].as_long())
    
        print(flag)
    
  • angr solver
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import angr
    import claripy
    
    # 建立一個project
    root = 'Reverse/HW3/Evil FlagChecker/'
    proj = angr.Project(root + 'test.exe')
    
    # 建立Claripy Symbol
    sym_arg = claripy.BVS('sym_arg', 8 * 23) # 就像z3一樣要建立symbol
    
    # 建立初始的state
    state = proj.factory.entry_state(stdin=sym_arg)
    simgr = proj.factory.simulation_manager(state)
    
    # 有了proj/symbol/initial state之後就要開始讓他跑起來
    # simgr.explore(find = lambda s: b'Good!' in s.posix.dumps(1))
    simgr.explore(find = lambda s: b"Good!" in s.posix.dumps(1), avoid=lambda s: b"No no no..." in s.posix.dumps(1))
    
    if len(simgr.found) > 0:
        print(simgr.found[0].solver.eval(sym_arg, cast_to=bytes))
    else:
        print("NONONONO")
    

Flag: FLAG{jmp1ng_a1l_ar0und}