---
# System prepended metadata

title: PICO CTF 2023

---

# PICO CTF 2023 
## ReadMyCert
### Đề bài:
Cho 1 file readmycert.csr và để giải thì đề bài đã cho hint và mình chỉ cần đọc được file này là có được flag , và ta chỉ cần dùng tool để decode là xong. Tool decode: 
https://certlogik.com/decoder/
reamycert.csr

    -----BEGIN CERTIFICATE REQUEST-----
    MIICpzCCAY8CAQAwPDEmMCQGA1UEAwwdcGljb0NURntyZWFkX215Y2VydF82OTNm
    N2MwM30xEjAQBgNVBCkMCWN0ZlBsYXllcjCCASIwDQYJKoZIhvcNAQEBBQADggEP
    ADCCAQoCggEBAPp+XuDB3ZkmrkvAsgtjP+mjIcYDWfptuZsJieu6eRl39wl4Sg38
    +/OfY24LV9sNmgKyTGvpmCaUoZMYkvkulYSoFzE0xqPBo6kruLEyIvqqpAFqRH2b
    mierLT6RcKgJHYr/Vt6SwP8NCCawCrvhQ4NZcuB49Hr/2AiGHzmf86/lG/c+lhmH
    gyqPb1kDghsVxi/GNs9i7AgniZikqT8OTp0INmmCgZtJn1Jo615Iu/tFiC8Sfhhg
    QHmTDLjgx1oP1kvZV2PE5UUN/oC05Zup8f31LksXZwpazZKwYC/LbN96HdqgVQ9K
    S8e/4I7MJQmPmLIsLp3sdL2FiDGML3smAi0CAwEAAaAmMCQGCSqGSIb3DQEJDjEX
    MBUwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQADggEBAOxSR8Fs
    Tdjfu9e0vRNqKWd09ISmYDQc3qnSbLRlYZyMK4pguALq310h/1nNgURWESbNJPOp
    FkBWG0XWhWyWP7rTqxo/pk9AKx0TNbHDrS6KiBnKPq0mxjPZsH1L7wNYDc5OANDl
    btvn3zT7lMms6z1qM7xUWXR76n2xL/81cdF725nBZ00mWmPW0S1pSmA4EEHCEgNW
    0vWQqsIDki3gYc4NCm8OHjx79kcwE+ksyc6vHgMOwsYoOFJnyayhl15oN/3x7hW3
    G1xovPupABpfOSNOcTwbgfrfjUDOLx/wirvj9L1N5EGDh4FOLaRZDs+tMrimGBBS
    zGU13BnykmQ5jOQ=
-----END CERTIFICATE REQUEST-----

==> FLag: ![](https://i.imgur.com/zhZGi5z.png)

## rotation
### Đề bài:
Cho file mã hóa như sau:xqkwKBN{z0bib1wv_l3kzgxb3l_949in1i1}

Nhìn đoạn mã hóa này ta nghĩ đến luôn là mã hóa caesar nên ta chỉ cần cho vào tool https://www.dcode.fr/caesar-cipher giải là xong:

Và ta thu được flag: 	picoCTF{r0tat1on_d3crypt3d_949af1a1}


## Hide to see
### Đề bài:
Cho mình 1 file image và yêu cầu tìm flag. Đề bài cũng cho ta hint là phân tích ảnh. Nên em nghĩ là trong ảnh có chuỗi được mã hóa nên ta dùng tool https://futureboy.us/stegano/decinput.html để lấy bí mật. Và dựa vào ảnh thì ta có thể thấy được là bí mật được mã hóa theo kểu atbash. Nghĩa là dịch đi 25 ký tự là được. Tool decode atbash: https://www.dcode.fr/atbash-cipher

Giải bí mật ảnh ta thu được chuỗi như sau: krxlXGU{zgyzhs_xizxp_8z0uvwwx}
![](https://i.imgur.com/3Od4c9W.png)

Giờ chỉ cần decode atbash nữa là xong 

Ta thu được flag: picoCTF{atbash_crack_6c4bcb08}

## SRA
### Đề bài: Cho mình 1 file source.py và yêu cầu giải mã

source.py:

    from Crypto.Util.number import getPrime, inverse, bytes_to_long
    from string import ascii_letters, digits
    from random import choice

    pride = "".join(choice(ascii_letters + digits) for _ in range(16))
    gluttony = getPrime(128)
    greed = getPrime(128)
    lust = gluttony * greed
    sloth = 65537
    envy = inverse(sloth, (gluttony - 1) * (greed - 1))

    anger = pow(bytes_to_long(pride.encode()), sloth, lust)

    print(f"{anger = }")
    print(f"{envy = }")

    print("vainglory?")
    vainglory = input("> ").strip()

    if vainglory == pride:
        print("Conquered!")
        with open("/challenge/flag.txt") as f:
            print(f.read())
    else:
        print("Hubris!")

**Phân tích**
Nhìn đề bài thì ta biết luôn đây là mã hóa RSA và nhiệm vụ của mình là đi tìm pride để nhập vào server để trả về cho mình flag. Đề bài và server đã cho mình C, d, e, và thiếu mất n nên nhiệm vụ của mình là tìm n. Để tìm được n thì ta cần tìm phi(n) vì phi(n) = (p-1) * (q-1) nên khi ta tìm được phi(n) thì ta sẽ phân tích phi(n) và ta sẽ tìm được (p-1) và (q-1) mà đề bài lại cho ta biết rằng p và q là 2 số nguyên tố có độ dài là 128 nữa nên ta so thêm cả độ dài cho chắc. Thế là ta đã có được p và q còn việc tìm phi(n) thì chịu nhưng k.phi(n) thì ta có công thức k.phi(n) = e*d-1. Ta thấy rằng khi ta phân tích ra thì k không ảnh hưởng vì mình đã xét thêm điều kiện độ dài nên khi lấy mình sẽ lấy được p và q mà không bị ảnh hưởng bởi k

**Code**

    from pwn import process, remote
    from sage.all import divisors, is_pseudoprime
    from Crypto.Util.number import long_to_bytes

    io = remote("saturn.picoctf.net", 65007)
    io.recvuntil(b"anger = ")
    c = int(io.recvline().strip())
    io.recvuntil(b"envy = ")
    d = int(io.recvline().strip())
    e = 65537
    kphi = e * d - 1
    msg = ""
    for pm1 in divisors(kphi):  # Phân tích kphi
        p = pm1 + 1
        if is_pseudoprime(
                p) and p.nbits() == 128:  # Kiểm tra xem p có là số nguyên tố hay không và độ dài có bằng 128 không nếu có thì mình tìm q còn không mình lại thử số p khác
            for k in range(1, e):
                if kphi % k != 0:
                    continue
                q = (kphi // k // pm1) + 1
                if is_pseudoprime(q) and q.nbits() == 128:
                    print(p)
                    print(q)
                    n = p * q
                    m = pow(c, d, n)
                    msg = long_to_bytes(m)

    io.recvuntil(b'> ')
    io.sendline(msg)
    io.interactive()

    ==> Flag: picoCTF{7h053_51n5_4r3_n0_m0r3_dd808298}
    

## Power Analysis: Warm up
### Đề bài:
Giải chall với hint: Thuật toán "mã hóa" rất đơn giản và mối tương quan giữa rò rỉ và khóa có thể được mô hình hóa rất dễ dàng.

Đọc challenge này thì đầu tiên search tìm power analysis là gì cái đã?

 Power analysis là một dạng tấn công kênh phụ , trong đó kẻ tấn công nghiên cứu mức tiêu thụ điện năng của một thiết bị phần cứng mật mã. Các cuộc tấn công này dựa trên các đặc tính vật lý cơ bản của thiết bị: các thiết bị bán dẫn chịu sự điều chỉnh của các định luật vật lý, quy luật này ra lệnh rằng sự thay đổi điện áp trong thiết bị yêu cầu chuyển động rất nhỏ của các điện tích (dòng điện). Bằng cách đo các dòng điện đó, có thể biết được một lượng nhỏ thông tin về dữ liệu đang được thao tác. 
 
 Theo em tìm hiểu thì có 3 loại tấn công power analysis:
 * Simple Power Analysis
 * Differential Power Analysis
 * Correlation Power Analysis

mà dựa vào hint thì ta chọn luôn chall này mã hóa loại thứ 3 nên em chỉ trình bày cách mã hóa của dạng thứ 3 này.


**Correlation Power Analysis**
Theo định nghĩa trên mạng thì các bước khá dài em sẽ tóm lại theo ý hiểu của em sai chỗ nào anh nhận xét cho em nhá.
Phương pháp này gồm 3 bước như sau:
* Bước 1: Xây dựng tập dữ liệu mô tả rò rỉ kênh kề, cụ thể là điện năng tiêu thụ thực tế của thiết bị khi nó thực thi thuật toán mật mã. Một tập dữ liệu D là dữ liệu đầu vào, T là độ dài của 1 dữ liệu D thì sau khi mã hóa thì ta sẽ được mà trận (D x T) nhưng mình chỉ gửi 1 bản trong số này đi.
* Bước 2: Xây dựng tập dữ liệu mô tả điện năng thụ giả định của thiết bị thông qua việc tính toán dựa trên mô hình toán học của thuật toán mật mã. Có nghĩa cùng 1 tập dữ liệu D ở bước 1. Thì này này mình sẽ bruteforce tất cả các trường hợp có trong tập D. Và sẽ đánh giá kết quả của 2 kết quả thu được bằng 1 trong 2 cách đó là trọng số Hamming hoặc khoảng cách Hamming. Và trong bài em dùng là trọng số Hamming. Nghĩa là mô hình trọng số Hamming, điện năng tiêu thụ giả định tỷ lệ với số bít có giá trị 1 trong dữ liệu mà thiết bị xử lý.


* Bước 3: Phân tích thống kê để quyết định khóa đúng được thiết bị sử dụng. So sánh các trường hợp mình brute force với trường hợp mình gửi cho server xem trường hợp nào thu được mô hình trọng số cao nhất thì lấy trường hợp đó và khóa chính là vị trị của trong ma trận (D x T) mà thu được nhiều giá trị 1 nhất

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


**Code**

    from pwn import *
    import numpy as np



    Sbox = (
        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,
    )



    def oracle(pt):
        io = remote("saturn.picoctf.net", 54236)
        io.sendline(pt.hex().encode())
        io.recvuntil(b"result: ")
        r = int(io.recvlineS().strip())
        io.close()
        return r


    def corr(xs, ys): # Phương trình so sánh trọng số Hamming
        dx = [a - b for a, b in zip(xs, xs[1:])]
        dy = [a - b for a, b in zip(ys, ys[1:])]
        return len([1 for a, b in zip(dx, dy) if a == b]) # Lấy số lượng bit 1 tương quan so sánh của 2 bản mã


    def recover_key_byte(idx):
        pt = bytearray([0] * 16) # Cái pt này chỉ để làm màu cho nó có 1 mảng để tạo giá trị để gửi bản rõ cho server để server trả về để mình so sánh
        res = []
        pt_samples = range(0, 256,16)
        for i in pt_samples:
            pt[idx] = i
            res.append(oracle(pt))
        out = [[Sbox[i ^ kb] & 1 for i in pt_samples] for kb in range(256)] # Bản mã mình brute force
        cor = [corr(res, o) for o in out] # Ma trận D * T ở vị trí mỗi bản rõ thay bằng giá trị trả về của hàm corr(số lượng bít 1)
        mx = np.argmax(cor)# lấy vị trí bít 1 nhiều nhất chính là khóa (Có thể hiểu là khóa chỉ trong [1,16] vì T là 16 nên là lấy vị trí theo hàng không thể lớn hơn 16)
        print(mx)
        print(np.sort(cor)[-10:])
        return mx


    key = bytes([recover_key_byte(i) for i in range(16)])
    print(key.hex())
    flag = f"picoCTF{{{key.hex()}}}"
    print(flag)
    #==> Flag:picoCTF{6f040f33f3521c634878c02f3be1f409}
    


## Power Analysis: Part 1
### Đề bài:
Sử dụng thông tin được leak ra và giải mã. Vì part 1 và part 2 giống nhau cách làm chỉ khác cái file nên em làm luôn part 1 và part 2 mình chỉ cần sửa cái path của traces là xong

**Phân tích**
Dựa vào hint "The power consumption is correlated with the Hamming weight of the bits being processed"
Thì theo power analysis ta có thể đoán được đây là simple power analysis

Tấn công phân tích năng lượng đơn giản (Simple Power Analysis - SPA) là một dạng tấn công kênh kề được sử dụng phổ biến trong lĩnh vực thám mã. Tấn công này khai thác mối quan hệ tuyến tính giữa năng lượng tiêu thụ và các quá trình thực thi của thuật toán mật mã nhằm tìm ra khóa lưu trữ trong thiết bị. Bài báo này trình bày kết quả thực hiện tấn công SPA lên phép nhân điểm phương pháp nhị phân của mật mã đường cong Elliptic (Elliptic Curve Cryptography - ECC). Tấn công được thực hiện thông qua việc phân tích và nhận dạng các phép tính cơ bản khi thuật toán thực thi trên phần cứng.

Về cách làm thì tương tự của cpa nhưng chỉ khác đó là cpa so sánh trọng số còn spa so sánh mức tiêu hao năng lượng



**Code**

    from pathlib import Path
    import ast
    import numpy as np
    from scipy.stats import pearsonr
    from tqdm import tqdm
    from pwn import remote
    from pathlib import Path
    import os, ast
    ## Tạo file traces giống như part 2
    traces = Path("traces")
    traces.mkdir(exist_ok=True)

    for i in range(100):
        io = remote("saturn.picoctf.net", 56344)
        pt = os.urandom(16)
        io.sendline(pt.hex().encode())
        io.recvuntil(b"result:  ")
        trace = ast.literal_eval(io.recvlineS().strip())
        f = traces / f"trace{i:02d}.txt" # Tạo tên file txt trong file zip traces từ 00 -> 99 gồm plaintext mình random và power server trả về
        f.write_text(
            f"""Plaintext: {pt.hex()}
            Power trace: {trace}
                """
        )
        io.close()
    # Lấy plaintext và trace trong file traces
    pts = []
    traces = []
    for f in Path("traces").iterdir():
        l = f.read_text().splitlines()
        pt = bytes.fromhex(l[0].split(": ")[1])
        trace = ast.literal_eval(l[1].split(": ")[1])
        pts.append(pt)
        traces.append(trace)

    # fmt: off
    sbox = [
        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
    ]


    # fmt: on


    def power_model(x): # Hàm đếm bit
        return x.bit_count()


    key = []
    for target_idx in range(16):
        M = np.array(
            [[sbox[pt[target_idx] ^ k] for k in range(256)] for pt in pts]
        )  # brute force các byte key
        MP = np.vectorize(power_model)(M)
        TR = np.array(traces)[:, 300:400]  # by guessing where the first sbox happens


    def get_max_corr(l):# Hàm đếm tiêu thụ năng lượng trong 1 khoảng thời gian
       
        mx = 0
        for r in TR.T:  # mình tìm trên tất cả các trường hợp mình brute force trên và chọn ra trường hợp tiêu thụ lớn nhất 
            mx = max(mx, pearsonr(l, r)[0])
        return mx

    # byte khóa chính là vị trí của tiêu thụ lớn nhất
    res = np.array([get_max_corr(x) for x in tqdm(MP.T)]) 
    mx = np.argmax(res)
    print(mx)
    print(res[mx])
    print(
        np.sort(res)[-10:]
    ) 
    key.append(mx)
    key = bytes(key)
    print(key)
    print(f"picoCTF{{{key.hex()}}}")
    #==> Flag: picoCTF{af55be9bb08d78491b0aa416f7043ed4}





## Power Analysis: Part 2
### Đề bài: 
Part 2 khá giống với part 1. Part 1 cho máy ảo để lấy dữ liệu còn part 2 cho file zip traces.zip để lấy dữ liệu và yêu cầu tìm khóa. Bài này cũng là spa


    from pathlib import Path
    import ast
    import numpy as np
    from scipy.stats import pearsonr
    from tqdm import tqdm

    pts = []
    traces = []
    for f in Path("D:\Python\pythonProject8\\traces").iterdir():
        l = f.read_text().splitlines()
        pt = bytes.fromhex(l[0].split(": ")[1])
        trace = ast.literal_eval(l[1].split(": ")[1])
        pts.append(pt)
        traces.append(trace)

    # fmt: off
    sbox = [
        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
    ]
    # fmt: on


    def power_model(x):
        return x.bit_count()


    key = []
    for target_idx in range(16):
        M = np.array(
            [[sbox[pt[target_idx] ^ k] for k in range(256)] for pt in pts]
        )  
        MP = np.vectorize(power_model)(M)
        TR = np.array(traces)[:, 300:400]  

        def get_max_corr(l):
            mx = 0
            for r in TR.T:  
                mx = max(mx, pearsonr(l, r)[0])
            return mx

        res = np.array([get_max_corr(x) for x in tqdm(MP.T)])
        mx = np.argmax(res)
        print(mx)
        print(res[mx])
        print(
            np.sort(res)[-10:]
        ) 
        key.append(mx)
    key = bytes(key)
    print(key)
    print(f"picoCTF{{{key.hex()}}}")
    #==> Flag: picoCTF{edb6ccb7f392059ae1129d8e74de7647}



