# BSides Ahmedabad CTF 2021 Writeup - SSSS.RNG, floorsa, Roda
## SSSS.RNG
$p$を512bitの素数、$a,b,x$を$p$未満のランダムな数とする。
以下すべて$\mathbb{Z}/p\mathbb{Z}$上の演算として、
$$
\begin{aligned}
g_1&=ax+b\\
g_2&=ag_1+b\\
g_3&=ag_2+b\\
g_4&=ag_3+b\\
g_5&=ag_4+b\\
g_6&=ag_5+b
\end{aligned}
$$
とする。
$$f(X):=g_1+g_2X+g_3X^2+g_4X^3+g_5X^4+g_6X^5$$
と定義して、$f(1),f(2),f(3),f(4),f(m),p$ が与えられるので$m$を復元する。
### 解法
線形合同法で生成される数値は $x$ と $b$ に関して1次の多項式となるので、$f(X)$ は $a$ に関する多項式 $l(X), k(X)$ を用いて
$$f(X)=l(X)x+k(X)b$$
と書ける。これと $f(1),f(2),f(3),f(4)$ を用いると $x,b$ を消去できるので $a$ に関する一変数高次合同式が得られる。この式の根を求めると $a$ が得られ、残りも解ける。
```python=
p = 2908561168050746475465170048583677924550221390147321314856251074876765877416890922338619139786060615096740196376171212325702080653039392939240436429222829
vs = [(1, 1651293975450381579706844999808202297670211173037061827272908790114230592434748044848097133563469251678879059156225205298834971071359017469397331605782920), (2, 49656064002974834481096383104316375265711545391722811288216446968986145936494966876404160910407919885451814058823146922107458035910700220495010462147112), (3, 1481214561214496310917942246038921499126047497749957535731608952096552856013930232284898279007009260107597472601959627310496773682697020898442717240484400), (4, 1950790377868548708758723604473108315857898618124646291056275632619091046294238343215502355242288776617394025418770078552886012721353626716473759644786481)]
fm = 708078355843841364722603057137729966137248436075776171805731561888875332687774464375592593202164126123800524500062359645261336234459019198930345744751457
PR.<a> = PolynomialRing(GF(p))
# f(X) := l(X) * x + k(X) * b
def l(y):
factor = a
ret = 0
for i in range(6):
ret += factor
factor *= a * y
return ret
def k(y):
factor = 0
ret = 0
for i in range(6):
factor += a ** i
ret += factor * y ** i
return ret
f1, f2, f3, f4 = vs[0][1], vs[1][1], vs[2][1], vs[3][1]
k1, k2, k3, k4 = k(1), k(2), k(3), k(4)
l1, l2, l3, l4 = l(1), l(2), l(3), l(4)
d = (l2 * f1 - l1 * f2) * (l4 * k3 - l3 * k4) - (l4 * f3 - l3 * f4) * (l2 * k1 - l1 * k2)
roots = d.roots()
raw_a = roots[0][0]
raw_l1, raw_l2 = l1(raw_a), l2(raw_a)
raw_k1, raw_k2 = k1(raw_a), k2(raw_a)
raw_b = (raw_l2 * f1 - raw_l1 * f2) / (raw_l2 * raw_k1 - raw_l1 * raw_k2)
raw_x = (f1 - raw_k1 * raw_b) / raw_l1
PR2.<m> = PolynomialRing(GF(p))
def g(i):
ret = raw_x
for j in range(i):
ret = raw_a * ret + raw_b
return ret
fm_g = g(1) + g(2) * m + g(3) * m^2 + g(4) * m^3 + g(5) * m^4 + g(6) * m^5
flag = int((fm_g - fm).roots()[0][0])
print(flag.to_bytes((flag.bit_length() + 7) // 8, 'big'))
```
## floorsa
通常のRSAの $N,c$ に加えて、[floor_sum](https://atcoder.jp/contests/practice2/tasks/practice2_c)関数を用いた値
$$s:=\mathrm{floor\_sum}(q,q,p,0)$$
が与えられる。
### 解法
$\mathrm{floor\_sum}(n,m,a,b)$ が直線 $((0,\frac{b}{m}),(n,\frac{an+b}{m}))$ 以下の格子点の数に等しいことに留意すると、$p$ と $q$ が互いに素のとき、
$$\mathrm{floor\_sum}(q,q,p,0)=\frac{pq-p-q+1}{2}$$
である。
これと $N:=pq$ を用いると $p+q$ が得られるので、解ける。
```ruby=
require 'openssl'
c = 23040235907187792043102377766239160347012425454756214402219399982044253963561544138187423569531882081170834886320190973854626011799820277883217582208960795474430615579336090533683566370889382239077437657567815790536809115842475993748108172855688647606634510990327206587307392015543017884876145469179123535144938693302707229724909443912012098405828416163212773471183275687343852756789315215914149905888864296778004572587778916842514807079884644431138433900314631727531570096879428541834626325720522038566946030094711700613155240677360427005636301342509966941323931780006792225168839057660986447452212763627881844882128
n = 25436172154773316497363731760659809627551021985352168624695689317365040018878195925779734249357382145683534839534348832631746578327288150976696513216052612085728199134879493012682967254530827617417753223998955022174337237825391634619718971640535408277659054217709169558518413217237374290054035438476060534235907848570089739603581573457194953083783917042829987113625590656741008590506988903895998008845547262465276679611851110911540261411521221317990139079888782193797945245078986517794660508171428778191152342783279365287710944280356669999624474416422142006473378042779555537142175392801014489187786764971671239717769
s = 12718086077386658248681865880329904813775510992676084312347844658682520009439097962889867124678691072841767419767174416315873289163644075488348256608026306042864099567439746506341483627265413808708876611999477511087168618912695817309859485820267704138829527108854584779259206608618687145027017719238030267117794390566380531016624830798422997060308480467087130633621890831591995264022449058406630323270130520401030807803477672651197312971884784226103671425190328967548002718406368654056897938481966140031709870266384782295285897095028680666943294657806202686252742158733266700286323555374087306844259404255328911060160
e = 65537
pq = n - (s * 2 - 1)
t2 = pq**2 - 4 * n
t = (1...t2).bsearch {|i| i * i >= t2}
q = (pq + t) / 2
p = n / q
phi = (p - 1) * (q - 1)
d = e.to_bn.mod_inverse(phi)
m = c.pow(d.to_i, n)
print([m.to_s(16)].pack('H*'))
```
## Roda
### 解法
`isValidFile`関数をごまかす。
```javascript=
const SIGNATURES = {
'png': new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
'jpg': new Uint8Array([0xff, 0xd8])
};
function compareUint8Arrays(known, input) {
if (known.length !== input.length) {
return false;
}
for (let i = 0; i < known.length; i++) {
if (known[i] !== input[i]) {
return false;
}
}
return true;
}
function isValidFile(ext, data) {
// extension should not have special chars
if (/[^0-9A-Za-z]/.test(ext)) {
return false;
}
// prevent uploading files other than images
if (!(ext in SIGNATURES)) {
return false;
}
const signature = SIGNATURES[ext];
return compareUint8Arrays(signature, data.slice(0, signature.length));
}
```
`ext = 'toString'` などとすれば `'toString' in SIGNATURES === true` かつ `SIGNATUERS.toString.length === 0` となるので任意の `data` がチェックを通過する。似たようなロジックでMIMETYPEが `text/html` となるので任意のHTMLをアップロードできることになる。あとは適当にXSSすれば勝ち。