PwnCollege - Pwntools Tutorials
PwnCollege - Hacker History
PwnCollege - The Art of the Shell - Abusing Expansion
TexSAW CTF 2026 Whats the Time?
This Pwn challenge is a classic buffer overflow with a twist: the input is obfuscated via a time-based XOR operation before being copied to the stack.
Challenge Description
I think one of the hands of my watch broke. Can you tell me what the time is?
nc chals.texsaw.org 3000
Flag format:
texsaw{flag}
Solution
1. Binary Analysis
Using checksec, we identify the binary's
protections:
- Arch: i386-32-little
- RELRO: Partial RELRO
- Stack: No canary found
- NX: NX enabled (cannot execute shellcode on the stack)
- PIE: PIE disabled (fixed addresses for code/data)
Since PIE is disabled and there is no stack canary, the primary goal
is a Return-to-PLT attack to call
system("/bin/sh").
2. Identifying Vulnerabilities
Disassembling the binary reveals two critical functions:
main and read_user_input.
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
1 | ssize_t __cdecl read_user_input(int key) |
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 intoread_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 thetime_val. Crucially, thetime_valincrements by 1 after every 4 bytes.Buffer Overflow: After XORing, the program uses
memcpyto copy the processed buffer into a local stack buffer (ebp-0x40). Since the stack buffer is only 64 bytes butmemcpycopies 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.
4. Script
1 | from pwn import * |
Flag
texsaw{7h4nk_u_f0r_y0ur_71m3}TexSAW CTF 2026 Return to Sender
Do you ever wonder what happens to your packages? So does your mail carrier.
nc 143.198.163.4 15858
Flag format:
texsaw{example_flag}
Solution
1. Initial Analysis
We start by analyzing the binary's protections:
1 | $ checksec chall |
- No Stack Canary: This means we can easily overwrite the return address on the stack.
- NX Disabled: The stack is executable, but we don't necessarily need shellcode for this exploit.
- PIE Disabled: Function addresses are static and will not change between runs.
2. Identifying Vulnerabilities
Using objdump and nm, we identify several
interesting functions:
main: The entry point.deliver: Called bymain; uses the unsafegets()function to read input into a 32-byte buffer.drive: A hidden function that checks an argument and, if correct, callssystem("/bin/sh").tool: Contains a useful ROP gadget:pop rdi; ret.
The vulnerable deliver function looks like this:
1 | int deliver() { |
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 | int __fastcall drive(__int64 a1) { |
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; retgadget. - Provide the value
0x48435344as the next item on the stack (to be popped intoRDI). - Include a
retgadget for stack alignment (often necessary forsystem()calls in 64-bit glibc). - Finally, call the
drivefunction.
4. Exploit Script
1 | from pwn import * |
Flag
texsaw{sm@sh_st4ck_2_r3turn_to_4nywh3re_y0u_w4nt}TexSAW CTF 2026 The Imitation Game
There's a spy amongst us! We found one of their messages, but can't seem to crack it. For some reason, they wrote the message down twice.
The challenge provides two large blocks of ciphertext, both starting with what appears to be an encrypted flag.
Solution
1. Identifying the Cipher
We are given two different ciphertexts that supposedly represent the same message. This immediately suggests a polyalphabetic substitution cipher, most likely Vigenere, where different parts of the key are being applied to the same plaintext.
2. Deducing the Key Prefix
We know that the flags in this CTF follow the format
texsaw{...}. By comparing the ciphertext prefixes with the
known plaintext texsaw, we can calculate the key characters
used at the start of each block (\(Key =
Ciphertext - Plaintext\)).
Block 1 Prefix (twhsnz):
t-t= A (0)w-e= S (18)h-x= K (10)s-s= A (0)n-a= N (13)z-w= D (3)- Key Prefix:
ASKAND
Block 2 Prefix (brassg):
b-t= I (8)r-e= N (13)a-x= D (3)s-s= A (0)s-a= S (18)g-w= K (10)- Key Prefix:
INDASK
3. Recovering the Full Key
The fragments ASKAND and INDASK strongly
suggest a famous quote from the Bible (Matthew 7:7):
"Ask, and it shall be given you; seek, and ye shall find: ask, and..."
By removing spaces and punctuation, we derive the full 41-character
repeating key:
ASKANDITSHALLBEGIVENYOUSEEKANDYESHALLFIND
4. Decrypting the Message
Using the recovered key, we can decrypt the rest of the message. The first message block uses an offset of 0, while the second block starts at a different position in the key loop.
Decryption reveals a message from a spy:
"they know im here, and its only a matter of time before they find out who i am. tell the general what the flag is as soon as possible..."
The signature at the bottom, - john cairncross, refers
to the real-life British intelligence officer who was a double agent for
the Soviet Union during World War II.
5. Extracting the Flag
1 | def vigenere_decrypt(ct, key, offset=0): |
Flag
texsaw{luojmfsgmkqltenaemdqlxgtyrfdlzxdmqmxysvdettsxpatcq}TexSAW CTF 2026 lostmykey
I can't find my original house key anywhere! Can you help me find it? Here's a picture of my keys the nanny took before they were lost. It must be hidden somewhere!
Flag format:
texsaw{flag_here}
Solution
1. Extracting Hidden Files
Using binwalk, we can identify and extract any embedded
files:
1 | ❯ binwalk -e Temoc_keyring.png |
After extraction, we have two similar images:
Temoc_keyring(orig).pngwhere_are_my_keys.png
Checking them with pngcheck confirms they are both
valid, but their file sizes and compression ratios differ.
2. Pixel Comparison
At first glance, the two images appear identical. However, the
difference in file size suggests that data might be hidden in the pixel
values themselves. We can use a Python script with the
Pillow library to compare them:
1 | from PIL import Image |
Running this reveals exactly 131 differing pixels, all located within
the very first row (y = 0).
4. Decoding the Steganography
The pattern of differing pixels suggests a binary encoding. We can treat each pixel in the first row as a bit:
- Bit 1: If the pixels at
(x, 0)are different. - Bit 0: If the pixels at
(x, 0)are identical.
We then group these bits into 8-bit bytes and convert them to ASCII characters to reveal the flag.
Extraction Script:
1 | from PIL import Image |
Flag
texsaw{you_found_me_at_key}TexSAW CTF 2026 Excellent Neurons
Find the flag by reverse engineering this neural network. Oh, and its in Excel.
Flag format:
texsaw{flag}(e.g.,texsaw{orthogonal})
前置知识
本题涉及神经网络逆向工程,且模型实现在 Excel 电子表格中。以下概念有助于理解:
| 概念 | 说明 |
|---|---|
| 前馈神经网络 | 输入层→隐藏层→输出层,数据逐层前向传递 |
| ReLU | f(x)=max(0,x),负输入→零输出(关键逻辑门控!) |
| Sigmoid | σ(x)=1/(1+e^{-x}),输出映射到 (0,1),>0.5 表示正输入 |
| 权重与偏置 | 网络参数,本例中 W1 是极端稀疏的 +1/-1 矩阵 |
| Excel 逆向 | .xlsx 本质是 ZIP,内部 XML 含公式与数据 |
| 前向传播公式 | ASCII/127 × weight + bias = 0 ⇒ ASCII = round(-bias × 127 / weight) |
参考:Excel NN 教程、hxp EXCELlent
Solution
1. Understanding the Success Condition
Examining the spreadsheet, specifically Row 112, we find the
conditions for a valid flag: - F > 0.5,
L < 0.5, A > 0.5,
G < 0.5
Since the output layer uses a Sigmoid activation
function: - To get output > 0.5, the input to the
sigmoid (z3) must be positive (> 0). - To
get output < 0.5, the input to the sigmoid
(z3) must be negative (< 0).
Analyzing the final weights (W3) and biases
(b3), we find that all weights in W3 are large
(300 or -300) and the biases are small (between 0.49 and 0.59). For
these specific constraints to be met, the output of Layer 2
(a2) must be an extremely small positive number, very close
to zero.
2. Reverse-Engineering Layer 2
Layer 2 uses the ReLU activation function:
a2 = ReLU(z2), where
z2 = sum(a1 * W2) + b2.
Key observations: - All weights in W2 are
negative (e.g., -0.954667). - The bias
b2 is a very small positive number (approx.
1/254). - a1 is the output of Layer 1's ReLU,
so a1 >= 0.
If any value in a1 is a positive number, its product
with the negative weights in W2 will likely make
z2 negative, resulting in a2 = 0 (via ReLU).
To maintain that tiny positive value for a2, we conclude
that all activation values in a1 must be forced to
zero.
3. Analyzing Layer 1
To force a1 = 0, we must ensure that
z1 = x * W1 + b1 <= 0 for all neurons in the first
layer.
Looking closely at the biases (b1), they appear to be
"random" decimals: - h1[0] = -0.795276 -
h1[3] = -0.90551 - h1[12] = -0.968504 -
h1[23] = 0.91339
If we multiply these values by 127 (the maximum
standard ASCII value), they resolve into integers. This reveals that
W1 and b1 are essentially implementing
boundary checks for each character of the input string.
Converting these non-zero biases back to ASCII: -
-0.795276 * 127 \(\approx\) -101 \(\rightarrow\)
e - -0.90551 * 127 \(\approx\) -115 \(\rightarrow\)
s - -0.968504 * 127 \(\approx\) -123 \(\rightarrow\)
{ - 0.91339 * 127 \(\approx\) 116 \(\rightarrow\)
t
4. Reconstructing the Flag
Extracting all valid b1 values and converting them
yields a multiset of 22 characters:
t, e, x, s, a, w, {, }, s, v, r, r, r, 3, 3, 3, 3, _, n, l, 4, u
We know the flag format is texsaw{flag}. Removing the
wrapper characters (t, e, x, s, a, w, {, }), we are left
with: s, v, r, r, r, 3, 3, 3, 3, _, n, l, 4, u
Rearranging these characters (Leetspeak for "neural reverse") gives us the inner flag content.
Flag
texsaw{n3ur4l_r3v3rs3}TexSAW CTF 2026 Idiosyncratic French
This chall's got a bit of history to it.
First, crack this initial cryptogram. Now, apply OSINT tools to find who authors that original script.
Flag format:
txsaw{first_last}(e.g.,txsaw{john_scalzi})
Solution
1. Cryptanalysis: Substitution Cipher
The challenge begins with a large block of ciphertext:
1 | Azza wfahv ztu. N rnvy, bndfah na zbfaztv vztak, n vztak ndfa uz n dcnqza zw n uzlvfa, icfuv nmztu... |
Using frequency analysis or an automated tool like quipqiup, we can determine that this is a simple substitution cipher. The decoded plaintext is:
Noon rings out. A wasp, making an ominous sound, a sound akin to a klaxon or a tocsin, flits about. Augustus, who has had a bad night, sits up blinking and purblind. Oh what was that word (is his thought) that ran through my brain all night, that idiotic word that, hard as I'd try to pun it down, was always just an inch or two out of my grasp...
2. OSINT: Identifying the Source
The title of the challenge, "Idiosyncratic French", and the nature of the decoded text provide vital clues.
Searching for the decoded string—specifically unique phrases like "A wasp, making an ominous sound, a sound akin to a klaxon or a tocsin"—reveals that this is an excerpt from the novel "A Void".
3. The "Idiosyncrasy": Lipograms
What makes this text "idiosyncratic"? "A Void" is the English translation of the French novel "La Disparition". The defining characteristic (idiosyncrasy) of both the original and the translation is that they are lipograms: they are written entirely without the letter "e".
4. Finding the Author
The author of the original French novel, La Disparition, is the famous French writer Georges Perec.