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

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

Source code

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

:::

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