【时间】2017.9
【出处】SEC-T CTF
【类型】PWN
【分值】250
程序功能简单,在开始的时候会要求输入Username,之后就会打印出菜单。
1) Change user 可以修改 Username
2) Make it rain 会要求验证一串hash值,验证通过可以再进行一次输入(栈溢出),验证没通过就直接exit
3) Exit exit()
main中只有四个函数
init()
从/dev/urandom读取四个字节的随机字节组成一个int变量rand_data,保存位置在bss上,而且紧挨着username
而在输入username的时候我们可以输入9个字节,我们输入八个字节的话就可以在打印username的时候泄漏出rand_data的值了。
而之后的srand()又是以rand_data作为种子,所以之后的rand()的值就是可预测的了。
login()
这里就是进行第一次的username的输入了,观察到可以输入九个字节,而且没有加上'\x00'截断。
create_secret()
首先创建一片内存映射区域,并且是可读可执行的,只有在往里面写入数据的时候才改为可读可写,之后又改回可读可执行。
它这片区域固定地从0x40000开始,并且长度为0x30D40个字节。
开头的前九个字节是输入的username,后面的数据则全部是由rand()生成的随机数。
menu()
这里就是主循环的地方了,主要的就是一个函数make_it_rain()这个函数下面有两个函数
verify_secure_hash()负责生成hash值和验证输入是否与hash值相等
而这串hash的生成主要就是和secret指向的username的值以及后面的随机数有关。
如果这里验证成功了,就可以进入withdraw函数,验证失败就exit()给你看。
withdraw()
这里就可以进行栈溢出了。
程序除了canary,其他保护全开,但是程序刚开始创建的那片mmap区域是可执行的,而且刚开始的九个字节可控。
所以我们首先要控制username的9个字节作为可以正常开启shell的shellcode,然后控制rip跳过去执行就可以了。
所以总体思路如下
将9个字节以内的shellcode作为username进行输入。
在进行hash验证之前计算出正确的hash。
栈溢出,控制rip到0x40000
什么样的shellcode可以控制在9个字节以内?
这里一共八个字节,只要控制好栈上的内容,这串shellcode就是有效的。
如何计算出hash?
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)