문제에서는 fglg.py , server.py 두 파일이 제공되었는데 server.py에서 fglg.py를 불러와서 사용하는 형태이다.
fglg.py는 단순히 그냥 행렬과 일반적인 연산을 만들어둔 코드이다.
def bytes2gl(b, n, p=None):
assert len(b) <= n * n
X = FiniteGeneralLinearGroup(n, p)
padlen = n * n - len(b)
b = bytes([padlen]) * padlen + b # 앞에 패딩 길이 만큼 padding
for i in range(n):
for j in range(n):
X.set_at((j, i), b[i*n + j])
return X
def recv_message(n, p):
print("Data: ", end="", flush=True)
b = base64.b64decode(input())
return bytes2gl(b, n, p)
def encrypt(U, X):
return U * X * U**-1
def decrypt(U, X):
return U**-1 * X * U
if __name__ == '__main__':
# Create flag F
n = math.ceil(math.sqrt(len(flag))) # len(flag) : 26~36
F = bytes2gl(flag, n)
p = F.p
# Generate private key
U = FiniteGeneralLinearGroup(n, p)
while U.determinant() == 0:
U.set_random()
주요 함수는 위의 4개인데,
byte2gl은, 입력을 받아서 앞에 패딩을 한 후 fglg.py에서 만든 형태로 행렬로 만든다.
recv_message를 보면 base64 인코딩된 입력을 받아서 이를 디코딩 한 뒤, 그것을 행렬로 바꾸어준다.
encrypt 와 decrypt는 단순 행렬 연산이다.
그리고 enc / dec 에서 곱하는 U는 determinant가 0으로, 역행렬이 존재하지 않는 것이다.
허나 fglg.py에서 U**-1를 정의해놓았기 때문에 저렇게 연산은 가능하다.
우리가 해야하는 것은 다음과 같다.
U ** -1 * flag * U
하지만 입력을 항상 base64 인코딩한 값만 넣기 때문에 0~255사이의 값으로 된 input만 입력할 수 있다.
만약 위 flag를 0~255 사이의 값들로 바꿀 수 있다면 우리는 서버의 기능으로 마음대로 enc / dec 할 수 있다는 의미이다.
여기서 생각할 수 있는 것은 homomorphism이다.
dec(A+B) = dec(A) + dec(B)
dec(A-B) = dec(A) - dec(B)
행렬의 단순 연산이기 때문이 위 연산이 성립한다.
우리는 flag를 받아서 이를 0~255 사이의 값들로 이루어진 행렬로 쪼개어 dec 후 더해준다면
flag를 얻어낼 수 있다.
문제에서 다루고 있는 행렬은 6 * 6 행렬이기 때문에 단순 무식하게 연산을 했다.
(i,j) 만 1이고 나머지 모두 0인 행렬을 만들어서 flag에서 (i,j) 값을 곱해주면 flag의 (i,j)번째 항을 만들 수 있다.
이렇게 36번만 반복하면 우리는 flag를 쪼갤 수 있고, flag를 얻어낼 수 있다.
'CTF > CTF_writeup' 카테고리의 다른 글
[Hack.lu 2018] Baby_Kernel_1 (1) | 2020.04.04 |
---|---|
[zer0pts] dirty laundry (0) | 2020.03.12 |
[zer0pts] diysig (0) | 2020.03.12 |
[zer0pts] ROR (0) | 2020.03.12 |
[CODEGATE 2020 예선] Halffeed (0) | 2020.02.09 |