Skip to content

jontay999/binary-exploitation

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Just my exploration into Binary Exploitation

  • Note: much of these notes are taken from pwn.college youtube videos

Many things to learn and do, but I'll list a basic todo for myself for now

  • Finish pwn.college youtube videos (6/13)
  • Try out the LinkedList imaginaryCTF 3 levels challenge
  • Get started on ROP Emporium
  • Get started on ProtoStar
  • Try a integer overflow/underflow challenge
  • Try a House of * Challenge
  • Figure out what is seccomp
  • Figure out what is qemu and mips

Kali Setup / Useful Tools

  • Install pwninit

    • Setup
      • install cargo: curl https://sh.rustup.rs -sSf | sh
      • install pwninit: cargo install pwninit
      • I faced an error
      warning: failed unstripping libc: failed running eu-unstrip, please install elfutils: No such file or directory (os error 2)
      
    • Usefulness:
      • Helps you download a linker that will help you load a provided libc without segfaulting
      • Downloads debug symbols and unstrips the libc
      • Follow this up with patchelf to use the correct RPATH and interpreter for the provided libc
  • Install patchelf

    • sudo apt -y install patchelf
    • configures the binary to use the ld linker file that was downloaded from pwninit rather than the system's one
    • e.g. patchelf --set-interpreter <ld file from pwninit> <binary file to use ld file>
    • Once this is done, can run LD_PRELOAD=<libc.so file> ./binary
  • Can run readelf -a <binary> to see the headers + segments, or just find out ELF info

  • ldd <binary> shows you which libc and which linker the binary is configured to use. Can use this to check if the binary is using the system libc or the one in your directory.

  • checksec --file=<binary> to see protections

    • RELRO: determines if Global Offset Table is writable
    • NX: determines if stack is executable --> can put shellcode on stack
    • PIE: depends if ASLR (Address Space Layout Randomization is enabled) --> different addresses each time
      • Note that in GDB its configured to disable ASLR all the time, see below for command to turn enable it
      • ASLR must maintain the last 3 nibbles (1.5 bytes) the same based on alignment and relative offsets
  • Don't forget to create a flag.txt file sometimes, otherwise the binary might give a segfault if it freads or fopen from that file

  • Install VSCode

  • Edit PATH to be able to use the binaries you install

    • check with shell you are using with ps -p $$
      • Bash --> edit ~/.bashrc
      • Zsh --> edit ~/.zshrc
    • export PATH="$PATH:/home/kali/.local/bin:/home/kali/.cargo/bin"
  • can use strace to view system calls

  • nm -D <binary> to see what function imports there are

    • shows symbols in binaries
  • rappel https:/yrp604/rappel lets you explore the effects of instructions.

Useful Links

GDB stuff

  • x/32gx <0xaddress>
    • x == examine
    • gx == 8 bytes values in hexadecimal
  • disass main
    • disassemble the main function
  • info functions
    • show list of all defined functions
  • info registers
    • see values held in registers
  • set disable-randomization off
    • enable ASLR
  • b *(0xaddress)
    • set breakpoint at the particular address
  • heap: examine heap (including tcache)
  • p $rdx: prints value of rdx register
    • p/x to print in hexadecimal format
  • bins to see bins lol
pwndbg> bins
tcachebins
0x90 [  2]: 0x603890 —▸ 0x603800 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
  • fini to step out of a function call, especially if you're going through a glibc function currently
  • press enter to repeat previous command
  • vis_heap_chunks: visualize heap chunks
  • info proc map: shows where process is loaded in memory
  • print the next 5 instructions: x/5i $rip
  • examine:
    • qwords: (x/gx $rsp)
    • dwords: (x/2dx $rsp)
    • halfwords: (x/4hx $rsp)
    • bytes: (x/8b $rsp)

Ghidra stuff

  • l to rename variable
  • If you see stuff like &DAT_00400b53, means it is a global variable
    • double click, if its a string, can just cmd + l to change variable data type to like char [3]

Shellcode basics

  • cos of the von neumann architecture, code can be treated the same as data, this leads to shellcode injection vulnerabilities

    • so to mitigate this, they support memory permissions (usually passed to mmap)
    • PROT_READ: read memory
    • PROT_WRITE: write memory
    • PROT_EXEC: allows process to execute memory
  • by default all code is located in .text segments, no need to execute code located on the stack or the heap

  • to bypass this, you can de-protect memory using the mprotect() system call

    • Code reuse through ROP ,trick the program into mprotect(PROT_EXEC) the shellcode
    • then jump to the shellcode
  • Other places that shellcoding is still vulnerabile is in JIT compilation

    • e.g. Javascript, Java, Lua, Python etc.
    • need to generate code that is executed
    • attacker can inject higher-level code can influence the resulting native code, including immediate values stored
    • pages must be writable for code-generation
    • pages must be executable for execution
    • pages must be writable for code re-generation
    • but syscalls are slow, but point of JIT is to be fast
      • so it is common to have writable and executable pages, (which are vulnerable to shellcoding!)
    • to see which processes could be vulnerable, or have a page that can be used to inject shellcode
    grep -l rwx */maps | parallel "ls -l {//}/exe
    • Even if JIT safely mprotect()s its pages, can still use JIT spraying
      • make constants in code that will be JIT-ed let evil = "..." // has to be valid assembly
      • JIT engine will mpotect(PROT_WRITE), compile the code into memory, then mprotect(PROT_EXEC), which means code is now present in exec memory
      • use vuln to redirect execution into that constant

JIT example

  • can spray by declaring the same thing thousands of times, jump and hope you get lucky

  • typical goal is to launch execve("/bin/sh", NULL, NULL);

.global _start
_start:
.intel_syntax noprefix
  mov rax, 59 # syscall number of execve
  lea rdi, [rip + binsh] #point first argument of execve to the /bin/sh string
  mov rsi, 0 # set second argument, argv NULL
  mov rdx, 0 # set third argument, envp, NULL
  syscall #trigger syscall

binsh: #label of where the string is
  .string "/bin/sh"
  • by default its ATNT, but can specify intel
  • assemble with gcc -nostdlib -static shellcode.s -o shellcode-elf
    • this gives ELF with shellcode as .text
  • extract with
objcopy --dump-section .text-shellcode-raw shellcode-elf
  • contains the raw bytes of the shellcode, and inject as part of exploit
  • interesting way to pipe in and pipe out
(cat shellcode.raw; cat) | ./<vulnerable-bin>
  • ways to include data in code
  1. .byte 0x48, 0x45, 0x4C, 0x4F # "HELLO"
  2. .string "HELLO" # "HELLO\0"
mov rbx, 0x0068732f6e69622f # movve "/bin/sh\0" into rbx
push rbx # push "/bin/sh\0" onto the stack
mov rdi, rsp #point rdi at the stack
  • Open /flag and send to stdout read

  • If you need to replicate exotic conditions that are hard to do as a preamble to shellcode, can build a shellcode loader in c

// create a function pointer, mmap a space that is executable and load the shellcode in and execute
page = mmap(0x1337000, 0x1000, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANON, 0,0)
read(0, page, 0x1000);
((void(*)())page)()
  • debug shellcode at high level using strace --> trace system calls

  • for more analysis in gdb

    • use si to step one instruction, following the call instructions, not s
    • use ni to step over call instructions, not n
  • breakpoints can be hardcoded with the int3 instruction

    • useful to do this right before the syscall, need to be handled either in code or in debugger
  • Shellcode for other architectures

    • Compiling
      • amd64: gcc -nostdlib -static shellcode.s -o shellcode-elf
      • mips: mips-linux-gnu-gcc -nostdlib shellcode-mips.s -o shellcode-mips-elf
    • Running
      • amd64: ./shellcode
      • mips: qemu-mips-static ./shellcode-mips
    • useful Qemu options:
      • -strace : print out log of system calls
      • -g 1234: wait for gdb connection on port 1234. Connect with target remote localhost:1234 in gdb-multiarch
  • Note: I have no idea how to work with qemu and mips, is a long todo

  • Common issues memoryaccesswidth forbiddenbytes forbiddenbytes2

Sandboxing basics

  • Untrusted code/data (i.e. downloaded JS, pngs, pdfs etc.) should live in a process with almost zero permissions
  • Basic process
    1. spawn privileged parent process (that can interact with your system)
    2. spawn sandboxed child process
    3. when child needs to perform privileged action, asks parent

Example Sandboxes

  • chroot(): changes meaning of root

    • for filesystem isolation
    • but no syscall filtering or other isolation
    • usually you attach busybox into the sandboxed directory so that you don't have to transfer the other linux utilities
    • sudo chroot /tmp/jail /busybox sh
    • does not cd into that jail for you, you have to do it manually
    • does not close previously open resources
      • use openat and execveat
        • int openat(int dirfd, char *pathname, int flags)
        • int execveat(int dirfd, char *pathname, char **argv, char **envp, flags)
        • execute and open relative to a particular file directory (or previously open directory)
        • e.g. current working directory is implicitly open
    • chroot can be done more than once, and wipes out the previous chroot
    • does not provide other form of isolation, e.g. process id isolations, network isolations, IPC etc.
    • now abandoned in favor of
      • cgroups
      • namespaces
      • seccomp
  • seccomp

    • Kernel level sandboxing mechanism
    • Chooses allow and disallowed certain system calls, even based on argument variables
    • are inherited by children
    • Example seccomp code
    scmp_filter_ctx ctx;
    
    // allow all syscalls
    ctx = seccomp_init(SCMP_ACT_ALLOW);
    
    // whenever execve is called, kill the program
    seccomp_rule_add(Ctx, SCMP_ACT_KILL, SCMP_SYS(execve), 0);
    
    // applies the filter
    seccomp_load(ctx);
    
    // will be killed
    execl("/bin/cat", "cat", "/flag", (char *)0);
    • To compile, need to install seccomp library
    sudo apt install libseccomp-dev
    
    • Compile with
    gcc -o <out-binary> <file>.c -lseccomp
    
    • In order to ensure that the sandboxed privileges are inherited, will call
    prctl(PR_SET_NO_NEW_PRIVS,1,0,0,0)
    
    • seccomp uses extended Berkeley packet Filters (eBPF) that run in an in-kernel "provably-safe" virtual machine

      • originally used to filter network traffic (iptables)
    • Seccomp Attack Vectors

      • Permissive Policies
        • ptrace() can use sandboxed process to puppet a non-sandboxed process
          • originally used to attach debuggers etc.
        • sendmsg() can transfer file descriptors between processes
        • prctl() got some weird stuff
        • process_vm_writev() allows direct access to other process' memory
      • Syscall Confusion
        • many 64 bit architectures are backwards compatible with their 32 bit ancestors
        • on some systems, can switch between 32-bit and 64-bit mode in same process
        • policies may fail to sandbox either 32/64-bit properly
        • e.g. exit() is syscall 60 on amd64 and 1 on x86
      • Trigger kernel vulnerabilities through the allowed syscalls

Reverse Engineering

  • The process of Forward Engineering
    • Starting from c code hello.c
    • run cpp hello.c to get preprocessed code with include statements expanded and comments tripped
    • run gcc -S -masm=intel hello-preprocessed.c to compile to intel assembly code
      • variable names are lost at this step
      • type information is lost
    • strip <binary> to remove semantic functions and reduce size
  • Functions often begin with a prologue (setup stack frame) and an epilogue (tear down stack frame)
    • Stack pointer (rsp): leftmost side of stack frame
    • Base pointer (rbp): rightmost side of stack frame
    • The stack used to store local variables and execution contexts
    • When function is called, the address that the function should return is implicitly pushed onto stack
    • The return address is implicitly popped when it returns
    • Prologue
      • save the caller's base pointer
      • set current stack pointer as base pointer
      • allocate space on the stack (subtract from stack pointer)
    • Epilogue
      • deallocate the stack mov rsp, rvp
        • but not actually removed
      • restore the old base pointer
      • return!

Random

  • The stack grows backwards
    • when you push to stack, rsp -= 8
    • when you pop from stack, rsp += 8
    • either bottom (0xFF) to top (0x00), or right (0xFF) to left (0x00)
    • The stack starts out storing the environment variables and the program arguments
  • Data Execution Prevention prevents injecting executable shellcode
  • Stack Canaries protect against stack buffer overflow
  • Address space layout randomization makes control flow hiajacking unreliable
  • returned values are stored in $rax
  • LANG environment variable determines sorting of file from ls
  • standard is int main(int argc, char **argv, char **envp)
  • objdump -d <binary> -M intel
    • use -d for disassemble
    • use -M for intel syntax because ATNT assembly is weird on x86
  • du -sb <file>: shows number of bytes in file
  • kill -l: lists all the signals
  • pgrep <process> === ps aux | grep <process>
    • used to find the process Id so you can do something like kill -9 <process> to kill it
  • /dev/shm is used for shared memory mapped files
    • requires system calls to establish, but afterwards don't need, just pass the file descriptors around
  • processes terminate only either by unhandled signals (e.g. SIG 10, segfault) or exit system call
    • after termination, processes remain in zombie state until wait() is called by their parents on them, then their exit code will be returned to parents and be freed
    • if parent dies before this happens, they are re-parented to PID 1 until cleaned up
      • PID 1 --> first process started by linux kernel /init process for shutting down + starting up system
  • getcwd : get current working directory
  • echo $$: echoes process id of current shell
  • use pwn.cyclic(200, n=8) to send cyclic pattern, check 8 bytes at a time
    • pwn.cyclic_find(0x...,n=8) to find offset
  • To preserve variable names and all, compile using the -g flag, e.g. gcc -g -o hello hello.c
  • -fomit-frame-pointer, allows rbp to be used as a general purpose register as modern compiler can omit frame pointers in most cases

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published