第四届 美团MTCTF Write-up(Crypto) by Schen

第四届 美团MTCTF Write-up (Crypto)

前言

比赛的时候做了两个Crypto,一个Misc,队友们做了两个Pwn,一个RE。
总共241个得分队伍,最终排第36名,离进决赛还是有挺大距离的。
这里,把我做的两道Crypto题总结一下。

Symbol

考察的是LaTeX的字符

oHhKAS.png

一堆不认识的字符,凭着对希腊字母的了解勉强得知其模式为根据其发音得到对应明文

找了很多东西发现这些都是LaTeX的字符于是很快得到flag{fun_latex_math}

但是md5之后提交错误,注意到字母里有大小写,于是把L、T、X、M大写 -->flag{fun_LaTeX_Math}

md5后提交正确,多一嘴,md5默认32位小写(不用像我一样把4种都试一遍

hamburgerRSA

题目:

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)

p,q中一个为19位一个为20位(因为如果都是20或19位的话PP和QQ肯定不是质数)

显然N的前19位就是p*q的前19位,N的后19位就是p*q的后19位

这样,p*q的位数是39或40位,只有一位不知道,只要爆破这一位再用sagefactor分解就可以了

以下脚本在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)

得到p,q为 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))

得到flag

Remeo’s Encrypting Machine

这道题拖了好久才来复现

当时打的时候太菜了

现在看起来觉得还是挺简单的

先贴个题目代码

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'[~]Please input your password:')
            str1 = self.recv().strip()[:8]
            true_num = 0
            for i in range(len(password)):
                if str1[i] != password[i]:
                    login = False
                    self.send(b'False!')
                    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"Login Success")
                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'Good Morning 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()

分析一下:

只要看login函数就好了,其他都是实现服务器连接的

我们传入password,他会进行一位位的比对,如果这一位正确了,就会进入一个AES加密循环,生成一个check,然而这个check和我们并没有关系,我们不需要知道也没法知道,而这层循环对我们来说的的意义就是会消耗一段比较长的时间。

那么就有思路了:我们将所有可视字符列为table,进行逐位的爆破,每次在正确字符上的时间于其他时间相比较会明显更大。

exp:(参考自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'False!' in feedback:
                    tip = 0
                    continue
                elif b'Success' 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
posted @ 2021-12-12 08:34  上辰  阅读(314)  评论(3编辑  收藏  举报