输入函数
read
- 达到nbyte上限
- 遇到/n
输出函数
puts
输出字符串,直到空字符,但不包括空字符(包括换行符)
有时可用于泄露地址
printf
printf(‘%s’)输出字符串,而字符串以末尾的\0为标志,所以printf(‘%s’)输出字符直到遇到*\0*
Others
strcpy
字符串拷贝,如何来界定一个字符串呢?’\0’
有时可用于覆盖特定数据
例如:2016 BCTF bcloud
因为又在这里栽跟头了所以写一篇博客来记录一下orz
例如:
1 | #include<stdio.h> |
可以看到输出的数据是0xe也就是14个字节
我们都知道一个64位程序的地址最多也就8个字节,为什么这个地址是14个字节呢?
原因是printf在输出的时候是将这个6个字节的地址转换成的字符串再加上0x进行输出的
知道原因过后我们的目标就很明确了:将这个14个字节的字符串转换为6个字节的数据
首先是要对获取到的数据进行处理:
我们需要用到python中的decode()
方法将字节数据转换为字符串再使用lstrip('0x')
去掉字符串左边的‘0x’
再使用bytes.fromhex()
方法将十六进制字符串转换为字节数据
但是还没完,我们知道在大部分机器上数据是以小端序的顺序储存的,也就是我们要把我们得到的字节再进行反序操作
即使用[::-1]
完整操作如下:
1 | raw_bytes = r(14)#接收数据 |
然后就可以使用u64(reversed_bytes.ljust(8,b’\x00’))对其处理在写入payload中
有师傅说可以直接用
1 | int(p.recvline()[:-1],16) |
直接一步进行处理,我自己还是见识太少了,另外师傅tql orz
构造ROP链来使puts函数泄露libc地址应该是最常见的情况了,puts函数也是输出的字符串,为什么就可以直接通过u64(p.recv(6).ljust(8,b’\x00’))来获得libc地址呢?
在构造payload输出的时候虽然输出的是字符串,但这个字符串本质是直接根据字节数据通过诸如ascii转换而来的,也就是,输出的数据仍是原来的字节,而不是像printf那样输出的是将每四位数据对应的字符将这个字符作为一个单独的字节输出
glibc(GNU C Library)中动态内存分配器(ptmalloc2) 的一个关键全局变量,用于控制 fast bins(快速分配区) 的最大内存块大小。
可以通过漏洞修改其大小来达到特殊的效果
单向链表
32位下fast bin范围为16到64
64位下fast bin范围为32到128
从块分配器的角度来看,fastbin 中的块被视为已分配的块。它们仅在 malloc_consolidate 中批量与相邻对象合并。
1 | _int_free |
这里对double free漏洞的检查:
获取fastbin链表中最后入链的old,然后判断即将释放的fastbin和old的指针是否相同
unsorted bins的bk和fd指向的是一个指针,而这个指针指向的是top chunk的地址,这个地址又储存在main_arena的0x58偏移处
main_arena - 0x10即为__malloc_hook的,__malloc_hook又是调用malloc时会调用的指针,所以可以通过劫持__malloc_hook来劫持程序控制流
然后通过fastbin attack将下一个堆块改为__malloc_hook - 0x23处,因为只有改为这处,视为堆块的此处的size位置才为正确的数据
物理存储
binmap
是malloc_state
结构体中的一个数组(unsigned int binmap[BINMAPSIZE]
),每个元素为32位无符号整数(unsigned int
)。
总长度:固定为4个int(即128位,对应63个large bin的索引需求)
分组逻辑:每个int(32位)称为一个block,每个block负责管理32个连续的large bin状态
通过上面的泄露unsorted_bin的fd指针
通过__malloc_trim函数直接获得
“比如把 .so
文件放到 IDA 中,找到 malloc_trim
函数,就可以获得偏移了。” -–ctfwiki
glibc 2.26后
两个结构体
仅限glibc2.26,可以直接对其进行double free
1 | /* We overlay this structure on the user-data portion of a chunk when |
一个单向链表
1 | /* There is one of these for each thread, which contains the |
通过counts记录tcache_entry上空闲的chunk数目
C 库函数 int rand(void) 返回一个范围在 0 到 RAND_MAX 之间的伪随机数。
RAND_MAX 是一个常量,它的默认值在不同的实现中会有所不同,但是值至少是 32767。
C 库函数 void srand(unsigned int seed) 播种由函数 rand 使用的随机数发生器。
void srand(unsigned int seed)
参数seed是一个整形值,用于伪随机数生成算法播种。
有时随机数的种子seed会在栈上,可以通过栈溢出等方法对齐进行覆盖为已知值,再通过这个已知值获取对应的随机数序列
ctypes是Python内建的用于调用动态链接库函数的功能模块,通过调用该模块以及利用与题目相同的libc文件,来达到一个与题目中随机数生成方式相同的效果,实现撞库,这种方式下如果提前覆盖了指定种子,是更容易达成,如果直接设置时间种子撞库,可能存在时间对不上的情况,需要多测试几次。
使用方法:
1 | from ctype import * |
1 | #使用相同时间为种子 |