0% found this document useful (0 votes)
8 views6 pages

Delphi

The document describes a method for exploiting a padding oracle attack using a remote oracle service to recover the internal state of a random number generator (RNG) based on linear feedback shift registers (LFSR). It includes code snippets for building matrices, solving linear systems, and decrypting messages in a structured manner. The final goal is to collect RNG bits, recover states, synchronize the RNG, and decrypt ciphertext to obtain the original message.

Uploaded by

gimdubu9
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
8 views6 pages

Delphi

The document describes a method for exploiting a padding oracle attack using a remote oracle service to recover the internal state of a random number generator (RNG) based on linear feedback shift registers (LFSR). It includes code snippets for building matrices, solving linear systems, and decrypting messages in a structured manner. The final goal is to collect RNG bits, recover states, synchronize the RNG, and decrypt ciphertext to obtain the original message.

Uploaded by

gimdubu9
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

DELPHI

팀명 : SecurityFact
점수 : 600
2번 오라클 출력의 결과는 패딩판정 ⊕ RNG 1비트(LFSR XOR) 이고

1. 블록 배수 아닌(17바이트) CT를 보내면 패딩판정이 항상 False이므로 출력이 RNG 비트.

2. 이렇게 모은 비트열로 두 개 64-bit LFSR 초기상태를 복구하고 예측 RNG 비트를 XOR해 패딩을 얻은 후

3. 표준 CBC 패딩 오라클 복호화 해서 msg를 얻는다.

from pwn import *


import os, random

HOST, PORT = "[Link]", 13372


context.log_level = "info"

# ---------------- LFSR / GF(2) ----------------

def build_T_matrix_64(fb):
T = [[0]*64 for _ in range(64)]
# shift-right: bit[i] <- old[i+1]
for i in range(63):
T[i][i+1] = 1
# MSB <- XOR of taps
for k in range(64):
if (fb >> k) & 1:
T[63][k] = 1
return T

def matmul_rowvec_mat(row, M):


N = len(row)
out = [0]*N
for j in range(N):
s=0
for i in range(N):
if row[i] and M[i][j]:
s ^= 1
out[j] = s
return out

def build_A_rows(T, M):


e0 = [0]*64
e0[0] = 1 # LSB 선택
rows = []
a = e0[:]
for _ in range(M):
[Link](a[:])
a = matmul_rowvec_mat(a, T)
return rows

def gf2_gauss_solve(A, b):


M = len(A); N = len(A[0])
aug = [A[i][:] + [b[i]] for i in range(M)]
r=c=0
where = [-1]*N

DELPHI 1
while r < M and c < N:
piv = r
while piv < M and aug[piv][c] == 0:
piv += 1
if piv == M:
c += 1
continue
aug[r], aug[piv] = aug[piv], aug[r]
where[c] = r
for i in range(M):
if i != r and aug[i][c]:
aug[i] = [aug[i][j] ^ aug[r][j] for j in range(N+1)]
r += 1; c += 1
for i in range(r, M):
if aug[i][N]:
raise ValueError("No solution (inconsistent)")
x = [0]*N
for j in range(N):
if where[j] != -1:
x[j] = aug[where[j]][N]
else:
x[j] = 0
return x

def bits_to_int_le(bits):
v=0
for i, b in enumerate(bits):
if b & 1:
v |= (1 << i)
return v

class LFSR:
def __init__(self, fb, state):
[Link] = fb & ((1<<64)-1)
[Link] = state & ((1<<64)-1)
@staticmethod
def parity64(x):
return x.bit_count() & 1
def clock(self):
out = [Link] & 1
new_msb = LFSR.parity64([Link] & [Link])
[Link] = ((new_msb << 63) | ([Link] >> 1)) & ((1<<64)-1)
return out

class RNG:
def __init__(self, s1, s2):
self.L1 = LFSR(11277095078203943143, s1)
self.L2 = LFSR(12022921549183311343, s2)
def bit(self):
return [Link]() ^ [Link]()

# ---------------- Remote IO ----------------

class OracleClient:
def __init__(self, host, port):
[Link] = remote(host, port)
self.q2_count = 0
[Link] = None

DELPHI 2
def prompt(self):
[Link](b"> ")

def menu1_get_ct(self):
[Link]()
[Link](b"1")
line = [Link]().strip()
return [Link]([Link]())

def menu2_query_raw(self, ct_bytes):


[Link]()
[Link](b"2")
[Link](b"(hex) > ")
[Link](ct_bytes.hex().encode())
line = [Link]().strip()
if line == b"No repeat":
return None
try:
val = int(line)
except Exception:
raise RuntimeError(f"Unexpected oracle output: {line!r}")
self.q2_count += 1
return val

def menu3_submit(self, pt_bytes):


[Link]()
[Link](b"3")
[Link](b"(hex) > ")
[Link](pt_bytes.hex().encode())
return [Link]().strip()

# ---------------- Attack helpers ----------------

def collect_rng_bits(cli, M):


bits = []
bar = [Link](f"[1/5] Collect RNG bits ({M})")
for i in range(M):
# 길이 바이트17 → check_padding=False → o = [Link]()
ct = bytearray([Link](16) + b"\x00")
ct[-1] = i & 0xFF
o = cli.menu2_query_raw(bytes(ct))
if o is None:
ct[0] ^= 1
o = cli.menu2_query_raw(bytes(ct))
assert o is not None
[Link](o & 1)
if (i+1) % 16 == 0:
[Link](f"{i+1}/{M}")
[Link]("done")
return bits

def recover_states(r_stream):
M = len(r_stream)
T1 = build_T_matrix_64(11277095078203943143)
T2 = build_T_matrix_64(12022921549183311343)
A1 = build_A_rows(T1, M)
A2 = build_A_rows(T2, M)

DELPHI 3
A = [A1[i] + A2[i] for i in range(M)]
b = r_stream[:]
[Link]("[2/5] Solving GF(2) linear system")
x = gf2_gauss_solve(A, b)
s1_bits = x[:64]; s2_bits = x[64:]
s1 = bits_to_int_le(s1_bits)
s2 = bits_to_int_le(s2_bits)
[Link](f"Recovered states: s1=0x{s1:016x}, s2=0x{s2:016x}")
return s1, s2

def verify_rng_sync(cli, trials=32):


if [Link] is None:
raise RuntimeError("rng not set")
ok = 0
for i in range(trials):
ct = bytearray([Link](16) + b"\x01")
ct[-1] ^= i & 0xFF
o = cli.menu2_query_raw(bytes(ct))
if o is None:
ct[0] ^= 1
o = cli.menu2_query_raw(bytes(ct))
r = [Link]()
if (o ^ 0) == r:
ok += 1
[Link](f"[check] RNG sync: {ok}/{trials} matched")
return ok == trials

def oracle_true(cli, ct_bytes):


o = cli.menu2_query_raw(ct_bytes)
assert o is not None
r = [Link]()
return o ^ r

def decrypt_block(cli, Cprev, C):


I = [0]*16
prog = [Link](" decrypt_block")

for pad in range(1, 17):


idx = 16 - pad

# 꼬리(k>idx)는 pad로 맞추기: P[k] = I[k] ^ Cprev'[k] = pad


# → Cprev'[k] = I[k] ^ pad
base_prev = bytearray([Link](16)) # 앞부분은 아무 값이어도 됨
for k in range(idx+1, 16):
base_prev[k] = I[k] ^ pad

found = False
order = list(range(256))
[Link](order)
for g in order:
prev = bytearray(base_prev)
prev[idx] = g
if pad == 1:
prev[0] ^= 0x01 # 1- 패딩 우연 매치 방지용 더미
iv_rand = [Link](16)
pkt = iv_rand + bytes(prev) + C
res = oracle_true(cli, pkt)

DELPHI 4
if res == 1:
I[idx] = g ^ pad
found = True
break

if not found:
[Link](f"pad={pad} failed")
raise RuntimeError(f"Pad search failed at pad={pad}")

[Link](f"bytes recovered: {17-pad}/16")

[Link]("ok")
P = bytes([I[i] ^ Cprev[i] for i in range(16)])
return P

def solve():
cli = OracleClient(HOST, PORT)

# 1) RNG 비트 수집
M = 512
r_stream = collect_rng_bits(cli, M)

# 2) 상태 복구
s1, s2 = recover_states(r_stream)

# 3) RNG 동기화
[Link] = RNG(s1, s2)
for _ in range(M):
[Link]()

# 3.5) 동기화 검증
if not verify_rng_sync(cli, trials=32):
[Link]("RNG mismatch. Increase M or re-run.")
[Link]()
return

# 4) 암호문 획득
[Link]("[3/5] Fetching ciphertext (menu 1)")
ct = cli.menu1_get_ct()
assert len(ct) == 48
iv, C1, C2 = ct[:16], ct[16:32], ct[32:48]
[Link](f"IV={[Link]()} C1={[Link]()} C2={[Link]()}")

# 5) P2, P1 복호 마지막 블록부터


( )
[Link]("[4/5] Decrypting P2")
P2 = decrypt_block(cli, C1, C2)
[Link](f"P2={[Link]()}")

[Link]("[4/5] Decrypting P1")


P1 = decrypt_block(cli, iv, C1)
[Link](f"P1={[Link]()}")

msg = P1 + P2
[Link](f"[5/5] MSG={[Link]()} (len={len(msg)})")

# 6) 제출
out = cli.menu3_submit(msg)
print([Link](errors="ignore"))

DELPHI 5
[Link]()

if __name__ == "__main__":
solve()

DELPHI 6

You might also like