Simple Reverse - 0x28(2023 Lab - Super Angry)

Simple Reverse - 0x28(2023 Lab - Super Angry)

Source code

  • main function
    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
    __int64 __fastcall main(int argc, char **argv, char **a3)
    {
      __int64 *user_input; // rcx
      __int64 v5; // rdx
      __int64 v6; // rdx
      char output[128]; // [rsp+10h] [rbp-B0h] BYREF
      __int64 user_input_cp[6]; // [rsp+90h] [rbp-30h] BYREF
    
      user_input_cp[5] = __readfsqword(0x28u);
      if ( argc == 2 )
      {
        user_input = (__int64 *)argv[1];
        v5 = user_input[1];
        user_input_cp[0] = *user_input;
        user_input_cp[1] = v5;
        v6 = user_input[3];
        user_input_cp[2] = user_input[2];
        user_input_cp[3] = v6;
        scramble_fn((__int64)user_input_cp, output, 0x20uLL);
        if ( !memcmp(output, verify_key, 0x80uLL) )
          puts("Correct!");
        else
          puts("Incorrect!");
        return 0LL;
      }
      else
      {
        printf("Usage: %s <input>\n", *argv);
        return 1LL;
      }
    }
    
  • scramble function
    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
    unsigned __int64 __fastcall scramble_fn(char *user_input, uint32_t *output, unsigned __int64 const_0x20)
    {
      unsigned __int64 result; // rax
      int cmd; // [rsp+24h] [rbp-Ch]
      unsigned __int64 i; // [rsp+28h] [rbp-8h]
    
      cmd = 1;
      memset(output, 0, 4 * const_0x20);            // 從這邊可以看得出來output的大小應該是int或是uint,因為有4 bytes
      for ( i = 0LL; ; ++i )
      {
        result = i;
        if ( i >= const_0x20 )
          break;
        switch ( cmd )
        {
          case 1:
            output[i] = (user_input[i] << 12) + 5308892;
            cmd = 3;
            break;
          case 2:
            output[i] = 4 * (user_input[i] + 1958409);
            cmd = 4;
            break;
          case 3:
            output[i] = user_input[i] + 192731;
            cmd = 5;
            break;
          case 4:
            output[i] = 4 * user_input[i] + 14474785;
            cmd = 1;
            break;
          case 5:
            output[i] = (user_input[i] << 17) + 176044;
            cmd = 6;
            break;
          case 6:
            output[i] = user_input[i] - 3874948;
            cmd = 2;
            break;
          default:
            continue;
        }
      }
      return result;
    }
    

Recon

可以從IDA解析出來的結果得知,這支程式的主要流程是我們執行的時候command多帶一個參數,而這個參數會直接進到scramble_fn做一些操作,最後會再跟verify_key進行memcmp,大略分析一下scramble_fn後發先他是一個偏簡單但我們懶得看得操作,所以可以試看看用angr解看看

angr基本流程:

  1. 建立一個project
  2. 建立claripy symbol - 以這個lab的例子來說就是建立我們輸入進去的程式的input string
  3. 建立初始的state - 以這個lab來說就是我們一開始輸入的input string
  4. 有了proj / symbol / initial state之後就要開始讓他跑起來

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import angr
import claripy

# 建立一個project
root = 'Reverse/Lab3/Super Angry/'
proj = angr.Project(root + 'super_angry')

# 建立Claripy Symbol
sym_arg = claripy.BVS('sym_arg', 8 * 32) # 就像z3一樣要建立symbol

# 建立初始的state
state = proj.factory.entry_state(args=[proj.filename, sym_arg])
simgr = proj.factory.simulation_manager(state)

# 有了proj/symbol/initial state之後就要開始讓他跑起來
simgr.explore(find = lambda s: b'Correct!' in  s.posix.dumps(1))

if len(simgr.found) > 0:
    print(simgr.found[0].solver.eval(sym_arg, cast_to=bytes))
else:
    print("NONONONO")
    
# b'FLAG{knowing_how_2_angr!}\x00DBUS_S'

Flag: FLAG{knowing_how_2_angr!}