ENPM809V

Injection

What we will be covering

  • What is Injection
  • LD_Preload
  • ELF Poisoning
  • /proc/pid/mem injection
  • Ptrace

What is Injection?

What is Injection?

Injection is the art of inserting code/data to manipulate the program. Particularly we will focus on code injection. 

Why is it useful?

  • Stealing Data:
    • Encryption Keys, Session Token, etc.
  • Modify Behavior:
    • Execute shellcode
    • Execute code at a higher privilege level (through stealing handles)
  • Dubugging - Yes Deubgging
    • Inject breakpoints 

Why is it hard?

  • Not a straightforward API
    • Some require system calls
    • Utilize compiler/linking features
  • Some features require higher privileges
    • ptrace requires permissions to utilize the system call, and additional privileges to use all of the features.
    • /proc/pid/mem injection requires the ability to write to the /proc filesystem

Why are we learning this?

  • Malware use this technique for avoiding detection
    • Hide in another process
    • Blending in with the noise
  • Exploits might use this to be able to launch things
  • Why we must have proper sandboxing...
    • And only give the minimum number of privileges necessary for a program to function

LD_PRELOAD 

What is LD_PRELOAD

LD_PRELOAD is an environment variable that will tell the linker to loader symbols in a shared library before executing a program

 

This has the side effect of potentially overwriting functions that the program depends on (e.g. other shared libraries). 

 

Example: LD_PRELOAD=./sharedobj.so ./my_prog

Modes and Helper-Variables

  • LD_LIBRARY_PATH - Defines a path to look for shared libraries for the program to use
  • Secure Mode - Ignores slashes in LD_PRELOAD (making it harder to look in a different directory)

Pros and Cons

  • Pros:
    • Non-invasive - Doesn't modify a running program
    • Low-overhead - Doesn't create extra 
  • Cons:
    • Can only replace dynamically-linked functions
    • Doesn't work all the time with setuid binaries
    • Needs to be done before executing the binary 
    • Requires ability to influence the environment

How to do this

  • Reverse engineer the binary, see what function are present.
    • Determine the function you want to overwrite
  • Compile a shared object that contains a function with the same name (with whatever code you want)
  • Run the binary with LD_PRELOAD
    • LDPRELOAD=</path/to/your/so> /path/to/original/binary

Your Turn!

Class Assignment

  • We have a binary called waiter...
  • It waits way too long before completing
  • Use LD_PRELOAD to make it go faster

ELF Poisoning

What is it?

  • A method for inserting code to a binary, which will then be executed when the binary is started.
  • Point the binary to your code, before calling the original

How do we get it to work?

  • Re-Link the binary
  • Take over an existing section of the binary that isn't necessary
  • Take over existing headers that aren't needed
  • Fit it in the cracks

What doe this actually look like?

R--

R--

R-E

RW-

R--

RW-

R-E

ELF Header

Header Table

Code

Data

What doe this actually look like?

R--

R--

R-E

RW-

R--

RW-

R-E

ELF Header

Header Table

Code

Data

Injected Code

How can this be done?

  • C and Python have libraries for parsing ELF files
  • Insert your code into the binary
    • Use Write or Ptrace to write code into PLT, GOT, or even .txt file
  • Manipulate the ELF header to point to your code

/proc/pid/mem injection

What is it?

  • Overwrite the /proc/<pid>/mem file with your own data/code
  • This allows manipulation of code and data while an application is running.
    • Change code flow
    • Manipulate data
    • Insert new code into another process
  • Requires root privileges

Process 1

Process 2

Process 1

Process 2

  1. open(/proc/2/mem)
  2. Seek to address

Write(fd, shellcode, size)

New Code

Breaking it Down

  • Determine what process we want to inject into
  • Find where we want to inject into.
    • Is there a place where we can put code?
    • Can we manipulate data?
    • This needs to be reverse engineered
  • Create the data that will be injected into the other process
    • shellcode
    • value/data

Breaking it Down

  • Open the /proc/<pid>/mem file
    • Make sure to retrieve the file descriptor we get
  • Seek to the address we want to go to
    • This is from what we reverse engineered
    • Use lseek for this
  • Write the data we would like into the file (at the position we seeked to).

Breaking it Down

/* What this would look like if it was a C program */ 

int main()
{
	//This would be actual shellcode
	char *shellcode = {0x1, 0x2, 0x3, ...}; 
    size_t shellcodeSize = 0x20;
    
	//Note O_WRONLY and SEEK_SET are #defines to integer values
    fd = open(fd, /proc/<insert_pid_num_here>/mem, O_WRONLY); 
    lseek(fd, offset, SEEK_SET); 
   	write(fd, shellcode, shellcodeSize);
    
    return 0;
}

Note: you may need to convert this to shellcode too

Helpful Resources

  • Utilize GDB to figure out where in the code you want to jump to (be careful if the binary is PIE/PIC)
  • Utilize /proc/pid/mmap to figure out where data/code is laid out.

PTrace Injection

Applications Using PTrace

  • GDB 
  • strace/ltrace
  • code-coverage tools

Why is this useful for injection?

We can manipulate the program's memory, registers, and file-descriptors. In a sense, we can patch the program dynamically using the ptrace system call. 

How PTrace work?

long ptrace (enum __ptrace_request request, pid_t pid, void *addr, void *data);
  • PTRACE_ATTACH - Sends the SIGSTOP Signal to the thread and attaches tracer
  • PTRACE_PEEKTEXT/DATA - Read data from the tracee
  • PTRACE_POKETEXT/DATA - Write data to the tracee
  • PTRACE_GET/SETREGS - Set the registers of the trace
  • PTRACE_TRACEME - Request that the current process should be the tracee
  • PTRACE_CONT - Continue execution

More in man ptrace

How would we inject a shared library via PTrace?

  • Attach to a tracee (wait until it receives SIGTRAP)
  • Find a safe spot in .text (code section) to overwrite
  • Write in code to map memory for path string and new stack
  • Update registers to call code and continue
  • Add code to call dlopen
  • Restore, cleanup, detach

Lets see an example of this

Note: there are multiple ways, but this is just one way.

See It In Action

Tracer:

  • Atttach to target (PTRACE_ATTACH)
  • Save State 
    • PTRACE_PEEKTEXT (introspect memory)
    • PTRACE_GETREGS (to manipulate later)

libc.so

./target

Stack

See It In Action

Tracer:

  • Inject Opcodes
    • PTRACE_POKETEXT - glibc generally has e_entry unused, so it's safe to clobber
      • Not necessarily the only place to do this
    • Ex: 0x0F, 0x05, 0xCC = syscall; int3

libc.so

./target

Stack

0x0F 0x05 0xCC

See It In Action

Tracer:

  • Update registers to point to e_entry
    • PTRACE_SETREGS
      • rip -> location of glibc e_entry
      • rax -> mmap syscall number
      • Use calling convention to fill out mmap arguments

libc.so

./target

Stack

0x0F 0x05 0xCC

See It In Action

Tracer:

  • PTRACE_CONT
  • Wait PID - wait to hit breakpoint

libc.so

./target

Stack

0x0F 0x05 0xCC

New Allocated Memory

See It In Action

Tracer:

  • Inject indirect call
  • Where do we put this?
    • glibc unused e_entry
    • 0xFF 0xD0 0xCC - call rax; int3

libc.so

./target

Stack

0xFF 0xDO 0xCC

New Allocated Memory

See It In Action

Tracer:

  • Inject indirect call
  • Where do we put this?
    • glibc unused e_entry
    • 0xFF 0xD0 0xCC - call rax; int3
  • How do we do this?
    • PTRACE_POKETEXT

libc.so

./target

Stack

0xFF 0xDO 0xCC

New Allocated Memory

See It In Action

Tracer:

  • PTRACE_POKETEXT path to injectable shared library into Allocated memory

libc.so

./target

Stack

0xFF 0xDO 0xCC

New Allocated Memory

./inject.so

See It In Action

Tracer:

  • PTRACE_SETREGS - For call to dlopen
    • rip -> glibc_e_entry
    • rax -> address to dlopen
    • rsp->Newly allocated stack
    • dlopen arguments
      • rdi->.so path
      • rsi->RTLD_LAZY - This is generally how shared libraries are loaded. It means that it will only fill the GOT/PLT when needed. 

libc.so

./target

Stack

0xFF 0xDO 0xCC

New Allocated Memory

./inject.so

See It In Action

Tracer:

  • PTRACE_CONT
  • waitpid - Let's see what happens

libc.so

./target

Stack

0xFF 0xDO 0xCC

New Allocated Memory

./inject.so

See It In Action

Tracer:

  • Cleanup
    • munmap
    • un-clobber glibc->e_entry
    • put registers back
    • PTRACE_DETACH
      • Continue program as normal
      • Ensure that we do not set PTRACE_O_EXITKILL or we will kill the process (very very very bad)

libc.so

./target

Stack

Ways to protect against this

  • Disable ptrace if it's not a development machine
    • Sandbox with seccomp
    • Disable it system wide
  • Do not have users with root privileges
    • Not full-proof, but reduces risk

Your Turn!

Homework

We have an updated version of limited resources. Instead of having the write system call, we now have ptrace available. Figure out how to use ptrace and  /proc/pid/mem injection in order to get the flag.