[0CTF/TCTF 2022] real_magic_dlog

没时间作,只作了一道题。不过有时间估计也是只作这一个题。

题目看上去很简单:

  1. 选过一个proof就是爆破一个sha256,4个字节
  2. 生成17字节的随机数,后边的东西就都是自己输入的了
  3. 输入p(头部为随机数),e,data
  4. 由data生成sha384,唯一的要求:
    pow(int(data, 16), E, P) == int(sha384(data).hexdigest(), 16) % P

    由于sha384不能逆,所以也就别想这东西了。

#!/usr/bin/env python3

import random
import signal
import socketserver
import string
from Crypto.Util.number import *
from hashlib import sha256, sha384
from os import urandom
from secret import flag

LEN = 17

class Task(socketserver.BaseRequestHandler):
    def __init__(self, *args, **kargs):
        super().__init__(*args, **kargs)

    def proof_of_work(self):
        random.seed(urandom(8))
        proof = ''.join([random.choice(string.ascii_letters + string.digits + '!#$%&*-?') for _ in range(20)])
        digest = sha256(proof.encode()).hexdigest()
        self.dosend('sha256(XXXX + {}) == {}'.format(proof[4: ], digest))
        self.dosend('Give me XXXX:')
        x = self.request.recv(10)
        x = (x.strip()).decode('utf-8') 
        if len(x) != 4 or sha256((x + proof[4: ]).encode()).hexdigest() != digest: 
            return False
        return True

    def dosend(self, msg):
        try:
            self.request.sendall(msg.encode('latin-1') + b'\n')
        except:
            pass

    def timeout_handler(self, signum, frame):
        raise TimeoutError

    def recv_fromhex(self, l):
        passwd = self.request.recv(l).strip()
        passwd = bytes.fromhex(passwd.decode('latin-1'))
        return passwd       

    def handle(self):
        try:
            signal.signal(signal.SIGALRM, self.timeout_handler)
            signal.alarm(50)
            if not self.proof_of_work():
                self.dosend('You must pass the PoW!')
                return
            signal.alarm(60)
            magic = urandom(LEN)
            magic_num = bytes_to_long(magic)
            self.dosend(magic.hex())
            self.dosend('P:>')
            P = int(self.request.recv(100).strip(), 16)
            self.dosend('E:>')
            E = int(self.request.recv(100).strip(), 16)
            self.dosend('data:>')
            data = self.request.recv(100).strip()
            num1 = int(data, 16)
            if P >> (384 - LEN * 8) == magic_num and isPrime(P):
                data2 = sha384(data).hexdigest()
                num2 = int(data2, 16)
                if pow(num1, E, P) == num2 % P:
                    self.dosend(flag)
                else:
                    self.dosend('try harder!!!')
        except TimeoutError:
            self.dosend('Timeout!')
            self.request.close()
        except:
            self.dosend('Wtf?')
            self.request.close()

class ThreadedServer(socketserver.ForkingMixIn, socketserver.TCPServer):
    pass

if __name__ == "__main__":
    HOST, PORT = '0.0.0.0', 15555
    server = ThreadedServer((HOST, PORT), Task)
    server.allow_reuse_address = True
    server.serve_forever()

最可恶的就是有时间限制:60秒。

由于sha384不能逆,所以data也就基本是定值了,可以操作的就是P和E,实际上就是求离散对数。由于有时间限制,就要想办法弄个数可以很容易的求出离散对数。

离散对数的安全性要求P-1的最大素因子足够大,所以解这个题就要爆破一个最大素因子很小的P-1。又由于头部固定,我的P爆破从邻接头部后边爆破,这样P-1后尾部全是2可以快速缩小。

#sagemath 
def aaa(p0):
    c = int(sha384(b'11').hexdigest(), 16)
    p0 = p0<<(384-17*8)
    for i in range(0x10000):
        tp = p0 + (i<<(384-17*8 - 16)) + 1
        if is_prime(tp):
            l = int(factor(tp-1)[-1][0]).bit_length()
            if l<48:
                print('p = ', hex(tp)[2:])
                print('e = ', hex(int(discrete_log(mod(c,tp),mod(17,tp))))
                return

aaa(0x46b3d5cb67ecabdd14ed8997f372d4e911)

然后组装就有个问题了,我机子上sage和pwntools组合不上,只能先把取得的东西放sage运算再把结果复制回去。经过几次手抖的过程后终于解决。

from pwn import *
from Crypto.Util.number import *
from hashlib import sha256, sha384
import string 

def proof(tail, s):
    tab = string.ascii_letters + string.digits + '!#$%&*-?'
    for i in tab:
        for j in tab:
            for k in tab:
                for l in tab:
                    msg = i+j+k+l+tail
                    r = sha256(msg.encode()).hexdigest() 
                    if r == s:
                        print(" "+i+j+k+l)
                        return i+j+k+l                       

p = remote('202.120.7.219', 15555)
context.log_level = 'debug'

'''
sha256(XXXX + Q6quUUnOKw693Z*k) == 8c0452ca7ca220652104d4bd0dd72f6fc25f0bee622b9b3f53c97741b52bfcd6
Give me XXXX:
'''
p.recvuntil(b'sha256(XXXX + ')
tail = p.recvuntil(b') == ', drop=True).decode()
s = p.recvuntil(b'\n', drop=True).decode()
m = proof(tail, s)
p.sendlineafter(b'Give me XXXX:\n', m.encode())

#b'1adb38ceb59d18365bc9aa199955af2da1\n'
data = p.recvline()[:-1]
print(data)
p0 = int(data, 16)

print('p0 = ', hex(p0))

sp = input("P:")
p.sendlineafter(b'P:>\n', sp)

se = input("E:")
p.sendlineafter(b'E:>\n', se)

p.sendlineafter(b'data:>\n', b'11') #0x11 = 17

p.interactive()

另外,由于要运算离散对数,g不能大,一般如果没特殊情况可以取2,但是2,3经常会有无法求解的情况(含因子)所以这里用的17,其实3有时候也行。最大因子当然越小越好,但太小不容易遇到测试两次后发现一个44位的,就随便写了48,因为这块也需要节省时间。

最后看看交互的过程:

$ py a.py
[+] Opening connection to 202.120.7.219 on port 15555: Done
[DEBUG] Received 0x64 bytes:
    b'sha256(XXXX + cRZm&a!8X3i$28YJ) == b23f429bace252093acf4b3ab0d059fa8a52102e30cb495e7b912560903209a9\n'
[DEBUG] Received 0xe bytes:
    b'Give me XXXX:\n'
[DEBUG] Sent 0x5 bytes:
    b'C6u7\n'
[DEBUG] Received 0x23 bytes:
    b'779d13f0f1ffda1735928284fd3f240801\n'
[DEBUG] Received 0x4 bytes:
    b'P:>\n'
[DEBUG] Sent 0x62 bytes:
    b'779d13f0f1ffda1735928284fd3f24080111790000000000000000000000000000000000000000000000000000000001\n'
    b'\n'
[DEBUG] Received 0x4 bytes:
    b'E:>\n'
[DEBUG] Sent 0x61 bytes:
    b'3fb7c5754190aa6425ba81af4b68a09154d708572f7491c56999fdae75a7787e83a90409311c2f7502c0779ebfb5e98\n'
    b'\n'
[DEBUG] Received 0x7 bytes:
    b'data:>\n'
[DEBUG] Sent 0x3 bytes:
    b'11\n'
[*] Switching to interactive mode
[DEBUG] Received 0x31 bytes:
    b'flag{Hope_you_can_solve_by_smoothness_this_time}\n'
flag{Hope_you_can_solve_by_smoothness_this_time}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值