# M☆CTF Quals 2023

Xin chào mọi người đến với bài writeup mới của tôi. Giải này chúng tôi làm với mục đích vô final ấy vậy đời không như mơ, điều gì đến rồi cũng đã đến. Vậy là đã kết thúc mà chúng tôi không thể vào top 10. Thôi không sau, ngã ở đâu đứng lên ở đó. Sau đây tôi sẽ viết các bài RE của giải Student và cả School quals nhé.

## 1: v-smoke (Solved)

<figure><img src="https://raw.githubusercontent.com/vietzettt/Source/main/src/2023/02mctf2023/1a.png" alt=""><figcaption></figcaption></figure>

Đầu tiên kiểm tra file bằng phần mềm Detect It Easy -> thấy đây là file elf64

Sau đó mở file này bằng IDA, xem qua các hàm ta nhận thấy hàm `main__main__main` chính là hàm chúng ta cần quan tâm

```cpp
__int64 __fastcall main__main__main(__int64 a1, __int64 a2)
{
  __int64 v2; // rdx
  __int64 v3; // rdi
  __int64 v4; // rsi
  __int64 v5; // rdx
  __int64 v6; // rcx
  __int64 v7; // rdi
  __int64 v8; // rsi
  __int64 v9; // rdx
  __int64 v10; // rcx
  char *v11; // rsi
  char *v12; // rdi
  __int64 v13; // rdx
  __int64 v14; // rcx
  char v15; // al
  int v16; // edx
  int v17; // ecx
  int v18; // r8d
  int v19; // r9d
  __int64 result; // rax
  int v21; // [rsp-20h] [rbp-120h] BYREF
  __int64 v22; // [rsp-18h] [rbp-118h]
  int v23; // [rsp-10h] [rbp-110h]
  void *v24; // [rsp+0h] [rbp-100h]
  unsigned int v25; // [rsp+8h] [rbp-F8h]
  int v26; // [rsp+Ch] [rbp-F4h]
  int v27; // [rsp+10h] [rbp-F0h]
  unsigned int i; // [rsp+14h] [rbp-ECh]
  int v29[26]; // [rsp+18h] [rbp-E8h] BYREF
  char src[32]; // [rsp+80h] [rbp-80h] BYREF
  char dest[32]; // [rsp+A0h] [rbp-60h] BYREF
  int v32; // [rsp+C0h] [rbp-40h]
  unsigned int v33; // [rsp+C4h] [rbp-3Ch]
  __int64 s[2]; // [rsp+C8h] [rbp-38h] BYREF
  __int64 v35; // [rsp+D8h] [rbp-28h]
  __int64 v36; // [rsp+E0h] [rbp-20h]
  unsigned __int8 v37; // [rsp+EFh] [rbp-11h]
  __int64 v38; // [rsp+F0h] [rbp-10h]
  __int64 v39; // [rsp+F8h] [rbp-8h]

  v38 = a1;
  v39 = a2;
  v37 = a2;
  v35 = string_substr(a1, a2, 0LL, 5LL);
  v36 = v2;
  memset(s, 0, sizeof(s));
  s[0] = (__int64)L_1372;
  s[1] = 0x100000005LL;
  v3 = v35;
  v4 = v36;
  if ( !(unsigned __int8)string__eq(v35, v36, L_1372, 0x100000005LL) )
    main__not__main(v3, v4, v5, v6);
  if ( v37 < 0x20u )
    main__not__main(v3, v4, v5, v6);
  v7 = v38;
  v8 = v39;
  if ( (unsigned __int8)string_at(v38, v39, 31LL) != 125 )
    main__not__main(v7, v8, v9, v10);
  v33 = 0;
  v32 = 1;
  memset(v29, 0, sizeof(v29));
  v29[0] = 3;
  v29[1] = 8;
  v29[2] = 37;
  v29[3] = 26;
  v29[4] = 218;
  v29[5] = 19;
  v29[6] = 232;
  v29[7] = 1;
  v29[8] = 173;
  v29[9] = 40;
  v29[10] = 207;
  v29[11] = 49;
  v29[12] = 112;
  v29[13] = 47;
  v29[14] = 142;
  v29[15] = 59;
  v29[16] = 81;
  v29[17] = 89;
  v29[18] = 126;
  v29[19] = 70;
  v29[20] = 23;
  v29[21] = 73;
  v29[22] = 62;
  v29[23] = 75;
  v29[24] = 241;
  v29[25] = 114;
  new_array_from_c_array_noscan(src, 26LL, 26LL, 4LL, v29);
  v11 = src;
  v12 = dest;
  memmove(dest, src, 0x20uLL);
  for ( i = 5; (int)i < 31; ++i )
  {
    v32 = (unsigned __int8)((v32 + 1) * v32);
    v27 = ((int)(unsigned __int8)string_at(v38, v39, i) >> 5) & 1;
    v15 = string_at(v38, v39, i);
    v26 = v32 ^ v15 & 0x1F;
    v25 = i - 5;
    v24 = &v21;
    v11 = dest;
    memmove(&v21, dest, 0x20uLL);
    v12 = (char *)v25;
    v13 = *(unsigned int *)array_get(v25, (unsigned int)dest, v16, v17, v18, v19, v21, v22, v23);
    v33 |= v13 ^ v26 | v27;
    v14 = i;
  }
  result = v33;
  if ( v33 )
    main__not__main(v12, v11, v13, v14);
  return result;
}
```

Kiểm tra qua phần strings trong chương trình ta có:<br>

<figure><img src="/files/2goOrUB39Xr2vqxQVpzy" alt=""><figcaption></figcaption></figure>

Ta sẽ phân tích code sau:

```cpp
main__main__main(v14[0], v14[1]);
```

Hàm này được truyền đầu vào là đối số, nhận thấy `v14[1]` chính là flag mà mình tìm để chương trình chạy đúng -> . Tiếp theo:

```cpp
if ( !(unsigned __int8)string__eq(v35, v36, L_1372, 0x100000005LL) )
    main__not__main(v3, v4, v5, v6);
```

Hàm này kiểm tra xem 5 bytes đầu của flag so với chuỗi `L_1372` mà mình vừa biết là `MTCF{`. Tiếp đến là kiểm tra kích thước của flag:

```c
if ( v37 < 0x20u )
    main__not__main(v3, v4, v5, v6);
```

Ta nhận thấy kích thước sẽ là không thể nhỏ hơn 32 bytes. Cộng thêm với phần này:

```c
if ( (unsigned __int8)string_at(v38, v39, 31LL) != 125 )
    main__not__main(v7, v8, v9, v10);
```

kiểm tra phần tử vị trí thứ 31 của flag là ký tự "}" -> nên ta suy ra chuỗi flag có độ dài 32 bytes.

Tiếp đến là một mảng dữ liệu được tạo lưu vào biến `src`

```c
new_array_from_c_array_noscan(src, 26LL, 26LL, 4LL, v18);
memmove(dest, src, 0x20uLL);
for ( i = 5; (int)i < 31; ++i )
{
  v21 = (unsigned __int8)((v21 + 1) * v21);
  v16 = ((int)(unsigned __int8)string_at(v27, v28, i) >> 5) & 1;
  v3 = string_at(v27, v28, i);
  v15 = v21 ^ v3 & 0x1F;
  v14 = i - 5;
  v13 = &v10;
  memmove(&v10, dest, 0x20uLL);
  v8 = (_DWORD *)array_get(v14, (unsigned int)dest, v4, v5, v6, v7, v10, v11, v12);
  v22 |= *v8 ^ v15 | v16;
}
result = v22;
if ( v22 )
  main__not__main();
return result;
}
```

Nhận thấy `v22` ban đầu gán giá trị bằng 0, sau khi vào vòng lặp thì có một câu lệnh sẽ làm thay đổi giá trị của `v22`, mà ta thấy nếu giá trị của v22 sau vòng lặp mà khác không thì sẽ sai. Nên như vậy ta sẽ phân tích câu lệnh sau:

```c
v22 |= *v8 ^ v15 | v16;
```

Để giá trị của `v22 = 0` thì giá trị biểu thức cũng phải bằng 0. Ta phân tích:

* `v15` là một hằng số được tạo ra bằng cách lấy giá trị của `v21` XOR với phần tử vị trí `i` của flag đã được`and` với giá trị 31.
* `*v8` chính là mảng mà bài cho.
* `v16` được tính bằng phần tử vị trị `i` của flag dịch bit sang phải 5 -> sau đó AND với 1.

Ta lưu ý về thứ tự thực hiện phép tính trong câu lệnh `v22` chính là toán tử XOR sẽ thực hiện trước sau đó mới đến toán tử OR. Như vậy để `v22` bằng 0 thì `v16` và `*v8^v15` cũng bằng 0. Vậy là xong. Dưới là code giải của tôi:

```python
v18 = [0]*26
v18[0] = 3
v18[1] = 8
v18[2] = 37
v18[3] = 26
v18[4] = 218
v18[5] = 19
v18[6] = 232
v18[7] = 1
v18[8] = 173
v18[9] = 40
v18[10] = 207
v18[11] = 49
v18[12] = 112
v18[13] = 47
v18[14] = 142
v18[15] = 59
v18[16] = 81
v18[17] = 89
v18[18] = 126
v18[19] = 70
v18[20] = 23
v18[21] = 73
v18[22] = 62
v18[23] = 75
v18[24] = 241
v18[25] = 114

lst = [2, 6, 42, 14, 210, 22, 250, 30, 162, 38, 202, 46, 114, 54, 154, 62, 66, 70, 106, 78, 18, 86, 58, 94, 226, 102]

for i in range(31-5):
    for j in range(48,127):
        if (j&31)^lst[i] == v18[i] and (j >> 5) & 1 == 0:
            print(chr(j),end="")
```

`lst` là mảng giá trị `v21` = giá trị trước + 1 -> nhân với giá trị trước rồi đem giá trị đó AND với `0xFF`&#x20;

Kết quả ta nhận được flag: `MCTF{ANOTHER_ONE_BYTES_THE_DUST}`

## 2: anaconde (Solved)

<figure><img src="/files/IAJndokJj373d8VMDy7f" alt=""><figcaption></figcaption></figure>

Tải về giải nén chương trình ta sẽ nhận được file được viết bằng python. Chương trình được đóng gói trong một số lớp bảo mật. Giải nén ra thông qua một số hàm:

```python
with open('anaconda.py', encoding='utf-8') as file:
    code = file.read()

for _ in range(10):
    code = eval(code[5:-1])

with open('unpacked_anaconda.py', 'w', encoding='utf-8') as file:
    file.write(code.decode())
```

Sau khi chạy chương trình này ta nhận được file có code sau:

```python
from zlib import crc32

a = crc32(input('Password: ').encode())
b = bytearray(b'O,\x1d}Y\x7f\x19(L\x10\\+W=\n(}8].}!Y,}{\x07D\x12?^*\x12!\x14')

for c, d in enumerate(b):
    b[c] = d ^ a >> (0, 8, 16, 24)[c % 4] & 0xFF

if b.startswith(b'mctf'):
    print(b.decode())
```

Ta phân tích đoạn code này một chút, mà chương trình yêu cầu ta nhập password vào -> sau đó lấy giá trị CRC32 của password đó rồi đem thực hiện rồi kiểm tra.

Tôi thấy không cần bận tâm đến Password là gì? chỉ cần biết giá trị crc32 là có flag. Nên tôi sử dụng z3 để giải thuần như sau:

```python
from zlib import crc32
from z3 import *

# Tạo s Z3
s = Solver()

crc = BitVec('crc', 32)

lst = [79, 44, 29, 125, 89, 127, 25, 40, 76, 16, 92, 43, 87, 61, 10, 40, 125, 56, 93, 46, 125, 33, 89, 44, 125, 123, 7, 68, 18, 63, 94, 42, 18, 33, 20]

lst_en = [0]*len(lst)

for i in range(len(lst)):
    lst_en[i] = lst[i] ^ crc >> (0, 8, 16, 24)[i % 4] & 0xFF

s.add(lst_en[0] == ord("m"))
s.add(lst_en[1] == ord("c"))
s.add(lst_en[2] == ord("t"))
s.add(lst_en[3] == ord("f"))


if s.check() == sat:
    model = s.model()
    crc_solution = model.eval(crc).as_long()
    print(f"CRC32 value: {crc_solution}")

    b = bytearray(b'O,\x1d}Y\x7f\x19(L\x10\\+W=\n(}8].}!Y,}{\x07D\x12?^*\x12!\x14')

    for c, d in enumerate(b):
        b[c] = d ^ crc_solution >> (0, 8, 16, 24)[c % 4] & 0xFF

    if b.startswith(b'mctf'):
        print(b.decode())
```

Như vậy là xong tôi nhận được CRC32 value: 459886370 và flag: `mctf{0p3n_50urc3_w45_n07_4n_0p710n}`. Done...

## 3: Unmanaged Calls (Solved)

<figure><img src="/files/6r48VfiRp3bjUrm7aQT0" alt=""><figcaption></figcaption></figure>

Mở bài này bằng DIE thì ta biết được rằng chương trình sử dụng .NET Reactor (6.x.x.x) để Protector.

Như vậy tìm kiếm trên mạng để unprotector thôi... (tôi sử dụng công cụ sau: <https://github.com/SychicBoy/NETReactorSlayer>)

Sau khi tải về và sử dụng ta được file mới:

<figure><img src="/files/NWIHnTa5mH1PlewLaoRs" alt=""><figcaption></figcaption></figure>

Nhận được kết quả, kiểm tra đây là file PE32 nên mình sử dụng dnSpyx86 để mở (đúng là code sạch luôn, cái gì cũng thấy và đọc rõ)

Lúc đầu tôi không đọc hết mà debug thì đang debug thì thấy luôn flag:)) Rồi bây giờ tôi kiểm tra đọc qua lại một lượt trước khi debug thì nhận được ở `Class0` ta có cái này:

<figure><img src="/files/9eyzdTFAi3tZFVt8L17T" alt=""><figcaption></figcaption></figure>

Và thế là xong... bài tiếp theo

## 4: worst keygen of them all

<figure><img src="/files/bIoXJYjGbUbA9mOwX20U" alt=""><figcaption></figcaption></figure>

Đến với bài này tôi không giải được, tôi không nghĩ ra rằng flag lại được tách ra thành từng phần nhỏ rồi sử dụng chương trình để chạy ra kết quả đem so sánh với file flag.txt mà họ cho.

Mở file này trong IDA -> sửa một số tên biến cho dễ hiểu hơn:

<figure><img src="/files/tEhZIItxUqS88MTu3Euq" alt=""><figcaption></figcaption></figure>

Ở phần trên sẽ gồm một số lệnh quan trọng mà mình quan tâm như sau:

```c
argv1 = argv[1];
```

Câu lệnh dùng để gán đối số là flag mình cần tìm. Và hàm `test`:

```c
__int64 __fastcall test(char *str, int length)
{
  int delimeter; // r9d
  int str_offset; // r8d
  int symbol_counter; // r10d
  __int64 v5; // rax
  __int64 v6; // rdx
  char v7; // cl

  delimeter = 2;
  if ( length > 2 )
  {
    do
      ++delimeter;
    while ( length != delimeter && (length % delimeter || (delimeter & 1) == 0) );
  }
  str_offset = 0;
  symbol_counter = 0;
LABEL_6:
  if ( str_offset < length )
  {
    v5 = str_offset;
    while ( 2 )
    {
      str_offset += delimeter;
      v6 = v5;
      do
      {
        v7 = str[v6];
        if ( v7 == '{' || v7 == '_' )
        {
          ++symbol_counter;
          goto LABEL_6;
        }
        ++v6;
      }
      while ( str_offset > (int)v6 );
      v5 += delimeter;
      if ( str_offset < length )
        continue;
      break;
    }
  }
  if ( length / delimeter != symbol_counter )
    return 0;
  return (unsigned int)delimeter;
}
```

Phân tích hàm này thì ta để ý câu điều kiện cuối kiểm tra. Theo như logic mà mình cần giá trị trả về phải khác không rồi. Như vậy thì ta cần tìm length của flag sao cho `length / delimeter == symbol_couter`. `delimeter` có thể được tính bằng đoạn lệnh python sau:

```python
for length in range(0,56):
    delimeter = 3
    if length > 2:
        while (length != delimeter and (length % delimeter or (delimeter & 1) == 0)):
            delimeter+=1
    print("Length = ",length, "/",delimeter, end="\n")
```

Nhận thấy đầu vào và đầu ra đều cùng kích thước:

<figure><img src="/files/NwWnGBEU3vZv7fzkT4Iq" alt=""><figcaption></figcaption></figure>

Như đã nhận được là flag encode nhận được có kích thước 56 bytes nên length có thể tối đa là 56 bytes.

Và ta thấy đoạn so sánh này:

<figure><img src="/files/6A0xINIbqDi2YSjtnrix" alt=""><figcaption></figcaption></figure>

ta để ý câu điều kiện if  khả năng phải thỏa mãn thì mới vào phần mà ta muốn vào: thì có điều kiện này cần phải thỏa mãn: `(delimeter - v21 - 1) <= 6` với `v21 = 0` hoặc `(v22 & 7) != 0` với `v22 = delimeter -v21` như vậy thì chỉ có `delimeter` bằng 7 mới thỏa mãn.

Quay trở lại phần câu lệnh python để tính delimeter ta nhận được các giá trị delimeter tương ứng với kích thước vào như sau:

<figure><img src="/files/wEHKU2CeSa5W9WElKz4m" alt=""><figcaption></figcaption></figure>

Vâng đúng vậy ta thấy được kích thước mà  có delimeter bằng 7 sẽ gồm các chuỗi có kích thước 7, 14, 28, 49, 56.

Như vậy là đã rõ mỗi một đoạn là 7 ký tự sẽ có 1 ký tự "{" hoặc "". chắc chắn để thỏa mãn định dạng flag thì chuỗi 1 sẽ có "{" và các chuỗi còn lại phải chứa 1 ký tự "\_"  trong 7 chuỗi.&#x20;

Tiếp theo ta chuyển đến file `./hshy`. Ta thực thi và sử dụng thêm câu lệnh `echo $?` - câu lệnh này dùng để hiển thị giá trị thoát của lệnh thực thi gần nhất:

<figure><img src="/files/fUT2sXTWUxEsv7E8ZYcD" alt=""><figcaption></figcaption></figure>

Như vậy kết quả nhận từ giá trị 4 đến 7.

Vậy kết luận lại như sau: tách từng phần FLAG nhận được thành đoạn 7 ký tự 1, đem nó thực hiện với file `./gruh` với các giá trị nhận được là 4->7. Sau đó đem XOR chuỗi nhận được với 1 số bất kỳ từ 0 -> 7 rồi kiểm tra chuỗi đó xem chuỗi đầu tiên có ký tự "{" và các chuỗi còn lại có đúng 1 ký tự "\_" là done.

Dưới đây là code giải của tác giả:

```python
import subprocess
from string import ascii_letters

def div(l: int) -> int:
    if not l:
        return None

    for i in range(2, l):
        if (l%i == 0) & (i%2):
            return i

def shift(string: str, n: int) -> str:
    execute = ["./gruh", string, str(n)]
    return subprocess.run(execute, capture_output=True).stdout.decode('ascii')[:7]

def process(string: str, key: int, res: str = '') -> str:
    for i in string:
        res += chr(ord(i) ^ key)
    return res

ALPH = ascii_letters + '_}{'
FLAG = 'EHYF~@WTSXUBJBJKDSTIGIZIEWVUPOBIX^HRXTWBKKXSOBXJFIXIFJBz'

length = len(FLAG)
divide = div(length)

result = ''
for i in range(0, length, divide):
    s = FLAG[i:i + divide]
    for num in range(0, divide + 1):
        p = process( shift(s, divide - num), num )

        if not ( ('{' in p) | ('_' in p) ):
            continue

        for char in p:
            if char not in ALPH:
                break
        else:
            break

    result += p

print(result);
print(FLAG);
```

Và nhận được flag: `MCTF{JUST_REMEMBER_ALL_CAPS_WHEN_YOU_SPELL_THE_MAN_NAME}`

***

Nguồn để ai đó có thể thực thi lại: <https://gitlab.com/mctf/mctf2023/student-quals-writeups>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://viettaliii.gitbook.io/home/ctf/write-up/m-ctf-quals-2023.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
