Post

PwnMe CTF 2025 quals

Write up PwnMe-CTF quals 2025

PwnMe CTF 2025 quals

Back to the past

image

1 challenge ELF64, load vào IDA:

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
int __fastcall main(int argc, const char **argv, const char **envp)
{
  const char *mode; // rsi
  int rand_num; // eax
  int flag_char; // [rsp+1Ch] [rbp-124h]
  __int64 time_stamp; // [rsp+20h] [rbp-120h]
  __int64 flag_file; // [rsp+28h] [rbp-118h]
  char v10[264]; // [rsp+30h] [rbp-110h] BYREF
  unsigned __int64 v11; // [rsp+138h] [rbp-8h]

  v11 = __readfsqword(0x28u);
  if ( argc > 1 )
  {
    time_stamp = time(0LL, argv, envp);
    printf((__int64)"time : %ld\n", time_stamp);
    srand((unsigned int)time_stamp);
    mode = "rb+";
    flag_file = fopen64(argv[1]);
    if ( flag_file )
    {
      while ( 1 )
      {
        flag_char = getc(flag_file, mode);
        if ( flag_char == -1 )
          break;
        fseek(flag_file, -1LL, 1LL);
        rand_num = rand();
        mode = (const char *)flag_file;
        fputc(flag_char ^ (unsigned int)(rand_num % 127), flag_file);
      }
      fclose(flag_file);
      strcpy(v10, argv[1]);
      strcat(v10, ".enc");
      if ( (unsigned int)rename(argv[1], v10) )
      {
        printf((__int64)"Can't rename %s filename to %s.enc", argv[1], argv[1]);
        return 1;
      }
      else
      {
        return 0;
      }
    }
    else
    {
      printf((__int64)"Can't open file %s\n", argv[1]);
      return 1;
    }
  }
  else
  {
    printf((__int64)"Usage: %s <filename>\n", *argv);
    return 1;
  }
}

Luồng chính của challenge hoạt động như sau:

  • Khi chạy chương trình phải kèm $input$
  • Lấy thời gian hiện tại làm seed cho hàm srand
  • Mở file flag.enc (tham số khi chạy chương trình) ở mode rb+ (đọc và chỉnh sửa)
  • Mã hoá lần lượt từng ký tự và ghi lại vào file:

image

Bài này cần chú ý ở hàm srand và hàm rand đã bị custom:

image image

Dễ dàng decrypt với 2 cách tìm seed là bruteforce theo thời gian được mô tả của chall:

1
2
3
Using the provided binary and the encrypted file, find a way to retrieve the flag 
contained in "flag.enc". 
Note that the binary would have been run in May 2024. 

hoặc dựa vào thời gian modify cụ thể file flag.enc đề cung cấp: image seed = 1715198477

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
seed = 1715198477 - 1
enc_flag = open('flag.enc', 'rb').read()
# enc_flag = [
#     0x20, 0x34, 0x0C, 0x1D, 0x2F, 0x2D, 0x0D, 0x4B, 0x3B, 0x6F, 
#     0x18, 0x15, 0x5D, 0x6C, 0x2F, 0x16, 0x42, 0x57, 0x66, 0x1B, 
#     0x16, 0x78, 0x07, 0x34, 0x1C, 0x2B, 0x6C, 0x66, 0x7B, 0x25, 
#     0x41, 0x51, 0x55, 0x3A, 0x4D, 0x5D, 0x5F, 0x25, 0x6B
# ]
flag = []

def cus_rand():
    global seed
    seed = 0x5851F42D4C957F2D * seed + 1 & 0xFFFFFFFFFFFFFFFF
    return seed >> 33

for i in range(len(enc_flag)):
    flag.append(enc_flag[i] ^ (cus_rand() % 127))
print(bytes(flag))

PWNME{4baf3723f62a15f22e86d57130bc40c3}

C4-License

image image

1 challenge ELF64 dạng keygen.

Yêu cầu nhập license ứng với user, khớp thì in ra flag.

Chú ý vào hàm on_checkKey_clicked (nút Check License):

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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
unsigned __int64 __fastcall C4License::on_checkKey_clicked(C4License *this)
{
  volatile signed __int32 *v1; // rdi
  signed __int32 v2; // et0
  int v3; // edx
  int v4; // edx
  volatile signed __int32 *v5; // rdi
  signed __int32 v6; // et0
  volatile signed __int32 *v7; // rdi
  signed __int32 v8; // et0
  size_t v9; // r12
  _QWORD *v10; // rax
  volatile signed __int32 *v11; // rdi
  signed __int32 v12; // et0
  int v13; // edx
  char check; // r12
  volatile signed __int32 *v15; // rdi
  signed __int32 v16; // et0
  int v17; // edx
  volatile signed __int32 *v18; // rdi
  signed __int32 v19; // et0
  volatile signed __int32 *v20; // rdi
  signed __int32 v21; // et0
  volatile signed __int32 *v22; // rdi
  int v23; // eax
  signed __int32 v24; // et0
  volatile signed __int32 *v25; // rdi
  volatile signed __int32 v26; // eax
  signed __int32 v27; // et0
  volatile signed __int32 *v28; // rdi
  volatile signed __int32 v29; // eax
  signed __int32 v30; // et0
  volatile signed __int32 *v31; // rdi
  signed __int32 v32; // et0
  volatile signed __int32 *v33; // rdi
  signed __int32 v34; // et0
  int v36; // edx
  signed __int32 v37; // et0
  _QWORD *v38; // rdi
  _BYTE *src; // [rsp+30h] [rbp-E8h]
  volatile signed __int32 *v40; // [rsp+48h] [rbp-D0h] BYREF
  volatile signed __int32 *v41; // [rsp+50h] [rbp-C8h] BYREF
  _BYTE v42[8]; // [rsp+58h] [rbp-C0h] BYREF
  volatile signed __int32 *v43; // [rsp+60h] [rbp-B8h] BYREF
  volatile signed __int32 *v44; // [rsp+68h] [rbp-B0h] BYREF
  volatile signed __int32 *serial; // [rsp+70h] [rbp-A8h] BYREF
  volatile signed __int32 *v46; // [rsp+78h] [rbp-A0h] BYREF
  _BYTE v47[16]; // [rsp+80h] [rbp-98h] BYREF
  volatile signed __int32 *v48[4]; // [rsp+90h] [rbp-88h] BYREF
  void *user[2]; // [rsp+B0h] [rbp-68h] BYREF
  _QWORD v50[3]; // [rsp+C0h] [rbp-58h] BYREF
  unsigned __int64 v51; // [rsp+D8h] [rbp-40h]

  v51 = __readfsqword(0x28u);
  QPlainTextEdit::document(*(QPlainTextEdit **)(*((_QWORD *)this + 6) + 24LL));
  QTextDocument::toPlainText((QTextDocument *)&v40);
  QString::toUtf8_helper((QString *)v48, (const QString *)&v40);
  QByteArray::fromBase64((QByteArray *)&v41, (const QByteArray *)v48);
  v1 = v48[0];
  if ( !*v48[0] || *v48[0] != -1 && (v2 = _InterlockedSub(v48[0], 1u), v1 = v48[0], !v2) )
    QArrayData::deallocate(v1, 1LL, 8LL);
  QJsonDocument::fromJson(v42, &v41, 0LL);
  QJsonDocument::object((QJsonDocument *)v47);
  v46 = (volatile signed __int32 *)QString::fromAscii_helper((QString *)"user", (const char *)4, v3);
  QJsonObject::value((QJsonObject *)v48, (const QString *)v47);
  QJsonValue::toString((QJsonValue *)&v43);
  QJsonValue::~QJsonValue((QJsonValue *)v48);
  v5 = v46;
  if ( !*v46 || *v46 != -1 && (v6 = _InterlockedSub(v46, 1u), v5 = v46, !v6) )
    QArrayData::deallocate(v5, 2LL, 8LL);
  v46 = (volatile signed __int32 *)QString::fromAscii_helper((QString *)"serial", (const char *)6, v4);
  QJsonObject::value((QJsonObject *)v48, (const QString *)v47);
  QJsonValue::toString((QJsonValue *)&v44);
  QJsonValue::~QJsonValue((QJsonValue *)v48);
  v7 = v46;
  if ( !*v46 || *v46 != -1 && (v8 = _InterlockedSub(v46, 1u), v7 = v46, !v8) )
    QArrayData::deallocate(v7, 2LL, 8LL);
  QString::toUtf8_helper((QString *)&serial, (const QString *)&v44);
  QString::toUtf8_helper((QString *)&v46, (const QString *)&v43);
  v9 = *((int *)v46 + 1);
  src = (char *)v46 + *((_QWORD *)v46 + 2);
  user[0] = v50;
  v48[0] = (volatile signed __int32 *)v9;
  if ( v9 > 0xF )
  {
    user[0] = (void *)std::string::_M_create(user, v48, 0LL);
    v38 = user[0];
    v50[0] = v48[0];
    goto LABEL_62;
  }
  if ( v9 != 1 )
  {
    if ( !v9 )
    {
      v10 = v50;
      goto LABEL_13;
    }
    v38 = v50;
LABEL_62:
    memcpy(v38, src, v9);
    v9 = (size_t)v48[0];
    v10 = user[0];
    goto LABEL_13;
  }
  LOBYTE(v50[0]) = *src;
  v10 = v50;
LABEL_13:
  user[1] = (void *)v9;
  *((_BYTE *)v10 + v9) = 0;
  v11 = v46;
  if ( !*v46 || *v46 != -1 && (v12 = _InterlockedSub(v46, 1u), v11 = v46, !v12) )
    QArrayData::deallocate(v11, 1LL, 8LL);
  check = checker((__int64 *)user, (const QByteArray *)&serial);
  if ( user[0] != v50 )
    operator delete(user[0]);
  v15 = serial;
  if ( !*serial || *serial != -1 && (v16 = _InterlockedSub(serial, 1u), v15 = serial, !v16) )
    QArrayData::deallocate(v15, 1LL, 8LL);
  if ( !check )
  {
    v48[0] = (volatile signed __int32 *)QString::fromAscii_helper(
                                          (QString *)"Invalid license key",
                                          (const char *)0x13,
                                          v13);
    v46 = (volatile signed __int32 *)QString::fromAscii_helper((QString *)"Error", (const char *)5, v36);
    QMessageBox::critical(0LL, &v46, v48, 1024LL, 0LL);
    v20 = v46;
    if ( *v46 )
    {
      if ( *v46 == -1 )
        goto LABEL_28;
      v37 = _InterlockedSub(v46, 1u);
      v20 = v46;
      if ( v37 )
        goto LABEL_28;
    }
LABEL_52:
    QArrayData::deallocate(v20, 2LL, 8LL);
    v22 = v48[0];
    v23 = *v48[0];
    if ( !*v48[0] )
      goto LABEL_53;
    goto LABEL_29;
  }
  v48[0] = (volatile signed __int32 *)QString::fromAscii_helper(
                                        (QString *)"Congratulation, your license key is valid !",
                                        (const char *)0x2B,
                                        v13);
  serial = (volatile signed __int32 *)QString::fromAscii_helper((QString *)"Welcome %1", (const char *)0xA, v17);
  QString::arg(&v46, &serial, &v43, 0LL, 32LL);
  QMessageBox::information(0LL, &v46, v48, 1024LL, 0LL);
  v18 = v46;
  if ( !*v46 || *v46 != -1 && (v19 = _InterlockedSub(v46, 1u), v18 = v46, !v19) )
    QArrayData::deallocate(v18, 2LL, 8LL);
  v20 = serial;
  if ( !*serial )
    goto LABEL_52;
  if ( *serial != -1 )
  {
    v21 = _InterlockedSub(serial, 1u);
    v20 = serial;
    if ( !v21 )
      goto LABEL_52;
  }
LABEL_28:
  v22 = v48[0];
  v23 = *v48[0];
  if ( !*v48[0] )
    goto LABEL_53;
LABEL_29:
  if ( v23 == -1 || (v24 = _InterlockedSub(v22, 1u), v22 = v48[0], v24) )
  {
    v25 = v44;
    v26 = *v44;
    if ( !*v44 )
      goto LABEL_54;
    goto LABEL_32;
  }
LABEL_53:
  QArrayData::deallocate(v22, 2LL, 8LL);
  v25 = v44;
  v26 = *v44;
  if ( !*v44 )
    goto LABEL_54;
LABEL_32:
  if ( v26 != -1 )
  {
    v27 = _InterlockedSub(v25, 1u);
    v25 = v44;
    if ( !v27 )
    {
LABEL_54:
      QArrayData::deallocate(v25, 2LL, 8LL);
      v28 = v43;
      v29 = *v43;
      if ( *v43 )
        goto LABEL_35;
LABEL_55:
      QArrayData::deallocate(v28, 2LL, 8LL);
      goto LABEL_37;
    }
  }
  v28 = v43;
  v29 = *v43;
  if ( !*v43 )
    goto LABEL_55;
LABEL_35:
  if ( v29 != -1 )
  {
    v30 = _InterlockedSub(v28, 1u);
    v28 = v43;
    if ( !v30 )
      goto LABEL_55;
  }
LABEL_37:
  QJsonObject::~QJsonObject((QJsonObject *)v47);
  QJsonDocument::~QJsonDocument((QJsonDocument *)v42);
  v31 = v41;
  if ( !*v41 || *v41 != -1 && (v32 = _InterlockedSub(v41, 1u), v31 = v41, !v32) )
    QArrayData::deallocate(v31, 1LL, 8LL);
  v33 = v40;
  if ( !*v40 || *v40 != -1 && (v34 = _InterlockedSub(v40, 1u), v33 = v40, !v34) )
    QArrayData::deallocate(v33, 2LL, 8LL);
  return v51 - __readfsqword(0x28u);
}

Hàm này nhận license –> giải mã base64 –> giải mã format json ({"user": "[username]", "serial": "[serial_key]"}): image

–> gọi hàm checker với 2 tham số user(str) và serial(hex):

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
__int64 __fastcall checker(__int64 *user, const QByteArray *enc_serial)
{
  __int64 length; // rdx
  __int64 username; // rsi
  unsigned int seed; // eax
  int random; // ebx
  volatile signed __int32 *v7; // rdi
  signed __int32 v8; // et0
  volatile signed __int32 **p_hash_data; // r13
  unsigned int v10; // edx
  _BYTE *v11; // rax
  unsigned __int64 v12; // rsi
  __int64 v13; // rax
  volatile signed __int32 *v14; // rdi
  __int64 v15; // r12
  signed __int32 v16; // et0
  volatile signed __int32 *v17; // rdi
  signed __int32 v18; // et0
  volatile signed __int32 *v19; // rdi
  signed __int32 v20; // et0
  volatile signed __int32 *dec_serial; // [rsp+8h] [rbp-160h] BYREF
  volatile signed __int32 *hash_data; // [rsp+10h] [rbp-158h] BYREF
  volatile signed __int32 *serial; // [rsp+18h] [rbp-150h] BYREF
  unsigned __int8 state[276]; // [rsp+20h] [rbp-148h] BYREF
  unsigned __int8 key[4]; // [rsp+134h] [rbp-34h] BYREF
  unsigned __int64 v27; // [rsp+138h] [rbp-30h]

  length = *((unsigned int *)user + 2);
  username = *user;
  v27 = __readfsqword(0x28u);
  seed = crc32(0LL, username, length);
  srand(seed);
  random = rand();
  *(_DWORD *)key = _byteswap_ulong(rand() % 0xFFFF * (random % 0xFFFF));
  RC4::RC4((RC4 *)state, key);
  QByteArray::fromHex((QByteArray *)&serial, enc_serial);
  RC4::decrypt(&dec_serial, state, &serial);
  v7 = serial;
  if ( !*serial || *serial != -1 && (v8 = _InterlockedSub(serial, 1u), v7 = serial, !v8) )
    QArrayData::deallocate(v7, 1LL, 8LL);
  p_hash_data = &hash_data;
  QCryptographicHash::hash(&hash_data, &dec_serial, 2LL);
  QByteArray::toHex((QByteArray *)&serial);
  v10 = *((_DWORD *)serial + 1);
  if ( v10 )
  {
    v11 = (char *)serial + *((_QWORD *)serial + 2);
    v12 = 0LL;
    while ( *v11 )
    {
      v12 = (unsigned int)(v12 + 1);
      ++v11;
      if ( v10 == (_DWORD)v12 )
      {
        v12 = v10;
        break;
      }
    }
  }
  else
  {
    v12 = 0LL;
  }
  v13 = QString::fromAscii_helper((QString *)((char *)serial + *((_QWORD *)serial + 2)), (const char *)v12, v10);
  v14 = serial;
  v15 = v13;
  if ( !*serial || *serial != -1 && (v16 = _InterlockedSub(serial, 1u), v14 = serial, !v16) )
    QArrayData::deallocate(v14, 1LL, 8LL);
  v17 = hash_data;
  if ( !*hash_data || *hash_data != -1 && (v18 = _InterlockedSub(hash_data, 1u), v17 = hash_data, !v18) )
    QArrayData::deallocate(v17, 1LL, 8LL);
  LOBYTE(p_hash_data) = (unsigned int)QString::compare_helper(
                                        v15 + *(_QWORD *)(v15 + 16),
                                        *(unsigned int *)(v15 + 4),
                                        "b039d6daea04c40874f80459bff40142bd25b995",
                                        0xFFFFFFFFLL,
                                        1LL) == 0;
  if ( !*(_DWORD *)v15 || *(_DWORD *)v15 != -1 && !_InterlockedSub((volatile signed __int32 *)v15, 1u) )
    QArrayData::deallocate(v15, 2LL, 8LL);
  v19 = dec_serial;
  if ( !*dec_serial || *dec_serial != -1 && (v20 = _InterlockedSub(dec_serial, 1u), v19 = dec_serial, !v20) )
    QArrayData::deallocate(v19, 1LL, 8LL);
  return (unsigned int)p_hash_data;
}

Luồng chính của hàm checker như sau:

  • crc32 $user$ để làm seed cho srand
  • $key$ được sinh ngẫu nhiên dựa theo $user$ –> gọi _byteswap_ulong để hoán đổi thứ tự byte
  • Khởi tạo $RC4$ với $key$ và state
  • Chuyển serial từ hex sang binary
  • Giải mã $RC4$ serial
  • Convert serial sang hex
  • Hash $SHA-1$ serial đã decrypt $RC4$

image image image

link

  • So sánh kết quả với target_hash = b039d6daea04c40874f80459bff40142bd25b995

image

Tóm lại để tìm được serial khớp với user bất kì và gen license thì ta sẽ dựa vào dec_serial (serial đã decrypt $RC4$) vì bất cứ license nào sau khi check kết quả băm luôn phải bằng với target_hash –> dec_serial phải luôn cố định ko đổi với mọi userserial.

image

dec_serial = "PwNmE_c4_message!137"

Để gen license đầu tiên khởi tạo $RC4$ từ user –> mã hoá $RC4$ (dec_serial cố định) để có được serial ==> kết hợp lại theo format json và gen license.

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
from pwn import *
from Crypto.Cipher import ARC4
from zlib import crc32
import ctypes 
import json
import base64

def gen_license(user, dec_serial):
    seed = crc32(user) & 0xFFFFFFFF
    libc = ctypes.CDLL('libc.so.6')
    libc.srand(seed)
    key = (libc.rand() % 0xffff) * (libc.rand() % 0xffff) & 0xFFFFFFFF
    key = key.to_bytes(4, 'big')
    cipher = ARC4.new(key)
    enc_serial = cipher.encrypt(dec_serial)
    license = json.dumps({'user': user.decode(), 'serial': enc_serial.hex()})
    license = base64.b64encode(license.encode()).decode()
    return license.encode()

if __name__ == '__main__':
    for i in range(100):
        conn = remote('c4license-9cb38df0fbb58f45.deploy.phreaks.fr', 443, ssl=True)
        # conn.interactive()

        conn.recvuntil(b' license for: ')
        user = conn.recv().split()[0]

        dec_serial = b'PwNmE_c4_message!137'
        license = gen_license(user, dec_serial)
        # print(license)
        conn.sendline(license)

    print(conn.recvall().decode())
    conn.close()

Mimirev

image image

1 challenge ELF64 viết = go dạng VM. Load vào IDA:

image

main.main

image

Đầu tiên kiểm tra đường dẫn tệp có file .mimi hay không.

image Nếu không có thì in ra thông báo như ban đầu và thoát chương trình.

image

Ngược lại sẽ đọc nội dung file và jump tới loc4BAD87 tiếp tục xử lý.

image

Sau đó compile file, nếu thành công –> tạo 1 NewVM và chạy = VM_Run.

image

VM_Run:

image

Tổng quan các hàm vm trong file có thể đoán sơ nó sẽ thực hiện các thao tác với stack –> verifyProof (xác minh) –> in ra nội dung gì đó.

Ta sẽ chú ý và hàm verifyProof:

image image

Đầu tiên pop lần lượt 2 giá trị trên đỉnh stack và gán cho yx.

Nếu pop thành công, tính toán 2 giá trị vừa pop –> so sánh với các hằng số –> thoả thì nhảy vào các block được highlight:

image image

Script tính x và y:

1
2
3
4
for y in range(0x4CB2F):
    x = 0x4CB2F - y
    if (x * x + y * y * y - x * y) % 0xFFFFD == 0x42B6E:
        print(x, y)

image

1
2
206712 107447
123456 190703

Đây là 1 bài VM dạng compiler tức là nó sẽ chạy code = file .mimi sau đó qua vm để thông dịch code –> thoả các điều kiện thì giải mã và in ra flag.

Mình nhận được trợ giúp từ mentor format để viết vào file .mimi như sau:

1
2
3
init x = ...;
init y = ...;
verifyProof(x, y);

Dựa vào đó ta sẽ truyền tham số x và y vừa tính ở trên vào file.

Thử với cặp đầu tiên:

init x = 206712;
init y = 107447;
verifyProof(x, y);

image

Vậy là cặp còn lại là chính xác:

init x = 123456;
init y = 190703;
verifyProof(x, y);

image

PWNME{R3v3rS1ng_Compil0_C4n_B3_good}

Flattened_Vyper

image

Mô tả chall:

I achieve to obtain this smart contract, but I can’t understand what it does. Can you help me? The only information that I have are the followings:

The flag is cut in three parts and each part is emitted once. The first part is emitted in raw bytes. The second part is emitted in base58 encoding. The last part has to be xor-ed with the second part.

Challenge liên quan đến smart contract sử dụng EVM opcodes thực thi các hợp đồng thông minh trên blockchain Ethereum.

Mình google và mentor trợ giúp thì được thông tin khá hữu ích: link

image

Tóm lại, EVM là một máy ảo dựa trên stack, có kích thước từ 256-bit và được sử dụng để chạy các hợp đồng thông minh.

Theo như mô tả $flag$ được chia làm 3 phần với part1raw bytes, part2base58 encodingpart3 là kết quả sau khi $xor$ với part2.

Mình sẽ tiến hành chuyển đổi file sang file bytes = cyberchef để debug = tool Dbgereum:

image

image

Ta sẽ chú ý vào phần opcode LOG (fires an event) tức là phát sự kiện - hiểu nôm na là phát $flag$ sau khi thực hiện các thao tác tính toán trên stack.

image

Part1:

image

F8 tới đoạn này thực hiện push offset 0xe0 và lưu kết quả sau khi tính toán tại offset đó:

image image

Decrypt 50574e4d457b: –> PWNME{

image

Part2:

image

Tiếp tục F8 tương tự lưu flag base58 encoding tại offset 0xe0:

image image

Decrypt base58: –> SuCh_4_M3t4

image

Part3:

image

Thực hiện tương tự và decrypt: –> R3veRS3r}

image

PWNME{SuCh_4_M3t4R3veRS3r}

Super_secure_network

image

1 challenge ELF64. Load vào IDA:

image

Có khá nhiều hàm rất lạ và sú , gây rối cho việc phân tích.

Mình sẽ bắt đầu phân tích hàm init_module()

Nói qua 1 chút thì trong hệ điều hành Linux, init_module() là một hàm hệ thống được sử dụng để tải một mô-đun nhân (kernel module) vào kernel.Khi một mô-đun được nạp vào kernel, hàm init_module() sẽ được gọi để khởi tạo và cấu hình các tài nguyên cần thiết cho mô-đun đó.

init_module:

image

File database ida mình xin tham khảo của anh $Sơn VH$, GPT, codebrowser giúp mình rename các tên biến và hàm chi tiết.

Phase 1:

Đầu tiên tính toán khóa Diffie-Hellman: image

$A = G^a\ mod\ P$

DH là một giao thức trao đổi quan trọng cho phép hai bên giao tiếp qua kênh công cộng để thiết lập một bí mật lẫn nhau mà không được truyền qua Internet.Cho phép cả hai sử dụng khóa công khai để mã hóa và giải mã cuộc trò chuyện hoặc dữ liệu của họ bằng mật mã đối xứng.

image

Gọi crypto_dh_key_len với tham số là dh_params để tính chiều dài cho khoá mã hoá ($public\ key$)

image

Nếu chiều dài khoá > 0 thì mã hoá key và gửi qua C2server 192.168.1.60:3333

Ta có thể thấy trong file pcapng đề cung cấp để ý ở dòng số 4 chính là $public\ key\ A$ đã được gửi đi.

dòng 1, 2, 3 thực hiện quy tắc bắt tay bước (Three-Way Handshake) để thiết lập kết nối giữa client và server.

image

Script lấy G, P, A:

image

calc a online image

Từ đó có $private\ key\ A$: a = 0x1275e27a22626694

Phase 2:

decrypt_and_forward_packet (recv)

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
__int64 __fastcall decrypt_and_forward_packet(__int64 rdi0, __int64 a2)
{
  int v2; // eax
  __int64 v3; // rax
  int v5; // [rsp+1Ch] [rbp-4Ch] BYREF
  __int64 a1; // [rsp+20h] [rbp-48h]
  __int64 v7; // [rsp+28h] [rbp-40h]
  int v8; // [rsp+34h] [rbp-34h]
  __int64 v9; // [rsp+38h] [rbp-30h]
  __int64 v10; // [rsp+40h] [rbp-28h]
  unsigned int v11; // [rsp+4Ch] [rbp-1Ch]
  __int64 v12; // [rsp+50h] [rbp-18h]
  __int64 v13; // [rsp+58h] [rbp-10h]

  v12 = 0LL;
  v11 = 0;
  v10 = 0LL;
  v9 = 0LL;
  v5 = 0;
  v13 = sub_215(a2);
  if ( v13 )
  {
    v8 = *(a2 + 120);
    v7 = sub_19C(a2);
    a1 = 4 * (*(v13 + 12) >> 4) + v13;
    v12 = sub_FC(a2);
    v11 = v12 - a1;
    if ( v12 != a1 )
    {
      v2 = *(v11 - 4LL + a1);
      if ( v2 == 0x86E35DE5 )
      {
        decrypt_somthing(a1);
      }
      else if ( v2 == 0x89E35DE5 )
      {
        if ( decryptable )
        {
          v11 -= 4;
          ll1lll11ll1ll11l(a1, v11 - 16, (a1 + v11 - 16LL));
          v11 -= 16;
          v10 = _alloc_skb_(v11 + 96, 0x1080020u);
          if ( v10 )
          {
            sub_123(v10, 96);
            v3 = skb_put(v10, v11);
            csum_partial_copy_from_user(a1, v3, v11, 0LL, &v5);
            *(v10 + 184) = 8;
            *(v10 + 186) = 0;
            *(v10 + 190) = 0;
            sub_1C5(v10);
            *(v10 + 16) = *(a2 + 16);
            v9 = sub_1FC(v10);
            if ( v9 )
              netif_rx(v10);
          }
        }
      }
    }
  }
  return 1LL;
}

Hàm này kiểm tra 4 byte cuối của packet và so sánh với magic number:

  • magic_number == 0x86E35DE5:

image image image

Packet client nhận được chính là $public\ key B$: public_keyB = 0x0498d077987c0265.

$public\ key\ B$ của server được tính pow_mod và hash SHA-256 làm key decrypt AES: key_AES = 0xbf98795c03cdbce091b8986cc599628ea6ba2d91b8e50443c4bb144477ce982b

image

Chỗ này mình mất nhiều thời gian tìm lỗi sai khi phải đổi sang kiểu little-endian thì kết quả mới đúng

Mình hơi khó hiểu 1 chút khi mình lấy packet từ file pcapng thì mình đã đổi sang kiểu little-endian để tính toán rồi nhưng tới bước này phải đổi lại 1 lần nữa.

  • magic_number == 0x89E35DE5

image

Đoạn này decrypt packet AES_CTR

Một lưu ý ở đây noncecounter khác nhau ở mỗi lần decrypt, nó phụ thuộc vào packet nhận được từ server

1
2
3
4
nonce = packet[-20:-4]
enc = packet[:-20]
cipher = AES.new(key, AES.MODE_CTR, counter=Counter.new(128, initial_value=int.from_bytes(nonce, 'big')))
decrypted = cipher.decrypt(enc)

encrypt_and_forward (send)

image

Hàm đơn giản là mã hoá packet ở client trước khi gửi lên server.

Toàn bộ nội dung còn lại chính là tương tác send/recv data giữa client và server:

image

Tóm lại, luồng hoạt động của challenge như sau:

  • Tại init_module() gửi $public\ key\ A$ + P, G của client đến server.
  • Server phản hồi gửi lại $public\ key\ B$ cho client.
  • Cuối cùng init_module() sử dụng Diffie–Hellman algorithm dùng khóa công khai để mã hóa và giải mã data từ key trên.
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
from scapy.all import *
from Crypto.Cipher import AES
from Crypto.Util import Counter

def aes_ctr_decrypt(packet):
    key = bytes.fromhex("bf98795c03cdbce091b8986cc599628ea6ba2d91b8e50443c4bb144477ce982b")
    nonce = packet[-20:-4]
    enc = packet[:-20]
    
    ctr = Counter.new(128, initial_value=int.from_bytes(nonce, byteorder='big'))
    cipher = AES.new(key, AES.MODE_CTR, counter=ctr)
    decrypted = cipher.decrypt(enc)
    return decrypted

if __name__ == "__main__":
    output = "output.txt"
    payloads = PcapNgReader("./capture.pcapng")
    
    with open(output, "w") as f:
        for p in payloads:
            if TCP in p:
                packet = raw(p[TCP].payload)
                if len(packet) < 36:
                    continue
                decrypted = aes_ctr_decrypt(packet)
                # print(decrypted.decode(errors='ignore'))
                f.write(decrypted.decode(errors='ignore'))

PWNME{Crypt0_&_B4ndwidth_m4k3s_m3_f33l_UN83474813!!!}

This post is licensed under CC BY 4.0 by the author.

Trending Tags