---
# System prepended metadata

title: HCMUS - CTF 2022

---

HCMUS - CTF 2022
===

### Substitution

Cho Ciphertext đã được xáo trộn
Ta thấy trong Ciphertext có một pattern dạng flag `XXXXX-XXX{XXXXXXXXXXXXXXXX}`
Dự đoán ngay 8 ký tự đầu là: `HCMUS-CTF`
Dựa thêm vào các từ còn lại trong Ciphertext để tính flag

![](https://i.imgur.com/qh3mmnz.png)

-> Flag: `HCMUS-CTF{NHANLUN_LIKES_TO_PLAY_CRYPTOGRAM}`

### No Backend

Truy cập vào source `.webpack`
Ta thấy điều kiện để truy cập vào `/dashboard` là:

- Trường `user` trong Local Storage phải khác null
- Cookie phải có `token`

Sau khi chỉnh sửa Local Storage và thêm `token` vào Cookie thì ta sẽ truy cập được vào `/dashboard`

```javascript  
document.cookie = "token=foobarbazfizzbuzz";
```

Gõ `flag` vào khung chat là ta lấy được flag của bài
-> Flag: `HCMUS-CTF{XXXXXXXXXXXXXX}` (không nhớ T_T)

### Timehash
Dịch ngược binary bài này, ta thấy chương trình tính mã PIN bằng cách gọi hàm `SeriousHash` với biến `v4` liên tục cho đến khi số này bé hơn `0x10000000` với `v4 = 573785173`.

Tuy nhiên, với mỗi lần lặp chương trình lại `sleep` tận 1 ngày cho mỗi lần lặp dẫn đến bạn sẽ không thể kiểm tra flag được.

Solution: Tách hàm `SeriousHash` ra và tự chạy để tính xem `v4` cuối cùng là bao nhiêu. Ta được PIN = `v4` = `96521168`

![](https://i.imgur.com/6DkSVTb.png)

-> Flag: `HCMUS-CTF{96521168}`
### Super Secret
Đề phần này được gợi ý là theo dạng HCMUS-CTF{secret}, và được giấu ở trong discord của cuộc thi. Theo gợi ý trên, có thể nhập thử cụm từ "secret" vào trong khung tìm kiếm của Discord:
![](https://i.imgur.com/7i1Sm7B.png)
Theo như kết quả tìm kiếm thì trong đoạn văn của BTC thì không chứa từ khóa "secret" nào, nhưng vẫn được Discord cho vào kết quả. Thử bấm vào tấm hình và nhìn vào URL của ảnh:
![](https://i.imgur.com/pZxgGWg.png)
-> Flag: HCMUS-CTF{c291872ada763ed9a480eca240552890}

### Babydroid

Using online tool to disassemble the apk file and retrieve Java code file, I found out in there MainActivity.java

```java
public void onClick(View v) {
    int color;
    String msg;
    if (FlagValidator.checkFlag(MainActivity.this, flagWidget.getText().toString())) {
        msg = "Valid flag!";
        color = -16737536;
    } else {
        msg = "Invalid flag";
        color = SupportMenu.CATEGORY_MASK;
    }
    resultWidget.setText(msg);
    resultWidget.setTextColor(color);
}
```

So we have the flag checker class. After a bit of cleaning, this is what I achieve

```java
package com.example.babydroid;
import android.content.Context;

public class FlagValidator {
    public static boolean checkFlag(Context ctx, String flag) {
        String result = Helper.retriever();
        if (flag.startsWith("HCMUS-CTF{") && flag.charAt(19) == '_' && flag.length() == 37
                && flag.toLowerCase().substring(10).startsWith("this_is_")
                && flag.charAt(26) == flag.charAt((19)
                && new StringBuilder(flag).reverse().toString().toLowerCase().substring(1)
                        .startsWith(ctx.getString("last_part"))
                && new StringBuilder(flag).reverse().toString().charAt(0) == '}'
                && Helper
                        .ran(flag.toUpperCase().substring(20,26))
                        .equals("ERNYYL")
                && flag.toLowerCase().charAt(18) == 'a' && flag.charAt(18) == flag.charAt(28)
                && flag.toUpperCase().charAt(27) == flag.toUpperCase().charAt(28) + 1) {
            return flag.substring(10, flag.length() - 1).matches(result);
        }
        return false;
    }
}
```

Base on this script, I made a python file to construct those checker and try to reverse engineer the flag

```python
import re
def ran(s):
    out = ""
    i = 0
    while(i < len(s)):
        c = ord(s[i])
        if (c >= ord('a') and c <= ord('m')):
            c = (c + 13)
        elif (c >= ord('A') and c <= ord('M')):
            c = (c + 13)
        elif (c >= ord('n') and c <= ord('z')):
            c = (c - 13)
        elif (c >= ord('N') and c <= ord('Z')):
            c = (c - 13)
            pass
        i += 1
        out = out + chr(c)
    return out

def solve_ran(s):
    out = ""
    i = 0
    while(i < len(s)):
        c = ord(s[i])
        if (c >= ord('a') + 13 and c <= ord('m') + 13):
            c = (c - 13)
        elif (c >= ord('A') + 13 and c <= ord('M') + 13):
            c = (c - 13)
        elif (c >= ord('n') - 13 and c <= ord('z') - 13):
            c = (c + 13)
        elif (c >= ord('N') - 13 and c <= ord('Z') - 13):
            c = (c + 13)
            pass
        i += 1
        out = out + chr(c)
    return out

# This is from Helper class
def retr():
    sb = ""
    s = ""
    r = ""
    upper = True
    i = 0
    while i < 5:
        if upper:
            sb += r
            s = "U"
        else:
            sb = r
            s = "l"

        sb += s
        r = sb
        i += 1
        upper = not upper
    return r

flag = "HCMUS-CTF{ThIs_iS_A_ReAlLy_bAsIc_rEv}"

print(flag.startswith("HCMUS-CTF"))
print(flag[10:].startswith("this_is_"))
print(len(flag) == 37, len(flag), 37)
print("26 and 19 and _ =>", flag[26], flag[19],  "_")
print("18 and 28 and a =>", flag[18],  flag[28],  "a")

print(flag[::-1].lower()[1:].startswith("ver_cis"))
print(flag.lower()[:-1].endswith("ver_cis"[::-1]))

print(flag[::-1].lower()[0] == "}")
print(flag[20:26].upper() == "REALLY")
print(flag[27].upper(), chr(ord(flag[28].upper()) + 1))
```

The flag also have to match with a RegEx, where it content is actually mean that, uppercase letter will follow with a lowercase letter, and otherwise, start with a upper case letter, only applied for the part that place inside of the bracket.

```java
flag = "HCMUS-CTF{ThIs_iS_A_ReAlLy_bAsIc_rEv}"
```