---
# System prepended metadata

title: WRITEUP WannaGame NMLT

---

# WRITEUP WannaGame NMLT
Đây là Writeup giành cho cuộc thi CTFs để lấy điểm NMLT.
## Pwn
### 1. Hello pwner

 ![image](https://hackmd.io/_uploads/SyPcemLL1g.png)
 
    Flag: W1{welcome_to_assembly}
## Crypto
### 2. Substitution

Tải file, giải nén và mở chall lên:
```python=
KEY = {
    'A': 'Q', 'B': 'W', 'C': 'E', 'D': 'R', 'E': 'T', 'F': 'Y', 'G': 'U', 'H': 'I', 'I': 'O',
    'J': 'P', 'K': 'A', 'L': 'S', 'M': 'D', 'N': 'F', 'O': 'G', 'P': 'H', 'Q': 'J', 'R': 'K',
    'S': 'L', 'T': 'Z', 'U': 'X', 'V': 'C', 'W': 'V', 'X': 'B', 'Y': 'N', 'Z': 'M',
    'a': 'q', 'b': 'w', 'c': 'e', 'd': 'r', 'e': 't', 'f': 'y', 'g': 'u', 'h': 'i', 'i': 'o',
    'j': 'p', 'k': 'a', 'l': 's', 'm': 'd', 'n': 'f', 'o': 'g', 'p': 'h', 'q': 'j', 'r': 'k',
    's': 'l', 't': 'z', 'u': 'x', 'v': 'c', 'w': 'v', 'x': 'b', 'y': 'n', 'z': 'm',
}

def hehe(data, key):
    return ''.join(key.get(char, char) for char in data)

def encrypt(plaintext):
    substituted = hehe(plaintext, KEY)
    return substituted

if __name__ == "__main__":
    plaintext = "W1{???????????????}"
    encrypted = encrypt(plaintext)
    with open("encrypted.txt", "w") as f:
        f.write(encrypted)
```

#### Phân tích:
```python=
def hehe(data, key):
    return ''.join(key.get(char, char) for char in data)
```

Hàm `hehe` lấy `data` và chuyển đổi từng ký tự trong dữ liệu đó sang ký tự tương ứng bằng danh sách của `KEY`.
```python=
def encrypt(plaintext):
    substituted = hehe(plaintext, KEY)
    return substituted
```

Hàm `encrypt` có mục đich là sử dụng hàm hehe để mã hóa dữ liệu.
 
#### Giải:
Để lấy được `plaintext` thì ta cần giải mã `encrypted`, nghĩa là phải đảo ngược lại quá trình chuyển đổi ký tự trong danh sách của `KEY`
```python
KEY_rev = {i : v for v, i in KEY.items()}
```

  Rồi sử dụng hàm `hehe` chuyển đổi tiếp 1 lần nữa để được `data` ban đầu.
```python=
def hehe(data, key):
    return ''.join(key.get(char, char) for char in data)
```

  Script:
  ```python=
KEY = {
    'A': 'Q', 'B': 'W', 'C': 'E', 'D': 'R', 'E': 'T', 'F': 'Y', 'G': 'U', 'H': 'I', 'I': 'O',
    'J': 'P', 'K': 'A', 'L': 'S', 'M': 'D', 'N': 'F', 'O': 'G', 'P': 'H', 'Q': 'J', 'R': 'K',
    'S': 'L', 'T': 'Z', 'U': 'X', 'V': 'C', 'W': 'V', 'X': 'B', 'Y': 'N', 'Z': 'M',
    'a': 'q', 'b': 'w', 'c': 'e', 'd': 'r', 'e': 't', 'f': 'y', 'g': 'u', 'h': 'i', 'i': 'o',
    'j': 'p', 'k': 'a', 'l': 's', 'm': 'd', 'n': 'f', 'o': 'g', 'p': 'h', 'q': 'j', 'r': 'k',
    's': 'l', 't': 'z', 'u': 'x', 'v': 'c', 'w': 'v', 'x': 'b', 'y': 'n', 'z': 'm',
}
KEY_rev = {i : v for v, i in KEY.items()}
def hehe(data, key):
    return "".join(key.get(char, char) for char in data)

def decrypt(plaintext):
    substituted = hehe(plaintext, KEY_rev)
    return substituted

if __name__ == "__main__":
    encrypted = "V1{lxwlzozxzogf}"
    decrypted = decrypt(encrypted)
    print(decrypted)
```

    Flag: W1{substitution}
 
### 3. Hix
Tải file, giải nén và mở chall lên:
```python=
import hashlib
import random

methods = ['md5', 'sha256', 'sha3_256', 'sha3_512', 'sha3_384', 'sha1', 'sha384', 'sha3_224', 'sha512', 'sha224']

def random_encrypt(x) :
    method = random.choice(methods)
    hash_obj = hashlib.new(method)
    hash_obj.update(x.encode())
    return hash_obj.hexdigest()

def main() :
    message = open("./../private/flag.txt", "r").read()
    enc = []

    for char in message :
        x = (ord(char) + 20) % 130
        x = hashlib.sha512(str(x).encode()).hexdigest()
        x = random_encrypt(x)
        enc.append(x)

    with open('encrypted_memory.txt', 'w') as f :
        f.write("ct = " + str(enc))

if __name__ == "__main__" :
    main()
```
#### Phân tích:
```python=
methods = ['md5', 'sha256', 'sha3_256', 'sha3_512', 'sha3_384', 'sha1', 'sha384', 'sha3_224', 'sha512', 'sha224']
```

Đây là các thuật toán được sử dụng cho hàm băm.
```python=
def random_encrypt(x) :
    method = random.choice(methods)
    hash_obj = hashlib.new(method)
    hash_obj.update(x.encode())
    return hash_obj.hexdigest()
```

Hàm `random_encrypt` dùng để mã hóa một chuỗi x bằng cách sử dụng một thuật toán băm bất kì có sẵn trong `methods`.
```python=
for char in message :
        x = (ord(char) + 20) % 130
        x = hashlib.sha512(str(x).encode()).hexdigest()
        x = random_encrypt(x)
        enc.append(x)
```

Lấy từng kí tự trong `message`, sau đó lấy giá trị của ký tự đó trong ASCII rồi cộng 20 và lấy modulo 130. Tiếp theo, chuyển nó về một chuỗi rồi băm bằng SHA-512. Đến cuối thì sử dụng hàm `random_encrypt`.
 
#### Giải:
Ý tưởng: Vì x nằm trong đoạn từ 0 đến 129 (là một số nhỏ) nên ta có thể brute-force từng giá trị x khi thử với mỗi phương pháp băm để tìm ra các x tương ứng với từng giá trị của `ct` một cách dễ dàng.
```python=
def reverse_encrypt(enc_hash):
    for method in methods:
        try:
            for x in range(130):
                test_input = hashlib.sha512(str(x).encode()).hexdigest()
                hash_obj = hashlib.new(method)
                hash_obj.update(test_input.encode())
                if hash_obj.hexdigest() == enc_hash:
                    return x
        except:
            continue
```

Sau đó, với từng giá trị trong `ct`, ta dùng hàm `reverse_encrypt` và truy ngược lại giá trị x ban đầu bằng các phép toán và đổi sang dạng ASCII để giải mã nó bằng hàm sau:
```python
def decrypt():
    decrypted_message = ""
    for ct_hash in ct:
        x = reverse_encrypt(ct_hash)
        original_ascii = (x - 20) % 130
        decrypted_message += chr(original_ascii)
    return decrypted_message
```

Script:
```python=
ct = ['f189636f8eef640b55d03387864fd17efd324453cc9276be5ff6bd4da88b13fca72438daaab00830a6d14330d37c0f7bee1e7c32d5dda0541a171f66a2343dc1', '1388cafa58065fa0c04372ce57f303cc4ec9fe62', 'f6266e2849bf8b8575701814cc3f3eb5369e887db54b34e85b1e4608b4fbf5e5', '31f33ac191e818db784cf8321d70f84763db2b2e599f90cf65868eec85a10f20ae0e23aa1cd48c2f13eec355b2975089490761a291ac2a1bcf33f5fbecead431', '981e4bce5dede3faa51a936f650e2c1d64169493860c67d68a1ffbbfa32f58598e7869f3f11aefc1620ee8d3ebe4e5f5', 'f06ffaaa6290bf47d26ba2c09c28dddd8f5bcad6ac464ec17fea48040acf1214d10bc109b7c47cffddb6bccd6b61b61a9e629a8f47ab26b80593f29c8c297489', 'a7d95b3bbde885b4eaa76afc6572e18e4483351005f637fe1f5a7bc0b000fe1f', '85245de371c327440a5f343f27d6df361225806e679950bab3a5a336', 'ea1923e909de3c3c3384ad9ae7696d73', '21df20aab35967470aada32375f535d4a735789bf0789fd421f85163c4d75c6e', 'b9491ae1a9de40d30a86c00139bd7d6f496f5bf4ce013bc2d5a43a97', '03f061f60f3527b15ff31d31dcce0761', '981e4bce5dede3faa51a936f650e2c1d64169493860c67d68a1ffbbfa32f58598e7869f3f11aefc1620ee8d3ebe4e5f5', 'f2a1a7e9dd5e6363050b0cdb0579ebfebdc5e348ab538bdcf47616139351cf2b9f92cb4d14446b3ad8bf182875b81e75', '24aaafc58a2b897aed5829b2e96d73b1de7cd680d76a1143cdc8baef', '6d80d11e5f1161ef86619dcdb186852b5218d6ac224b81b63555fe73741631c36ae0bcb5b3228fbed796c22dedeed587c9d65ddb825aee4fae92b6619e7ffd8f', '6f8b39550106044625102ee0cabf9fe1393f0013388633d5742fcc7e8df7708793a96885b9d18b795a2b0d9014704b9f', 'ddf3c543be9cac44f3af078583fe5fddb64104d93308c146c23f52ff25b2a6e23606c42dc0060a4dd9b11b446759cb5de1844471eb3d6d25c43c6fcc0d8d60c4', '95f2739053cf64555b0c0662b5e2d63822433f7fcac6960de6d57efda427461a58c6e2ffac6da6f4caa9407df10cc0be', 'a1bd4e0efc7ce8bd1d63433a0baa87e3a486fbfe2729d73d1dbf7d2822d201ee8726c6d94da1f09f1a53554e440ad6041ecab545b2085dc28c6f6849f0fcea23', 'a7d95b3bbde885b4eaa76afc6572e18e4483351005f637fe1f5a7bc0b000fe1f', '2b4561a521a82af6a26dfb76078ca97ba53a720f7ee67d923a6d3a13', 'b21ed1f3d501a8a842ef1b26ed3863cf10cf8231ee23a079f749cfa322702c8e', 'd798a32b52384219f8779dccf8b2173f4b73f075cbeb4507ee83c94e', 'b863fa3492fb87edcdef766f38a508ed', '9f876db4b58c1b7e499f35cdbd533a810060a0c8250bfc5421e0f42b2715b027', '4b14748ba0f3da581ddd7ec49dac41d34ea1ee6dae90818333b11501', '85153b2a5f8dea7f5488906cb65d61e9ac0666057636ff6b356dd4d8d0fc5d20', '6b91d6259827176bcb3f312a8faca297e56c7e627235b930cf8163b3e7a5328b', 'b21ed1f3d501a8a842ef1b26ed3863cf10cf8231ee23a079f749cfa322702c8e', '4c8740f90af1055f194a4c8e1b69522da228812465eb72b82b35c927bc48bf9d', 'b248b6b2f2c9365aa9a0e9b37a8057effd29bb2f34c79ec0b40124d08986832b5d227db95cb97b176541589985762d9a', '7260f9b5d1c58d0609523114ed324f396335d940f852dba558461b34c5a53630', 'a1bd4e0efc7ce8bd1d63433a0baa87e3a486fbfe2729d73d1dbf7d2822d201ee8726c6d94da1f09f1a53554e440ad6041ecab545b2085dc28c6f6849f0fcea23', '1077caf3ed754ed8fbd49c76134906e8', 'f3565219d115ec74a85056997cc25e98e3e4912a31c858c1e45b841047698e93', '83315b8fa07a35b12e3f47ebb365268b4a4a8ef2', '64c008d6460c2b98aba616b1d0d11a06b9df564b87d3aeedda83b36aacd3d0c160465109eb06c62e86e360cf026faa27a616dbbf2bec269be9ad128af96073bb', '60bbd94b3ac3ea7149fc6cd850d72d4f1750601275832815dd9a23d4c3757d84aca29d716da5dd72a0045f15ff969925', '94327e8c8321421e72f52cd726336e824630ec7dda31b07ce83f11b8234aea7a', 'a69ef62254280226cc4223a2341c727afcd7ce4e3ffd3f2f1c57d9d3cd30659b52b1c2b56f911a7157041b5f0ff8176f', '3c904622c8d8d79c6704d50ae0175b049b3a5708705ecdce932fe426b9f46f1bd6585b8288c1d38f6301c31af5feac02', 'a3939bf491ffd9824056e249d6e355d8423855f0']
import hashlib

methods = ['md5', 'sha256', 'sha3_256', 'sha3_512', 'sha3_384', 'sha1', 'sha384', 'sha3_224', 'sha512', 'sha224']

def reverse_encrypt(enc_hash):
    for method in methods:
        try:
            for x in range(130):
                test_input = hashlib.sha512(str(x).encode()).hexdigest()
                hash_obj = hashlib.new(method)
                hash_obj.update(test_input.encode())
                if hash_obj.hexdigest() == enc_hash:
                    return x
        except:
            continue

def decrypt():
    decrypted_message = ""
    for ct_hash in ct:
        x = reverse_encrypt(ct_hash)
        original_ascii = (x - 20) % 130
        decrypted_message += chr(original_ascii)
    return decrypted_message

if __name__ == "__main__":
    flag = decrypt()
    print(flag)
```

    Flag: W1{are_you_trying_to_predict_randomness@_@}
 
### 4. DH
Tải file, giải nén và mở chall lên:
```python=
from Crypto.Util.number import isPrime, long_to_bytes, getPrime
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from random import randint
from hashlib import sha256


FLAG = b"W1{fake-flag}"

class DH:   

    def __init__(self):
        self.gen_params()

    def gen_params(self):
        self.r = getPrime(512)

        while True:
            self.q = getPrime(42)
            self.p = (2 * self.q * self.r) + 1
            if isPrime(self.p):
                break

        while True:
            self.h = getPrime(42)
            self.g = pow(self.h, 2 * self.r, self.p)
            if self.g != 1:
                break   

        self.a = randint(2, self.p - 2)
        self.b = randint(2, self.p - 2)

        self.A, self.B = pow(self.g, self.a, self.p), pow(self.g, self.b, self.p)
        self.ss = pow(self.A, self.b, self.p)

    def encrypt(self, flag_part):
        key = sha256(long_to_bytes(self.ss)).digest()[:16]
        cipher = AES.new(key, AES.MODE_ECB)
        ct = cipher.encrypt(pad(flag_part, 16)).hex()
        return f"encrypted = {ct}"

    def get_params(self):
        return f"p = {self.p}\ng = {self.g}\nA = {self.A}\nB = {self.B}"

def main():

    dh = DH()
    print(dh.get_params())
    print(dh.encrypt(FLAG))

if __name__ == "__main__":
    main()

p = 85013941328859365232686230728938372320812319905627686919070637645614632817039920673725615375841158719310596592903101914818137738460649589340349796188816568005092757847
g = 20033344683527080232439150682925185454003164954955126339094967675384779782733210350757021743656898625398860187361281262413493941502725149445995471514781822892886669776
A = 76548721461171533747911417838852759206858825205673491250696441734297318615226024320798706656529038703728631231084155790148283919370554345818139818854112841655270107839
B = 2103083080159597422706551446020625757109756570951674830166998494220734179439318911618156966499109201221652320384817270671579741987575328177442670242481963924501204498
encrypted = "240e7b7678aaaa0dcbe06de7c5598a1ca0be7e2ae584bc7dfd2388cdb1d4fb6a37ceb94556757afc293999cbe5a5a2dbb4071ebf6cfd4332088555f9b2de1922"
```

 
#### Phân tích:
Trong class DH, có:
```python=
def gen_params(self):
        self.r = getPrime(512)

        while True:
            self.q = getPrime(42)
            self.p = (2 * self.q * self.r) + 1
            if isPrime(self.p):
                break

        while True:
            self.h = getPrime(42)
            self.g = pow(self.h, 2 * self.r, self.p)
            if self.g != 1:
                break   

        self.a = randint(2, self.p - 2)
        self.b = randint(2, self.p - 2)

        self.A, self.B = pow(self.g, self.a, self.p), pow(self.g, self.b, self.p)
        self.ss = pow(self.A, self.b, self.p)
```

Hàm `gen_params` tạo một bộ khóa Diffie-Hellman.
```python=
def get_params(self):
        return f"p = {self.p}\ng = {self.g}\nA = {self.A}\nB = {self.B}"
```

Khi lấy hàm `get_params` thì sẽ trả về các giá trị p, g, A, B.
```python=
def encrypt(self, flag_part):
        key = sha256(long_to_bytes(self.ss)).digest()[:16]
        cipher = AES.new(key, AES.MODE_ECB)
        ct = cipher.encrypt(pad(flag_part, 16)).hex()
        return f"encrypted = {ct}"
```
Ngoài ra, còn có hàm `encrypt` lấy giá trị `ss` từ hàm `gen_params` để tạo key cho thuật toán mã hóa AES chế độ ECB, cuối cùng dùng nó để mã hóa `FLAG`.

#### Giải:
Để tìm ra `FLAG` thì ta cần lấy được `key` để giải mã `cipher`
```python=
key = sha256(long_to_bytes(ss)).digest()[:16]
cipher = AES.new(key, AES.MODE_ECB)
flag = cipher.decrypt(encrypted)
```
Do đó, ta phải tìm ra giá trị `ss` để tạo được `key`. Ta có các phương trình sau:
$A≡g^a$ (mod p); $B≡g^b$ $⇒ss≡A^b≡(g^a )^b≡g^{ab}≡(g^b )^a≡B^a$ (mod p)
Ta đã có p, g, A, vậy ta chỉ cần có thêm b là có thể tìm ra `ss`. Tới đây, tôi sử dụng thuật toán Pohlig-Hellman để tìm b qua công thức $B≡g^b$ (mod p) bằng công cụ Alpetron.

 ![image](https://hackmd.io/_uploads/B1qrVPIIJg.png)

Set k = 0, lấy được b = exp và tính được `ss`. Sau đây là script cho thử thách:
 ```python=
from Crypto.Util.number import long_to_bytes
from Crypto.Cipher import AES
from hashlib import sha256

p = 85013941328859365232686230728938372320812319905627686919070637645614632817039920673725615375841158719310596592903101914818137738460649589340349796188816568005092757847
g = 20033344683527080232439150682925185454003164954955126339094967675384779782733210350757021743656898625398860187361281262413493941502725149445995471514781822892886669776
A = 76548721461171533747911417838852759206858825205673491250696441734297318615226024320798706656529038703728631231084155790148283919370554345818139818854112841655270107839
B = 2103083080159597422706551446020625757109756570951674830166998494220734179439318911618156966499109201221652320384817270671579741987575328177442670242481963924501204498
encrypted = "240e7b7678aaaa0dcbe06de7c5598a1ca0be7e2ae584bc7dfd2388cdb1d4fb6a37ceb94556757afc293999cbe5a5a2dbb4071ebf6cfd4332088555f9b2de1922"
encrypted = bytes.fromhex(encrypted)
b = 2891401095038
ss = pow(A, b, p)

key = sha256(long_to_bytes(ss)).digest()[:16]
cipher = AES.new(key, AES.MODE_ECB)
flag = cipher.decrypt(encrypted)
print(flag.decode())
```
    Flag: W1{so_you_know_about_the_Diffie-Hellman-key_exchange}
   
## Reverse
### 5. GiacMoTrua2
Tải file về và phiên dịch mã giả bằng IDA, ta có:

 ![image](https://hackmd.io/_uploads/rymjEDIIJl.png)

Chương trình yêu cầu ta nhập một chuỗi bí mật và đem chuỗi đó so sánh với flag sau khi qua hàm `Lookthis` được mã hóa thành: "W1{live_speels_a5_NoOn_4v4ry_emit!}"

![image](https://hackmd.io/_uploads/BJY7rwIIkx.png)
![image](https://hackmd.io/_uploads/BJ37rvI81e.png)

Hàm `swap` dùng để đổi hai kí tự cho nhau. Hàm `Lookthis` có 3 vòng lặp riêng biệt trên từng khoảng với nhiệm vụ đổi ký tự tương ứng. Như vậy, ta chỉ cần viết script đảo ngược lại và đổi ký tự thì sẽ cho ra được `flag` ban đầu:
 ```python=
def reverse_lookthis(flag):

    for k in range(32, 28, -1):
        if k < (61 - k):
            flag[k], flag[61 - k] = flag[61 - k], flag[k]

    for j in range(13, 7, -1):
        if j < (21 - j):
            flag[j], flag[21 - j] = flag[21 - j], flag[j]

    for i in range(6, 2, -1):
        if i < (9 - i):
            flag[i], flag[9 - i] = flag[9 - i], flag[i]

    return flag


fake_flag = [ord(c) for c in "W1{live_speels_a5_NoOn_4v4ry_emit!}"]
real_flag = reverse_lookthis(fake_flag)

flag = "".join(chr(c) for c in real_flag)
print(flag)

```
    Flag: W1{evil_sleeps_a5_NoOn_4v4ry_time!}
 
### 6. Easy Flag Checker
Tải file và phiên dịch mã giả bằng IDA, có:

 ![image](https://hackmd.io/_uploads/S1iYSwI8kg.png)

Qua hàm main, chương trình yêu cầu ta nhập một chuỗi 20 ký tự, sau đó thực hiện vòng lặp kiểm tra với mỗi ký tự thứ 4*i+32 XOR với 0x38. Sau đó lấy ký tự đó so sánh ngay với `dword_4020` thứ i. Chúng ta sẽ truy cập thử vào `dword_4020`:

 ![image](https://hackmd.io/_uploads/rkrcrvULJx.png)

Ở đây, các giá trị trong `unk_4020` là các byte đã được mã hóa. Do đó, ta sẽ lấy các giá trị khác 0 để thực hiện phép XOR và nối lại tạo thành flag.
Sau đây là script cho thử thách:
 ```python=
def reverse_lookthis(flag):

    for k in range(32, 28, -1):
        if k < (61 - k):
            flag[k], flag[61 - k] = flag[61 - k], flag[k]

    for j in range(13, 7, -1):
        if j < (21 - j):
            flag[j], flag[21 - j] = flag[21 - j], flag[j]

    for i in range(6, 2, -1):
        if i < (9 - i):
            flag[i], flag[9 - i] = flag[9 - i], flag[i]

    return flag


fake_flag = [ord(c) for c in "W1{live_speels_a5_NoOn_4v4ry_emit!}"]
real_flag = reverse_lookthis(fake_flag)

flag = "".join(chr(c) for c in real_flag)
print(flag)
```

    Flag: W1{v3ry_34sy_r1gh7?}
 
### 7. GiacMoTrua1
Ta thấy tệp thử thách có định dạng .pyc. Sau khi thử tìm kiếm thì tôi biết được đây là một tệp đầu ra được biên dịch được tạo từ mã nguồn được viết bằng ngôn ngữ lập trình Python. Do đó tôi sử dụng PyLingual – một công cụ online để dịch ngược và khôi phục mã nguồn Python:
 
 ![image](https://hackmd.io/_uploads/S1zzUDULkg.png)

Tải file python này về, ta nhận được thử thách như sau:
 ```python=
dic = [0] * 85
dic[0] = 33
dic[1] = 35
dic[2] = 36
dic[3] = 37
dic[4] = 38
dic[5] = 40
dic[6] = 41
dic[7] = 42
dic[8] = 43
dic[9] = 44
dic[10] = 45
dic[11] = 46
dic[12] = 47
dic[13] = 48
dic[14] = 49
dic[15] = 50
dic[16] = 51
dic[17] = 52
dic[18] = 53
dic[19] = 54
dic[20] = 55
dic[21] = 56
dic[22] = 57
dic[23] = 58
dic[24] = 59
dic[25] = 60
dic[26] = 61
dic[27] = 62
dic[28] = 63
dic[29] = 64
dic[30] = 65
dic[31] = 66
dic[32] = 67
dic[33] = 68
dic[34] = 69
dic[35] = 70
dic[36] = 71
dic[37] = 72
dic[38] = 73
dic[39] = 74
dic[40] = 75
dic[41] = 76
dic[42] = 77
dic[43] = 78
dic[44] = 79
dic[45] = 80
dic[46] = 81
dic[47] = 82
dic[48] = 83
dic[49] = 84
dic[50] = 85
dic[51] = 86
dic[52] = 87
dic[53] = 88
dic[54] = 89
dic[55] = 90
dic[56] = 91
dic[57] = 97
dic[58] = 98
dic[59] = 99
dic[60] = 100
dic[61] = 101
dic[62] = 102
dic[63] = 103
dic[64] = 104
dic[65] = 105
dic[66] = 106
dic[67] = 107
dic[68] = 108
dic[69] = 109
dic[70] = 110
dic[71] = 111
dic[72] = 112
dic[73] = 113
dic[74] = 114
dic[75] = 115
dic[76] = 116
dic[77] = 117
dic[78] = 118
dic[79] = 119
dic[80] = 120
dic[81] = 121
dic[82] = 122
dic[83] = 123
dic[84] = 125
flag = input("Let me help you check your flag: ")
length = len(flag)
ans = [0] * length * 2
for i in range(length):
    ans[i] = dic[ord(flag[i]) ^ 112]
for i in range(length, length * 2):
    ans[i] = ans[i - length]
fin = ""
for i in range((23 * length + 16) % length, (23 * length + 16) % length + length):
    fin += chr(ans[i])
if fin == "R8Abq,R&;j%R6;kiiR%hR@k6iy0Ji.[k!8R,kHR*i??":
    print("Rightttt!")
    print("Heyy you are really lovely, i promise!")
else:
    print("Think more....")
```
#### Phân tích:
Flag được nhập vào và qua một số bước biến đổi trở thành một đoạn mã hóa cho trước là: `“R8Abq,R&;j%R6;kiiR%hR@k6iy0Ji.[k!8R,kHR*i??”`
Đầu tiên, chúng ta lấy từng giá trị của `flag` dạng thập phân ở hệ Unicode để XOR với 112 rồi lấy đó làm chỉ số để tra bảng `dic` và gán vào dãy `ans`, tiếp theo lấy `ans` thêm vào sau để tạo thành 1 dãy có độ dài gấp đôi `flag`.
 ```python=
for i in range(length):
    ans[i] = dic[ord(flag[i]) ^ 112]
for i in range(length, length * 2):
    ans[i] = ans[i - length]
```
Sau đó lập thành dãy `fin` mà ta đã biết trước bằng cách lấy i trong khoảng từ $(23*length+16)$  % $length$ tới $(23*length+16)$  % $length+length$ và lấy đó làm chỉ số với `ans` để chuyển nó về dạng Unicode và thêm vào `fin`.
```python=
fin = ""
for i in range((23 * length + 16) % length, (23 * length + 16) % length + length):
    fin += chr(ans[i])
```
 
#### Giải:
Nhận thấy, `fin` có độ dài bằng length và lớn hơn 16 nên:
$23×length+16≡16$ (mod length)
Do đó, `ans` sẽ được tính từ chỉ số 16 của `fin` tăng dần và quay ngược lên 15.
Sau đó, ta phải đảo ngược bảng giá trị `dic` cùng với việc thực hiện phép XOR với 112 để truy ngược lại giá trị ban đầu của flag.
Đây là script cho thử thách:
 ```python=
dic = [0] * 85
dic[0] = 33
dic[1] = 35
dic[2] = 36
dic[3] = 37
dic[4] = 38
dic[5] = 40
dic[6] = 41
dic[7] = 42
dic[8] = 43
dic[9] = 44
dic[10] = 45
dic[11] = 46
dic[12] = 47
dic[13] = 48
dic[14] = 49
dic[15] = 50
dic[16] = 51
dic[17] = 52
dic[18] = 53
dic[19] = 54
dic[20] = 55
dic[21] = 56
dic[22] = 57
dic[23] = 58
dic[24] = 59
dic[25] = 60
dic[26] = 61
dic[27] = 62
dic[28] = 63
dic[29] = 64
dic[30] = 65
dic[31] = 66
dic[32] = 67
dic[33] = 68
dic[34] = 69
dic[35] = 70
dic[36] = 71
dic[37] = 72
dic[38] = 73
dic[39] = 74
dic[40] = 75
dic[41] = 76
dic[42] = 77
dic[43] = 78
dic[44] = 79
dic[45] = 80
dic[46] = 81
dic[47] = 82
dic[48] = 83
dic[49] = 84
dic[50] = 85
dic[51] = 86
dic[52] = 87
dic[53] = 88
dic[54] = 89
dic[55] = 90
dic[56] = 91
dic[57] = 97
dic[58] = 98
dic[59] = 99
dic[60] = 100
dic[61] = 101
dic[62] = 102
dic[63] = 103
dic[64] = 104
dic[65] = 105
dic[66] = 106
dic[67] = 107
dic[68] = 108
dic[69] = 109
dic[70] = 110
dic[71] = 111
dic[72] = 112
dic[73] = 113
dic[74] = 114
dic[75] = 115
dic[76] = 116
dic[77] = 117
dic[78] = 118
dic[79] = 119
dic[80] = 120
dic[81] = 121
dic[82] = 122
dic[83] = 123
dic[84] = 125
fin = "R8Abq,R&;j%R6;kiiR%hR@k6iy0Ji.[k!8R,kHR*i??"

length = len(fin)

ans = [ord(fin[i - 16]) for i in range(length)]

reverse_dic = {v: i for i, v in enumerate(dic)}
flag = "".join(chr(reverse_dic[ans[i]] ^ 112) for i in range(length))

print(flag)
```
    Flag: W1{H3pe_y3U_w1ll_enJ9y_th2s_ch311_s0_m3c1!}
