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

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

::: :::spoiler 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

::: :::spoiler IDA notify_debugger

1
2
3
4
5
6
void __cdecl __noreturn notify_debugger()
{
  if ( IsDebuggerPresent() )
    ExitProcess(1u);
  __debugbreak();
}

::: :::spoiler 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

::: :::spoiler IDA sub_401170

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

::: :::spoiler 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);
}

::: :::spoiler 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掉 :::spoiler Patch Sleep Function Result 圖片 圖片 :::
  2. 接著會進到loc_401AE0,這部分應該是一個function但不知道為甚麼IDA翻譯不出來,不過看了一下source code也是蠻簡單的,就是一直跳到==sub_401220==,這個在動態也可以patch :::spoiler 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掉,就看自己方便 :::danger 在寫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)

:::spoiler 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)

::: :::spoiler 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}