---
tags: 資安實務 - write up
---
# Crypyto - write up
# Week1 - write up
## [Lab] Grams
### 題意
維吉尼亞加密
題目包含四個檔案
![](https://i.imgur.com/QUwPJzR.png)
- ngrams.json 是解題時,需要用到的機率表
- output.txt 是已經加密過後的檔案
- solve.py 包含了解題時有用的function
- vigenere.py 是一隻加密的檔案,可以看出output.txt是由一段文字、flag加密而來的。
### 解題
```python=
import random
import json
import functools as fn
import numpy as np
import string
import hashlib
charset = string.ascii_lowercase+string.digits+',. '
charset_idmap = {e: i for i, e in enumerate(charset)}
ksz = 80
def decrypt(ctx, key):
N, ksz = len(charset), len(key)
return ''.join(charset[(c-key[i % ksz]) % N] for i, c in enumerate(ctx))
def toPrintable(data):
ul = ord('_')
data = bytes(c if 32 <= c < 127 else ul for c in data)
return data.decode('ascii')
with open('./output.txt') as f:
ctx = f.readline().strip()[4:]
enc = bytes.fromhex(f.readline().strip()[6:])
ctx = [charset_idmap[c] for c in ctx]
with open('./ngrams.json') as f:
ngrams = json.load(f)
@fn.lru_cache(10000)
def get_trigram(x):
x = ''.join(x)
y = ngrams.get(x)
if y is not None:
return y
ys = []
a, b = ngrams.get(x[:2]), ngrams.get(x[2:])
if a is not None and b is not None:
ys.append(a+b)
a, b = ngrams.get(x[:1]), ngrams.get(x[1:])
if a is not None and b is not None:
ys.append(a+b)
if len(ys):
return max(ys)
if any(c not in ngrams for c in x):
return -25
return sum(map(ngrams.get, x))
@fn.lru_cache(10000)
def fitness(a):
plain = decrypt(ctx, a)
tgs = zip(plain, plain[1:], plain[2:])
score = sum(get_trigram(tg) for tg in tgs)
return score
def initialize(size):
population = []
for i in range(size):
key = tuple(random.randrange(len(charset)) for _ in range(ksz))
population.append(key)
return population
def crossover(a, b, prob):
r = list(a)
for i in range(len(r)):
if random.random() < prob:
r[i] = b[i]
return tuple(r)
def mutate(a):
r = list(a)
i = random.randrange(len(a))
r[i] = random.randrange(len(charset))
return tuple(r)
#=======================#
keys = np.array(initialize(7000))
scores = np.array([])
for i in keys:
scores = np.append(scores, fitness(tuple(i)))
keys = keys[scores.argsort()[::-1]][:600]
for m in range(2000):
np.random.shuffle(keys)
for i in range(len(keys)//2):
child = np.array(crossover(keys[i*2], keys[i*2+1], 0.5))
keys = np.concatenate((keys, [child]))
np.random.shuffle(keys)
for i in range(len(keys[i])//2):
keys[i] = mutate(keys[i])
scores = np.array([])
for i in keys:
scores = np.append(scores, fitness(tuple(i)))
keys = keys[scores.argsort()[::-1]][:600]
scores = scores[scores.argsort()[::-1]][:600]
print(m, int(scores[0]), decrypt(ctx, keys[0]))
```
其中 key[0]為評分最高的key[0],
透過key將密文解密後,比對是否有錯誤,再微調key。
![](https://i.imgur.com/c2hvR5c.png)
將key解出來後,做vigenere.py裡的逆運算,即可得到flag。
## [Lab] POA
> nc edu-ctf.csie.org 42070
### 題意
```python=
#!/usr/bin/env python3
import os
from Crypto.Cipher import AES
key = os.urandom(16)
with open('flag', 'rb') as f:
flag = f.read()
class PaddingError(Exception):
pass
def pad(data):
padlen = 16 - len(data) % 16
return data + int('1' + '0' * (padlen * 8 - 1), 2).to_bytes(padlen, 'big')
def unpad(data):
for i in range(len(data) - 1, len(data) - 1 - 16, -1):
if data[i] == 0x80:
return data[:i]
elif data[i] != 0x00:
raise PaddingError
raise PaddingError
def encrypt(plain):
# random iv
iv = os.urandom(16)
# encrypt
aes = AES.new(key, AES.MODE_CBC, iv)
cipher = aes.encrypt(pad(plain))
return iv + cipher
def decrypt(cipher):
# split iv, cipher
iv, cipher = cipher[:16], cipher[16:]
# decrypt
aes = AES.new(key, AES.MODE_CBC, iv)
plain = aes.decrypt(cipher)
return unpad(plain)
def main():
print(f'cipher = {encrypt(flag).hex()}')
while True:
try:
decrypt(bytes.fromhex(input('cipher = ')))
print('YESSSSSSSS')
except PaddingError:
print('NOOOOOOOOO')
except:
return
if __name__ == '__main__':
main()
```
透過觀察發現在41行題目使用encrypt function將flag加密, 且轉換成16進制。
在42行的while迴圈就開始解密,但是程式不會告訴使用者解密的結果,而是padding是否正確
pad function是在block後面加一個0x80,再補0到長度為16的倍數。
所以unpad function就是從最後面一個block往前找,找第一個0x80,如果都是0的話,就代表padding正確,如果到0x80有一個不是0的byte的話那就代表padding錯誤。
### 解題
```python=
from pwn import *
r = remote('edu-ctf.csie.org', 42070)
q = r.recvline()
cipher = bytes.fromhex(q.split(b' ')[-1][:-1].decode())
print(cipher)
def xor(a,b):
return bytes(i^j for i,j in zip(a,b))
for block in range(2):
ok = b''
for i in range(16):
for j in range(256):
c = b'\x00'*(15-i)+bytes([j^0x80])
if i == 15:
c = b'\x00'*16 + c
c += ok
c += cipher[16*block+16:16*block+32]
p = c.hex().encode()
r.sendlineafter(b'cipher = ', p)
q = r.recvline()
print(p)
if q != b'NOOOOOOOOO\n':
ok = bytes([j]) + ok
print(xor(cipher[:16*block+16][-i-1:], ok))
break
r.interactive()
```
先去訪問server得到flag的密文。
對block裡面的16個bytes去做迴圈,每次都爆搜256 bytes的IV。
IV得到之後就去加上一個cipher block,解密之後如果得到不是NOOOOOO的話,那就把回傳的j放在ok裡面。
最終得到 :
![](https://i.imgur.com/EMz9X72.png)
![](https://i.imgur.com/PKqudvb.png)
> FLAG{d9f37ffa479f47ff}
## [0x01] nLFSR
### 題意
```python=
#!/bin/env python3 -u
import os
state = int.from_bytes(os.urandom(8), 'little')
poly = 0xaa0d3a677e1be0bf
def step():
global state
out = state & 1
state >>= 1
if out:
state ^= poly
return out
def random():
for _ in range(42):
step()
return step()
money = 1.2
while money > 0:
y = random()
x = int(input('> '))
if x == y:
money += 0.02
else:
money -= 0.04
print(money)
if money > 2.4:
print("Here's your flag:")
with open('./flag.txt') as f:
print(f.read())
exit(0)
print('E( G_G)')
```
由題目定義的step()中可以發現,題目是將一串隨機生成的亂數,作為state,透過LFSR每次輸出1 bit,而我們可以透過跟伺服器玩64次,收集每次LFSR的結果,再利用poly,還原出state,以此預測接下來每次伺服器做LFSR的結果。
Note:題目所使用的是https://www.embeddedrelated.com/showarticle/1114.php
Golios的LFSR,做法與上課教的LFSR不太一樣,但結果會一樣。 在此副上參考連結。
### 解題
```python=
from pwn import *
from sage.all import *
def guessToGetAns(r):
money = 1.2
outputArray = []
for guess in range(64):
r.sendline("0")
output = r.recvline()
output = output.decode("utf-8")[2:-1]
output = float(output)
#print(output)
if output > money:
outputArray.append(0)
else:
outputArray.append(1)
money = output
return outputArray
poly = 0xaa0d3a677e1be0bf
print(bin(poly))
#F.<x> = PolynomialRing(GF(2))
#F = PolynomialRing(GF(2), 'x')
F,x = PolynomialRing(GF(2), 'x').objgen()
P = x ** 64
for i in range(64):
#P = P + x**i*int(bin(poly)[65-i])
P = P + x**i*int(bin(poly)[i+2])
print(P)
C = companion_matrix(P, format='bottom')
print(C)
#generate reverce matrix of Poly_function
sol_C = copy(C) #let sol_C same size with C
temp = C ** 42
sol_C[0] = temp[0]
#excute next 63 round
for i in range(63):
temp = C ** (43*(i+2)-1)
sol_C[i+1] = temp[0]
print(sol_C.rank())
sol_C_invert = sol_C.inverse()
#get input
r = remote('edu-ctf.csie.org', '42069')
ans = guessToGetAns(r)
#reverce seed
b = vector(ans)
#sol_C * x = b
#x = sol_C^-1 * b
sol_C_invert = sol_C.inverse()
x = sol_C_invert * b
#pridict
prid_rusult = []
for i in range(65, 300):
temp = C ** (43*i-1)
prid = temp * x
prid_rusult.append(prid[0])
print(prid_rusult)
for i in prid_rusult:
r.sendline(str(i))
output = r.recvline()
output = output.decode("utf-8")
print(output)
```
![](https://i.imgur.com/asN1QFZ.png)
## 心路歷程...
第一次接觸Crypyto老實說真的是挫折連連,從環境的安裝,到有許多令人害怕的數學,尤其是第二周跟第三周的課程,老實說重看了好幾次,但還是聽不太懂,更不用說解題了,請助教手下留情> <
Note: 試了許多安裝Sage的方式,最後成功安裝是使用Conda建一個新的環境,並且環境要在Linux底下才能運行! 光環境就搞了兩三天,最後還是遇到友善的台大同學,才得以裝完。
感謝nagi199a同學的幫助。
# Week2 - write up
## 【Lab】: LSB (Write Up)
### 題意
> nc edu-ctf.csie.org 42071
題目將Flag, 做random paddin 將Flag填充的長一點, 接著完成加密後, 將密文output給我們
```python=
#!/usr/bin/env python3
import random
from Crypto.Util.number import *
FLAG = open('./flag', 'rb').read()
def pad(data, block_size):
padlen = block_size - len(data) - 2
if padlen < 8:
raise ValueError
return b'\x00' + bytes([random.randint(1, 255) for _ in range(padlen)]) + b'\x00' + data
def main():
p = getPrime(512)
q = getPrime(512)
n = p * q
e = 65537
d = inverse(e, (p - 1) * (q - 1))
m = bytes_to_long(pad(FLAG, 128))
c = pow(m, e, n)
print(f'n = {n}')
print(f'c = {c}')
while True:
c = int(input())
m = pow(c, d, n)
print(f'm % 3 = {m % 3}')
try:
main()
except:
...
```
### 解題
此題LAB, 並不是回傳LSB的結果, 因為LSB是mod2,而此題是mod3, 因此在解密上, 我們需要乘的是3的反元素
```python=
from pwn import *
from Crypto.Util.number import *
r = remote('edu-ctf.csie.org', 42071)
q = r.recvline()
n = int(q[4:])
q = r.recvline()
c = int(q[4:])
e = 65537
a = inverse(3, n)
m = 0
b = 0
i = 0
f = 0
while True:
r.sendline(str(pow(a, i*e, n)*c%n).encode())
q = r.recvline()
mm = (int(q.split()[-1])-(a*b)%n)%3
if mm==0:
f+=1
if f==10:
break
else:
f = 0
b = (a*b+mm)%n
m = 3**i*mm+m
i+=1
print(m)
print(long_to_bytes(m))
```
![](https://i.imgur.com/ZnTuTij.png)
FLAG{fcf8ab2bc7b42bbd00e5be2b3d311ec6e8a89526}
## 【Lab】: Pohel
### 題意
題目包含兩個檔案, 分別為
> pohel.py
> output.txt
pohel為加密了Flag的程式, 而output則是輸出結果
根據觀察, 可以看到我們只要解出key, 就能用還原出flag
pohel.py
```python=
#!/bin/env python3
import gmpy2
import random
import hashlib
def gen():
while True:
p = [gmpy2.next_prime(random.randrange(1<<32)) for _ in range(16)]
o = 2
for pi in p:
o *= pi
n = o + 1
if gmpy2.is_prime(n):
g = 2
if pow(g, o // 2, n) == n - 1:
return g, n
def main():
g, n = gen()
k = random.randrange(n - 1)
c = pow(g, k, n)
with open('flag.txt', 'rb') as f:
flag = f.read()
k = hashlib.sha512(str(k).encode('ascii')).digest()
enc = bytes(ci ^ ki for ci, ki in zip(flag.ljust(len(k), b'\0'), k))
print('g =', g)
print('n =', n)
print('c =', c)
print('flag =', enc.hex())
if __name__ == '__main__':
main()
```
output.txt
```
g = 2
n = 22975024372641088191783192950030840455936651367831116706532074148973552639475113523713622342956112126457710740633725263638116108451282253328304547
c = 3391562491073162069780474526700107230909189849786338234577033763865425503028855096698198069367193995675035849507973902223745251492606324520756666
flag = c401549a04656914f9288164f6298ccc09771d8805db7248e3162b86237cefd2621df96509d8d9f32dbd2f56c6c41414971b990f31f80ced0cfe4eac89f55a93
```
### 解題
利用Pohlig-Hellman解出key
#### Pohlig-Hellman
![](https://i.imgur.com/498CYLI.png)
是其中一種解離散對數的方式
假如我們今天要算g^x^ (mod p) = y, 求x
根據費馬小定理, 一定有個order p-1而假設p-1為p~1~ * p~2~ * ... * p~k~
設g~i~ = g^(p-1)/Pi^ 有 order P~i~ (因為Pi乘進去會消掉P~i~ ,所以成立)
因為g~i~有order P~i~所以mod p時, (g~i~)^x^ = (g~i~)^(xmodPi)^ = y^(p-1)/Pi^ = h~i~
接著利用BSGS 列出各個X~i~ = x (mod P~i~)
再利用中國餘式定理解出X
```python=
from sage.all import *
from sage.groups.generic import bsgs
import random
import hashlib
with open("output.txt") as f:
g = int(f.readline().split()[-1])
n = int(f.readline().split()[-1])
c = int(f.readline().split()[-1])
flag_encrypt = f.readline().split()[-1]
print("g = " + g)
print("n = " + n)
print("c = " + c)
print("flag_encrypt = " flag_encrtpt)
fac = factor(n-1)
for i in fac:
f_list.append(i)
for i in f_list:
x = pow(g, (n-1) // i, n)
#print(f"x = {x}")
g_list.append(x)
for i in f_list:
x = pow(c, (n-1)//fa_, n)
h_list.append(x)
for i in range(len(f_list)):
x = bsgs(gi[i], hi[i], (0, f_list[i]))
#print(f"x{i} = {x}")
x_list.append(x)
k = crt(x_list, f_list)
k = hashlib.sha512(str(k).encode('ascii')).digest()
flag = bytes(int(k_) ^ int(f_) for k_, f_ in zip(k, bytes.fromhex(flag_encrypt)))
print(flag)
```
Flag
> FLAG{0e8dc88cd3dc6717bf1e98126ccd295e559f9a03}
#### 心路歷程
要搞懂Pohlig-Hellman真的很辛苦,感謝Leon Wang在解題時,幫忙解惑Pohlig-Hellman的問題,還有參考她的課堂筆記。
# Week3 - write up
## 【Lab】: LEA (Write Up)
### 題意
> nc edu-ctf.csie.org 42073
```python=
#!/usr/bin/env python3
import os,random,sys,string
from math import sin
from secret import FLAG, SECRET_PASSWORD
from hashlib import sha256
USERS = {}
USERS[b'Admin'] = SECRET_PASSWORD
USERS[b'Guest'] = b'No FLAG'
def MyHash(s):
A = 0x464c4147
B = 0x7b754669
C = 0x6e645468
D = 0x65456173
E = 0x74657245
F = 0x6767217D
def G(X,Y,Z):
return (X ^ (~Z | ~Y) ^ Z) & 0xFFFFFFFF
def H(X,Y):
return (X << Y | X >> (32 - Y)) & 0xFFFFFFFF
X = [int((0xFFFFFFFE) * sin(i)) & 0xFFFFFFFF for i in range(256)]
s_size = len(s)
s += bytes([0x80])
if len(s) % 128 > 120:
while len(s) % 128 != 0: s += bytes(1)
while len(s) % 128 < 120: s += bytes(1)
s += bytes.fromhex(hex(s_size * 8)[2:].rjust(16, '0'))
for i, b in enumerate(s):
k, l = int(b), i & 0x1f
A = (B + H(A + G(B,C,D) + X[k], l)) & 0xFFFFFFFF
B = (C + H(B + G(C,D,E) + X[k], l)) & 0xFFFFFFFF
C = (D + H(C + G(D,E,F) + X[k], l)) & 0xFFFFFFFF
D = (E + H(D + G(E,F,A) + X[k], l)) & 0xFFFFFFFF
E = (F + H(E + G(F,A,B) + X[k], l)) & 0xFFFFFFFF
F = (A + H(F + G(A,B,C) + X[k], l)) & 0xFFFFFFFF
return ''.join(map(lambda x : hex(x)[2:].rjust(8, '0'), [A, F, C, B, D, E]))
def verify(*stuff):
return MyHash(b'&&'.join(stuff)).encode()
def main():
username = input('Welcome to our system!\nPlease Input your username: ').encode()
if b'&' in username:
print('nope')
exit(-1)
if username in USERS:
password = USERS[username]
else:
password = input("Are you new here?\nLet's set a password: ").encode()
USERS[username] = password
print(f'Hello {username.decode()}')
session = bytes.hex(os.urandom(10)).encode()
print(f'Here is your session ID: {session.decode()}')
print(f'and your MAC(username&&password&&sessionID): {verify(username,password,session).decode()}')
while True:
mac, *sess, cmd = bytes.fromhex(input('\nWhat do you want to do? ')).split(b'&&')
if mac == verify(username,password,*sess,cmd) and session in sess[0]:
if cmd == b'flag':
if username == b'Admin':
print(FLAG)
return
else:
print('Permission denied')
elif cmd == b'exit':
print('exit')
break
else:
print('Unknown command.')
else:
print('Refused!')
print('See you next time.')
if __name__ == '__main__':
main()
```
### 解題
```python=
from pwn import *
import os, random, sys, string
from math import sin
from hashlib import sha256
import socketserver
import signal
def ExtendedMao(s, A, B, C, D, E, F):
def G(X, Y, Z):
return (X ^ (~Z | ~Y) ^ Z) & 0xFFFFFFFF
def M(X, Y):
return (X << Y | X >> (32 - Y)) & 0xFFFFFFFF
X = [int((0xFFFFFFFE) * sin(i)) & 0xFFFFFFFF for i in range(256)]
s_size = len(s) + 128
s += bytes([0x80])
if len(s) % 128 > 120:
while len(s) % 128 != 0: s += bytes(1)
while len(s) % 128 < 120: s += bytes(1)
s += bytes.fromhex(hex(s_size * 8)[2:].rjust(16, '0'))
for i, b in enumerate(s):
k, l = int(b), i & 0x1f
A = (B + M(A + G(B, C, D) + X[k], l)) & 0xFFFFFFFF
B = (C + M(B + G(C, D, E) + X[k], l)) & 0xFFFFFFFF
C = (D + M(C + G(D, E, F) + X[k], l)) & 0xFFFFFFFF
D = (E + M(D + G(E, F, A) + X[k], l)) & 0xFFFFFFFF
E = (F + M(E + G(F, A, B) + X[k], l)) & 0xFFFFFFFF
F = (A + M(F + G(A, B, C) + X[k], l)) & 0xFFFFFFFF
return ''.join(map(lambda x : hex(x)[2:].rjust(8, '0'), [A, F, C, B, D, E]))
for i in range(20):
passlength = i
r = remote('edu-ctf.csie.org', 42073)
r.sendlineafter(b'username: ', b'Admin')
r.recvline()
session = r.recvline()
session = session.decode().split(': ')[1][:-1]
mac = r.recvline()
mac = mac.decode().split(': ')[1][:-1]
A = int(mac[:8], 16)
F = int(mac[8:16], 16)
C = int(mac[16:24], 16)
B = int(mac[24:32], 16)
D = int(mac[32:40], 16)
E = int(mac[40:], 16)
MAC = ExtendedMao(b'&&flag', A, B, C, D, E, F)
print(MAC)
def partofmao(s):
s_size = len(s)
print(s_size-29)
s += bytes([0x80])
if len(s) % 128 > 120:
while len(s) % 128 != 0: s += bytes(1)
while len(s) % 128 < 120: s += bytes(1)
s += bytes.fromhex(hex(s_size * 8)[2:].rjust(16, '0'))
return s
partofMessage = partofmao(b'&&'.join([b'Admin', b'a'*passlength, session.encode()]))
partofMessage = partofMessage[9 + passlength:]
p = MAC.encode() + b'&&' + partofMessage + b'&&flag'
print("p:", p)
r.sendlineafter(b'do? ', p.hex().encode())
q = r.recvline()
print("q:", q)
r.close()
```
在ExtendedMao()定義了要回傳給server的hash值
然後第15行的s_size就需要加上上一個byte的長度128。
註:****此題code, 來自講師上課Demo****
第30行的for迴圈,因為passlenth大於20,所以重複執行20次,先傳Admin過去,就會回傳一個session、一個mac,然後得到的hash就是上一個block出來的A、F、C、B、D、E,再傳進去第47行的ExtendedMao繼續算hash。
明文就變成原本的message加上padding,所以第50行的partofmao就是再做padding的動作然後回傳。
最後在第62行補上'&&flag'。
執行完為下圖 :
![](https://i.imgur.com/cQ9MT5t.png)
![](https://i.imgur.com/TBuiAeg.png)
FLAG{c2adf08fdd2baa1e0b0b0caacdcda65c8f5bad36}