Post

BackDoorCTF2023

Cuối tuần rồi mình có tham gia chơi BackDoorCTF cùng blackpinker, đây là lời giải của mình cho một vài bài crypto mà mình làm được trong kì thi.

AAA

Mini RSA

Mini RSA

Script đề bài:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
from Crypto.Util.number import getPrime , bytes_to_long , GCD
import random
import time

random.seed(time.time())
flag = b"flag{REDACTED}" #Flag has been removed

KEY_SIZE = 512
RSA_E = 3

def fast_exp(a, b, n):
    output = 1
    while b > 0:
        if b & 1:
            output = output * a % n
        a = a * a % n
        b >>= 1 
    return output    

def check(p, q, n):
    a_ = random.randint(1, 100)
    b_ = random.randint(1, 100)
    s = fast_exp(p, fast_exp(q, a_, (p - 1) * (q - 1)), n)
    t = fast_exp(q, fast_exp(p, b_, (p - 1) * (q - 1)), n)
    result = s + t
    print(result)

def gen_RSA_params(N, e):
    p = getPrime(N)
    q = getPrime(N)

    while GCD(e, (p - 1) * (q - 1)) > 1:
        p = getPrime(N)
        q = getPrime(N)

    n = p * q

    check(p, q, n) 
    return (p, q, n)

p, q, n = gen_RSA_params(KEY_SIZE, RSA_E) 

m = bytes_to_long(flag)
c = pow(m, RSA_E, n)

print(c)
print(n)

Nhìn chung thì đây là 1 bài RSA với e = 3, check file output thì chúng ta có:

1
2
3
s+t=24986288511406610689718446624210347240800254679541887917496550238025724025245366296475758347972917098615315083893786596239213463034880126152583583770452304
c=5926440800047066468184992240057621921188346083131741617482777221394411358243130401052973132050605103035491365016082149869814064434831123043357292949645845605278066636109516907741970960547141266810284132826982396956610111589
n=155735289132981544011017189391760271645447983310532929187034314934077442930131653227631280820261488048477635481834924391697025189196282777696908403230429985112108890167443195955327245288626689006734302524489187183667470192109923398146045404320502820234742450852031718895027266342435688387321102862096023537079

alt text

Đầu tiên mình nhận thấy c khá nhỏ so với n, từ đây mình có thể lấy căn bậc $3$ để thu hồi lại flag

Solve script của mình:

1
2
3
4
5
6
7
8
9
10
from Crypto.Util.number import long_to_bytes as ltb  
from sage.all import * 

c=5926440800047066468184992240057621921188346083131741617482777221394411358243130401052973132050605103035491365016082149869814064434831123043357292949645845605278066636109516907741970960547141266810284132826982396956610111589

m = ZZ(c).nth_root(3)

print(ltb(m).decode())

# flag{S0_y0u_c4n_s0lv3_s0m3_RSA}

Mini RSA 2

MiniRSA2

Bài này script cũng tương tự như bài trước, chỉ khác rằng lúc này e = 65537 thay vì e = 3. Bây giờ thì mình không thể sử dụng cách giải cũ nữa, tuy nhiên ở bài trước chúng ta có đại lượng s + t chưa sử dụng đến

Trong đó: $s = p^x$ và $ t = q^y$ với $x, y$ nào đó

Ở đây sẽ có 2 loại:

  1. Bạn là người siêng năng và sẽ chứng minh s + t = p + q

  2. Bạn lười như mình và nhận thấy s + t chỉ có $513$ bits mà $p$ và $q$ đều là các số $512$ bits $\Rightarrow$ s + t = p + q

Khi đã có p + q thì mình chỉ cần tính phi = (p - 1) * (q - 1) = n - (p + q) + 1 và lấy flag hoy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from Crypto.Util.number import long_to_bytes as ltb  
from sage.all import * 

st=19238118289292540845900132045328657353776835000175884072088390761517035980189490490459144989703825736320337279576084998885094661611740596902279433080118842
c=8706151122704717355844546946300718218661297718003809762659247903847434328687915436733170976217817899448539289359073472514594584336897461146424215943312922513357040989774130659844482065845748436287563176648482538678604968897565248411495214863860346421741054695295323750935794123435244378357713784569300292101
n=91006417473818125376038413443680810078297391307262694881962992972007772034646331080442026536488576075677538183326514650683359389396367200966050480101000687177953951819685597201494449347363819516742644021041231090322946128861477233797216337306770224218011823327837033401012386386102401543925525885359433574841

print(st.bit_length()) # 513

phi = n - st + 1 
e = 65537

d = pow(e, -1, phi)

print(ltb(pow(c, d, n)).decode())

# flag{I_4m_5tuck_0n_Th3_pl4n3t_Eg0_s4ve_M3}

Something in common

Alt text

Script của đề bài:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from Crypto.Util.number import *

flag = "This flag has been REDACTED"
moduli = "This array has been REDACTED"

m = bytes_to_long(flag.encode())
e = 3
remainders = [pow(m,e,n) for n in moduli]

f = open('output.txt','w')
for i in range(len(moduli)):
    f.write(f"m ^ e mod {moduli[i]} = {remainders[i]}\n")
f.write(f"\ne = {e}")
f.close()

Đúng như description, đây là một bài RSA nhưng encrypt m $7$ lần thay vì $1$ lần

Nhìn vào file output ta có:

1
2
3
4
5
6
7
8
9
m ^ e mod 231689896592553079225008346159565141292942746185614335113030628126523977770897610833 = 70932244057518414814271820586538428333420562252483260602196856595136636875881109254
m ^ e mod 7171431858055720778675521 = 6776581747370220150625940
m ^ e mod 66926822362327139196541990168817936306935699 = 48565469191356626147008517582743644359421796
m ^ e mod 437335592290538364420374052921942150635299817629860400585996176158735283605573507708521 = 8794419984130129081066440741470891653922464557881503503363167507918405790466608773101
m ^ e mod 289641633885807692370107575915133663791 = 172864555741817549854149625512946760571
m ^ e mod 667489211907833441904090408183964916738111 = 123698332225047871848637413013333477895868
m ^ e mod 3567528272153764003837574317682649383619949327607 = 2621823962661199268500092259451160990545103771980

e = 3

Ở đây một lần nữa chúng ta gặp lại e = 3, vậy liệu mình có thể sử dụng lại cách như bài Mini RSA hay không?

Thật ra mình cũng chưa check (do mình chả thấy cái $c_i$ nào quá bé so với $n_i$ cả) =))). Nhưng mà mình tận dụng việc tác giả encrypt cùng m $7$ lần bằng cách sử dụng Thặng dư Trung Hoa

Ta sẽ thu được $c \equiv m^3 \pmod{\prod_{i}{n_i}}$. Với $c$ rất bé so với $\prod_{i}{n_i}$, lúc này chúng ta có thể lấy căn bậc $3$ như bài Mini RSA 1 để lấy flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from sage.all import * 
from Crypto.Util.number import long_to_bytes as ltb 

mod = [231689896592553079225008346159565141292942746185614335113030628126523977770897610833, 7171431858055720778675521, 66926822362327139196541990168817936306935699, 437335592290538364420374052921942150635299817629860400585996176158735283605573507708521, 289641633885807692370107575915133663791, 667489211907833441904090408183964916738111, 3567528272153764003837574317682649383619949327607]
rem = [70932244057518414814271820586538428333420562252483260602196856595136636875881109254, 6776581747370220150625940, 48565469191356626147008517582743644359421796, 8794419984130129081066440741470891653922464557881503503363167507918405790466608773101, 172864555741817549854149625512946760571, 123698332225047871848637413013333477895868, 2621823962661199268500092259451160990545103771980]

f = crt(rem, mod)

print(product(mod).bit_length()) # 1221

print(ZZ(f).bit_length()) # 1053

print(ltb(ZZ(f).nth_root(3)).decode())

# flag{Wh4t_d0_y0u_m34n_1t_h4s_t0_b3_co-pr1m3}

PRSA

Alt text

Script của đề bài

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from sage.all import *
from Crypto.Util.number import bytes_to_long, getPrime

import random
import time
random.seed(time.time())

message = b'flag{REDACTED}' ## the flag has been removed
F.<x> = PolynomialRing(GF(2), x)

p, q = [F.irreducible_element(random.randint(2 ** 10, 2 ** 12)) for _ in range(2)]
R.<y> = F.quotient_ring(p * q)

n = sum(int(bit) * y ** (len(bin(bytes_to_long(message))[2:]) - 1 - i) for i, bit in enumerate(bin(bytes_to_long(message))[2:]))

e = 2 ** 256
c = n ** e 

print(e) ## to be given to the user
print(c) ## to be given to the user
print(p * q) ## to be given to the user

Tiếp tục là $1$ bài về RSA nhưng không dựa vào tập $Z/nZ$ mà chúng ta vẫn hay sử dụng, thay vào đó là trên tập các đa thức $R[x] = GF_2[x]/(p[x] * q[x])$

Cách mình tiếp cận những bài này chính là nghĩ về RSA thông thường chúng ta làm như thế nào rồi chuyển nó về tập mà chúng ta đang xét

RSA thông thường:

Khởi tạo:

Tạo ra một con $n = pq$ với $p$, $q$ là 2 số nguyên tố sao cho từ $n$ chúng ta không thể phân tích ngược lại $p$ và $q$

Encryption:

Giả sử chúng ta muốn encrypt plaintext $m$, chúng ta sẽ đi tính $c \equiv m^e \pmod{n}$. Lúc này $c$ chính là ciphertext

Decryption:

Tính $d$ sao cho $ed \equiv 1 \pmod{\phi(n)}$ với $\phi(n)$ chính là phi hàm Euler của $n$ (ta cần tính $\phi(n)$ là do $a^{\phi(n)} \equiv 1 \pmod{n}$ $\forall a$ sao cho $(a, n) = 1$)

Lúc này ta sẽ có thể decrypt $c$ và thu lại $m$ như sau: $m \equiv c^d \pmod{n}$

Vậy vì sao RSA thông thường an toàn?

Đó là do chúng ta không thể phân tích được $p$ hay $q$ từ $n$. Tuy nhiên trong bài toán mà chúng ta đang xét, từ đa thức $p * q$, chúng ta có thể dễ dàng phân tích ngược lại đa thức $p$, $q$

Alt text

Việc tiếp theo mà chúng ta cần làm chính là tính xem $\phi(n)$ (từ đây mình sẽ gọi nó là bậc của $R[x]$) bằng bao nhiêu?

Và từ đây mình xin phép giới thiệu

Ngu lần 1

Mình đã đi tính bậc 1 cách ngây thơ đó chính là sử dụng hàm .order() trong sage

Alt text

Lúc này mình nhận thấy bậc mà mình đã tính ra chia hết cho $e$, tức $(o, e) \neq 1$ mà như vậy thì làm sao mình có thể tạo ra con $d$ như lúc làm bình thường được? Thế là mình đã thử rất nhiều attack khác nhau đến khi mình bí ý tưởng thì mình mới check lại xem bản thân đã tính bậc đúng chưa.

Alt text

Bạn có nhận ra mình sai ở đâu không =))

Đúng là mình đã tính bậc đúng, nhưng là bậc đối với phép cộng chứ không phải là phép nhân mà mình đang cần tìm lmaoooo.

Sau đó mình đã tính lại bằng hàm .multiplicative_order() thay vì .order() và nhận được dòng lỗi:

AttributeError: 'PolynomialQuotientRing_generic_with_category' object has no attribute 'multiplicative_order'

Vậy là trong sage không có cài đặt sẵn tính bậc đối với phép nhân trong PolynomialQuotientRing. Mình đành lên mạng tra công thức tính

Qua đó mình biết bậc mình cần tính o = lcm([2^p.degree() - 1, 2^q.degree() - 1])

Check lại (sai nữa thì ko biết nói gì, skill issue cực mạnh):

Alt text

Hên quá nó đúng rồi =)))

Mình đã có hết mọi con số mình cần rồi, giờ thì lấy flag thui :p

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from sage.all import *
from Crypto.Util.number import bytes_to_long, getPrime, long_to_bytes as ltb 
import random
import time

F.<x> = PolynomialRing(GF(2), x)

pq = F(x^4547 + x^3524 + x^3518 + x^3517 + x^3512 + x^1072 + x^1070 + x^1041 + x^1035 + x^49 + x^47 + x^43 + x^42 + x^41 + x^40 + x^37 + x^35 + x^18 + x^11 + x^5 + 1)

# Factor p*q -> p, q
p, q = F(x^1035 + x^12 + x^6 + x^5 + 1), F(x^3512 + x^37 + x^35 + x^6 + 1)

R.<y> = F.quotient_ring(p * q)
Rp.<rp> = F.quotient_ring(p)
Rq.<rq> = F.quotient_ring(q)

c = ...

e = 115792089237316195423570985008687907853269984665640564039457584007913129639936

M = lcm([2^p.degree() - 1, 2^q.degree() - 1])

d = pow(e, -1, M)

dec = (c ** d).list()[::-1]

t = "".join(str(i) for i in dec)

from Crypto.Util.number import long_to_bytes as ltb 

print(ltb(int(t, 2)).decode())

# flag{S0_1mPL3m3n71nG_R54_0n_P0lYn0m1aL5_1n5734d_d1dn7_w0rk_hUh!!?}

Knapsack

Alt text

Script đề bài:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import random
import hashlib
from Crypto.Util.number import bytes_to_long
from Crypto.Cipher import AES

flag = b"The flag has been REDACTED"
secret = b"The seccret has been REDACTED"
key = hashlib.sha256(secret).digest()[:16]

cipher = AES.new(key, AES.MODE_ECB)
padded_flag = flag + b'\x00'*(-len(flag)%16)

ciphertext = cipher.encrypt(padded_flag)

f = open('output.txt','w')
f.write(f"Ciphertext: {ciphertext.hex()}\n\n")

arr = [ random.randint(1,1000000000000) for i in range(40) ]
k = bytes_to_long(secret)
s = 0
for i in range(40):
    if k&(1<<i):
        s+=arr[i]

f.write(f'numbers: {str(arr)[1:-1]}\nsum: {s}\n')
f.close()

Đọc một chút thì chúng ta sẽ biết rằng mục tiêu của bài sẽ là đi tìm secret

Ở đây thì phương trình có liên quan đến secret chính là

\[s = \sum_{i=0}^{39}{b_i * arr_i}\]

Trong đó thì $b_i \in {0, 1}$ chính là bit thứ $i$ của secret, còn arr chính là mảng các số được random mà đề đã cho chúng ta.

Vậy thì mục tiêu của chúng ta sẽ là xác định $b_i$ $(0 \leq i \leq 39)$ từ arrs. Sau đó thì ta có thể lấy lại secret

Nhìn chung thì đây là 1 bài subset sum cơ bản, chúng ta sẽ dùng LLL để giải bài này

Ở đây thì mình sẽ dùng script trong lbc_toolkit của Joseph (goated tool)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
def subset_sum(weights, targets, modulus=None, N=None, lattice_reduction=None, verbose=False):
    verbose = (lambda *a: print('[subset_sum]', *a)) if verbose else lambda *_: None

    if type(weights[0]) is list:
        k = len(weights)
        n = len(weights[0])
    else:
        k = 1
        n = len(weights)
        weights = [weights]
        targets = [targets]

    if modulus is not None:
        density = n / (k * log(modulus, 2))
    else:
        density = n / (k * log(max(flatten(weights)), 2))
    verbose('Density:', round(density.n(), 4))

    N = N or ceil(sqrt((n+1)/4))
    B = 2 * Matrix.identity(n)
    B = B.augment(vector([0] * n))
    for j in range(k):
        B = B.augment(vector([N * a for a in weights[j]]))
    if modulus is not None:
        B = B.stack(Matrix.zero(k, n + 1).augment(N * modulus * Matrix.identity(k)))
    B = B.stack(vector([1] * (n + 1) + [N * s for s in targets]))

    verbose('Lattice dimensions:', B.dimensions())
    lattice_reduction_timer = cputime()
    if lattice_reduction:
        B = lattice_reduction(B)
    else:
        B = B.LLL()
    verbose(f'Lattice reduction took {cputime(lattice_reduction_timer):.3f}s')

    for row in B:
        if row[n] < 0:
            sol = [ZZ((x + 1)//2) for x in row[:n]]
        else:
            sol = [ZZ((1 - x)//2) for x in row[:n]]
        if any(x not in [0, 1] for x in sol):
            continue
        for j in range(k):
            t = sum(ZZ(e) * ZZ(a) for e, a in zip(sol, weights[j]))
            tj = targets[j]
            if modulus > 0:
                t %= modulus
                tj %= modulus
            if t != tj:
                break
        else:
            return sol
        
    return None

numbers = [600848253359, 617370603129, 506919465064, 218995773533, 831016169202, 501743312177, 15915022145, 902217876313, 16106924577, 339484425400, 372255158657, 612977795139, 755932592051, 188931588244, 266379866558, 661628157071, 428027838199, 929094803770, 917715204448, 103431741147, 549163664804, 398306592361, 442876575930, 641158284784, 492384131229, 524027495955, 232203211652, 213223394430, 322608432478, 721091079509, 518513918024, 397397503488, 62846154328, 725196249396, 443022485079, 547194537747, 348150826751, 522851553238, 421636467374, 12712949979]
sum = 7929089016814

print(subset_sum(numbers, sum, verbose=True))

Tuy nhiên trái ngược lại với mong đợi, mình đã không tìm được $b_i$

1
2
3
4
[subset_sum] Density: 1.0061
[subset_sum] Lattice dimensions: (41, 42)
[subset_sum] Lattice reduction took 0.015s
None

Lúc này mình nhận ra rằng density của mảng quá cao so với thuật toán mà mình đang sử dụng

Cụ thể trong tutorial.pdf của Joseph có note rằng:

Alt text

Rõ ràng $1.0061 > 0.9408$ cho nên có thể đây là lý do mà mình không LLL ra được đáp án, vậy thì tới đây các bạn sẽ làm gì?

Đối với mình thì mình sẽ đưa nó về những gì mình biết để giải :D. Mình không biết cách nào để giải bài knapsack density cao cả, do thế mình sẽ cố gắng khiến cho density thấp xuống. Vậy thì density được tính như thế nào?

Nhìn lại trong script của hàm subset_sum ta có:

1
density = n / (k * log(max(flatten(weights)), 2))

Ở đây mình thấy density sẽ phụ thuộc vào $n$, $k$ và max($arr_i$) $n$, $k$ là $2$ số cố định rồi, vì thế mình chỉ có thể thay đổi các số trong arr. Tuy nhiên chúng ta sẽ phải thay đổi như thế nào?

Cách 1:

Mình sẽ nhân tất cả phần tử của arr một lượng scale_factor nào đó sao cho density mình đủ nhỏ để giải. Khi này mình tự gen số và nhận thấy cách này cũng không cho mình đáp án mà mình mong muốn

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
scale_factor = 100000

arr = [ randint(1,1000000000000) for i in range(40) ]
arr_ = [scale_factor * x for x in arr]

for i in range(len(arr)):
    assert arr[i] * scale_factor == arr_[i]

k = bytes_to_long(secret)
s = 0
s_ = 0 

ans = [0 for i in range(40)]

for i in range(40):
    if k&(1<<i):
        s+=arr[i]
        s_ += arr_[i]
        ans[i] = 1

print(f"Wanted: {ans}")

sol = subset_sum(arr_, s_, verbose=True)

print(sol)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(sage) elita@tehoang:/mnt/d/CTF/practice/idk/WU$ python3 test-knapsack.sage.py
Wanted: [0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0]
[subset_sum] Density: 0.7087
[subset_sum] Lattice dimensions: (41, 42)
[subset_sum] Lattice reduction took 0.018s
None
(sage) elita@tehoang:/mnt/d/CTF/practice/idk/WU$ python3 test-knapsack.sage.py
Wanted: [1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1]
[subset_sum] Density: 0.7087
[subset_sum] Lattice dimensions: (41, 42)
[subset_sum] Lattice reduction took 0.021s
None
(sage) elita@tehoang:/mnt/d/CTF/practice/idk/WU$ python3 test-knapsack.sage.py
Wanted: [1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1]
[subset_sum] Density: 0.7085
[subset_sum] Lattice dimensions: (41, 42)
[subset_sum] Lattice reduction took 0.019s
None
(sage) elita@tehoang:/mnt/d/CTF/practice/idk/WU$ python3 test-knapsack.sage.py
Wanted: [1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1]
[subset_sum] Density: 0.7084
[subset_sum] Lattice dimensions: (41, 42)
[subset_sum] Lattice reduction took 0.015s
None

Do đó mình đã nghĩ thêm cách khác

Cách 2

Do density chỉ phụ thuộc vào số lớn nhất trong trong arr, do đó mình sẽ thử chỉ thay đổi một vài số, tuy nhiên mình cần chọn những số không được cộng vào trong s, vì nếu như cộng vào trong s thì mình sẽ phải thay đổi s nhưng lúc làm bài thật thì làm sao mà mình biết được số nào được cộng và số nào không (mình đang đi tìm điều đó mà :D).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
secret = randbytes(5)
key = hashlib.sha256(secret).digest()[:16]

arr = [ randint(1,1000000000000) for i in range(40) ]

k = bytes_to_long(secret)
s = 0

ans = [0 for i in range(40)]

cnt = 0 

for i in range(40):
    if k&(1<<i):
        s+=arr[i]
        ans[i] = 1
    elif cnt < 10:
        arr[i] = 2^640 
        cnt += 1

print(f"Wanted: {ans}")

sol = subset_sum(arr, s, verbose=True)

print(sol)

Ở đây mình đã chọn 10 số đầu tiên không nằm trong tổng thành $2^{640}$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(sage) elita@tehoang:/mnt/d/CTF/practice/idk/WU$ python3 test-knapsack.sage.py
Wanted: [1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1]
[subset_sum] Density: 0.0625
[subset_sum] Lattice dimensions: (41, 42)
[subset_sum] Lattice reduction took 0.030s
[1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1]
(sage) elita@tehoang:/mnt/d/CTF/practice/idk/WU$ python3 test-knapsack.sage.py
Wanted: [1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1]
[subset_sum] Density: 0.0625
[subset_sum] Lattice dimensions: (41, 42)
[subset_sum] Lattice reduction took 0.034s
None
(sage) elita@tehoang:/mnt/d/CTF/practice/idk/WU$ python3 test-knapsack.sage.py
Wanted: [0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0]
[subset_sum] Density: 0.0625
[subset_sum] Lattice dimensions: (41, 42)
[subset_sum] Lattice reduction took 0.033s
None
(sage) elita@tehoang:/mnt/d/CTF/practice/idk/WU$ python3 test-knapsack.sage.py
Wanted: [0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1]
[subset_sum] Density: 0.0625
[subset_sum] Lattice dimensions: (41, 42)
[subset_sum] Lattice reduction took 0.035s
[0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1]

Lúc thì ra kết quả, lúc thì không :D. Tuy nhiên như vậy là quá đủ so với 1 đứa như mình, mình thử luôn cách này cho bài mình đang làm nào :D.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import itertools

poss = itertools.combinations(range(40), 4)

poss = list(poss)

t = 1

for cur_comb in poss:
    print(t)
    nums = [x for x in numbers]
    for a in cur_comb:
        nums[a] = 2 ** 640 
    x = subset_sum(nums, sum, verbose=True)
    if x != None:
        print(x)
        break
    t += 1 

Như mình đã nói, lúc làm bài thật thì mình sẽ không biết những vị trí nào không được cộng vào trong tổng, do đó mình sẽ phải tạo ra tổ hợp các index để thay từ từ. Ở đây mình chọn thay 4 vị trí.

Chạy tới lần 305 thì mình đã có 1 vector thỏa yêu cầu :D

1
2
3
4
5
305
[subset_sum] Density: 0.0625
[subset_sum] Lattice dimensions: (41, 42)
[subset_sum] Lattice reduction took 0.033s
[0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0]

Decrypt lấy flag thôi :D

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
t = [0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0]

from Crypto.Util.number import long_to_bytes as ltb

secret = "".join(str(x) for x in t[::-1])

secret = int(secret, 2)

secret = ltb(secret) 

key = hashlib.sha256(secret).digest()[:16]

cipher = AES.new(key, AES.MODE_ECB)

print(cipher.decrypt(bytes.fromhex(Ciphertext)))

# flag{N0t_r34dy_f0r_M3rkl3-H3llman}

ColL3g10n

Alt text

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
from hashlib import md5, sha256
import random
from Crypto.Util.number import *

alphanum = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'

flag = "This flag has been REDACTED"
secret = ''.join(random.choices(alphanum,k=10))
sec_num = bytes_to_long(secret.encode())

history = []
tries = 3
while tries:
    print(f"Tries left : {tries}")
    print("Provide the hex of first message: ",end='')
    m1 = bytes.fromhex(input())
    print("Provide the hex of second message: ",end='')
    m2 = bytes.fromhex(input())
    if m1==m2:
        print("Do you take me as a fool?. Give me 2 different messages.\n")
        tries-=1
        continue
    if m1[0] in history or m2[0] in history:
        print("You have already provided messages with the same first byte. You still lose a try.\n")
        tries-=1
        continue
    if md5(m1).hexdigest()==md5(m2).hexdigest() and sha256(m1).hexdigest()[:5]==sha256(m2).hexdigest()[:5]:
        history.append(m1[0])
        print("Wow! Both messages have the same signature.")
        print("Okay I can reveal some part of the secret to you. Give me a number with no more than 24 set bits and I will reveal those bits of the secret to you.")
        print("num: ",end='')
        num = int(input())
        bit_count = bin(num).count('1')
        if bit_count>24:
            print("Read the rules properly. No more than 24 bits allowed.\n")
            tries-=1
            continue
        print(f"Revealing bits : {sec_num&num}\n")
        tries-=1
        continue
    else:
        print("The signatures don't match.\n")
        tries-=1
        continue

print("Can you Guess the secret: ",end='')
guess = input()
if guess == secret:
    print(f"You gussed correctly. Here is your flag: {flag}")
else:
    print(f"Wrong guess. Bye bye!")

Đây là một bài hash collision. Cụ thể chúng ta sẽ cần phải collision 1 lần, mỗi lần là 1 cặp (m0, m1) sao cho md5(m1).hexdigest()==md5(m2).hexdigest() and sha256(m1).hexdigest()[:5]==sha256(m2).hexdigest()[:5] cùng thời cùng với đó, byte đầu tiên của cặp sau phải những cặp trước

1
2
3
4
if m1[0] in history or m2[0] in history:
        print("You have already provided messages with the same first byte. You still lose a try.\n")
        tries-=1
        continue

Chắc cái này để không cho chúng ta chơi 1 cặp 3 lần thôi chứ không có gì

Mỗi lần mình collision thành công thì đề sẽ cho mình biết tối đa là 24 bits của secret. Tới đây thì mình check xem secret khoảng bao nhiêu bits.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
In [8]: def lmao():
   ...:     secret = ''.join(random.choices(alphanum,k=10))
   ...:     sec_num = bytes_to_long(secret.encode())
   ...:     return sec_num.bit_length()
   ...:

In [9]: print(lmao())
78

In [10]: print(lmao())
79

In [11]: print(lmao())
79

In [12]: print(lmao())
79

In [13]: print(lmao())
79

Hmm vậy là khoảng $79$ bits, vậy mình có thể có $24 * 3 = 72$ bits, vậy mình còn $7$ bits chưa biết. Tới đây thì chiến thuật của mình là lấy các bits từ trái sang phải, còn lại $7$ bits cuối cùng không biết thì mình ngồi test local xem cái nào có khả năng xuất hiện nhiều rồi quất cái đó luôn =))). Mình sẽ remote server tới khi nó đúng thì thôi. Ở đây mình đã chọn $7$ bits cuối cùng của mình là 0110000

  • Fun fact: lúc làm bài mình đã nhận ra rằng mình có thể biết đến $73$ bits do bits đầu tiên sẽ luôn luôn là $1$, vì thế mình không cần phải lấy bits đầu tiên nữa, với điều này thì chúng ta chỉ còn lại $6$ bits chưa biết. Tuy nhiên mình lại nhận thấy chưa biết $6$ hay chưa biết $7$ thì cũng không ảnh hưởng quá nhiều tới tốc độ ra đáp án đúng, thậm chí có những lúc chọn $7$ bits như mình ở trên cho ra output đúng nhanh hơn cả $1$ vài $6$ bits bất kì

Nói đủ rồi giờ mình exploit thôi =)))

Cái chính của bài này vẫn là phần hash collision. Từ trước đến giờ mình vẫn luôn luôn xem MD5 như 1 block cipher, tức là output của state hiện tại sẽ phụ thuộc vào state trước và nếu như mình có 1 cặp (m0, m1) sao cho md5(m0) = md5(m1) và độ dài của m0, m1 vừa đủ số nguyên lần block (tức là bội của 64 bytes) thì mình có quyền pad thêm 1 đoạn s và vẫn sẽ có được md5(m0 + s) = md5(m1 + s). Tới đây thì mình chỉ cần random s đến khi nào thỏa mãn điều kiện phía sau tức sha256(m1).hexdigest()[:5]==sha256(m2).hexdigest()[:5]. Đây chỉ là 5 characters hexa nên brute cũng khá nhanh thôi. Lúc làm bài thì mình đã sử dụng Hashclash trước để tìm m0, m1 sao cho cùng MD5. Có rồi thì brute như trên hoy.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from hashlib import md5, sha256
import os 

# Từ hashclash
with open('../collision1.bin', "rb") as f:
    a = f.read()

with open('../collision2.bin', "rb") as f:
    b = f.read()

assert len(a) == len(b) and len(a) % 64 == 0 and a != b

assert md5(a).hexdigest() == md5(b).hexdigest()

s = os.urandom(5)

while sha256(a + s).hexdigest()[:5] != sha256(b + s).hexdigest()[:5]:
    s = os.urandom(5)

a += s 
b += s 

assert md5(a).hexdigest() == md5(b).hexdigest() and sha256(a).hexdigest()[:5] == sha256(b).hexdigest()[:5]

print("OK found")

Mình làm 3 lần như vậy, mỗi lần nhớ để byte đầu tiên khác đi, sau đó chỉ cần lấy bits rồi remote tới khi nó đúng hoy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
from pwn import *
from Crypto.Util.number import *
import random

alphanum = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'

a1 = ['5445535440b011627e6079b6b165aa0273d88817f6b3b8b2d9222354701611a383cbba524286e64db3875fd151455dfb3abc096cc945863e2efda21b6e327dc9422703cbcf694a9bcf60fe1537b0ff74e3f93c3529352c27680b2120306805091ba661f6685dc46276f99b6afd5269a4cb02059cef0403e44ab21e2d14eef4f5d4be745763de0e5f76a00caa763bf540', '4553545429080e76bdbee8adef684aafec31abae8ea74d2b26a84cb9d0ea89cb499b553ad309d01c93b332390c5577badc8aff68cb7083222cc4de5f23619139075c99bfbfe65724bc5bc4c8c51ac0305b92ab0697fa5c11b11a1f7a419b4fb212af0926a8081988115c77bfe3eb1b80533fbbf7974f80e3135cc022b4328a5c062d8c76b899c83c68c11b0675ae0ef6', '53455454ea2e55fec1696eb5388dd93dd326b9127b4a8402ce72d8e02bf19baaacf96f40276dcb0b44a6b401a83547e3fd89d53d183084ff4e009bffabcf9bc642c1528901b6a6a014bdd783547555ced1d946b9a13aae4fc2b4103e06355a76e10d0ac2ccfe0563cffe0a4a785f2ceaa602081303ca229445cd2055bf4b5361a1fb148bca069a131dbc518ad0a4e205']
b1 = ['5445535440b011627e6179b6b165aa0273d88817f6b3b8b2d9222354701611a383cbba524286e64db3875fd151455dfb3abc096cc945863e2efda21b6e327dc9422703cbcf694a9bcf5ffe1537b0ff74e3f93c3529352c27680b2120306805091ba661f6685dc46276f99b6afd5269a4cb02059cef0403e44ab21e2d14eef4f5d4be745763de0e5f76a00caa763bf540', '4553545429080e76bdbfe8adef684aafec31abae8ea74d2b26a84cb9d0ea89cb499b553ad309d01c93b332390c5577badc8aff68cb7083222cc4de5f23619139075c99bfbfe65724bc5ac4c8c51ac0305b92ab0697fa5c11b11a1f7a419b4fb212af0926a8081988115c77bfe3eb1b80533fbbf7974f80e3135cc022b4328a5c062d8c76b899c83c68c11b0675ae0ef6', '53455454ea2e55fec16a6eb5388dd93dd326b9127b4a8402ce72d8e02bf19baaacf96f40276dcb0b44a6b401a83547e3fd89d53d183084ff4e009bffabcf9bc642c1528901b6a6a014bcd783547555ced1d946b9a13aae4fc2b4103e06355a76e10d0ac2ccfe0563cffe0a4a785f2ceaa602081303ca229445cd2055bf4b5361a1fb148bca069a131dbc518ad0a4e205']
num = ['1' * 24 + '0' * (79 - 24), '0' * 24 + '1' * 24 + '0' * (79 - 24 * 2), '0' * (24 * 2) + '1' * 24 + '0' * (79 - 24 * 3)]

for a in num:
    assert len(a) == 79 

cnt = 0

while True:
    cnt += 1
    t = ''
    print(cnt)
    io = remote('34.70.212.151', 8000)
    for i in range(3):
        io.sendlineafter(b'first message: ', a1[i])
        io.sendlineafter(b'second message: ', b1[i])
        io.sendlineafter(b'num: ', str(int(num[i], 2)))
        data = int(io.recvline().strip().split(b'Revealing bits : ')[-1])
        # print(data)
        t += bin(data)[2:][:24]
    t = t + '0110000'
    assert len(t) == 79
    try:
        t = long_to_bytes(int(t, 2)).decode()
    except UnicodeDecodeError: continue
    if len(t) != 10: continue
    print(t)
    # exit()
    io.sendlineafter(b'Can you Guess the secret:', t)
    data = io.recvline().decode()
    print(data)
    if 'flag' in data:
        break 
    if 'Wrong guess. Bye bye!' not in data:
        print(data)
        break  
    else:
        print("failed")
        io.close()

Cái code này mình viết lúc 4 giờ sáng nên nhìn nó ngu điên =))))

  • Note: Sau giải mình có biết thêm 1 trò đó chính là chúng ta đi send -1 vào để and bits =))) lúc này thay vì phải làm 3 lần thì có thể làm 1 lần thôi vì -1 & a = a trong python =)))
1
2
3
4
5
6
7
8
9
10
alphanum = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'

secret = ''.join(random.choices(alphanum,k=10))
sec_num = bytes_to_long(secret.encode())
num = -1 
bit_count = bin(num).count('1')
assert bit_count <= 24

print(sec_num)
print(sec_num&num)
1
2
271064412279359715358026
271064412279359715358026

Vjp pr0 thật sự =))

Curvy Curves

Alt text

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
from Crypto.Util.number import getRandomNBitInteger, bytes_to_long, long_to_bytes
from sage.all import *

# non-residue
D = 136449572493235894105040063345648963382768741227829225155873529439788000141924302071247144068377223170502438469323595278711906213653227972959011573520003821372215616761555719247287249928879121278574549473346526897917771460153933981713383608662604675157541813068900456012262173614716378648849079776150946352466

# redacted
p = "REDACTED" 
q = "REDACTED"

# n = p*q
n = 22409692526386997228129877156813506752754387447752717527887987964559571432427892983480051412477389130668335262274931995291504411262883294295070539542625671556700675266826067588284189832712291138415510613208544808040871773692292843299067831286462494693987261585149330989738677007709580904907799587705949221601393

flag = b"flag{REDACTED}"

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __add__(self, other):
        x = (self.x*other.x + D*self.y*other.y)%n
        y = (self.y*other.x + self.x*other.y)%n
        return Point(x, y)
    def __mul__(self, d):
        Q = Point(1, 0)
        P = Point(self.x, self.y)
        while d != 0:
            if d&1 == 1:
                Q += P
            P += P
            d >>= 1
        return Q
    def __str__(self) -> str:
        return f"{self.x}, {self.y}"

def check_residue(y):
    if pow(y, (p - 1)//2, p) == 1 and pow(y, (q - 1)//2, q) == 1:
        return True
    return False

def gen_point():
    while True:
        x = getRandomNBitInteger(1023 - 240)
        x = bytes_to_long(flag + long_to_bytes(x))
        x %= n
        y2 = ((x*x - 1)*pow(D, -1, n))%n
        if(check_residue(y2)):
            yp = pow(y2, (p + 1) // 4, p)
            yq = pow(y2, (q + 1) // 4, q)
            y = crt([yp, yq], [p, q])
            return Point(x, y)

M = gen_point()
e = 65537
C = M*e
print(C)
# Cx = 10800064805285540717966506671755608695842888167470823375167618999987859282439818341340065691157186820773262778917703163576074192246707402694994764789796637450974439232033955461105503709247073521710698748730331929281150539060841390912041191898310821665024428887410019391364779755961320507576829130434805472435025, Cy = 2768587745458504508888671295007858261576650648888677215556202595582810243646501012099700700934297424175692110043143649129142339125437893189997882008360626232164112542648695106763870768328088062485508904856696799117514392142656010321241751972060171400632856162388575536779942744760787860721273632723718380811912

Đầu tiên mình đi xác định xem con flag nằm ở chỗ nào rồi sẽ đi ngược từ đó.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def gen_point():
    while True:
        x = getRandomNBitInteger(1023 - 240)
        x = bytes_to_long(flag + long_to_bytes(x))
        x %= n
        y2 = ((x*x - 1)*pow(D, -1, n))%n
        if(check_residue(y2)):
            yp = pow(y2, (p + 1) // 4, p)
            yq = pow(y2, (q + 1) // 4, q)
            y = crt([yp, yq], [p, q])
            return Point(x, y)

M = gen_point()
e = 65537
C = M*e
print(C)

Oke, vậy là ban đầu người ta sẽ tạo ra một điểm $M(x_M, y_M)$ thuộc cái curve gì đấy mà mình chưa đọc, trong đó $x$ sẽ chứa flag. Người ta đưa mình điểm $C= e * M$ với $ e = 65537$. Đề bài nghe rất Elliptic Curve nhưng script thì lại rất RSA =)))). Và như mình đã nói ở bài PRSA, khi gặp những bài như thế này thì chúng ta nên đi xác định xem cái nhóm mà chúng ta đang làm việc là nhóm gì, có order như thế nào từ đó ta có thể truy ngược lại $M$ từ $C$.

1
2
# non-residue
D = 136449572493235894105040063345648963382768741227829225155873529439788000141924302071247144068377223170502438469323595278711906213653227972959011573520003821372215616761555719247287249928879121278574549473346526897917771460153933981713383608662604675157541813068900456012262173614716378648849079776150946352466

Mình không hiểu sao nhưng mỗi lần mình thấy đề bài mà có con $D$ như trên thì mình luôn nghĩ tới phương trình Pell, và mình có biết một vài bài có isomorphism từ một đường cong $E$ sang một đường cong gồm các nghiệm của phương trình Pell có order là $p+1$. Cộng thêm việc đề bài có đề cập đến smooth. Mình đã đoán rằng $n = p * q$ với $(p + 1)$ smooth. Lúc này mình đã thử dùng thuật toán William p+1 để factor $n$…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from primefac import *  


D = 136449572493235894105040063345648963382768741227829225155873529439788000141924302071247144068377223170502438469323595278711906213653227972959011573520003821372215616761555719247287249928879121278574549473346526897917771460153933981713383608662604675157541813068900456012262173614716378648849079776150946352466

n = 22409692526386997228129877156813506752754387447752717527887987964559571432427892983480051412477389130668335262274931995291504411262883294295070539542625671556700675266826067588284189832712291138415510613208544808040871773692292843299067831286462494693987261585149330989738677007709580904907799587705949221601393

p = williams_pp1(n)

q = n // p 

assert p * q == n

print(p)

Và kết quả là….

p = 1779816733304736381084864812803761214302272494619817595962262887353495881068607828373749191179960417738609613170856737290048073479147894242862637932199748403

Nó ra thật =)))

1
2
3
sage: p = 1779816733304736381084864812803761214302272494619817595962262887353495881068607828373749191179960417738609613170856737290048073479147894242862637932199748403
sage: factor(p+1)
2^2 * 268547 * 271969 * 274213 * 287501 * 294181 * 304357 * 306517 * 309433 * 320209 * 333491 * 337277 * 341953 * 351587 * 351749 * 354139 * 379133 * 386651 * 387253 * 395849 * 409523 * 414503 * 448013 * 449629 * 454609 * 455899 * 461323 * 470153 * 488407

Như các bạn có thể thấy, $(p+1)$ smooth như cái cách mà mình tuột khỏi bảng xếp hạng top 10

Sau đó thì mình check được order dưới mod p là (p + 1) và tương tự với mod q, do đó mình lại sử dụng CRT để tìm lại x trong mod n và lấy flag thôi. Bài troll

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
from Crypto.Util.number import *
from primefac import *  
from sage.all import *

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __add__(self, other):
        x = (self.x*other.x + D*self.y*other.y)%n
        y = (self.y*other.x + self.x*other.y)%n
        return Point(x, y)
    def __mul__(self, d):
        Q = Point(1, 0)
        P = Point(self.x, self.y)
        while d != 0:
            if d&1 == 1:
                Q += P
            P += P
            d >>= 1
        return Q
    def __str__(self) -> str:
        return f"{self.x}, {self.y}"


D = 136449572493235894105040063345648963382768741227829225155873529439788000141924302071247144068377223170502438469323595278711906213653227972959011573520003821372215616761555719247287249928879121278574549473346526897917771460153933981713383608662604675157541813068900456012262173614716378648849079776150946352466

n = 22409692526386997228129877156813506752754387447752717527887987964559571432427892983480051412477389130668335262274931995291504411262883294295070539542625671556700675266826067588284189832712291138415510613208544808040871773692292843299067831286462494693987261585149330989738677007709580904907799587705949221601393

p = 1779816733304736381084864812803761214302272494619817595962262887353495881068607828373749191179960417738609613170856737290048073479147894242862637932199748403

q = n // p 

assert p * q == n

t = crt([p + 1, q + 1], [p, q])

Cx = 10800064805285540717966506671755608695842888167470823375167618999987859282439818341340065691157186820773262778917703163576074192246707402694994764789796637450974439232033955461105503709247073521710698748730331929281150539060841390912041191898310821665024428887410019391364779755961320507576829130434805472435025 
Cy = 2768587745458504508888671295007858261576650648888677215556202595582810243646501012099700700934297424175692110043143649129142339125437893189997882008360626232164112542648695106763870768328088062485508904856696799117514392142656010321241751972060171400632856162388575536779942744760787860721273632723718380811912

e = 65537 

C = Point(Cx, Cy)

xp = (C * pow(e, -1, p + 1)).x
xq = (C * pow(e, -1, q + 1)).x

x = crt([xp, xq], [p, q])

print(long_to_bytes(x))

# flag{pHUCk_150M0rPh15m_1n70_p2}
  • Note: Sau khi CTF kết thúc, mình nhận ra có vài người đã giải bài này bằng cách dùng factordb để factor $n$. Có vẻ như có ai đó đã factor $n$ vài phút sau khi CTF bắt đầu và đưa lên database của factordb =)))).

Safe Curvy Curve

Alt text

Đây là version tiếp theo của bài trước, cùng mình xem nó khác gì nhé

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
from Crypto.Util.number import getPrime, getRandomNBitInteger, bytes_to_long, long_to_bytes
from sage.all import *

n = 325325755182481058670589439237195225483797901549838835146388756505962515992731682902174843378473793118013824587686743801443229435097379024643408429957717590072275498734396840489261986361764935791083084431387935565119970246214821662977047205360090509590387268197591520307878877103247121412898793371812283196864079961633428662039795223806526580626911586062085681619829271800589543048515261459541945856915535056363750996243146294104205192635607675773145823355324367791593757860302117944097894321078809559514655550115178790472355420484756646090492860950760854985816746318432545157312928351659730275368368710393481
# non-residue
D = 104195424559311137181271279442498654957365659039072230133307906910289876977215387204406606621273182995744469913980318794193540266885069529501579897759819085330481440863835062469771268905581027223137570462332151650553239341513789960350350245008144164176339007365876329719830893966710072622193879546377346644622

# redacted
p = "REDACTED"
q = n // p

flag = b"REDACTED"

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __add__(self, other):
        x = (self.x*other.x + D*self.y*other.y)%n
        y = (self.y*other.x + self.x*other.y)%n
        return Point(x, y)
    def __mul__(self, d):
        Q = Point(1, 0)
        P = Point(self.x, self.y)
        while d != 0:
            if d&1 == 1:
                Q += P
            P += P
            d >>= 1
        return Q
    def __str__(self) -> str:
        return f"{self.x}, {self.y}"

def check_residue(y):
    if pow(y, (p - 1)//2, p) == 1 and pow(y, (q - 1)//2, q) == 1:
        return True
    return False

def gen_point():
    while True:
        x = getRandomNBitInteger(2000 - 464)
        x = bytes_to_long(flag + long_to_bytes(x))
        x %= n
        y2 = ((x*x - 1)*pow(D, -1, n))%n
        if(check_residue(y2)):
            yp = pow(y2, (p + 1) // 4, p)
            yq = pow(y2, (q + 1) // 4, q)
            y = crt([yp, yq], [p, q])
            return Point(x, y)

def add(a, b):
    if a == "i":
        return b
    return (D + a*b)*pow(a + b, - 1, n)%n

def power(a, d):
    res = "i"
    x = a
    while d != 0:
        if d&1 == 1:
            res = add(res, x)
        x = add(x, x)
        d >>= 1
    return res

def point(m):
    return Point((m**2 + D)*pow(m**2 - D, -1, n)%n, 2*m*pow(m*m - D, -1, n)%n)

M = gen_point()
Mx, My = M.x, M.y

assert (Mx**2 - D*My**2)%n == 1

e = 65537

C = M*e
print(C)
# 162961013908573567883708515220539578804107270477973090499542519193835498763624042601337274226440312066476160075983577251445943502548471650694617020354363039427231006183897304593798946447482514180041848851245384617847515040075915213698357885114317390276898478740949700799011676589252761736123613456698426984426140183972297018822418150981970598934664464599487393545946613698687046341448558294255407327000026161574779286948527797313848936686929888861150592859573221003557737086661845641459024783317432643308034709718127845066442672512067552552244030535478274512743971354192981490674475749853430371212307520421190, 239668740066200913943562381887193634676899220686058888912170632953311361194017666904277854011894382855202483252661143091091993190645295938873381009873178536800816517615786834926664362950059833369410239281041504946121643304746955139178731148307192034732522723412439705530514113775245607466575056817920259552225402143084439183697819510515790086246564683416312338905722676300264277098153045593619223527605305078928548583405756368607546463429755374790443440435124030514385157787083987202901009878849122637030860266764644062073632184729561756322435322264642199576207018861730005366413597578298926774145872631956206

Hmm…Các bạn có biết nó khác gì không

Mình cũng không biết nữa =))) mình thấy nó lại xuất hiện phương trình Pell này =))

assert (Mx**2 - D*My**2)%n == 1

Mình thử lại code cũ xem nó có ra không…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
n = 325325755182481058670589439237195225483797901549838835146388756505962515992731682902174843378473793118013824587686743801443229435097379024643408429957717590072275498734396840489261986361764935791083084431387935565119970246214821662977047205360090509590387268197591520307878877103247121412898793371812283196864079961633428662039795223806526580626911586062085681619829271800589543048515261459541945856915535056363750996243146294104205192635607675773145823355324367791593757860302117944097894321078809559514655550115178790472355420484756646090492860950760854985816746318432545157312928351659730275368368710393481

p = williams_pp1(n)

q = n // p 

assert p * q == n

D = 104195424559311137181271279442498654957365659039072230133307906910289876977215387204406606621273182995744469913980318794193540266885069529501579897759819085330481440863835062469771268905581027223137570462332151650553239341513789960350350245008144164176339007365876329719830893966710072622193879546377346644622

e = 65537

Cx, Cy = 162961013908573567883708515220539578804107270477973090499542519193835498763624042601337274226440312066476160075983577251445943502548471650694617020354363039427231006183897304593798946447482514180041848851245384617847515040075915213698357885114317390276898478740949700799011676589252761736123613456698426984426140183972297018822418150981970598934664464599487393545946613698687046341448558294255407327000026161574779286948527797313848936686929888861150592859573221003557737086661845641459024783317432643308034709718127845066442672512067552552244030535478274512743971354192981490674475749853430371212307520421190, 239668740066200913943562381887193634676899220686058888912170632953311361194017666904277854011894382855202483252661143091091993190645295938873381009873178536800816517615786834926664362950059833369410239281041504946121643304746955139178731148307192034732522723412439705530514113775245607466575056817920259552225402143084439183697819510515790086246564683416312338905722676300264277098153045593619223527605305078928548583405756368607546463429755374790443440435124030514385157787083987202901009878849122637030860266764644062073632184729561756322435322264642199576207018861730005366413597578298926774145872631956206

C = Point(Cx, Cy)

xp = (C * pow(e, -1, p + 1)).x
xq = (C * pow(e, -1, q + 1)).x

x = crt([xp, xq], [p, q])

print(long_to_bytes(x))

# flag{p+1_factor_AND_150M0rPh15m_1n70_p2}

Code mình chả khác gì bài trước, chỉ thay đổi $n$, $D$, $C$ thôi

Thêm một lần hụt first blood

Alt text

  • Note sau CTF: Alt text

Thế thì chịu lmao

Secure Matrix Transmissions

Alt text

Nghe tên là biết sắp phải làm việc với ma trận òi

Script đề bài:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
from sage.all import *
from Crypto.Util.number import bytes_to_long, long_to_bytes
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import random
import os
import hashlib
from base64 import b64encode, b64decode

FLAG = b'flag{REDACTED}' ## removed the flag

f = open('./intercepted.txt', 'w')

p = 33184772290615481426295675425316668758122179640330548849957081783509
N = 6
gl = GL(N, GF(p))

secret = matrix(gl.random_element())
secret_inv = secret ** (-1)

def encrypt(flag, shared_secret):
    i = 0
    key = 0
    for row in shared_secret:
        for item in row:
            key += item ** i
            i += 1

    key = hashlib.sha256(long_to_bytes(int(key))).digest()
    iv = os.urandom(16)

    cipher = AES.new(key, AES.MODE_CBC, iv)
    encrypted_text = cipher.encrypt(pad(flag, AES.block_size))

    return b64encode(iv + encrypted_text).decode("utf-8")

def decrypt(cipher, shared_secret):
    iv, enc = b64decode(cipher)[:16], b64decode(cipher)[16:]
    i = 0
    key = 0
    for row in shared_secret:
        for item in row:
            key += item**i
            i += 1

    key = hashlib.sha256(long_to_bytes(int(key))).digest()
    cipher = AES.new(key, AES.MODE_CBC, iv)

    flag = cipher.decrypt(enc)
    return unpad(flag, AES.block_size)

def secure_key_gen():
    temp = zero_matrix(GF(p), N)
    for i in range(N):
        temp[i, i] = random.randint(1, p - 1)
    return temp

def gen_params():
    
    A = matrix(gl.random_element())
    B = matrix(gl.random_element())

    while A.det() == 0 or B.det() == 0:
        A = matrix(gl.random_element())
        B = matrix(gl.random_element())

    f.write(f"A = \n{A}\n")   ## intercepted successfully!  
    f.write(f"B = \n{B}\n")   ## intercepted successfully!

    return A, B

## generate public parameters
A, B = gen_params()

## for Alice
a = secure_key_gen()

## Sends to Bob
u = secret_inv * A * secret * a * secret_inv * A ** (-1) * secret
f.write(f"u = \n{u}\n")   ## intercepted successfully!


## for Bob
b = secure_key_gen()
a_ = secret_inv * A ** (-1) * secret * u * secret_inv * A * secret
key_b = a_ + b

## Sends to Alice
v = secret_inv * B * secret * b * secret_inv * B ** (-1) * secret
f.write(f"v = \n{v}\n")   ## intercepted successfully!

## for Alice
b_ = secret_inv * B ** (-1) * secret * v * secret_inv * B * secret
key_a = b_ + a

## Sends to Bob
ct1 = encrypt(b'send me the flag', key_a)  
f.write(f"ct1 = {ct1}\n")   ## intercepted successfully!

## for Bob
f.write(f"{decrypt(ct1, key_b).decode()}\n")

## Sends to Alice
ct2 = encrypt(FLAG, key_b)
f.write(f"ct2 = {ct2}\n")   ## intercepted successfully!

## Read the Flag
# f.write(decrypt(ct2, key_a).decode())

Các bạn thấy dòng cuối của đề bài không f.write(decrypt(ct2, key_a).decode()). Đây cũng chính là mục tiêu của tụi mình.

Bây giờ ct2 thì đề bài đã cho rồi, vậy thì mình cũng nhau xem key_a là gì

key_a = b_ + a với b_ = secret_inv * B ** (-1) * secret * v * secret_inv * B * secreta = secure_key_gen()

Thường mà trong CTF mình thấy hàm nào có chữ secure là mình nghi ngờ lắm =)). Nên mình sẽ đi xem a là gì trước

1
2
3
4
5
def secure_key_gen():
    temp = zero_matrix(GF(p), N)
    for i in range(N):
        temp[i, i] = random.randint(1, p - 1)
    return temp

Hmm ok, có vẻ a là một ma trận đường chéo $6\times6$ trong $GF(p)$. Chưa có vẻ gì là sus lắm, mình tiếp tục xem tiếp đề bài.

Do là chúng ta muốn tìm được key_a thì phải tìm được a nên mình xem những gì liên quan đến a mà mình biết. Ở đây ta có u = secret_inv * A * secret * a * secret_inv * A ** (-1) * secret với u, A được cho trước. Nếu mình nhìn kỹ 1 chút và viết lại secret_inv * A * secret = M_a thì secret_inv * A ** (-1) * secret chính là M_a ** (-1) tức ma trận nghịch đảo của ma trận M_a. Lúc này mình có thể viết lại

\[u = M_a a M_a^{-1}\]

Với a là một ma trận đường chéo. Lúc này mình đã ra được ý tưởng làm bài bởi vì công thức trên chính là Jordan form của u

Rồi a có vẻ mình sẽ tìm được…bây giờ thử coi b_ là gì

b_ = secret_inv * B ** (-1) * secret * v * secret_inv * B * secret

Do v mình đã được đề cho rồi nên nếu ta thay v = secret_inv * B * secret * b * secret_inv * B ** (-1) * secret vào b_ thì thấy b_ = b luôn mà b cũng được gen như a vậy là mình chỉ cần đổi Jordan form là xong bài

Hoặc là không =))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
p = 17
N = 4
gl = GL(N, GF(p))

secret = matrix(gl.random_element())
secret_inv = secret ** (-1)

A, B = gen_params()
a = secure_key_gen()
b = secure_key_gen()

u = secret_inv * A * secret * a * secret_inv * A ** (-1) * secret
v = secret_inv * B * secret * b * secret_inv * B ** (-1) * secret

Ju, Pu = u.jordan_form(transformation = True)
Jv, Pv = v.jordan_form(transformation = True)

border = '-' * 100

print(f"want a: \n{a}")

print(border)

print(f"test a: \n{Ju}")

print(border)

print(f"want b: \n{b}")

print(border)

print(f"test b: \n{Jv}")

Mình thử tự gen số nhỏ để test ý tưởng trước

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
want a:
[12  0  0  0]
[ 0  9  0  0]
[ 0  0  3  0]
[ 0  0  0 13]
----------------------------------------------------------------------------------------------------
test a:
[13| 0| 0| 0]
[--+--+--+--]
[ 0|12| 0| 0]
[--+--+--+--]
[ 0| 0| 9| 0]
[--+--+--+--]
[ 0| 0| 0| 3]
----------------------------------------------------------------------------------------------------
want b:
[15  0  0  0]
[ 0 10  0  0]
[ 0  0  1  0]
[ 0  0  0 13]
----------------------------------------------------------------------------------------------------
test b:
[15| 0| 0| 0]
[--+--+--+--]
[ 0|13| 0| 0]
[--+--+--+--]
[ 0| 0|10| 0]
[--+--+--+--]
[ 0| 0| 0| 1]

Để ý thấy thì ma trận a, b mình tự tính hơi khác cái đã được gen ở chỗ là nó đã được sort giảm dần trên cái đường chéo. Lúc này do thời gian cũng không còn nhiều và mình buồn ngủ =)) nên mình đã hoán vị trên đường chéo của 2 ma trận và test từng cái cho tới khi ra được flag. Điều này hoàn toàn khả thi do ma trận đề bài chỉ là $6 \times 6$ vậy mình chỉ cần khoảng $(6!)^2 + abc$ thao tác với abc là mấy cái thao tác decrypt rồi với vấn gì đấy.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
from sage.all import *
from Crypto.Util.number import bytes_to_long, long_to_bytes
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import random
import os
import hashlib
from base64 import b64encode, b64decode

p = 33184772290615481426295675425316668758122179640330548849957081783509
N = 6
gl = GL(N, GF(p))

def encrypt(flag, shared_secret):
    i = 0
    key = 0
    for row in shared_secret:
        for item in row:
            key += item ** i
            i += 1

    key = hashlib.sha256(long_to_bytes(int(key))).digest()
    iv = os.urandom(16)

    cipher = AES.new(key, AES.MODE_CBC, iv)
    encrypted_text = cipher.encrypt(pad(flag, AES.block_size))

    return b64encode(iv + encrypted_text).decode("utf-8")

def decrypt(cipher, shared_secret):
    iv, enc = b64decode(cipher)[:16], b64decode(cipher)[16:]
    i = 0
    key = 0
    for row in shared_secret:
        for item in row:
            key += item**i
            i += 1

    key = hashlib.sha256(long_to_bytes(int(key))).digest()
    cipher = AES.new(key, AES.MODE_CBC, iv)

    flag = cipher.decrypt(enc)
    # print(flag)
    return flag



u = [
[28879155726567049983454622837721881713004421469649633653126003880251, 22074946600328057026240224251921516029989227864483537878311928528684, 11985344636547053777186855301874485401032363227366605932898421631165, 11038047153398875511855062921770308445072978178537727544345865556069, 17913589562779438181451311229483343663752773108623377725718452883819, 6988548052196701160389870027180255419046432117124570998462066223760],
[12442646139394994295898159421691536460921770233410583384741128805851, 26791343238957883953511341730456926026845067572390616134588257575715, 20044295739622127408570744416765049400182932494144906328843165683951, 3716946288972247122675229801442487271330697050902983897960257015353, 28203806658422488413821254734838742146597120966298092240011671950418, 4762041594051527320517864505450207655232589320366293231953595725863],
[30088717492536940712495821833412730937575223939046572858904284613916, 27041904353687235206996420047457376344449825180129098056224307456338, 24437749819141358658247648214143755332360241994475937257542642573583, 7796437298901905297245658644368499053223939852560146715778064632640, 15614447029063100426172954288407641313530323021423668136405899310194, 8922479681646925758338291598256040373376681973788891756406422763139],
[19322178296033807354940969679463195308540921806504252678149867173138, 33097125072731086770607131468725547500825910975790412321782115869838, 12883750093703623804432147375290510155697961091062129358847651674123, 24597869343287697641622709721671426915780058312255624403180234192713, 26537027184297668426345397663386813121592656652583736980586291216445, 11617206120365401967915791192731000879817682674901777733903856797733],
[16710090447056060108731601191705411717377136736726300253063986063308, 13112164116392985346393033303671764991807289870465207976284880921022, 31389068001989884948885767386697332939461030078718007394707803846255, 29908327338977214304615826417150579857655888232250984362081852839023, 22583975168456327742148140296560922679546031771450762766874213823663, 20255781234412108781956807555843619052915858233204195711555194680279],
[ 8639975336305093270106069074993623126891247360031281653825740040741, 13403900498226599196478196563037659383351290165716358605156795361654, 19127443140313962513371049800518743806366324348931149667137542330176 , 1587790520831406950292877253889772565635165863077419349924612463554, 28260643245678615796033409984375030239968039321917236595612922928109, 27245620107342379746895845516019185969531890676595911744377909557532]]
v = [ 
[14593469507593520336534893606693100073219081551387449023943771554442, 2283021458111487923710380205365151066104282754263745192237326490551, 13170584523093998223191696419879547969801075848834311698387381471505, 19259980148204759114097202057965316584985415828667111050145931544542, 29885290219184042616179535742918968947354026457415023160998318453828, 15123772566136939254700272466681741447495954560621338927076401710465],
[8217862512033149496876384094296269532452490863111766288591338421520, 13729461020836068836298855334139849351346259147300854170266838049852, 11104728330778098008854657250493837339864716593096275438194673593723, 10798384644614928362418932155290042574495162355923347471159291144698, 31877189181091302604647976237742181979760578672930582051196549136046, 1918629350716436456009684853559872579413764560427000121836347921516],
[17260689732862422635435505221480922746751317064535215923993790192886, 15747877220363614821162725299191209258209710570887630567232690904197, 8617228034985901541131740069450254422228498135170302566742947957775, 14253933715820016671570267997896238444667901637428211288460788838246, 16499164346493998999442730191394982099986918128009049722691512093014, 13025912772710188038104092079604197742726564877681113284285303849554],
[ 2573456644853874338239370492651477088261797251129609392543920453838, 32896562598550390417662631073664427877599458467029721692315947839203, 21609625125792777949315379171355985793236349536655762275501735276637, 2064423690239912348059209794358643838603106021323566574579622769345, 10416995935649914543433851087690149206442589809600595549054529630023, 14743736977132006506906381861127077616280508458806441865058344748656],
[ 4225662325959455234970599933733361974460852167061568484358345163405, 10702969665926869787857799410201005975577054702738405546384443139142, 1359255767431309667828876308576650892896838633542052871017414741807, 31671777458706492709787940377250078973397972903701973598970849268174, 24125683246160911728533816896560068159204522435801109506125539244342, 6566591950072749090239378195580530026605442552103644998279546358496],
[ 2340617144223968993993626239720251771746071020532956496851092249201, 18464940637598472739277490361512029859881180718968424684424994902717, 27460971987555371094455429952593747078655439217048574014441041685569, 6667117338244005137290181664972559554258953283885089203943302963181, 20312635823091168798778853401004417385080412291250382174823545520392, 32296141232133239312731746201245526393343079356560448241095997297137]]
ct1 = 't22qVcUY/TYh9hwuASW/6PliQ03dLEWpOTqZHF82rQ/VqmqX7fj2l1YHzX7Cq0Sy'
ct2 = '8t5wC5gsdXOG7vDvw+uStOT1P2ZhB2FGsr/lvNaBMX+FAZ9aVY4UMOk8neL3KcELjQ3F62aX/sxN4KexO/krK2mm/v10qYrKbLPXljdwvck='


u = matrix(gl(u))
v = matrix(gl(v))

Ju, Pu = u.jordan_form(transformation = True)
Jv, Pv = v.jordan_form(transformation = True)

def get_per(a, permu):
    b = zero_matrix(GF(p), N)
    for i in range(len(permu)):
        b[i, i] = a[permu[i], permu[i]]

    return matrix(gl(b)) 

import itertools

per = list(itertools.permutations(range(int(6)), int(6)))

from tqdm import tqdm

while True:
    for p1 in tqdm(per):
        cur_ju = get_per(Ju, p1)
        for p2 in per[::-1]:
            cur_jv = get_per(Jv, p2)
            key_a = cur_ju + cur_jv
            f = decrypt(ct2, key_a)
            if b'flag' in f:
                print(f)
                exit()

# flag{In53cUr3_K3Y_3xch4ng3_0n_n0n-4b3L14n_gR0up5!!}

Mình đi ăn sáng với bạn, về nhà là lấy flag (khoảng 2 tiếng)

Rebellious

Alt text

Tiếp tục 1 bài Elliptic Curve

Script đề bài

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
from sage.all import *
from Crypto.Util.number import *
import random
import time
random.seed(time.time())


FLAG = b'flag{REDACTED}' ## flag removed
flag = bytes_to_long(FLAG)

f = open('./output.txt', 'w')

p = 2653051169113192956861029164111253541045934291082610203810454254019428617095444726883436858598608538656059147405437762506043477080240989129049112094108099635297397071457202368492446105766960803000479292350528610656494430695078098921185726745996662867511185527654339397
F = GF(p)

a, b = random.randint(2, p), random.randint(2, p)
f.write(f"a = {a}\nb = {b}")

def point_addition(px, py, qx, qy):
    rx = F((pow(a, -1, p) * (px * qx - py * qy * (a ** 2) * (pow(b ** 2, - 1, p)))) % p)
    ry = F((pow(a, -1, p) * (px * qy + py * qx)) % p)
    return (rx, ry)

def scalar_multiplication(px, py, k):
    rx = a
    ry = 0
    while k:
        if k % 2:
            rx, ry = point_addition(rx, ry, px, py)
        px, py = point_addition(px, py, px, py)
        k >>= 1
    return rx, ry

G = generator_curve() ## implementation hidden for enhancing security

x1 = G[0]
y1 = G[1]

H = scalar(multiplication(x1, y1, flag))

x2 = H[0]
y2 = H[1]

f.write(f"x1 = {x1}\n")
f.write(f"y1 = {y1}\n")
f.write(f"x2 = {x2}\n")
f.write(f"y2 = {y2}\n")

Đầu tiên là mình xác định flag nằm ở đâu

$ H = flag * G$

Với $H, G$ là $2$ điểm thuộc curve cho trước. Vậy đây là 1 bài DLP trong 1 cái group nào đấy. Thông thường cách mình tiếp cận những bài này sẽ là tìm một cái isomorphism sang một cái group khác mà việc giải DLP trên group mới dễ dành hơn so với group ban đầu. Vậy thế nào là dễ dàng? Mình thì chỉ biết làm DLP khi order smooth thôi nên mình cố gắng kiếm cái group nào mà có order như vậy.

Mình check $p-1$ thì thấy nó khá smooth:

1
2
3
sage: p = 2653051169113192956861029164111253541045934291082610203810454254019428617095444726883436858598608538656059147405437762506043477080240989129049112094108099635297397071457202368492446105766960803000479292350528610656494430695078098921185726745996662867511185527654339397
sage: factor(p-1)
2^2 * 2199768451 * 2523276191 * 2674991047 * 2737748831 * 3017308807 * 3074323591 * 3116578807 * 3169245991 * 3210267583 * 3218788571 * 3326149831 * 3346140859 * 3353408659 * 3355659943 * 3366693263 * 3388813691 * 3398273927 * 3463644143 * 3736045787 * 3745049927 * 3781238647 * 3803691487 * 3954763051 * 4038986663 * 4193283007 * 4196830147 * 4254066703 * 4258097359

Vậy lúc này mình sẽ cố gắng đi tìm $\phi$ là một isomorphism từ $F_p \times F_p$ $\rightarrow F_p$ (do $F_p$ có order là $(p - 1)$ smooth)

Đây cũng chính là phần quan trọng của bài này. Tới đây thì mình chỉ làm việc với giấy trắng và bút chì thôi. Vậy thì mình cần nháp cái gì?

Để dễ hình dung nhất thì mình đã thử $(x, y) + (x, y) = ???$

Dựa vào script của đề bài

1
2
3
4
def point_addition(px, py, qx, qy):
    rx = F((pow(a, -1, p) * (px * qx - py * qy * (a ** 2) * (pow(b ** 2, - 1, p)))) % p)
    ry = F((pow(a, -1, p) * (px * qy + py * qx)) % p)
    return (rx, ry)

Viết ra thì mình thấy

$(x, y) + (x, y) = (a^{-1}(x^2 - y^2 a^2 b^{-2}), a^{-1}2xy)$

Để tiện nhìn thì mình sẽ thay $D = -a^2b^{-2}$. Lúc này mình có

$(x, y) + (x, y) = (a^{-1}(x^2 + Dy^2), a^{-1}2xy)$

Có phải tới đây nếu bạn lấy $x + \sqrt{D}y$ thì nó sẽ ra được hàng đẳng thức không? Cụ thể là

\[x + \sqrt{D}y = a^{-1} (x^2 + 2\sqrt{D}xy + Dy^2) = a^{-1}(x + \sqrt{D}y)^2\]

Công thức này nhìn có vẻ đẹp. Vậy chúng ta có thể lấy $\phi(x,y) = x + \sqrt{D}y$ không?

Để trả lời câu hỏi này thì chúng ta sẽ cần phải check xem:

$\phi((x, y) + (x, y)) = \phi(x,y) * \phi(x,y)$ ?

Ở công thức trước thì mình đa biết vế trái = $a^{-1}(x + \sqrt{D}y)^2$

Còn vế phải thì sẽ là $(x + \sqrt{D}y) * (x + \sqrt{D}y) = (x + \sqrt{D}y)^2$

Rõ ràng vế trái $\neq$ vế phải. Tuy nhiên khác này không nhiều mình hoàn toàn có thể thêm $a^{-1}$ vào $\phi$, khi này $\phi = a^{-1}(x + \sqrt{D}y)$. Khi check lại $\phi((x, y) + (x, y)) = \phi(x,y) * \phi(x,y)$ thì chúng ta sẽ thấy vế trái $=$ vế phải $= a^{-2}(x+\sqrt{D}y)^2$

Vậy là mình đã có thể map sang $F_p$ để dễ dàng solve DLP hơn

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from Crypto.Util.number import * 
from sage.all import * 

a = 896628675751371304018467516533039804176775933799175745538502687106559694221345569606835023924573486284340033527118135205049330439505968603591951157299842692320540140045548212956876194361589003179447395457270969770617973628611227780145733474250476117969057162498481006
b = 1594141190031606167637964033584529087047496888670585921966214269273813000209103412838275833742634054062344129649888363945908517421292739690476942454492374399040827799113145071785163083279816370591990677345359419792838696359950227158548645341674614589920736730841882901
x1 = 2447311429245810608453598977721926525743937062790274783828355485892913926324404325450262209205021127183454615611616550259333401589502955319014494993678853183049373710785536450442318295835466655474548890496853017541519682383303084185775030215811426581216914216950270956
y1 = 410833649474095008286303725355620198973100699243884756648778973683976772919818182547446663925208006608569743058617356228744003207376383969798498652649980419669823060847067368605737714840647452806304738527797730984565975278507624074132585153613359147658622391652166219
x2 = 1566139005633252473556201451242364632915578424632734493066937660397101401569493967529787221059042873428725135212835566524682514836524110403515558447949858337441010924644644029611890576656944925197135969895065157485428935029723186607110153424152429492883484492551653983
y2 = 2465843379413974191156984954011192642764891821373485281212757233528541318669422125099128501651227153989791786833109663869690960602895820768585534236263284886527281849932400111969526972107723029072948750348496980814617349507385444584704833764454106237879211652308963312

p = 2653051169113192956861029164111253541045934291082610203810454254019428617095444726883436858598608538656059147405437762506043477080240989129049112094108099635297397071457202368492446105766960803000479292350528610656494430695078098921185726745996662867511185527654339397
F = GF(p)

dd = -1 * (a ** 2) * (pow(b ** 2, - 1, p)) % p

d_ = F(dd).sqrt()

assert pow(d_, 2, p) == dd 

def f(x, y):
    return pow(a, -1, p) * (x + d_ * y) % p 

g = F(f(x1, y1))
h = F(f(x2, y2))

o = discrete_log(h, g)

print(long_to_bytes(o).decode())

# flag{gR0uP_150M0rPH15m5_4R3_g0NN4_b3_7h3_3nD_0f_y0Ur_L1f3!}
This post is licensed under CC BY 4.0 by the author.