没时间作,只作了一道题。不过有时间估计也是只作这一个题。
题目看上去很简单:
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}