Bài viết giải đề thi MTCTF lần thứ 4 (Mã hóa)
Giới thiệu
Trong cuộc thi, mình đã giải 2 bài mã hóa, 1 bài Misc, đồng đội giải 2 bài Pwn và 1 bài RE. Tổng cộng có 241 đội đạt điểm, cuối cùng mình xếp hạng 36, còn cách vòng chung kết khá xa. Dưới đây là tổng hợp 2 bài mã hóa mà mình đã làm.
Ký hiệu đặc biệt
Bài này kiểm tra kiến thức về ký hiệu trong LaTeX
Một loạt ký hiệu lạ, dựa vào hiểu biết về chữ cái Hy Lạp, mình nhận ra quy luật là ánh xạ theo phát âm để tìm ra bản rõ.
Sau khi tra cứu nhiều tài liệu, phát hiện đây đều là ký hiệu trong LaTeX, nhanh chóng tìm được flag{fun_latex_math}
Tuy nhiên MD5 không khớp khi gửi, chú ý rằng chữ cái có cả in hoa và thường, nên sửa thành flag{fun_LaTeX_Math} --> gửi đúng.
Thừa nhận rằng MD5 mặc định ra 32 ký tự thường(không cần thử 4 trường hợp như mình)
RSA Bánh Hamburger
Đề bài:
from Crypto.Util.number import *
flag = open('flag.txt').read()
nbit = 64
while True:
p, q = getPrime(nbit), getPrime(nbit)
PP = int(str(p) + str(p) + str(q) + str(q))
QQ = int(str(q) + str(q) + str(p) + str(p))
if isPrime(PP) and isPrime(QQ):
break
n = PP * QQ
m = bytes_to_long(flag.encode())
c = pow(m, 65537, n)
print('n =', n)
print('c =', c)
Một trong số p,q có 19 chữ số, một có 20 chữ số (vì nếu cả hai đều 20 hoặc 19 chữ số thì PP và QQ chắc chắn không phải số nguyên tố)
Rõ ràng 19 chữ số đầu của N chính là 19 chữ số đầu của p*q, 19 chữ số cuối của N là 19 chữ số cuối của p*q
Như vậy, p*q có 39 hoặc 40 chữ số, chỉ thiếu 1 chữ số, chỉ cần bạo lực chữ số còn lại rồi dùng sage để phân tích thừa số.
Script dưới đây chạy trong sage:
N=177269125756508652546242326065138402971542751112423326033880862868822164234452280738170245589798474033047460920552550018968571267978283756742722231922451193
low=str(N)[-19:]
high=str(N)[:19]
for i in ['']+[str(i) for i in range(10)]:
n=int(high+i+low)
f=factor(n)
if len(f)==2:
print(f)
Tìm được p,q là 9788542938580474429, 18109858317913867117
from Crypto.Util.number import*
PP=978854293858047442997885429385804744291810985831791386711718109858317913867117
QQ=181098583179138671171810985831791386711797885429385804744299788542938580474429
phi=(PP-1)*(QQ-1)
n=PP*QQ
c=47718022601324543399078395957095083753201631332808949406927091589044837556469300807728484035581447960954603540348152501053100067139486887367207461593404096
d=inverse(65537,phi)
m=pow(c,d,n)
print(long_to_bytes(m))
Tìm được flag
Máy mã hóa của Romeo
Bài này mình trì hoãn khá lâu mới quay lại
Khi thi thời gian đó mình quá yếu
Giờ nhìn lại thấy khá dễ
Trước tiên là đoạn code đề bài:
from Crypto.Util.number import*
from Crypto.Cipher import AES
from secret import msg,password,flag
import socketserver
import signal
assert len(msg) == 32
assert len(password) == 8
def padding(msg):
return msg + bytes([0 for i in range((16 - len(msg))%16)])
class Task(socketserver.BaseRequestHandler):
def _recvall(self):
BUFF_SIZE = 2048
data = b''
while True:
part = self.request.recv(BUFF_SIZE)
data += part
if len(part) < BUFF_SIZE:
break
return data.strip()
def send(self, msg, newline=True):
try:
if newline:
msg += b'\n'
self.request.sendall(msg)
except:
pass
def recv(self):
return self._recvall()
def login(self):
right_num = 0
while 1:
self.send(b'[~]Vui lòng nhập mật khẩu của bạn:')
str1 = self.recv().strip()[:8]
true_num = 0
for i in range(len(password)):
if str1[i] != password[i]:
login = False
self.send(b'Sai!')
break
else:
true_num = i + 1
if right_num > true_num:
continue
else:
right_num = true_num
if true_num == len(password):
login = True
check = b''
for i in range(0x2000):
check = self.aes.encrypt(padding(check[:-1] + str1[:i+1]))
if login == True:
self.send(b"Đăng nhập thành công")
return True,check[:16]
return False
def handle(self):
signal.alarm(100)
self.aes = AES.new(padding(password),AES.MODE_ECB)
_,final_check = self.login()
if _ == 1:
assert msg.decode() == final_check.hex()
self.send(b'Chào buổi sáng Master!')
self.send(flag)
class ThreadedServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass
class ForkedServer(socketserver.ForkingMixIn, socketserver.TCPServer):
pass
if __name__ == "__main__":
HOST, PORT = '0.0.0.0', 9999
print("HOST:POST " + HOST+":" + str(PORT))
server = ForkedServer((HOST, PORT), Task)
server.allow_reuse_address = True
server.serve_forever()
Phân tích:
Chỉ cần xem hàm login là đủ, các phần còn lại là thực hiện kết nối máy chủ
Chúng ta gửi mật khẩu, nó sẽ so sánh từng ký tự. Nếu ký tự đúng, sẽ vào vòng lặp mã hóa AES, tạo ra một giá trị check. Tuy nhiên giá trị này không liên quan đến chúng ta, không thể biết được, nhưng ý nghĩa của vòng lặp này là làm tiêu tốn thời gian đáng kể.
Do đó có ý tưởng: Liệt kê tất cả ký tự có thể nhìn thấy thành bảng, thực hiện bạo lực từng ký tự. Thời gian trên ký tự đúng sẽ rõ ràng lớn hơn thời gian trên các ký tự khác.
Code exploit (tham khảo từ blog 4XWi11):
from pwn import *
from tqdm import tqdm
from string import printable
import time
import sys
#context.log_level = 'debug'
table = printable
length = len(printable)
pwd = ''
t = pwd
index = 0
i = 0
tip = 1
for _ in range(8):
while 1:
io = remote('127.0.0.1', 9999)
tip = 1
try:
signal.alarm(105)
for i in tqdm(range(index, length)):
t = pwd + table[i]
io.recvline()
io.sendline(t.encode())
feedback = io.recvline()
if b'Sai!' in feedback:
tip = 0
continue
elif b'Thành công' in feedback:
pwd = t
tip = 1
assert 1 == 0
signal.alarm(0)
except:
io.close()
if tip:
pwd = t
if len(pwd) == 8:
print(pwd)
io.recvline()
io.recvline()
sys.exit(0)
index = 0
break
else:
index = i
io.close()
continue