---
# System prepended metadata

title: Crypto Hack

---

# Crypto Hack

## I.Introduction 

3 bài đầu chỉ cần cho chạy file là xong, không có gì đáng kể (bé tập gõ phím).

## II. General
### Encoding 
#### 1. ASCII

Hiểu về bảng ASCII và cách đổi.
Tìm flag = Cho chạy hàm là xong: 
```
lst = [99, 114, 121, 112, 116, 111, 123, 65, 83, 67, 73, 73, 95, 112, 114, 49, 110, 116, 52, 98, 108, 51, 125]

print(''.join(char(i) for i in lst))
```

- Flag : crypto{ASCII_pr1nt4bl3}

#### 2. Hex

Hiểu thêm về hex, cơ bản là chuyển ASCII thành string cs16: 
Tìm flag bằng chạy hàm: 
```
s = '63727970746f7b596f755f77696c6c5f62655f776f726b696e675f776974685f6865785f737472696e67735f615f6c6f747d'
res = bytes.fromhex(s).decode()
```
- Flag : crypto{You_will_be_working_with_hex_strings_a_lot}

#### 3. Base64 

Hiểu thêm về base64, đơn giản là nó chuyển dữ liệu nhị phân thành bảng chữ số với 64 kí tự. 

1 ký tự ăn 6 bit => 3 ký tự ăn 8 bytes 

Tìm flag = cách chạy hàm: 
```
import base64

s = '72bca9b68fc16ac7beeb8f849dca1d8a783e8acf9679bf9269f7bf'

res = bytes.fromhex(s)
print(base64.b64encode(res).decode())
```

#### 4. Bytes and big integers 

Đơn giản là chuyển text thành dạng số tính toán được:

Tìm flag = chạy hàm đơn giản: 
```
from Crypto.Util.number import *

num = 11515195063862318899931685488813747395775516287289682636499965282714637259206269
print(long_to_bytes(num).decode())
```
Flag là : crypto{3nc0d1n6_4ll_7h3_w4y_d0wn}

#### Encoding Challenge 

Đề cho 1 file sever, giải 100 decoding levels để nhận flag.

Ta modify file pwntools bằng cách dùng vòng lặp chạy từng level, mỗi level sử dụng cách decode riêng để giải cho đến khi qua 100 level và in ra flag: 
```
from pwn import * # pip install pwntools
import json
import codecs
import base64
from Crypto.Util.number import long_to_bytes, bytes_to_long

r = remote('socket.cryptohack.org', 13377, level = 'debug')

def json_recv(): #nhan du lieu tu sever 
    line = r.recvline()
    return json.loads(line.decode())

def json_send(hsh): # gui du lieu den sever 
    request = json.dumps(hsh).encode()
    r.sendline(request)

for i in range (100):
    received = json_recv()
    decoded = 0 
    word = received['encoded']
    encoding = received['type']
    if 'flag' in received:
        print("Flag:", received["flag"])
        break
    if encoding == "base64":
        decoded = base64.b64decode(word).decode()    
    elif encoding == "hex":
        decoded = bytes.fromhex(word).decode()
    elif encoding == "rot13":
        decoded = codecs.decode(word, 'rot_13')
    elif encoding == "bigint":
        decoded = long_to_bytes(int(word, 16)).decode()
    elif encoding == "utf-8":
        decoded = ''.join(chr(i) for i in word)
# lap xu ly cac du lieu tu level

    to_send = {
        "decoded": decoded
    }
    json_send(to_send)

json_recv()
```

flag là : crypto{3nc0d3_d3c0d3_3nc0d3}

### XOR 
#### XOR Starter
XOR là toán tử bitwise trả về 0 nếu các bit giống nhau và trả về 1 nếu không giống nhau. Trong sách giáo khoa, toán tử XOR được ký hiệu là ⊕, nhưng trong hầu hết các thử thách và ngôn ngữ lập trình, bạn sẽ thấy dấu mũ ^được sử dụng thay thế.

Ex: 0110 ^ 1010 = 1100 
- Check từng ptu, giống trả 0 khác trả 1

Tìm flag: 
```
from pwn import *

s = 'label'
re = []
for i in s:
    re.append(ord(i)^13)
print(''.join(chr(i) for i in re))
```
flag : crypto{aloha}

#### XOR properties

Có 6 thuộc tính cần chú ý: 

- Commutative: A ⊕ B = B ⊕ A
- Associative: A ⊕ (B ⊕ C) = (A ⊕ B) ⊕ C
- Identity: A ⊕ 0 = A
- Self-Inverse: A ⊕ A = 0
(*Nhìn quen nhỉ)

Breakdown:
- Commutaitive: chứng tỏ thứ tự ko quan trọng. 
- Associative: không quan tâm ngoặc 
- Identity: Xor với 0 vẫn trả lại A(0 là phần tử đơn vị)
- Self - Inverse: a xor a thành 0 

Tìm flag: áp dụng công thức cho sẵn là ra:
```
keyfinal = bytes.fromhex('04ee9855208a2cd59091d04767ae47963170d1660df7f56f5faf')
key1 = bytes.fromhex('a6c8b6733c9b22de7bc0253266a3867df55acde8635e19c73313')
key23 = bytes.fromhex(c1545756687e7573db23aa1c3452a098b71a7fbf0fddddde5fc1)
print(xor(keyfinal, key1, key23))
```
flag : crypto{x0r_i5_ass0c1at1v3}
#### Favorite byte 

Tìm flag: Câu này e chỉ biết bruteforce 255 giá trị có thể có từ 8 bits :v 
```
from pwn import *
key = bytes.fromhex('73626960647f6b206821204f21254f7d694f7624662065622127234f726927756d').decode()
for i in range (256):
    print(xor(key, i).decode())
```
flag : crypto{0x10_15_my_f4v0ur173_by7e}

#### You either know, XOR you don't
Tìm flag: key của bài luôn có dạng crypto{...} nên từ kết quả là flag ^ key, ta lấy kết quả ^ crypto{ từ đó giải mã được 1 phần của key(câu này độ dài của key đúng bằng crypto{ lun :v.
- Code 1:
```
from pwn import *
key = bytes.fromhex('0e0b213f26041e480b26217f27342e175d0e070a3c5b103e2526217f27342e175d0e077e263451150104').decode()
print(xor(key,'crypto{'))
```
-> Lúc này kết quả trả về là: b'myXORke+y_Q\x0bHOMe$~seG8bGURN\x04DFWg)a|\x1dTM!an\x7f'. Thây được hint key là myXORkey ta chỉ cần lắp lại key vào dòng cuối đoạn code là được flag.
- Code 2:
```
from pwn import *
key = bytes.fromhex('0e0b213f26041e480b26217f27342e175d0e070a3c5b103e2526217f27342e175d0e077e263451150104').decode()
print(xor(key,'myXORkey').decode())
```
flag là: crypto{1f_y0u_Kn0w_En0uGH_y0u_Kn0w_1t_4ll}

#### Lemur XOR 

Để giải quyết câu này trước tiên cần cài đặt thư viện pillow để xứ lý ảnh. 

Tìm flag: 
```
from PIL import Image #Cai dat thu vien pillow de lam viec voi anh
from pwn import xor 

def solution (flag, lemur, output): 

    # dua 2 anh vao chuong trinh de lam viec 
    anh1 = Image.open(flag).convert('RGB') 
    anh2 = Image.open(lemur).convert('RGB')

    # check xem neu kich thuoc 2 anh khong bang nhau thi cho cut 
    if anh1.size != anh2.size: 
        raise ValueError('2 anh co kich thuoc khac nhau')

    # tao anh du lieu dau ra 
    res_img = Image.new('RGB', anh1.size) #tao file anh voi kich thuoc bang 2 anh bai cho
    res_img_pixel = res_img.load() #ham load() de thao tac thay doi tren tung pixel cua anh

    # chay vong lap tung tao tung pixel cua anh ket qua bang cach xor lan luot tung pixel cua anh1 va anh2
    for x in range (anh1.width):
        for y in range (anh1.height):      
            #ta lay gia tri red green blue cua tung pixel anh 1 va 2 de chuan bi xor chung voi nhau 
            
            r1, g1, b1 = anh1.getpixel((x, y))
            r2, g2, b2 = anh2.getpixel((x,y)) 
            
            # ta xor từng gia tri cua pixel lai de duoc gia tri pixel cua anh dau ra
            r_res = xor(bytes([r1]), bytes([r2]))[0]
            g_res = xor(bytes([g1]), bytes([g2]))[0]
            b_res = xor(bytes([b1]), bytes([b2]))[0]
            #Dac biet, vi gia tri cua xor la 1 chuoi byte, vi vay nen de dat duoc gia tri rgb cua pixel von la so nguyen, ta phai lay byte dau tien cua chuoi byte bang cach them [0] vao sau cung vi chuoi chi co 1 byte 

            res_img_pixel[x, y] = (r_res, g_res, b_res) #tao 1 pixel cua anh dau ra
    # luu anh voi ten la:
    res_img.save(output)
    print('anh da duoc luu den {output}')

solution('flag.png', 'lemur.png','output.png') 
``` 
flag: Được nằm trong ảnh đầu ta sau khi chạy xong chương trình: 
![output](https://hackmd.io/_uploads/HJd1naoD1l.png)

### Mathematic 
#### Greatest Common Divisor 
Đoạn này giới thiệu về ucln hay gcd của a, b và tính chất của nó. Đồng thời ta tìm hiểu về thuật toán Euclid 

Để giải bài tập: ta có 2 cách 

Code 1 là dùng hàm gcd(): 
```
import math 
a=66528
b=52920
print(math.gcd(a,b))
```

Code 2 là dùng thuật toán Euclid: 
```
def euclid(a, b): 
    while b > 0: 
        r = a % b
        a = b
        b = r
    return a

print(euclid(int(input()), int(input())))
```

Kết quả: 1512 

#### Extended GCD 

Ta hiểu thêm về phương trình: ax + by = d, với d là gcd(a, b), cùng vời đó là hàm Euclid mở rộng. 
Để tìm đáp án bài cho, ta sử dụng hàm Euclid mở rộng: 
```
def euclid(a, b): 
    if b == 0: 
        d = a
        x = 1
        y = 0

    x2, x1 = 1, 0 
    y2, y1 = 0, 1

    while b > 0: 
        q = a // b 
        r = a % b
        x = x2 - q*x1
        y = y2 - q*y1

        a = b 
        b = r 
        x2 = x1 
        x1 = x 
        y2 = y1 
        y1 = y 
    d = a 
    x = x2 
    y = y2 
    return d,x,y

print(euclid(int(input()), int(input())))
```
Đáp án của bài là: -8404 

#### Modular 1 

Basically phép chia dư...

```
a = 11 % 6
b = 8146798528947 % 17 

print (min(a,b))
```

đáp án là: 4

#### Modular 2 

Phần này giúp ta hiểu thêm về các định nghĩa của modular: 

- Đầu tiên là kiến thức về trường(Field): Được định nghĩa là 1 modulus p trong đó p là số nguyên tố(prime) thì các số nguyên mod p được xác định là 1 trường số Fp. Trường số hữu hạn Fp là tập hợp các số nguyên {0, 1, 2, ..., p -1 }. Trong trường này cả phép cộng và phép nhân đều tồn tại phần tử nghịch đảo.
-> a + b = 0 
-> Or a*b = 1 
- Nếu p không phải là nguyên tố thì tập số nguyên mod p là 1 vành (ring).
- Định lý nhỏ của Fermat được phát biểu: 
-> a^(p-1) đồng dư 1 mod p (Với điều kiện a không phải bội của p, p nguyên tố). 

Đáp án câu hỏi đề bài chỉ cần áp dụng định lý nhỏ của Fermat ta được kết quả là 1. 

#### Modular Inverting 

Nghịch đảo modular, nghịch đảo nhân được phát biểu: 

- Trường Fp, với tất cả phần tử g trong trường tồn tại 1 phần tử d sao cho g*d đồng dư với 1 mod p. 

Để tìm phần tử nghịch đảo, có 2 cách: 

- C1: nếu p nguyên tố, nghịch đảo của g mod p là g^(p - 2) mod p
- C2: dùng thuật toán Euclid mở rộng để tính x là nghịch đảo của g mod p và y là nghịch đảo của p mod g (điều kiện là g và p nguyên tố cùng nhau) 

-> Áp dụng 1 trong 2 cách, ta tính được nghịch đảo 3 mod 13 là 9.

### Data Format  
#### Privacy Enhanced Mail?

PEM là 1 định dạng phổ biến để truyền tải dữ liệu key, chứng chỉ hay các thông tin mật khác.

```
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKC... (a whole bunch of base64)
-----END RSA PUBLIC KEY-----
```
* Điểm quan trọng: 
-> Header và footer phải được viết đúng như số lượng dấu gạch ngang hay nội dung tiêu đề. 

Một số thuật ngữ: 
- ASN.1: 1 chuẩn để định nghĩa or mô tả 1 dữ liệu 1 cách trừu tượng. 
- DER: 1 cách để chuyển ASN.1 thành binary
- base64 sẽ chuyển chuỗi bi thành chuỗi ASCII. 

Đề bài muốn ta tìm dữ liệu private key ban đầu(private exponent hay d) từ dữ liệu được mã hóa RSA. Việc cần làm là đưa dữ liệu vào thuật toán trích xuất trong python được viết như sau: 

```
from Crypto.PublicKey import RSA #Cai dat thu vien Crypto.PublicKey de lam viec voi RSA 

with open('privacy_enhanced_mail.pem', 'r') as f: # doan nay se duoc giai thich o duoi


    pem_data = f.read() #dat bien de lam viec voi ciphertext 

key_data = RSA.import_key(pem_data) #Ham trich xuat ciphertext  

print(key_data.d) # trich xuat key mong muon, de bai yeu cau trich xuat key d(private exponent)
```

** Giải thích phần "with open('privacy_enhanced_mail.pem', 'r') as f": 

- with open(): từ khóa trong python, chức năng tự động đóng file khi làm việc xong kể cả nếu có lỗi mà không cần dùng hàm f.close() thủ công.
- Bên trong hàm open() là tên file và 'r' tức chỉ chế độ read only, không thể chỉnh sửa. 
- as f: đặt biến f đại diện cho file vừa mở. 

Đoạn số sau khi được giải mã: 
```
15682700288056331364787171045819973654991149949197959929860861228180021707316851924456205543665565810892674190059831330231436970914474774562714945620519144389785158908994181951348846017432506464163564960993784254153395406799101314760033445065193429592512349952020982932218524462341002102063435489318813316464511621736943938440710470694912336237680219746204595128959161800595216366237538296447335375818871952520026993102148328897083547184286493241191505953601668858941129790966909236941127851370202421135897091086763569884760099112291072056970636380417349019579768748054760104838790424708988260443926906673795975104689
```
#### CERtainly not

Ta hiểu thêm về chứng chỉ SSL. SSL là 1 phần quan trọng trong web hiện nay, nó giúp liên kết 1 khóa mã hóa với thông tin của tổ chức. 

Đề bìa bắt ta trích xuất modulus n của chứng chỉ RSA x509: 
- RSA x509 là chứng chỉ kết hớp x509 và RSA. Với RSA để mã hóa public key trong chứng chỉ và để ký và xác mingh Signature. x509 là chứng chỉ chứa các thông tin của tổ chức. 

Thuật toán trích xuất: 
```
from Crypto.PublicKey import RSA 

with open('2048b-rsa-example-cert.der', 'rb') as f: # 'rb' laà read as bytes 
    der_data = f.read() 

key_data = RSA.import_key(der_data) 

print(key_data.n)
```

modulus tìm được là :  22825373692019530804306212864609512775374171823993708516509897631547513634635856375624003737068034549047677999310941837454378829351398302382629658264078775456838626207507725494030600516872852306191255492926495965536379271875310457319107936020730050476235278671528265817571433919561175665096171189758406136453987966255236963782666066962654678464950075923060327358691356632908606498231755963567382339010985222623205586923466405809217426670333410014429905146941652293366212903733630083016398810887356019977409467374742266276267137547021576874204809506045914964491063393800499167416471949021995447722415959979785959569497

#### SSH Key 

Để tóm tắt phần này, ta hiểu rằng: 

Secure Shell Protocol(SSH) là 1 giao thức mạng sử dụng mật mã để thiết lập 1 kênh an toàn qua mạng không an toàn(kiểu như Internet). SSH cho phép chạy lệnh trên các máy chủ từ xa mà không lo về việc lộ thông tin hoặc mật khẩu. 

SSH sử dụng kiến trúc client-sever, nghĩa là máy chủ chạy dịch vụ SSH daemon luôn online. Người dùng sử dụng 1 SSH client để kết nối đến máy chủ. 

SSH xác thực bằng cặp private- public key: 
- Trên máy chủ: 1 bản sao của pub key của người dùng được lưu trữ để mã hóa. 
- Trên máy tính cá nhân: khóa riêng tư được lưu cục bộ. 

##### Qúa trình kết nối SSH (ví dụ đối với Dương)

Gỉa sử Dương muốn kết nối đến tài khoản 'duongdz' ở máy chủ 'nguoidz-sever'. Tại máy tính, dương chạy lệnh: 

```
ssh duongdz@nguoidz-sever 
``` 
1. Khởi tạo kết nối: 
- SSH client mở một kết nối tới máy chủ qua cổng 22, nơi dịch vụ SSH deamon listens. 

2. Thỏa thuận mã hóa: 
- 2 bên thống nhất thuật toán cipher được sd
- 1 session key được tạo bằng thuật toán trao đổi khóa Diffie-Hellman 

3. Xác thực bằng key: 
- Máy chủ được gửi 1 thông điệp ngẫu nhiên được mã hóa bằng public key của Dương
- Dương sẽ dùng private key của mình để decrypt thông điệp, sau đó gửi lại 1 hash của thông điệp cho máy chủ. 
- Máy chủ xác minh hash khớp, chứng tỏ Dương sở hữu private key đúng, nên cậu được xác thực 

4. Cấp quyền truy cập 
- Máy chủ cấp cho Dương 1 'shell' để chạy lệnh. 

##### Khóa SSH và định dạng 
1. Private key 
- Được lưu trên máy người dùng: 
VD: /home/duongdz/.ssh/id_rsa 
- Định dạng: PEM, dạng base64: 

2. Public key 
- Có định dạng khác khóa riêng tư 
- Khi public key có mặt trong file :'/home/bschneier/.ssh/authorized_keys', khóa riêng tư tương ứng có thể được sử dụng để xác thực với máy chủ. 

Quay lại với đề bài, ta chỉ cần trích modulus n từ public key của Người dơi(Bruce)

```
from Crypto.PublicKey import RSA 

with open('bruce_rsa.pub', 'r') as f: # 'rb' laà read as bytes 
    der_data = f.read() 

key_data = RSA.import_key(der_data) 

print(key_data.n)
``` 

kết quả: 3931406272922523448436194599820093016241472658151801552845094518579507815990600459669259603645261532927611152984942840889898756532060894857045175300145765800633499005451738872081381267004069865557395638550041114206143085403607234109293286336393552756893984605214352988705258638979454736514997314223669075900783806715398880310695945945147755132919037973889075191785977797861557228678159538882153544717797100401096435062359474129755625453831882490603560134477043235433202708948615234536984715872113343812760102812323180391544496030163653046931414723851374554873036582282389904838597668286543337426581680817796038711228401443244655162199302352017964997866677317161014083116730535875521286631858102768961098851209400973899393964931605067856005410998631842673030901078008408649613538143799959803685041566964514489809211962984534322348394428010908984318940411698961150731204316670646676976361958828528229837610795843145048243492909

#### Transparency 

Khi trình duyệt kết nối đến 1 website qua https, nó thực hiện handshake TLS. Phía sever sẽ gửi thông điệp 'SeverHello', bao gồm chứng chỉ TLS của sever. Trình duyệt xác minh chứng chỉ này dựa trên các yếu tố: 
- Tên chứng chỉ khớp tên miền 
- Chứng chỉ còn hạn 
- Chứng chỉ được ký bởi 1 CA đắng tin, qua 'chain of trust'.

Đối với task của bài cho là trích xuất các tham số trong file public key 'transparency.pem' để tìm subdomain của cryptohack.org có chứng chỉ TLS chứa tham số kia. Em thử trích modulus n và public exponent e để tìm nhưng kết quả khá là no hope vì dùng modulus n để tìm thì không thấy domain nào có certificate chứa modulus n trong public key tương ứng. 

Sau đó em dùng crt.sh để tìm subdomain bằng cách nhập 'cryptohack.org' vào thanh tìm kiếm. Kết quả là em tìm được domain: https://thetransparencyflagishere.cryptohack.org/

Domain có chứa flag, khi em so sánh chỉ số trong phần certificate của trang, em phát hiện ra ngoài public exponent ra thì modulus khác nhau hoàn toàn so với cái em tìm được ở public key :>.

## III. Symmetric Cipher


### How AES work

#### Keyed permutation

AES là loại mật mã khối, thực hiện thao tác 'hoán vị có khóa'(keyed permutation). Điều này tức là nó ánh xạ mọi block đầu vào có thể ra 1 block đầu ra duy nhất, với 1 khóa quyết định hoán vị nào sẽ được thực hiện. 

- 'Phần này ta sẽ làm việc với AES-128 là chủ yếu

Bằng cách sử dụng cùng 1 key, ta có thể đảo ngược hoán vị, ánh xạ khối đầu ra quay trở lại khối ban đầu. Một điều quan trọng là có 1 sự tương ứng 1 - 1 giữa khối đầu vào và khối đầu ra, nếu không ta sẽ không thể dựa vào ciphertext để decrypt lại thành plaintext đầu vào được.

Task của đề bài là tìm thuật ngữ toán học nào đồng nghĩa với tương ứng 1 - 1, thì đó sẽ là:" Song ánh - bijection". 

Song ánh(bijextion) nghĩa là: 
- Ánh xạ đơn ánh: không có 2 đầu vào khác nhau nào ánh xạ đến cùng 1 đầu ra 
- Mọi đầu ra khả dĩ đều được ánh xạ từ ít nhất 1 đầu vào. 

#### Resisting Bruteforce 

Nếu 1 mã hóa khối là an toàn, thì không thể nào kẻ tấn công phân biệt được đầu ra của AES với 1 chuỗi bit ngẫu nhiên, và không có cách nào để đảo ngược hoán vị ngoài bruteforce. Thuật toán mã hóa khối chỉ bị coi là 'bị phá' nếu ta tìm được cách bẻ khóa nó nhanh hơn bruteforce. 

Có vài cách đã được sử dụng để làm giảm tính hiệu quả của AES-128 nhưng không nhiều, nhưng vẫn tồn tại mối đe dọa đến với AES 128 và bài dành lời khuyên nên dùng AES-256. 

Task của bài là muốn ta điền cuộc tấn công single-key hiệu quả nhất nhắm tới AES thì đó là: biclique (attack).

#### Structure of AES 

AES thực hiện 1 số lượng lớn các phép toán mixing trên đầu vào. 

Ở mức độ tổng quát, AES-128 bắt đầu với bước 'key-schedule' sau đó chạy qua 10 vòng xử lý 1 state. Ở state đầu tiên chỉ có block plaintext mà ta muốn encrypt, được biểu diễn bằng 1 ma trận bytes 4x4. Trong suốt 10 vòng xử lý, state được liên tục biến đổi liên tục bới các phép biển đổi có thể đảo ngược. 

Tổng quan các giai đoạn mã hóa AES: 

1. KeyExpansion/Key schedule: 
- Từ 1 key 128 bit, đẻ ra 11 round key 128 bit: mỗi key được sử dụng tại mỗi bước AddRoundKey
2. Initial RoundKey addition 
- Add roundkey: mỗi byte của roundkey đầu tiên được XOR với mỗi byte của state ban đầu. 
3. Round - Giai đoạn này được lặp 10 lần, 9 vòng chính và 1 vòng cuối: 
- Subbytes: mỗi byte của state được thay thế bởi 1 byte khác dựa trên bảng S - box 
- ShiftRows: 3 dòng cuối của ma trận state hiện tại được dịch trái qua 1, 2 or 3 cột. 
- MixColumns: Nhân ma trận được thực hiện tại các cột của ma trận state, kết hợp 4 bytes trên các cột. Bước này được skip ở round cuối. 
- AddRoundKey: Mỗi byte của roundkey hiện tại được XOR với mỗi byte của state. 

** Ví dụ XOR roundkey: 
![image](https://hackmd.io/_uploads/Sy132tgdyl.png)
![image](https://hackmd.io/_uploads/By03hKxuJl.png)

Đề bài muốn ta viết hàm chuyển ma trận state trở lại thành chuỗi byte cũ trước khi bị chuyển thành ma trận. Ta bytes hóa từng row của ma trận xong ghép lại bằng ''.join và thêm b đằng trước để biến thành 1 chuỗi byte duy nhất: 
```
def bytes2matrix(text):
    """ Converts a 16-byte array into a 4x4 matrix.  """
    return [list(text[i:i+4]) for i in range(0, len(text), 4)]

def matrix2bytes(matrix):
    """ Converts a 4x4 matrix into a 16-byte array.  """
    return (b''.join(bytes(row) for row in matrix))

matrix = [
    [99, 114, 121, 112],
    [116, 111, 123, 105],
    [110, 109, 97, 116],
    [114, 105, 120, 125],
]

print(matrix2bytes(matrix))

```
flag nhận được là: crypto{inmatrix
#### Round key 

Mục tiêu chính của phase KeyExpansion đó là nó dùng key 16 byte của chúng ta để tạo ra 11 ma trận round key 4x4, tận dụng tối đa key gốc.

Bước 'Initial key addition', có 1 bước AddRoundKey. Bước này rất đơn giản, ta chỉ cần XOR state hiện tại với roundkey hiện tại. Bước addroundkey là rất quan trọng trong AES-128 

Task của đề bài là muốn ta XOR state và roundkey cho trước lại với nhau, sau đó chuyển ma trận mới có được thành chuỗi byte để lấy flag 

```
state = [
    [206, 243, 61, 34],
    [171, 11, 93, 31],
    [16, 200, 91, 108],
    [150, 3, 194, 51],
]

round_key = [
    [173, 129, 68, 82],
    [223, 100, 38, 109],
    [32, 189, 53, 8],
    [253, 48, 187, 78],
]


def add_round_key(s, k):
    for row in range (len(s)): 
        for col in range (len(s)): 
            s[row][col] = s[row][col] ^ k[row][col]
    return (matrix2bytes(s))
    
def matrix2bytes(s): 
    return b''.join(bytes(row) for row in s)


print(add_round_key(state, round_key))
```

flag nhận được: crypto{r0undk3y}

#### Confusion through Substitution 

Bước đầu tiên của mỗi vòng AES là Subbytes. Cơ bản là lấy mỗi byte của ma trận state thay bằng byte khác trong bảng Sbox. Gía trị của byte này tương ứng với index của byte thay thế.

Task của đề bài muốn ta biến đổi ma trận state hiện có với ma trận inverse s box cho sẵn. Để làm điều đó, ta chạy hàm thay thế từng phần tử byte của state với byte nằm ở vị trí index tương đương với giá trị của byte state đó: 

```
def sub_bytes(s, sbox=s_box):
    for row in range(len(s)): 
        for col in range(len(s)): 
            s[row][col] = sbox[s[row][col]]
    
    return b''.join(bytes(row) for row in s)

print(sub_bytes(state, sbox=inv_s_box))
```
flag nhận được là: crypto{l1n34rly}

#### Diffusion through permutation

Nếu phần thay đổi giá trị theo sbox là làm phi tuyến tính thì 2 phase Shiftrow và Mixcolumn sẽ làm nhiễu loạn ma trận state, khiến tất cả các byte bị ảnh hưởng. 

Shiftrow đơn giản là thay đổi vị trí của phần tử ma trận state bằng cách dịch 1 2 or 3 đơn vị bắt đầu từ hàng 2 lần lượt ... 

Mixcolumn phức tạp hơn. Nó thực hiện nhân ma trận trong trường GF(2^8) với mỗi cột với một ma trận cho trước. 

Task của phần này đơn giản là chỉ cần tạo hàm đảo ngược shiftrow, nhét state  vào thuật toán mixcolumn đảo ngược và shiftrow đảo ngược rồi chuyển về byte là xong: 
```
def shift_rows(s):
    s[0][1], s[1][1], s[2][1], s[3][1] = s[1][1], s[2][1], s[3][1], s[0][1]
    s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
    s[0][3], s[1][3], s[2][3], s[3][3] = s[3][3], s[0][3], s[1][3], s[2][3]


def inv_shift_rows(s):
    s[1][1], s[2][1], s[3][1], s[0][1] = s[0][1], s[1][1], s[2][1], s[3][1]
    s[2][2], s[3][2], s[0][2], s[1][2] = s[0][2], s[1][2], s[2][2], s[3][2]
    s[3][3], s[0][3], s[1][3], s[2][3] = s[0][3], s[1][3], s[2][3], s[3][3]
    return s

# learned from http://cs.ucsb.edu/~koc/cs178/projects/JT/aes.c
xtime = lambda a: (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1)


def mix_single_column(a):
    # see Sec 4.1.2 in The Design of Rijndael
    t = a[0] ^ a[1] ^ a[2] ^ a[3]
    u = a[0]
    a[0] ^= t ^ xtime(a[0] ^ a[1])
    a[1] ^= t ^ xtime(a[1] ^ a[2])
    a[2] ^= t ^ xtime(a[2] ^ a[3])
    a[3] ^= t ^ xtime(a[3] ^ u)


def mix_columns(s):
    for i in range(4):
        mix_single_column(s[i])


def inv_mix_columns(s):
    # see Sec 4.1.3 in The Design of Rijndael
    for i in range(4):
        u = xtime(xtime(s[i][0] ^ s[i][2]))
        v = xtime(xtime(s[i][1] ^ s[i][3]))
        s[i][0] ^= u
        s[i][1] ^= v
        s[i][2] ^= u
        s[i][3] ^= v

    mix_columns(s)
    


state = [
    [108, 106, 71, 86],
    [96, 62, 38, 72],
    [42, 184, 92, 209],
    [94, 79, 8, 54],
]

inv_mix_columns(state) 
print(state)
```
flag nhận được là: crypto{d1ffUs3R}
#### Bringing It All Together 

Sau khi đã tìm hiểu về những bước cơ bản trừ Key Schedule. Tại task này tìm hiểu cách để reverse quá trình mã hóa AES 
![image](https://hackmd.io/_uploads/H19lJGHdyg.png)

Đề bài yêu câu ta trình bày nốt đoạn code giải mã theo bảng trên, hoàn thiện đoạn code là ta có flag rồi: 
```
N_ROUNDS = 10

key        = b'\xc3,\\\xa6\xb5\x80^\x0c\xdb\x8d\xa5z*\xb6\xfe\\'
ciphertext = b'\xd1O\x14j\xa4+O\xb6\xa1\xc4\x08B)\x8f\x12\xdd'

xtime = lambda a: (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1)

s_box = (
    0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
    0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
    0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
    0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
    0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
    0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
    0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
    0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
    0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
    0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
    0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
    0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
    0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
    0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
    0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
    0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16,
)

inv_s_box = (
    0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
    0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
    0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
    0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
    0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
    0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
    0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
    0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
    0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
    0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
    0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
    0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
    0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
    0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
    0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
    0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D,
)


def mix_single_column(a): 
    t = a[0] ^ a[1] ^ a[3] ^ a[2]
    u = a[0]
    a[0] ^= t ^ xtime(a[0] ^ a[1]) 
    a[1] ^= t ^ xtime(a[1] ^ a[2])
    a[2] ^= t ^ xtime(a[2] ^ a[3])
    a[3] ^= t ^ xtime(a[3] ^ u)

def mixcolumn(s):
    for i in range (4): 
        mix_single_column(s[i])

def inv_mixcolumns (s):
    for i in range (4): 
        u = xtime(xtime(s[i][0] ^ s[i][2]))
        v = xtime(xtime(s[i][1] ^ s[i][3]))

        s[i][0] ^= u
        s[i][1] ^= v 
        s[i][2] ^= u 
        s[i][3] ^= v 
    mixcolumn(s)

def inv_shiftrow(s): 
    s[1][1], s[2][1], s[3][1], s[0][1] = s[0][1], s[1][1], s[2][1], s[3][1]
    s[2][2], s[3][2], s[0][2], s[1][2] = s[0][2], s[1][2], s[2][2], s[3][2]
    s[3][3], s[0][3], s[1][3], s[2][3] = s[0][3], s[1][3], s[2][3], s[3][3]
    return s

def inv_subbytes(s): 
    for row in range(4):
        for col in range (4): 
            s[row][col] = inv_s_box[s[row][col]]

def add_roundkey(s, k): 
    for row in range(4):
        for col in range(4): 
            s[row][col] ^= k[row][col]

def bytes2matrix(s): 
    return [list(s[i:i+4]) for i in range(0, len(s), 4)]

def matrix2bytes(s): 
    plaintext = ''
    for row in range (4): 
        for col in range(4): 
            plaintext += chr(s[row][col])
    return plaintext


def expand_key(master_key):
    """
    Expands and returns a list of key matrices for the given master_key.
    """

    # Round constants https://en.wikipedia.org/wiki/AES_key_schedule#Round_constants
    r_con = (
        0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40,
        0x80, 0x1B, 0x36, 0x6C, 0xD8, 0xAB, 0x4D, 0x9A,
        0x2F, 0x5E, 0xBC, 0x63, 0xC6, 0x97, 0x35, 0x6A,
        0xD4, 0xB3, 0x7D, 0xFA, 0xEF, 0xC5, 0x91, 0x39,
    )

    # Initialize round keys with raw key material.
    key_columns = bytes2matrix(master_key)
    iteration_size = len(master_key) // 4

    # Each iteration has exactly as many columns as the key material.
    i = 1
    while len(key_columns) < (N_ROUNDS + 1) * 4:
        # Copy previous word.
        word = list(key_columns[-1])

        # Perform schedule_core once every "row".
        if len(key_columns) % iteration_size == 0:
            # Circular shift.
            word.append(word.pop(0))
            # Map to S-BOX.
            word = [s_box[b] for b in word]
            # XOR with first byte of R-CON, since the others bytes of R-CON are 0.
            word[0] ^= r_con[i]
            i += 1
        elif len(master_key) == 32 and len(key_columns) % iteration_size == 4:
            # Run word through S-box in the fourth iteration when using a
            # 256-bit key.
            word = [s_box[b] for b in word]

        # XOR with equivalent word from previous iteration.
        word = bytes(i^j for i, j in zip(word, key_columns[-iteration_size]))
        key_columns.append(word)

    # Group key words in 4x4 byte matrices.
    return [key_columns[4*i : 4*(i+1)] for i in range(len(key_columns) // 4)]


def decrypt(key, ciphertext):
    round_keys = expand_key(key) # Remember to start from the last round key and work backwards through them when decrypting
    
    # Convert ciphertext to state matrix
    plain = bytes2matrix(ciphertext)
    # Initial add round key step
    add_roundkey(plain, round_keys[10])
    for i in range(N_ROUNDS - 1, 0, -1):
        # Do round
        inv_shiftrow(plain)
        inv_subbytes(plain)
        add_roundkey(plain, round_keys[i])
        inv_mixcolumns(plain)
    # Run final round (skips the InvMixColumns step)
    inv_shiftrow(plain)
    inv_subbytes(plain)
    add_roundkey(plain, round_keys[0])
    # Convert state matrix to plaintext
    plaintext = matrix2bytes(plain)
    return plaintext

# print(decrypt(key, ciphertext))
print(decrypt(key, ciphertext))
```
flag: crypto{MYAES128}

### Symmetric Starter  

#### Modes of operation starter 

Một chế độ hoạt động miêu tả cách sử dụng mật mã như AES trên 1 đoạn tin nhắn dài thay vì 1 khối. Mọi chế độ hoạt động đều có điểm yếu chí mạng nếu được sử dụng sai cách. 

Task của đề muốn ta tìm flag được decrypt bằng AES. Nhưng trước tiên ta nên làm rõ 1 số thứ: 

- Encryption mode (ECB): chia nhỏ plaintext thành tửng khối. Sau đó, mỗi khối được mã hóa riêng lẻ bằng khóa đã chọn bằng công thức: 
-> Ci = E(Pi, K)
![image](https://hackmd.io/_uploads/B1Pl8MIoyx.png)


- Nhược điểm là nếu có 2 plain giống nhau cùng được mã hóa thì ciphertech sẽ giống nhau.

Đề: 
```
from Crypto.Cipher import AES


KEY = ?
FLAG = ?


@chal.route('/block_cipher_starter/decrypt/<ciphertext>/')
def decrypt(ciphertext):
    ciphertext = bytes.fromhex(ciphertext)

    cipher = AES.new(KEY, AES.MODE_ECB)
    try:
        decrypted = cipher.decrypt(ciphertext)
    except ValueError as e:
        return {"error": str(e)}

    return {"plaintext": decrypted.hex()}


@chal.route('/block_cipher_starter/encrypt_flag/')
def encrypt_flag():
    cipher = AES.new(KEY, AES.MODE_ECB)
    encrypted = cipher.encrypt(FLAG.encode())

    return {"ciphertext": encrypted.hex()}
```

Để tìm flag, đầu tiên ta lấy ciphertext, sau đó chạy qua hàm decrypt, vì ta có key nên giải mã thành công cipher thành chuỗi hex plaintext, decode và ta nhận được flag: 
```
import requests 
import json 
url = "https://aes.cryptohack.org/block_cipher_starter/"
r = requests.get(url + "encrypt_flag/").json()
cipher = r["ciphertext"]
r = requests.get(url + "decrypt/" + cipher +'/').json()
print(bytes.fromhex(r['plaintext']))
```
flag: crypto{bl0ck_c1ph3r5_4r3_f457_!}
#### Password as Keys
Điều cần thiết là các khóa trong thuật toán khóa đối xứng là các byte ngẫu nhiên, thay vì mật khẩu hoặc dữ liệu có thể dự đoán khác. Các byte ngẫu nhiên phải được tạo bằng trình tạo số giả ngẫu nhiên an toàn về mặt mật mã (CSPRNG). Nếu các khóa có thể dự đoán theo bất kỳ cách nào, thì mức độ bảo mật của mã hóa sẽ giảm và kẻ tấn công có thể truy cập vào văn bản mã hóa để giải mã nó.

Chỉ vì một khóa trông giống như được tạo thành từ các byte ngẫu nhiên, không có nghĩa là nó nhất thiết phải như vậy. Trong trường hợp này, khóa đã được lấy từ một mật khẩu đơn giản bằng cách sử dụng hàm băm, khiến văn bản mã hóa có thể bị bẻ khóa.

Task này bắt ta tìm plaintext bằng cách truy password trong 1 list các từ. Để tìm password_key đúng, ta sẽ dùng bruteforce thử giải mã ciphertext với từng key cho đến khi tìm được plaintext chứa "crypto"
```
from Crypto.Cipher import AES
import hashlib
import random

def decrypt(ciphertext, password_hash):
    ciphertext = bytes.fromhex(ciphertext)
    key =(password_hash)

    cipher = AES.new(key, AES.MODE_ECB)
    try:
        decrypted = cipher.decrypt(ciphertext)
    except ValueError as e:
        return {"error": str(e)}

    return decrypted


with open('output alo alo.txt') as f: 
    keylist = [w.strip() for w in f.readlines()]


for i in keylist: 
    KEY = hashlib.md5(i.encode()).digest()
    decrypted = decrypt('c92b7734070205bdf6c0087a751466ec13ae15e6f1bcdd3f3a535ec0f4bbae66', KEY)
    
    
    if b'crypto' in decrypted:
        print(decrypted)

```
Ta nhét đống từ vào file output, sau đó dùng vòng lặp chạy mỗi từ trong nó: 
flag: crypto{k3y5__r__n07__p455w0rdz?}

### Block Ciphers 1 
#### ECB CBC WTF 

Task của bài này là tìm flag bằng cách giải mã khối cipher được ghép bởi 2 block mã hóa và 1 iv cho trước. Để hoàn thành ta đơn giản là trích iv block1 và block2 lần lượt 16 bytes liên tiếp trong chuỗi ciphertext. Sau đó ta giải mã bằng cách xor ngược lại theo công thức và ghép đáp án để được flag: 
![image](https://hackmd.io/_uploads/BJWan_UuJe.png)
- Đây là mô hình cách hoạt động của CBC, cụ thể lạ nó XOR Block plaintext đầu với IV, sau đó PlainBlock sau được XOR với CipherBlock trước. Khi giải mã thì ta làm ngược lại. 
```
import requests 
import json 

def xor(a, b):
    return bytes(x ^ y for x, y in zip(a,b))

url = "https://aes.cryptohack.org/ecbcbcwtf/"
r = requests.get(url + "encrypt_flag/").json()
ciphertext = r["ciphertext"]
iv = bytes.fromhex(ciphertext[:32])
print(iv)
print(ciphertext[32:64])
print(ciphertext[64:])
r = requests.get(url + "decrypt/" + ciphertext[32:] +'/').json()
D = bytes.fromhex(r["plaintext"])
print(D)
plain1 = xor((D[:16]), iv)
print((plain1))
plain2 = xor((D[16:]), (bytes.fromhex(ciphertext[32:64])))
print((plain2))
```
Sau khi hoàn thành, flag nhận được là: crypto{3cb_5uck5_4v01d_17_!!!!!}

#### ECB Oracle
Task này cho chỉ cho ta ciphertext từ 1 đoạn mã hóa plaintext được jeets hợp giữa 'dữ liệu đầu vào và Flag'. 

Để tìm flag, ta cần thực hiện: 
- Đầu tiên tìm size của flag bằng cách chỉnh input cho đến khi nào số byte tăng lên từ 32 lên 48 thì dừng lại. Sau đó ta nhận được size của flag là 25 bytes. 
- Vì mod ECB mã hóa mỗi khối riêng biệt, ta có thể khai thác cipher bằng cách bruteforce giải mã từng byte trong Flag bằng cách tạo string giả 31 bytes và bytes cuối cùng là bytes của flag, từ đó ta bruceforce lần lượt từng giá trị ASCII rồi tham chiếu nó đến ký tự tương đương được mã hóa của Flag để dần dần tìm được các ký tự trong flag. 

```
import requests 

flag = b'crypto{' # Đây là 7 bytes đầu tiên của flag

def output(bytes_str): # Đây là hàm trả về ciphertext
    url = 'https://aes.cryptohack.org/ecb_oracle/encrypt/'
    url = url + bytes_str.hex() + '/'
    r = requests.get(url)
    js = r.json()
    return bytes.fromhex(js['ciphertext'])

for i in range (7, 26): # BruceForce Loop 
    bytes_str = b'' #Tạo chuỗi bytes để làm việc 
    bytes_str +=  b'\x00' * (31 - i) #Ta tạo chuỗi byte giả có độ dài đầu tiên là 24 byte, mỗi khi tìm được 1 phần tử của flag, ta lùi lại 1 byte
    pattern = output(bytes_str)[:32] #Tạo tham chiếu, chỉ lấy 32 bytes vì ta chỉ làm việc với 2 block 
    bytes_str += flag # Ghép chuỗi bytes giải với flag để tiến hành bruceforce ký tự trong flag 
    for j in range(33, 128): # Chạy vòng lặp thử với các ký tự trong bộ mã ASCII 
        bytes_str = bytes_str[:31] # Lấy đúng 2 block - 1 byte
        bytes_str += bytes([j]) # Ghép ký tự vào chuỗi bytes để check 

        res = output(bytes_str)[:32] #Kết quả ciphertext với mỗi ký tự được thử 
        if res == pattern: # Nếu kết quả giống tham chiếu
            flag += bytes([j]) #flag được tìm thấy thêm 1 phần tử 
            print(flag)
            break
```
Flag ta tìm được là: crypto{p3n6u1n5_h473_3cb}
#### Flipping Cookie 

Task cho ta 1 cookie được mã hóa ghép chung với IV. Việc ta cần làm đó là lợi dụng mod CBC cùng với IV cho trước để điều chỉnh cookie sao cho nó có chứa: admin=True;... là ta đã tìm được flag rồi: 
- Đầu tiên, ta thấy rằng block đầu tiên được đưa vào mã hóa là: 'admin=False;expi', ta cần chuyển False thành True. 
- Khi đưa block mã hóa vào hàm giải mã kèm theo 1 IV. Ta nghĩ đến việc thay đổi IV đầu vào so với IV cho trước bằng cách XOR: Cụ thể, ta XOR liên tiếp: 'admin = True;[][][][][]' với 'admin=False;expi' với IV cho trước.
- Đưa block đầu tiên vào cookie, và khối XOR khi nãy vào IV. 
- Submit là ta đã được flag rồi. 
```
import requests

def xor(a, b):
    return bytes(x ^ y for x, y in zip(a, b))
url = "https://aes.cryptohack.org/flipping_cookie/get_cookie/"
r = requests.get(url).json()
ciphertext = bytes.fromhex(r["cookie"]) 

iv = ciphertext[:16] 
ciphertext_blocks = ciphertext[16:]  

original = b"admin=False;expi"
target = b"admin=True;     "

new_iv = xor(xor(iv, original), target)

new_iv_hex = new_iv.hex()
ciphertext_hex = ciphertext_blocks.hex()

check_url = f"https://aes.cryptohack.org/flipping_cookie/check_admin/{ciphertext_hex}/{new_iv_hex}/"
result = requests.get(check_url).json()

print(result)
```
Flag : crypto{4u7h3n71c4710n_15_3553n714l}
#### Lazy CBC 
Task cho 1 sever encrypt mắc 1 lỗi nghiêm trọng là: 'nó lấy Key CHÍNH LÀ IV'. Vì vậy để timg flag, ta đơn giản chỉ cần: 
- Đưa vào Receive 1 chuỗi hex 0000....00(16 byte không có gì). Sau đó ta nhận được chuỗi cipher với 2 block 16 bytes.
- Block1 có dạng: key^decrypt. Block2 có dạng:16bytes(00)^decrypt. Khi xor 2 block với nhau ta nhận được KEY rồi tìm flag thui. 
```
import requests

def xor(a, b):
    return bytes(x ^ y for x, y in zip(a, b))
url = "https://aes.cryptohack.org/lazy_cbc/get_flag/"
BLOCK = bytes.fromhex("4287f39b5b34bea3fe5aebc42a21284fee418331b289b1729b52c536bd92600d") #nhan duoc khi gui 16 byte 00 den sever
Key = xor(BLOCK[:16],BLOCK[16:]).hex()
r = requests.get(url + Key + "/").json()
print(bytes.fromhex(r["plaintext"]))
```
Flag: crypto{50m3_p30pl3_d0n7_7h1nk_IV_15_1mp0r74n7_?}
#### Triple DES 

Câu này sử dụng kiến thức Weak Key trong DES, với WeakKey nếu được sử dụng , ta mã hóa plaintext 2 lần bằng nó thì sẽ trả lại chính plaintext, bài này ta sử dụng weakkey: 0101010101010101FEFEFEFEFEFEFEFE(Ta có thể thử với nhiều Weak key khác được lấy từ Chat GPT để thử) để nhập vào hàm encrypt_flag, sau đó lấy cipher có được đem vào hàm mã hóa với weakkey 1 lần nữa là ta đã có flag rồi: 
```
import requests

def xor(a, b):
    return bytes(x ^ y for x, y in zip(a, b))
url = "https://aes.cryptohack.org/triple_des/"
weak_key_hex = "0101010101010101fefefefefefefefe" 
r = requests.get(url + "encrypt_flag/" + weak_key_hex).json()
cipher = r["ciphertext"]
r = requests.get(url + "encrypt/" + weak_key_hex + '/' + cipher).json()
print(bytes.fromhex(r["ciphertext"]))
```
Flag: crypto{n0t_4ll_k3ys_4r3_g00d_k3ys}
### Stream Cipher
#### Symmetry 
Một số chế độ mật mã khối, chẳng hạn như OFB, CTR hoặc CFB, biến mật mã khối thành mật mã luồng. Ý tưởng đằng sau mật mã luồng là tạo ra một dòng khóa giả ngẫu nhiên, sau đó được XOR với bản rõ. Một ưu điểm của mật mã dòng là chúng có thể hoạt động với văn bản gốc có độ dài tùy ý mà không cần đệm.
![image](https://hackmd.io/_uploads/BJbOLMIskl.png)

OFB là một chế độ mật mã khó hiểu, ngày nay không có lợi ích thực sự nào khi sử dụng nó that cho CTR. Thử thách này giới thiệu một tính chất bất thường của OFB.

Cụ thể, nó hoạt động bằng cách XOR Plainblock với Enc_IV, các PlainBlock sau được XOR với Enc_IV trước được mã hóa 1 lần nữa

Task này yêu cầu chúng ta tìm flag từ IV đã cho trước, để tìm flag, dựa vào bảng mô tả cách hoạt động của OFB, ta thực hiện các bước sau: 

- Ta lấy được IV chính là 16 bytes đầu tiên của ciphertext lấy được từ Encrypt Flag. Sau đó là 16 bytes plaintext block 1 và 2. 
- Dựa vào cách hoạt động của OFB, ta đưa IV và block1 vào hàm Encrypt, ta tìm được 16 bytes đầu tiên của flag 
- Tiếp theo, ta tìm Đầu vào XOR của block2 chính là encrypted IV, bằng cách đựa iv vào cả phần 2 phần của hàm Encrypt, ta lấy được khối cipher mà khi XOR nó với IV ta được encrypted iv. 
- Ta lấy block2 và encrypted IV đưa vào hàm encrypt để tìm block2 
- Khi này ta được flag là:crypto{0fb_15_5ymm37r1c4l_!!!11!. Nếu để ý khi đếm số bytes trong ciphertext nhận được đầu tiên, ta thấy nếu chia flag thành 2 khối 16 bytes thì sẽ thừa ra 1 bytes, chính vì thế ta đoán luôn được byte này sẽ là '}"'
```
import requests

def xor(a, b):
    return bytes(x ^ y for x, y in zip(a, b))
url = "https://aes.cryptohack.org/symmetry/"
r = requests.get(url + "encrypt_flag/").json()
ciphertext = r["ciphertext"]
iv = ciphertext[:32]
block1 = ciphertext[32:64]
block2 = ciphertext[64:96]
print(block2)
r = requests.get(url + "encrypt/" + block1 + '/' + iv + '/').json()
flag1 = bytes.fromhex(r["ciphertext"])
iv_2 = (requests.get(url + "encrypt/" + iv + '/' + iv + '/').json())["ciphertext"]
iv_2 = xor(bytes.fromhex(iv_2), bytes.fromhex(iv))
flag2 = (requests.get(url + "encrypt/" + block2 + '/' + iv_2.hex() + '/').json())["ciphertext"]
print(flag1 + bytes.fromhex(flag2))
```
Ta nhận được flag là: crypto{0fb_15_5ymm37r1c4l_!!!11!}
#### Bean Counter 
![image](https://hackmd.io/_uploads/S1zWPMUs1e.png)
- CRT - Mã hóa PlainBlock bằng cách XOR PlainBlock đầu với Nonce+Couter(0) được mã hóa, và PlainBlock sau được XOR với Nonce+(Couter n + 1)

Đề bài muốn ta giải mã ảnh dạng png đã được mã hóa. 

Khi đọc source code ta phát hiện ra rằng thực chất mỗi block của file flag đều được mã hóa bằng cùng 1 key - Điều này là hệ quả của việc hàm self.stup bị thiết lập sai . Nhiệm vụ của ta là tìm lại key sau đó dùng nó để giải mã lại ảnh là xong: 
- Đầu tiên ta cần biết rằng file PNG đều bắt đầu bằng: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52] - Trong đó có 8 bytes đầu là mặc định đối với file PNG và 8 bytes sau là mặc định của IHDR. Chính vì vậy, ta có thể dễ dàng đem chuỗi bytes này XOR với 16 bytes đầu của ciphertext là ta đã có key rồi. 
- Khi đã có key, ta chỉ cần XOR key với file ảnh được mã hóa và xuất ảnh ra là ta đã có được flag: 
```
def xor(block, keystream): 
    xored = [a^b for a, b in zip(block, keystream)]
    return bytes(xored)

cp = bytes.fromhex('b9bf...')
png_header = bytes([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52])
key = xor(png_header, cp[:len(png_header)])

data = xor(cp, key*(len(cp)//len(key)))
with open('bean.png','wb') as f: 
    f.write(data)
```

flag nhận được trong ảnh: ![bean](https://hackmd.io/_uploads/BJEEDsh_Je.png)
#### CRTime 
Phần mã hóa của chal gợi ý rằng phần Plaintext sẽ được nén kèm Flag trong lệnh 'Compress' của 'Zlib'. Ta hiểu rằng 'zlib.compress' sẽ nén các thành phần khác nhau của Flag và Plaintext riêng và phần chung theo 1 cách riêng. 
- Giả sử nếu Plaintext trùng với Flag 1 phần, thì lúc này nếu cộng thêm Plaintext 1 byte và làm nó trở nên khác với Flag, Số ký tự đầu ra của hàm mã hóa sẽ thay đổi theo chiều tăng lên so với việc nếu Plaintext chỉ chứa hoàn toàn phần trùng với Flag. 

Hiểu được điều này, ta biết rằng Flag sẽ bắt đầu bằng: 'crypto{' nên ta sẽ tiến hành thử từng ký tự cho đến khi ta được 1 bản đầu ra của hàm giải mã mà nó có số ký tự nhỏ hơn hoặc bằng số ký tự của đầu ra giải mã của 'Plaintext là 1 phần của Flag + Flag'.
> Trong quá trình thử, khi chạy đến cryto{CRIM thì ký tự sau không tìm được vì 1 lý do gì đó :v.  Ta đoán đây là ký tự E-CRIME. Thay lại vào code giải và code sẽ giải thành công từ đầu đến cuối :vvvvv

```
import requests
def ouput(send):
    send = send + '/'
    response = requests.get('https://aes.cryptohack.org/ctrime/encrypt/' + send)
    cipher = bytes.fromhex(response.json()['ciphertext'])
    return len(cipher)
flag = 'crypto{CRIME'
char = [chr(i) for i in range(32, 127)]

while '}' not in flag:
    base_length = ouput(flag.encode().hex())
    for i in (char):
        print(i)
        sending = flag +(i)
        check = ouput(sending.encode().hex())
        if check <= base_length:
            flag += (i)
            print(flag)
            break
        
print(flag)
```
Flag: crypto{CRIME_571ll_p4y5}
### Padding Attack 
#### Pad Thai 
Với đề bài cho ta IV và Cipher cùng với hàm check Padding, ta sử dụng Padding Oracle được triển khai như sau: 
- Đầu tiên ta lấy IV và các Block Cipher từ 'Encrypt'.
- Ta làm việc với 2 block 1, bắt đầu với block đầu tiên là block $IV$ và block $1$. Block sau cũng tương tự.
- Ta giải mã dần các bytes từ cuối về đầu của block $1$. Gía trị của bytes padding được tăng dần theo thứ tự các bytes. Tạo 1 chuỗi bytes 16 byte để chứa block $1$ được giải mã. 
- Ta tạo block $IV$ giả, ta biến đổi từng bytes từ đầu đến cuối của block này sao cho khi đem nó đi $\oplus$ với $D_Pi$ cho ra giá trị Padding mong muốn:
Giai đoạn đầu ta cần biến đổi byte cuối block $IV$ giả để byte cuối block $1$ trở thành $0x01$. Ta lấy: $C_{16}' = C_{16} \oplus Padvalue \oplus P_{32}$
Ta biết rằng bytes cuối của block 1 hay $P_{32}$ chưa được giải nên giờ giá trị của nó vẫn là 0x00, sau khi giải được bytes này thì bytes tương ứng $IV$ giả cũng sẽ được tính dễ dàng vì ta có đủ thành phần rồi. 
- Ta đoán bytes tại vị trí hiện tại của $IV$ giả đến khi nào cho ra được bytes tương ứng của Block 1 là 1 giá trị padding thỏa mãn, sau đó bytes đó sẽ được tính bằng:$C'(guess) \oplus C \oplus Padvalue = Pi$  
- Làm lần lượt cho đến khi giải được toàn bộ block, sau đó hoàn thiện được Message cũ để đưa vào hàm 'Check' để lấy flag thôi:
```
from pwn import *
import json 

def send_json(r, data):
    msg = json.dumps(data) + "\n"  
    r.send(msg.encode())           
    response = r.recvline().decode().strip()  
    return json.loads(response)

def check_padding(r, test):
    response = send_json(r, {"option" : "unpad", "ct" : test.hex()})
    return response["result"]

r = remote("socket.cryptohack.org", 13421)
welcome = r.recvline().decode()
print(welcome)

# Lay cipher
response = send_json(r, {"option" : "encrypt"}) 
iv_cipher= bytes.fromhex(response["ct"])
print(iv_cipher)

iv = iv_cipher[:16]
cipher_blocks = [iv_cipher[i: i + 16] for i in range(16,len(iv_cipher), 16)]
msg = b""

for block_idx in range(len(cipher_blocks)):
    block = cipher_blocks[block_idx]
    block_decrypted = bytearray(16)
    if block_idx == 0:
        pre_block = iv
    else:
        pre_block = cipher_blocks[block_idx - 1]

    for byte_idx in range(1, 17):
        pad_value = byte_idx
        mod_pre_block = bytearray(pre_block)
        for idx in range(1, byte_idx):
            mod_pre_block[-idx] ^= block_decrypted[-idx] ^ pad_value
        
        for guess in range(256):
            mod_pre_block[-byte_idx] = guess
            test = mod_pre_block + block
            if check_padding(r, test):
                block_decrypted[-byte_idx] = pad_value ^ pre_block[-byte_idx] ^ guess
                break

    msg += bytes(block_decrypted) 

#lay Flag 

response = send_json(r, {"option" : "check", "message" : msg.decode("ascii")})
print(response["flag"])
```
Flag: crypto{if_you_ask_enough_times_you_usually_get_what_you_want}
#### The Good, The Pad, The Ugly
Khác 1 chút so với Pad Thai, chal lần này có thêm bộ đếm Queries cùng với đó hàm check padding sẽ có 60% tỉ lệ nếu padding trả là False thì nó vẫn trả là True. Hiểu được điều này, ta chỉ cần biến đổi 1 chút code tấn công Padding Oracle như sau: 
- Vì 'Message' của đề bài cho là dạng Hex của 1 chuỗi 16 bytes, có nghĩa là nó có 32 bytes tồn tại dưới dạng "01234...def" - 16 ký tự. 
- Dựa vào đó, ta biết rằng công thức rút ra từ Padding Oracle Attack để tính $P_i$ là: 
$P_i = Padvalue \oplus Guess \oplus Cprev_i$
Vì $P_i$ được đặt trong giới hạn đúng 16 bytes, thì khi Brutefore $Guess$, chỉ có 16 bytes phù hợp trong số 255 giá trị do tính đơn ánh của nó, ta tiết kiệm được 1 số queries vô cùng lớn nếu hiểu được điều này.
- Vì hàm sẽ trả về kết quả sai 60% nếu False, nếu mỗi lần check padding ta check tầm 19 lần thì lúc này khả năng trả về kết quả sai giảm chỉ còn khoảng 0.06%. 

```
from pwn import *
import json 

def send_json(r, data):
    msg = json.dumps(data) + "\n"  
    r.send(msg.encode())           
    response = r.recvline().decode().strip()  
    return json.loads(response)

def check_padding(r, test):
    response = send_json(r, {"option" : "unpad", "ct" : test.hex()})
    return response["result"]
def check_true_padding(r, test):
    for i in range(19):
        if not check_padding(r, test):
            return False 
    return True 

r = remote("socket.cryptohack.org", 13421)
welcome = r.recvline().decode()
print(welcome)

# Lay cipher
response = send_json(r, {"option" : "encrypt"}) 
iv_cipher= bytes.fromhex(response["ct"])
print(iv_cipher)

iv = iv_cipher[:16]
cipher_blocks = [iv_cipher[i: i + 16] for i in range(16,len(iv_cipher), 16)]
msg = b""

for i in range(len(cipher_blocks)):
    block = cipher_blocks[i]
    block_decrypted = bytearray(16)
    if i == 0:
        pre_block = iv
    else:
        pre_block = cipher_blocks[i - 1]

    for j in range(1, 17):
        pad_value = j
        mod_pre_block = bytearray(pre_block)
        for idx in range(1, j):
            mod_pre_block[-idx] ^= block_decrypted[-idx] ^ pad_value
        proces = 0
        for guess in range(256):
            mod_pre_block[-j] = guess
            test = mod_pre_block + block
            if check_true_padding(r, test):
                block_decrypted[-j] = pad_value ^ pre_block[-j] ^ guess
                proces = 1
                break
        if proces == 0:
            print("Loi")
            break
    msg += bytes(block_decrypted) 

#lay Flag 

response = send_json(r, {"option" : "check", "message" : msg.decode("ascii")})
print(response["flag"])
``` 
Flag: crypto{even_a_faulty_oracle_leaks_all_information}
## IV. Mathematics
### Modular Math
#### Quadratic Residues 
Phần đấu nói chung giới thiệu về thặng dư bậc 2 và bất thặng dư bậc 2, ta cơ bản chú ý 2 điều: 

TRONG TRƯỜNG HỮU HẠN :
- Số nguyên x là thặng dư bậc 2 nếu tồn tại a sao cho a^2 đồng dư với x mod p. Nếu không thì số nguyên đó là bất thặng dư bậc 2. 
- Ngoài ra, nếu x là thặng dư bậc 2 trong 1 trường hữu hạn nào đó, luôn có 2 a. 
- Số các thặng dư bậc 2 chiếm 1 nửa số phần tử của Zp*
*More: ![image](https://hackmd.io/_uploads/HysS7OyY1l.png)

Task của đề bài là tìm căn bậc 2 của thặng dư bậc 2 xuất hiện trong list trong trường Fp với p = 29. Ta tìm được 2 giá trị là 8 và -8(hay 21 vì -8 đồng dư 29 - 8 mod p). Vậy số nhỏ hơn là 8 và Flag chính là 8. 
```
p = 29
lst = [14, 6, 11]
for i in range(1,p):
    if pow(i,2,p) in lst:
        print (i) 
```
#### Legrende Symbol 
Trước nói về kí hiệu Lagrende, ta cần đi qua 1 số tính chất của thặng dư và bất thặng dư bậc 2: 
- thặng dư * thặng dư = thặng dư (bậc 2)
- thặng dư * bất thặng dư = bất thặng dư (bậc 2)
- bất thặng dư * bất thặng dư = thặng dư (bậc 2)


** Để dễ nhớ hãy đặt thặng dư b2 là 1 và bất là -1. 

Legrende Symbol hay ký hiệu Lagrende: ![image](https://hackmd.io/_uploads/SJVVbYyYyg.png)

Tính chất: Với(a/p), ta có: 
- = 0, p|a
- = 1, a là thặng dư bậc 2
- = -1, a là bất thặng dư 

Task của đề bài muốn ta tìm thặng dư bậc 2 trong list số cho trước, sau đó tìm căn bậc 2 của chúng và trả về giá trị lớn nhất. Bằng gợi ý sử dụng định lý nhỏ của Fermat: 
- Với x là thặng dư bậc 2 mod p, p nguyên tố, có: 
a là căn 2 đồng dư với x^((p+1)/4) mod p, khi p đồng dư 3 mod 4. 
```
import multiprocessing

p = 101524035174539890485408575671085261788758965189060164484385690801466167356667036677932998889725476582421738788500738738503134356158197247473850273565349249573867251280253564698939768700489401960767007716413932851838937641880157263936985954881657889497583485535527613578457628399173971810541670838543309159139

ints = [
    25081841204695904475894082974192007718642931811040324543182130088804239047149283334700530600468528298920930150221871666297194395061462592781551275161695411167049544771049769000895119729307495913024360169904315078028798025169985966732789207320203861858234048872508633514498384390497048416012928086480326832803,
    45471765180330439060504647480621449634904192839383897212809808339619841633826534856109999027962620381874878086991125854247108359699799913776917227058286090426484548349388138935504299609200377899052716663351188664096302672712078508601311725863678223874157861163196340391008634419348573975841578359355931590555,
    17364140182001694956465593533200623738590196990236340894554145562517924989208719245429557645254953527658049246737589538280332010533027062477684237933221198639948938784244510469138826808187365678322547992099715229218615475923754896960363138890331502811292427146595752813297603265829581292183917027983351121325,
    14388109104985808487337749876058284426747816961971581447380608277949200244660381570568531129775053684256071819837294436069133592772543582735985855506250660938574234958754211349215293281645205354069970790155237033436065434572020652955666855773232074749487007626050323967496732359278657193580493324467258802863,
    4379499308310772821004090447650785095356643590411706358119239166662089428685562719233435615196994728767593223519226235062647670077854687031681041462632566890129595506430188602238753450337691441293042716909901692570971955078924699306873191983953501093343423248482960643055943413031768521782634679536276233318,
    85256449776780591202928235662805033201684571648990042997557084658000067050672130152734911919581661523957075992761662315262685030115255938352540032297113615687815976039390537716707854569980516690246592112936796917504034711418465442893323439490171095447109457355598873230115172636184525449905022174536414781771,
    50576597458517451578431293746926099486388286246142012476814190030935689430726042810458344828563913001012415702876199708216875020997112089693759638454900092580746638631062117961876611545851157613835724635005253792316142379239047654392970415343694657580353333217547079551304961116837545648785312490665576832987,
    96868738830341112368094632337476840272563704408573054404213766500407517251810212494515862176356916912627172280446141202661640191237336568731069327906100896178776245311689857997012187599140875912026589672629935267844696976980890380730867520071059572350667913710344648377601017758188404474812654737363275994871,
    4881261656846638800623549662943393234361061827128610120046315649707078244180313661063004390750821317096754282796876479695558644108492317407662131441224257537276274962372021273583478509416358764706098471849536036184924640593888902859441388472856822541452041181244337124767666161645827145408781917658423571721,
    18237936726367556664171427575475596460727369368246286138804284742124256700367133250078608537129877968287885457417957868580553371999414227484737603688992620953200143688061024092623556471053006464123205133894607923801371986027458274343737860395496260538663183193877539815179246700525865152165600985105257601565
]
def check_Qp(i): 
    
    return i if pow(i,(p-1) // 2, p) == 1 else None
if  __name__ == "__main__":
    with multiprocessing.Pool() as pool: 
        Qp = list(filter(None, pool.map(check_Qp, ints)))
    Sr = []
    for i in Qp: 
        Sr.append(pow(i, (p+1)//4, p))
    print (Sr)
```
Vì nếu sử dụng vòng lặp thông thường để check từng int trong ints 1 cách truyền thống thì sẽ rất lâu. Ta dùng 'multiprocessing', dùng toàn bộ CPU để đẩy nhanh quá trình. 
Flag: 93291799125366706806545638475797430512104976066103610269938025709952247020061090804870186195285998727680200979853848718589126765742550855954805290253592144209552123062161458584575060939481368210688629862036958857604707468372384278049741369153506182660264876115428251983455344219194133033177700490981696141526
#### Modular Square Root 

Tại phần này lưu ý kiến thức về thuật toán Tonelli-Shanks để tìm căn bậc 2 của thặng dư bậc 2 a mod p, cụ thể trong bài: 
```
def tonelli_shanks(a, p):
    """Giải x^2 ≡ a (mod p) bằng thuật toán Tonelli-Shanks."""
    if pow(a, (p - 1) // 2, p) != 1:
        return None  # Không có nghiệm

    # Phân tích p - 1 = q * 2^s
    s = 0
    q = p - 1
    while q % 2 == 0:
        q //= 2
        s += 1

    # Tìm số dư bậc không phải bậc hai z
    z = 2
    while pow(z, (p - 1) // 2, p) == 1:
        z += 1

    # Khởi tạo biến
    m = s
    c = pow(z, q, p)
    t = pow(a, q, p)
    r = pow(a, (q + 1) // 2, p)

    # Lặp tìm nghiệm
    while t != 1:
        # Tìm k nhỏ nhất sao cho t^(2^k) ≡ 1 (mod p)
        i = 0
        temp = t
        while temp != 1:
            temp = pow(temp, 2, p)
            i += 1
            if i == m:  # Dự phòng
                return None

        # Cập nhật biến
        b = pow(c, 2**(m - i - 1), p)
        r = (r * b) % p
        t = (t * b * b) % p
        c = (b * b) % p
        m = i

    return r  # Một trong hai nghiệm (nghiệm còn lại là p - r)
a = 8479994658316772151941616510097127087554541274812435112009425778595495359700244470400642403747058566807127814165396640215844192327900454116257979487432016769329970767046735091249898678088061634796559556704959846424131820416048436501387617211770124292793308079214153179977624440438616958575058361193975686620046439877308339989295604537867493683872778843921771307305602776398786978353866231661453376056771972069776398999013769588936194859344941268223184197231368887060609212875507518936172060702209557124430477137421847130682601666968691651447236917018634902407704797328509461854842432015009878011354022108661461024768
p = 30531851861994333252675935111487950694414332763909083514133769861350960895076504687261369815735742549428789138300843082086550059082835141454526618160634109969195486322015775943030060449557090064811940139431735209185996454739163555910726493597222646855506445602953689527405362207926990442391705014604777038685880527537489845359101552442292804398472642356609304810680731556542002301547846635101455995732584071355903010856718680732337369128498655255277003643669031694516851390505923416710601212618443109844041514942401969629158975457079026906304328749039997262960301209158175920051890620947063936347307238412281568760161    
print(tonelli_shanks(a, p))
```
* Có thể sử dụng thư viện sympy có hàm sqrt_mod được build sẵn: 
```
from sympy import * 
a = 8479994658316772151941616510097127087554541274812435112009425778595495359700244470400642403747058566807127814165396640215844192327900454116257979487432016769329970767046735091249898678088061634796559556704959846424131820416048436501387617211770124292793308079214153179977624440438616958575058361193975686620046439877308339989295604537867493683872778843921771307305602776398786978353866231661453376056771972069776398999013769588936194859344941268223184197231368887060609212875507518936172060702209557124430477137421847130682601666968691651447236917018634902407704797328509461854842432015009878011354022108661461024768
p = 30531851861994333252675935111487950694414332763909083514133769861350960895076504687261369815735742549428789138300843082086550059082835141454526618160634109969195486322015775943030060449557090064811940139431735209185996454739163555910726493597222646855506445602953689527405362207926990442391705014604777038685880527537489845359101552442292804398472642356609304810680731556542002301547846635101455995732584071355903010856718680732337369128498655255277003643669031694516851390505923416710601212618443109844041514942401969629158975457079026906304328749039997262960301209158175920051890620947063936347307238412281568760161
print(sqrt_mod(a, p))
```
Flag: 2362339307683048638327773298580489298932137505520500388338271052053734747862351779647314176817953359071871560041125289919247146074907151612762640868199621186559522068338032600991311882224016021222672243139362180461232646732465848840425458257930887856583379600967761738596782877851318489355679822813155123045705285112099448146426755110160002515592418850432103641815811071548456284263507805589445073657565381850521367969675699760755310784623577076440037747681760302434924932113640061738777601194622244192758024180853916244427254065441962557282572849162772740798989647948645207349737457445440405057156897508368531939120
#### Chinese Remainder Theorem

Đoạn này nói về định lý Phần Dư Trung Hoa, ta cần chú ý những điều sau: 
- Với ![image](https://hackmd.io/_uploads/BJ2GVYlYye.png)
Ta có: ![Screenshot 2025-02-05 132346](https://hackmd.io/_uploads/SkXN4tgKkx.png)
Nghiệm x chính là a mod N, với N = n^1 . n^2. ... .n^n

Task này đề bài bắt ta tìm nghiệm, ta đơn giản chỉ cần sử dụng lý thuyết phần dư trung hoa: 
![image](https://hackmd.io/_uploads/HJzdN5xFkg.png)

```
import math
def Du_Tau_Khua(ai, ni): 
	res = 0
	N = math.prod(ni)
	for i in range(len(ni)): 
		Ni = N // ni[i]
		Mi = pow(Ni, -1, ni[i])
		res += ai[i]*Ni*Mi
	return res % N
ai = [2, 3, 5]
ni = [5, 11, 17]
print(Du_Tau_Khua(ai, ni))
``` 
Flag: 872
### Brainteasers Part 1 
#### Successive Powers
Ta được cho 1 list chuỗi các lũy thừa liên tiếp của số nguyên x được mod p. Nhiệm vụ của ta là tìm x và p, với là số nguyên tố 3 chữ số. 

Ta cần biết chuỗi lũy thừa liên tiếp của số nguyên x theo modulo p được viết: 
- a, ax, a(x^2), ... (mod p)

Lúc này ta suy ra được 1 điều kiện lý tưởng để tìm p: 
- Với 3 phần tử liên tiếp của chuỗi a, b, c ta có: 
b^2 đồng dư a.c (mod p)
- Lúc này, ta đơn giản chọn ra 4 phần tử đầu tiên từ chuỗi cho trước, ta hoàn toàn có thể tính p: 
```
from math import gcd 
# Ta lấy 4 phần tử liên tiếp đầu tiên 
x1 = 588 
x2 = 665 
x3 = 216 
x4 = 113 

# Theo tính chất, ta có:
Kp = pow(x2, 2) - x1 * x3
Ep = pow(x3, 2) - x2 * x4
print(gcd(Kp, Ep))
```
Khi đã có được p, ta tiếp tục tìm x bằng cách: 
- Ta biết: 665 đồng dư 588 . x (mod p). Việc ta cần làm giờ là chỉ cần tìm nghịch đảo 588 mod p rồi nhân nó mới cả 2 về là ta dễ dàng có được x rồi:
```
x = (665 * pow(588, - 1, 919)) % 919
print(x)
```
Flag: crypto{919, 209}
#### Adrien's Signs 
Đề bài: 
```
from random import randint

a = 288260533169915
p = 1007621497415251

FLAG = b'crypto{????????????????????}'


def encrypt_flag(flag):
    ciphertext = []
    plaintext = ''.join([bin(i)[2:].zfill(8) for i in flag])
    for b in plaintext:
        e = randint(1, p)
        n = pow(a, e, p)
        if b == '1':
            ciphertext.append(n)
        else:
            n = -n % p
            ciphertext.append(n)
    return ciphertext


print(encrypt_flag(FLAG))
```

Flag trong bài được xử lý khi đưa vào hàm mã hóa như sau: 
- Khi đưa vào hàm, mỗi ký tự của flag được chuyển thành giá trị nhị phân, xẻ 0b và thêm 1 bit 0. 
- 1 vòng lặp được chạy xử lý từng ký tự trong chuỗi binary flag, nếu là '1' thì thay thế nó bằng (n = a^e mod p), nếu là '0' thì là (-n mod p). 

Để xử lý bài tập, trước hết ta biết rằng từ kiến thức đã học được phía trên, nếu check được giá trị x trong output bằng ký hiệu Legrende = 1 thì nghĩa là x là thặng dư bậc 2, nó sẽ không tồn tại là p - x' của x' mod p nào khác. Từ đó ta biết rằng nếu legrende trả về là - 1 thì đây chính là -n mod p. 

Ta giải lần lượt: 
```
res = [67594220461269, 501237540280788, 718316769824518, 296304224247167, 48290626940198, 30829701196032, 521453693392074, 840985324383794, 770420008897119, 745131486581197, 729163531979577, 334563813238599, 289746215495432, 538664937794468, 894085795317163, 983410189487558, 863330928724430, 996272871140947, 352175210511707, 306237700811584, 631393408838583, 589243747914057, 538776819034934, 365364592128161, 454970171810424, 986711310037393, 657756453404881, 388329936724352, 90991447679370, 714742162831112, 62293519842555, 653941126489711, 448552658212336, 970169071154259, 339472870407614, 406225588145372, 205721593331090, 926225022409823, 904451547059845, 789074084078342, 886420071481685, 796827329208633, 433047156347276, 21271315846750, 719248860593631, 534059295222748, 879864647580512, 918055794962142, 635545050939893, 319549343320339, 93008646178282, 926080110625306, 385476640825005, 483740420173050, 866208659796189, 883359067574584, 913405110264883, 898864873510337, 208598541987988, 23412800024088, 911541450703474, 57446699305445, 513296484586451, 180356843554043, 756391301483653, 823695939808936, 452898981558365, 383286682802447, 381394258915860, 385482809649632, 357950424436020, 212891024562585, 906036654538589, 706766032862393, 500658491083279, 134746243085697, 240386541491998, 850341345692155, 826490944132718, 329513332018620, 41046816597282, 396581286424992, 488863267297267, 92023040998362, 529684488438507, 925328511390026, 524897846090435, 413156582909097, 840524616502482, 325719016994120, 402494835113608, 145033960690364, 43932113323388, 683561775499473, 434510534220939, 92584300328516, 763767269974656, 289837041593468, 11468527450938, 628247946152943, 8844724571683, 813851806959975, 72001988637120, 875394575395153, 70667866716476, 75304931994100, 226809172374264, 767059176444181, 45462007920789, 472607315695803, 325973946551448, 64200767729194, 534886246409921, 950408390792175, 492288777130394, 226746605380806, 944479111810431, 776057001143579, 658971626589122, 231918349590349, 699710172246548, 122457405264610, 643115611310737, 999072890586878, 203230862786955, 348112034218733, 240143417330886, 927148962961842, 661569511006072, 190334725550806, 763365444730995, 516228913786395, 846501182194443, 741210200995504, 511935604454925, 687689993302203, 631038090127480, 961606522916414, 138550017953034, 932105540686829, 215285284639233, 772628158955819, 496858298527292, 730971468815108, 896733219370353, 967083685727881, 607660822695530, 650953466617730, 133773994258132, 623283311953090, 436380836970128, 237114930094468, 115451711811481, 674593269112948, 140400921371770, 659335660634071, 536749311958781, 854645598266824, 303305169095255, 91430489108219, 573739385205188, 400604977158702, 728593782212529, 807432219147040, 893541884126828, 183964371201281, 422680633277230, 218817645778789, 313025293025224, 657253930848472, 747562211812373, 83456701182914, 470417289614736, 641146659305859, 468130225316006, 46960547227850, 875638267674897, 662661765336441, 186533085001285, 743250648436106, 451414956181714, 527954145201673, 922589993405001, 242119479617901, 865476357142231, 988987578447349, 430198555146088, 477890180119931, 844464003254807, 503374203275928, 775374254241792, 346653210679737, 789242808338116, 48503976498612, 604300186163323, 475930096252359, 860836853339514, 994513691290102, 591343659366796, 944852018048514, 82396968629164, 152776642436549, 916070996204621, 305574094667054, 981194179562189, 126174175810273, 55636640522694, 44670495393401, 74724541586529, 988608465654705, 870533906709633, 374564052429787, 486493568142979, 469485372072295, 221153171135022, 289713227465073, 952450431038075, 107298466441025, 938262809228861, 253919870663003, 835790485199226, 655456538877798, 595464842927075, 191621819564547]
flag = ''
p = 1007621497415251

# Check n, -n bằng ký hiệu Legrende
for i in res: 
    check = pow(i, (p-1)//2, p) 
    if check == 1:
        flag += '1'
    else: 
        flag += '0'

result = []

# Giải mã ngược từ chuỗi nhị phân
for i in range (0, len(flag), 8): 
    result.append(chr(int(flag[i+1:i+8], 2)))
print(''.join(result))

```
** Goodnews: ta có thể rút gọn quá trình tính toàn bằng cách check từ số thứ 57 đổ đi trong output vì ta biết Flag sẽ bắt đầu bằng 'crypto{'. 
Flag: crypto{p4tterns_1n_re5idu3s}
#### Modular Binomials 
> Man this thing took me a night :/

Task cho ta 1 hệ phương trình modulo N, ta cần tìm q và p mà N = p*q

Ý tưởng chính ở đây chính là: Làm mất p ở 1 vế, cô lập q, tìm q bằng gcd sau đó là p. 

- Bằng cách mũ chéo c1 c2 với e2, e1, ta cân bằng mũ của 2 pt: 
c1^e2 = (2 . p + 3 . q)^(e1 . e2) mod n 
c2^e1 = (5 . p + 7 . q)^(e1 . e2) mod n 
- Sử dụng nhị thức Newton, ta chỉ còn 2 phần tử trong 2 phương trình trong hệ mỗi phần tử có bậc cao nhất:
c1^e2 =  (2p)^(e1 . e2) + (3q)^(e1 . e2) mod n 
c2^e1 =  (5p) ^(e1 . e2) + (7q)^(e1 . e2) mod n 
- Cả 2 pt nhân 2 vế với nghịch đảo hệ số p, sau đó trừ chúng cho nhau ta được 1 pt mod N tuyệt vời với 1 vế là số được tính toán sẵn đồng dư với 1 hàm chứa q duy nhất được mod N:
5^(-e1e2) . (c2^e1) - 2^(-e1e2) . (c1^e2) đồng dư q^(e1e2).(A) mod n 
- Ta chỉ cần tìm gcd của vế tính toán sẵn và N là đã có q rồi, giải p ez.
Cụ thể hơn, ta tham khảo Ys tưởng gốc tại:https://www.ctfrecipes.com/cryptography/general-knowledge/maths/modular-arithmetic/modular-binomial
```
from math import gcd
N = 14905562257842714057932724129575002825405393502650869767115942606408600343380327866258982402447992564988466588305174271674657844352454543958847568190372446723549627752274442789184236490768272313187410077124234699854724907039770193680822495470532218905083459730998003622926152590597710213127952141056029516116785229504645179830037937222022291571738973603920664929150436463632305664687903244972880062028301085749434688159905768052041207513149370212313943117665914802379158613359049957688563885391972151218676545972118494969247440489763431359679770422939441710783575668679693678435669541781490217731619224470152467768073
e1 = 12886657667389660800780796462970504910193928992888518978200029826975978624718627799215564700096007849924866627154987365059524315097631111242449314835868137
e2 = 12110586673991788415780355139635579057920926864887110308343229256046868242179445444897790171351302575188607117081580121488253540215781625598048021161675697
c1 = 14010729418703228234352465883041270611113735889838753433295478495763409056136734155612156934673988344882629541204985909650433819205298939877837314145082403528055884752079219150739849992921393509593620449489882380176216648401057401569934043087087362272303101549800941212057354903559653373299153430753882035233354304783275982332995766778499425529570008008029401325668301144188970480975565215953953985078281395545902102245755862663621187438677596628109967066418993851632543137353041712721919291521767262678140115188735994447949166616101182806820741928292882642234238450207472914232596747755261325098225968268926580993051
c2 = 14386997138637978860748278986945098648507142864584111124202580365103793165811666987664851210230009375267398957979494066880296418013345006977654742303441030008490816239306394492168516278328851513359596253775965916326353050138738183351643338294802012193721879700283088378587949921991198231956871429805847767716137817313612304833733918657887480468724409753522369325138502059408241232155633806496752350562284794715321835226991147547651155287812485862794935695241612676255374480132722940682140395725089329445356434489384831036205387293760789976615210310436732813848937666608611803196199865435145094486231635966885932646519
a1 = 2 
a2 = 5
q = gcd(pow(a2,(-e2 * e1),N) * pow(c2, e1, N) - pow(a1, (-e1 * e2), N) * pow(c1, e2, N), N)
print(q)
print(N // q)
```
Flag:crypto{112274000169258486390262064441991200608556376127408952701514962644340921899196091557519382763356534106376906489445103255177593594898966250176773605432765983897105047795619470659157057093771407309168345670541418772427807148039207489900810013783673957984006269120652134007689272484517805398390277308001719431273,132760587806365301971479157072031448380135765794466787456948786731168095877956875295282661565488242190731593282663694728914945967253173047324353981530949360031535707374701705328450856944598803228299967009004598984671293494375599408764139743217465012770376728876547958852025425539298410751132782632817947101601}
#### Broken RSA 
> If this is intermediate. then im so dead

Đề bài muốn ta giải 1 thuật toán RSA lỗi khi e lúc này không nguyên tố cùng nhau với phi(n), từ đó ta không thể tìm được d để giải. 

Với hint là sử dụng Bruteforce, chúng ta sẽ tiến hành Bruteforce plaintext, but how? 

Sẽ là không khôn ngoan nếu ta thử từng giá trị của m. Thay vì đó, ta bruteforce với các giá trị của e_roots. 

Cụ thể: 
- Ta biết rằng nếu chỉ có điều kiện ct = m0^e mod n. Nghiệm m có dạng là m.r với r^e mod p = 1 và ta cần tìm các giá trị m.r đó. 
**(r^e mod p chính là các e_roots)
- r ở đây sẽ là i^(phi(n)coprime) mod n (phi(n coprime chính là phi(n) được chia cho toàn bộ ước chung của nó với e)). i sẽ là giá trị bất kì mà trong bài này ta sẽ thử bruteforce nó chạy từ 1 đến 1000.
- Khi ta tìm được e_rooths và phi(n)coprime rồi, ta tiến hành nhân từng giá trị của m với r rồi mod n để tìm ra plaintext gốc. 
**(m ở đây được tính bằng ct^spe_d mod n, với spe_d là e^-1 mod(phi(n))coprime). 
- Tìm flag trong đống kết quả. 
```
from Crypto.Util.number import GCD, long_to_bytes

n = 27772857409875257529415990911214211975844307184430241451899407838750503024323367895540981606586709985980003435082116995888017731426634845808624796292507989171497629109450825818587383112280639037484593490692935998202437639626747133650990603333094513531505209954273004473567193235535061942991750932725808679249964667090723480397916715320876867803719301313440005075056481203859010490836599717523664197112053206745235908610484907715210436413015546671034478367679465233737115549451849810421017181842615880836253875862101545582922437858358265964489786463923280312860843031914516061327752183283528015684588796400861331354873
e = 16
ct = 11303174761894431146735697569489134747234975144162172162401674567273034831391936916397234068346115459134602443963604063679379285919302225719050193590179240191429612072131629779948379821039610415099784351073443218911356328815458050694493726951231241096695626477586428880220528001269746547018741237131741255022371957489462380305100634600499204435763201371188769446054925748151987175656677342779043435047048130599123081581036362712208692748034620245590448762406543804069935873123161582756799517226666835316588896306926659321054276507714414876684738121421124177324568084533020088172040422767194971217814466953837590498718

def roots_finding(e, phi, lts = 1000): 
    phi_coprime = phi 
    while GCD(e, phi_coprime) != 1: 
        phi_coprime = phi_coprime // GCD(phi_coprime, e)
    
    e_roots = set(pow(i, phi_coprime, n) for i in range (lts))

    return phi_coprime, e_roots

phi_coprime, e_roots = roots_finding(e, n - 1)

spe_d = pow(e, -1, phi_coprime)
m = pow(ct, spe_d, n)

res = []

for i in e_roots: 
    plain = long_to_bytes((m * i) % n)
    res.append(plain)
print(res)
```
Flag: crypto{m0dul4r_squ4r3_r00t}

CÁCH 2: SỬ DỤNG CĂN BẬC 2 THẶNG DƯ BẬC 2: 
->e = 16 = 2 . 2 . 2 . 2, Ta nghĩ đến việc tìm căn ct 4 lần để tìm các giá trị chứa m khả dĩ.
```
from sympy import sqrt_mod
from Crypto.Util.number import long_to_bytes

plain = []
plain.append(sqrt_mod(ct, n))
plain.append(n - sqrt_mod(ct, n)) 
for i in range(3): 
    plaincur = []
    for j in plain:
        x = sqrt_mod(j,n)
        if x is not None:
            plaincur.append(x)
            plaincur.append(n - x)
    plain = []
    plain.extend(plaincur)
    plaincur = []
    if i == 2 :
        for e in plain: 
            print(long_to_bytes(e))
```
#### No way back home 
> ftsio! 

Mấu chốt giải bài này chính là cách v được tạo ra: v = p * r mod n. Ta có: 
- p.r = v + k.N =>r = v/p + k.q
Vậy nghĩa là r mod q = v / p 
- v % p == 0, lý do là bởi: 
Nếu có a = p . r thì a mod n = p . (r - q . k)

Điều này dẫn đến việc ta hoàn toàn có thể biến đổi các giá trị vka, vkb, vkbka trong bài bằng cách tương tự, ta ta được: 
- xA = vKA / p = r.KA mod q 
- xB = vKB / p = r.KB mod q 
- xAB = vKAKB / p = r.KAKB mod q 

Lúc này để tìm r mod q, ta đơn giản lấy xA*xB nhân với nghịch đảo xAB mod q. Từ đó dễ dàng tính v bằng cách lấy: r mod q . p. 

Giải flag bằng decrypt AES với key = v là xong: 
```
from Crypto.Cipher import AES 
from Crypto.Util.number import long_to_bytes
from hashlib import sha256
p, q = (10699940648196411028170713430726559470427113689721202803392638457920771439452897032229838317321639599506283870585924807089941510579727013041135771337631951, 11956676387836512151480744979869173960415735990945471431153245263360714040288733895951317727355037104240049869019766679351362643879028085294045007143623763) 
vka = 124641741967121300068241280971408306625050636261192655845274494695382484894973990899018981438824398885984003880665335336872849819983045790478166909381968949910717906136475842568208640203811766079825364974168541198988879036997489130022151352858776555178444457677074095521488219905950926757695656018450299948207 
vkakb = 114778245184091677576134046724609868204771151111446457870524843414356897479473739627212552495413311985409829523700919603502616667323311977056345059189257932050632105761365449853358722065048852091755612586569454771946427631498462394616623706064561443106503673008210435922340001958432623802886222040403262923652 
vkb = 6568897840127713147382345832798645667110237168011335640630440006583923102503659273104899584827637961921428677335180620421654712000512310008036693022785945317428066257236409339677041133038317088022368203160674699948914222030034711433252914821805540365972835274052062305301998463475108156010447054013166491083 
c = 'fef29e5ff72f28160027959474fc462e2a9e0b2d84b1508f7bd0e270bc98fac942e1402aa12db6e6a36fb380e7b53323'

xA = vka // p 
xB = vkb // p 
xAB = vkakb // p 
iv_xAB = pow(xAB,-1,q)
rmodq = (xA*xB*iv_xAB) % q 
v = (rmodq * p)
key = sha256(long_to_bytes(v)).digest()
cipher = AES.new(key, AES.MODE_ECB)
m = cipher.decrypt(bytes.fromhex(c))
print(m)
```
CÁCH 2: TÍNH THUẦN V:
```

iv_vAB = pow(vkakb,-1,q)
v = (vka*vkb*iv_vAB) % (p*q)

key = sha256(long_to_bytes(v)).digest()
cipher = AES.new(key, AES.MODE_ECB)
m = cipher.decrypt(bytes.fromhex(c))
print(m)

```
Flag: crypto{1nv3rt1bl3_k3y_3xch4ng3_pr0t0c0l}
### Brain Teaser 2
#### Unencryptable 
> If this is medium or ez, then i surrender :/

Đề bài cho ta 1 hint tuyệt vời rằng DATA^e mod N vẫn bằng DATA., ta có thể khai thác điều này như sau: 
- Từ hint => DATA^(e - 1) mod N = 1, ,mà e - 1 lúc này chính bằng 2^16, ta nhận xét 1 số điều: 
- - Ta đoán được rằng bậc của DATA sẽ là lũy thừa của 2: 2^k ( cụ thể là 2^9)
- - Ta nên biết 1 điều rất quan trọng, đó chính là bậc của DATA mod N chính là LCM(a, b) với (a, b) là bậc của DATA mod p or q. 
- Vì ta đã có bậc của DATA mod N, lúc này ta hoàn có thể nhận ra bậc của DATA mod q hay p có dạng 2^i nào đó và có thể được bruteforce i để tìm p hoặc q. 
Ta rút ra điều này vì trong khoảng i từ 0 đến 9 sẽ xuất hiện bậc của p hoặc q với tính chất là nếu DATA ^ 2i mod p = 1 thì nó sẽ không = 1 với q. 
- Nếu đúng 2^i là bậc của 1 trong q or p thì DATA^2i - 1 tức sẽ có ước chung với N khác (1, N) thì đó chính là p or q đang cần tìm. 
- Giải p hoặc q còn lại, sau đó dễ dàng giải mã lấy flag:
```
from Crypto.Util.number import *

N = 0x7fe8cafec59886e9318830f33747cafd200588406e7c42741859e15994ab62410438991ab5d9fc94f386219e3c27d6ffc73754f791e7b2c565611f8fe5054dd132b8c4f3eadcf1180cd8f2a3cc756b06996f2d5b67c390adcba9d444697b13d12b2badfc3c7d5459df16a047ca25f4d18570cd6fa727aed46394576cfdb56b41
e = 0x10001
c = 0x5233da71cc1dc1c5f21039f51eb51c80657e1af217d563aa25a8104a4e84a42379040ecdfdd5afa191156ccb40b6f188f4ad96c58922428c4c0bc17fd5384456853e139afde40c3f95988879629297f48d0efa6b335716a4c24bfee36f714d34a4e810a9689e93a0af8502528844ae578100b0188a2790518c695c095c9d677b
data = 0x372f0e88f6f7189da7c06ed49e87e0664b988ecbee583586dfd1c6af99bf20345ae7442012c6807b3493d8936f5b48e553f614754deb3da6230fa1e16a8d5953a94c886699fc2bf409556264d5dced76a1780a90fd22f3701fdbcb183ddab4046affdc4dc6379090f79f4cd50673b24d0b08458cdbe509d60a4ad88a7b4e2921

p = 0
for i in range(1,9): 
    cur = pow(data,2**i) - 1
    if GCD(cur,N) not in [1, N]: 
        print(i)
        p = GCD(cur, N)
        break
q = N // p 

d = pow(e, -1, (p - 1)*(q - 1))
flag = long_to_bytes(pow(c, d, N))
print(flag)
```
Flag: crypto{R3m3mb3r!_F1x3d_P0iNts_aR3_s3crE7s_t00}