栈溢出
首先获取摄像头的固件,下载地址: https://service.tp-link.com.cn/detail_download_7631.html
固件本身没有加密,可以直接使用 binwalk 解开,解开之后得到一个标准的 linux 文件系统,存在漏洞的文件是 /usr/bin/dsd。此二进制文件是 32bit ARM 架构的,IDA 可以直接进行反编译,但是在实际操作中,IDA 的交叉引用识别上面存在一定的问题,所以推荐搭配 Ghidra 使用。
在分析之前需要介绍一下如何才能定位关键文件。摄像头之类的设备一般都存在 web 管理界面,所以拿到固件之后可以先尝试寻找包含 http 字样的 web server,某些设备考虑到性能等问题,可能会把所有逻辑集成在一个二进制文件中,而其他一些设备性能较强,或者应用了某些框架,则会把 web 服务器和具体的业务逻辑分散开。
本例中 web 服务器位于 /usr/sbin/uhttpd,它负责解析 web 请求,以及一部分设备功能。通过进一步分析此程序,可以发现更多的业务逻辑(主要和设备的参数设置相关)被放置在了 /usr/bin/dsd 中,所以 dsd 文件就是我们的主要分析对象。
IDA 直接打开 dsd 文件,可以通过搜索字符串的方式尝试定位关键函数,也可以结合抓包手段拿到一些关键的字符串信息,经过分析,发现关键函数是 0x155FC,抓包样本:
1 | POST /stok=9ffcd0b497aa1902380ea5d5e1ee2eea/ds HTTP/1.1 |
函数的几个关键信息:
- 处理的数据是 json 格式,基本参数包括 method、request
- 函数根据不同的 method 会做不同的处理,具体包含 get、add、delete、set、do
- 处理逻辑类似于 switch case 结构,根据不同的 request 调用不同的 handler。
关键的代码片段:
1 | if ( (v33 - 1) <= 4 ) |
访问了 aDsHandleMethod 全局变量,根据不同的 method 调用不同的 handler,利用 IDA 查找可以发现 5 个 handler 函数指针:
1 | .rodata:00029A24 method_get_handler DCD get_handler+1 |
其中第 2 ~ 4 个 handler 共用一个函数。
找到 do_handler,代码如下
1 | int *__fastcall do_handler(_DWORD *a1) |
第 93 行附近使用了动态的函数指针调用不同函数,静态分析中暂时无法定位具体的函数列表。
由于手中有真实设备,所以可以通过抓包的方式获得一些功能接口,通过字符串搜索发现了疑似的函数参照表:
1 | .data:00045EC0 dword_45EC0 DCD 0 ; DATA XREF: sub_25F4C↑o |
IDA 默认没有把这里识别成任何的变量,但是从 Ghidra 中寻找这部分数据可以得到以下解析结果:
1 | DAT_00045ec0 XREF[2]: 00025f4c (*) , 00025f54 (*) |
这段数据是 字符串、函数指针、字符串、函数指针 这种形式排列的,大概分析以下可以发现每个字符串下方的函数就是这个字符串对应的 handler,例如 0x45EDC 位置的字符串是 change_user_info,而 0x45EE0 位置的函数指针正是 change_user_info 接口的 handler。
由此我们找到了一个相对比较完整的设备接口 handler 列表,接下来的操作可以是提取接口然后依次分析。
在 0x45EEC 位置的字符串是 check_user_info,对应的 handler 代码如下:
1 | signed int __fastcall do_login(int a1, int a2) |
从逻辑上来看,此函数是用于检查账户信息的,用户可以传入 username 以及 password,其中 password 是经过加密的。
函数接受到 password 之后会调用 private_decrypt 对 password 解密,从解密函数名上来看 password 很可能是经过公钥密码加密,例如 RSA。经过解密的字符串会被带入 sscanf 函数拷贝到 stack 某个变量中。
注意这里的 sscanf 格式化字符串并没有限制拷贝字节的数量,考虑到 RSA 最大的明文可以是 128 字节,但是 sscanf 的目标 buffer (大小可在开头的 memset 看到) 加起来只有 80 字节,而且查看 IDA 的变量定义发现两个 buffer 都靠近栈底。所以很可能会导致溢出。
至于能否产生溢出,还要看 private_decrypt 函数的具体实现逻辑,经过搜索发现此函数是 /usr/lib/libdecrypter.so 库导出的,代码如下:
1 | int __fastcall private_decrypt(int a1, signed int a2, int a3, int a4) |
简单分析发现此函数确实是 RSA 算法的解密函数,并且内部限制了明文的长度为 128 字节。
传入的密文应该是 BASE64 编码的,根据 sscanf 参数判断明文中应该以冒号作为分隔符,前半部分是 password,后半部分是 nonce 值。
弄清楚数据的基本格式之后,可以尝试构造 payload,不过首先要解决一个问题,那就是 RSA 的公钥从哪里获取。
由于我们分析的都是 web 端接口,首先考虑的就是数据是否由前端加密,或者在正式发送此请求之前,会发送其他请求获取 RSA 公钥。
抓包分析之后发现使用的是第二种方法,在发送正式请求之前,会首先发送请求获取 RSA 公钥,使用的接口是 user_management,参数为 get_encrypt_info。
所以到这里我们的思路就是首先请求服务器获取一个 RSA 公钥,然后对构造的 payload 进行加密操作,之后发送请求,验证是否能够触发漏洞。
设备的 web 管理界面存在日志窗口:
是否触发了漏洞可以通过此窗口查看。
下面是我编写的一个简单的 poc:
1 | import requests |
由于请求 dsd 文件需要登录权限,所以首先要获取一个合法的 stok 值,可通过抓包获取。之后设置好 ip 参数执行程序即可看到效果。
1 | ➜ Desktop python TP-LinkIPC.py |
可以看到 dsd 程序崩溃,守护进程重置了 dsd,多次发送数据看到 dsd 多次崩溃。
但是进一步利用会遇到困难,由于没有合适的调试环境,无法确定溢出到什么位置,另外设备可能开启了 ASLR,导致盲测难度很高。
另外在测试漏洞的过程中,我发现了另外一个能够使 dsd 程序崩溃的 POC:
1 | POST /stok=*/ds HTTP/1.1 |
- 本文作者: CataLpa
- 本文链接: https://wzt.ac.cn/2020/05/23/IPC43AN/
-
版权声明:
本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。