server.py
from hash import secureHash
import SocketServer
import string
import random
from text import *
import os
from hashlib import sha256
PORT = 2000
ROUNDS = 10
TIMEOUT = 120
sigma = string.ascii_letters+string.digits+'!@#$%^&*()-_=+[{]}<>.,?;:'
def isPrintable(x):
global sigma
alpha = set(sigma)
beta = set(x)
return alpha.intersection(beta) == beta
def get_random_string(l, s):
return ''.join([random.choice(s) for i in range(l)])
class ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler):
def PoW(self):
s = os.urandom(10)
h = sha256(s).hexdigest()
self.request.sendall("Provide a hex string X such that sha256(X)[-6:] = {}\n".format(h[-6:]))
inp = self.request.recv(2048).strip().lower()
is_hex = 1
for c in inp:
if not c in '0123456789abcdef':
is_hex = 0
if is_hex and sha256(inp.decode('hex')).hexdigest()[-6:] == h[-6:]:
self.request.sendall('Good, you can continue!\n')
return True
else:
self.request.sendall('Oops, your string didn\'t respect the criterion.\n')
return False
def challenge(self, n):
s = get_random_string(random.randint(30,35), sigma)
H = secureHash()
H.update(s)
h = H.hexdigest()
self.request.sendall(chall_intro.format(n, s, h))
inp = self.request.recv(2048).strip()
H_inp = secureHash()
H_inp.update(inp)
h_inp = H_inp.hexdigest()
if(inp == s or not isPrintable(inp)):
self.request.sendall(chall_wrong)
return False
if(h_inp != h):
self.request.sendall(chall_wrong)
return False
self.request.sendall(chall_ok)
return True
def handle(self):
self.request.settimeout(TIMEOUT)
if not self.PoW():
return
self.request.sendall(intro.format(ROUNDS))
for i in range(ROUNDS):
if(not self.challenge(i+1)):
self.request.sendall(losing_outro)
return
self.request.sendall(winning_outro.format(FLAG))
def finish(self):
logger.info("%s client disconnected" % self.client_address[0])
class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
pass
if __name__ == '__main__':
server = ThreadedTCPServer(('0.0.0.0', PORT), ThreadedTCPRequestHandler)
server.allow_reuse_address = True
server.serve_forever()
hash.py
class secureHash(object):
def __init__(self):
self.bits = 128
self.mod = 2**128
self.mask = 2**128 - 1
self.step = 23643483844282862943960719738L
self.hash = 9144491976215488621715609182563L
def update(self, inp):
for ch in inp:
self.hash = ((self.hash + ord(ch)) * self.step) & self.mask
def hexdigest(self):
x = self.hash
out = ''
for i in range(self.bits/8):
out=hex(x & 0xff)[2:].replace('L','').zfill(2)+out
x >>= 8
return out
Provided hash is a rabin-karp hash. To capture a flag, we must find a second preimage. Rabin-karp hash is not that secure, and I have read a post about collision in rabin-karp. (Post) However, the method provides in that post is not that fit on this situation.
After more googling, I find a similar problem - TWCTF 2017 Palindrome Pairs. According to Solution, LLL algorithm can be used to find a collision.
Although I am not that familiar with LLL algorithm, just copy & paste & modified.
collision-poly.sage
p = 23643483844282862943960719738
mod = pow(2,128)
N = 22 ## 22 to 34, to increase a success probability
print N
def sparse_poly():
L_rows = []
B = 100000
for i in xrange(N):
row = [1 if j==i else 0 for j in xrange(N)] + [B*Integer(pow(p,N+1-i,mod))]
L_rows.append(row)
L_matrix = Matrix(L_rows)
print 'Built matrix'
print type(L_matrix)
L_redux = L_matrix.LLL()
sparse = L_redux[0]
print sparse[:-1]
poly, vals = sparse[:N], sparse[N:]
assert all(abs(x) <=70 for x in poly)
assert all(x == 0 for x in vals)
return poly
def build_strings(poly):
a = [0 for _ in xrange(N)]
b = [0 for _ in xrange(N)]
# a[i] - b[(N-1)-i] = poly[i]
for i in xrange(N):
if poly[i] >= 0:
a[i] = poly[i]
b[i] = 0
else:
a[i] = 0
b[i] = -poly[i]
a_str = ''.join(chr(ord('a')+v) for v in a)
b_str = ''.join(chr(ord('a')+v) for v in b)
return a_str, b_str
def solve():
poly = sparse_poly()
#s1, s2 = build_strings(poly)
#print s1, s2
solve()
(Modified form Here)
Let me explain more: polynomial is (6, -18, -3, -2, -31, -1, 4, -10, -31, 11, -33, 3, 16, 37, -29, 11, 13, -18, -1, 26, -31, 6) when N = 22.
It means if $a[0]-b[0] = 6$, $a[1]-b[1] = -18$, ... , $a[21]-b[21] = 6$ than $a$ and $b$ has same hash.
Is is little annoying that there is limit of character. So probability is not 100%. But you can capture a flag at most 5~10 min.
solver.py
from hashlib import sha256
from Crypto.Util.number import *
import string
import socket
############ my socket ###############
def interactive(socket):
print("[+] interactive mode")
while True:
rr = socket.recv(2**16).decode()
if not rr:
print("[!] socket closed")
return None
print(rr)
socket.send((input('> ')+'\n').encode())
def remote(ip, port):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print("[+] Connecting to {}:{}".format(ip,port))
sock.connect((ip,port))
print("[+] Done!")
return sock
def sendline(socket, msg):
if type(msg) == str: msg = msg.encode()
if msg[-1] != b'\n': msg += b'\n'
socket.send(msg)
def recv(socket):
return socket.recv(2**16).decode()
mod = 2**128
p = 23643483844282862943960719738
v = 9144491976215488621715609182563
s = 'abc'
sigma = string.ascii_letters+string.digits+'!@#$%^&*()-_=+[{]}<>.,?;:'
def hash(s):
h = v
for ch in s:
h = (h+ord(ch))*p % mod
return hex(h)[2:]
s = dict()
def PoW(query):
i = 0
while True:
if i % 1000000 == 0:
if input() == "quit":
return None
i += 1
h = sha256(str(i).encode()).hexdigest()
if h[-6:] == query:
return str(i).encode().hex()
poly22 = (6, -18, -3, -2, -31, -1, 4, -10, -31, 11, -33, 3, 16, 37, -29, 11, 13, -18, -1, 26, -31, 6)
poly23 = (21, 15, 5, -1, 2, -24, 10, -11, -11, -22, -9, -11, -10, -16, -9, -54, -11, -7, 4, 15, 5, 8, 4)
poly24 = (-4, -3, -2, -1, -14, 0, 23, -9, 7, 21, 2, -13, -6, -5, 10, 9, -11, -8, 20, -3, 1, -32, -1, 2)
poly25 = (-3, -9, -7, 16, 3, 0, -6, -17, 0, -8, -8, 24, -3, -8, 23, 3, 5, -10, -7, 15, -9, 0, -11, -1, -10)
poly26 = (-14, -7, 11, 1, 1, 8, -13, 5, -12, 16, 10, 1, 7, -7, -3, -7, 4, -6, -1, -6, -1, 2, -8, 2, -1, -14)
poly27 = (2, -3, -7, 5, 3, 12, -13, 1, -13, -8, 13, 8, -8, -9, 0, 2, 13, 1, -3, -1, -11, -3, 6, 7, -8, 8, -8)
poly28 = (-6, 5, 4, 4, 3, -5, -8, -8, -6, -5, 3, -17, 8, 8, 5, -3, -6, 5, -6, -9, 11, 14, 10, 3, 6, 1, 1, 2)
poly29 = (14, 2, -1, -2, -16, 8, 6, 3, -1, -2, -9, -3, -3, 2, -10, -2, 11, -3, -1, 1, -7, 9, 7, 5, 0, 14, -3, 6, 0)
poly30 = (-7, -8, 5, -5, 3, 4, 6, -2, 0, 3, -15, -1, 14, 5, 3, -8, -3, -1, 6, -6, 1, -8, 0, -5, -5, 12, -7, 2, 3, 2)
poly31 = (-6, -13, -5, 10, 1, -3, 2, 4, -6, 1, -6, 3, -2, -5, 9, 4, -2, -14, -2, 1, 0, -3, 0, -2, 4, -1, 3, 4, -5, -1, -2)
poly32 = (-2, -4, 10, -3, 0, 1, 2, -5, -5, 5, -4, 1, 7, 1, 10, -6, 2, 0, -5, 4, 3, 0, -5, 1, -1, 4, 8, -5, 4, 2, 4, 0)
poly33 = (4, -4, -2, 1, 4, -8, -1, 8, -5, 2, 4, 1, 4, -1, 3, -5, -5, 0, -4, 0, -14, 2, 4, 9, 0, -9, -8, 7, -3, 2, 0, 0, 0)
poly34 = (-7, 1, -3, -5, -4, -5, 3, 3, 4, 3, -1, -9, 2, 4, 2, 3, 1, 2, 1, -3, 2, 3, 2, 0, 5, 0, -1, -1, -2, -9, -1, 2, 3, 2)
def collision(s):
h = hash(s)
n = len(s)
polycount = 21
for poly in [poly22, poly23, poly24, poly25, poly26, poly27, poly28, poly29, poly30, poly31, poly32, poly33, poly34]:
polycount+=1
if len(poly) > n: continue
for factor in range(1,10):
for st in range(n-len(poly)+1):
L = list(s)
ret = ''
ret += s[:st]
pos = True
for i in range(st,st+len(poly)):
ii = ord(s[i])+factor*poly[i-st]
if ii < 0 or chr(ii) not in sigma:
pos = False
break
ret += chr(ii)
if not pos: continue
ret += s[st+len(poly):]
if hash(ret) == h:
return ret
return "FAIL"
s = dict()
for i in range(10000000):
if i%1000000==0: print(i)
h = sha256(str(i).encode()).hexdigest()
s[h[-6:]] = str(i).encode().hex()
while True:
r = remote('challs.xmas.htsp.ro', 10003)
pow_target = recv(r).strip()[-6:]
print(pow_target)
if not pow_target in s: continue
sendline(r, s[pow_target])
x = recv(r) # Good, you can continue
pos = True
for step in range(10):
if step != 0:
recv(r) # Nice one, keep going!
print ("step ", step)
x = recv(r).strip()
print("received : ",x)
string = x[x.find("string")+8:]
string = string[:string.find("and")-1]
print(string)
ans = collision(string)
if ans == "FAIL":
pos = False
break
sendline(r, ans)
if pos:
print(recv(r))
print(recv(r))
print(recv(r))
exit(0)
'CTF > Crypto' 카테고리의 다른 글
[2021 PBCTF] Steroid Stream (2) | 2021.10.11 |
---|---|
[2021 PBCTF] Alkaloid Stream (2) | 2021.10.11 |
[2019 X-MAS CTF] Santa Knows Crypto (0) | 2019.12.14 |
[2019 X-MAS CTF] DeFUNct Ransomware (0) | 2019.12.14 |
[HITCON CTF 2019 Quals] Very Simple Haskell (0) | 2019.10.14 |
[HITCON CTF 2019 Quals] Lost Modulus Again (2) | 2019.10.14 |