0x00 写在前面
本实验是对 CVE-2018-18708 漏洞的调试分析和复现,所有实验过程均在本地搭建的虚拟机中进行。通过该实验比较深入地理解了该漏洞的成因和利用过程。
请严格遵守所在地法律法规。
0x01 环境搭建
本次调试是在 Ubuntu18.04
环境下,借助 qemu-arm-static
进行的,调试使用 gdb
和 ida
结合着。
环境搭建过程与上一篇博文《CVE-2018-5767调试复现分析》相似,很多操作都一样,这里就简写了。
首先访问 tenda
官网的下载中心 然后搜素版本 AC15 V15.
并下载固件,下载完毕后,在虚拟机中进行解压。
解压后使用 binwalk
提取固件
1 | binwalk -Me US_AC15V1.0BR_V15.03.05.19_multi_TD01.bin |
然后找到存在漏洞的文件
1 | cd xxx.bin.extracted/squashfs-root |
然后尝试启动这个程序,
程序停止了,这是由于 qemu
的模拟有些不健全,所以需要简单 patch
一下。通过将 httpd
在 ida 中打开后,全局搜索字符串 Welcome to
然后定位到下面这个地方,需要将红框中的两个指令 patch
掉。
成功 patch
之后的控制流如下,让比较恒成立。
两处都要 patch
然后保存
然后将 httpd
拷贝进去进行替换原始文件并添加权限即可。
因为这里我在之前已经配置好了网络,所以直接就成功了,否则请查看之前的博文配置一下环境
但是这里要进行 http
的直接访问还有点问题,不过这个无伤大雅,似乎不需要 web 界面的访问即可。
发送这个错误的原因是文件不存在,只需要将对应的文件夹告诉它即可。
1 | rm -rf webroot |
然后再刷新页面即可成功访问。
0x02 漏洞分析
查看 nvd 的描述,可以得知问题点出在函数 fromAddressNat
中,其在处理 POST
传递过来的 page
参数时不当,这个值会直接传递到函数 sprintf
中的一个局部变量中,然后就会造成栈溢出。
An issue was discovered on Tenda AC7 V15.03.06.44_CN, AC9 V15.03.05.19(6318)_CN, AC10 V15.03.06.23_CN, AC15 V15.03.05.19_CN, and AC18 V15.03.05.19(6318)_CN devices. It is a buffer overflow vulnerability in the router’s web server — httpd. When processing the “page” parameter of the function “fromAddressNat” for a post request, the value is directly used in a sprintf to a local variable placed on the stack, which overrides the return address of the function.
所以我们直接在 ida
中搜索函数 fromAddressNat
找到漏洞点,可以看到对应的参数 v7
应该就是描述的 page
参数,前面的路径就对应相应的路由。
这里我就懒得去一步一步分析控制流找到这个参数具体是哪部分受控的,我们直接简单粗暴,往这个接口中灌入很长的
page
参数,观测程序是否崩溃来看我们是否能正常触发这个接口,如果不能再进一步调试找条件。
于是,根据上面的路径可以很容易生成一个相应的 POST
请求。
0x03 漏洞调试
先运行一个 poc
观测程序崩溃
1 | #!/usr/bin/python |
这里每次程序 poc 程序都要运行两次才会触发,我也不清楚为啥,但是不影响使用。
然后观测程序的行为,在一个终端中以可调试方式启动
1 | sudo chroot ./ ./qemu-arm-static -L ./ -g 1234 ./bin/httpd |
再开一个终端执行
1 | gdb-multiarch ./bin/httpd |
然后输入 c
后程序可以继续执行,说明我们可以正常进行调试。
使用 cyclic
生成可定位 payload
然后找到 fromAddressNat
退出时的偏移,用于下断点,观测溢出覆盖情况。
下断点
1 | b *0x0007A0CC |
然后以调试方式执行程序,运行可定位的 poc
,此时可以观测到程序停下的指令和栈中的数据。
然后慢慢走,在崩溃前停下来 于是可以发现 pc
对应的是 laac
,所以我们定位 laac
的偏移即可找到控制流劫持位置的偏移。
这里刚好
l
的ascii
码是0x6c
最后一位是0
,所以没有看到异样。但其实这里是有一个切换的,这是arm
指令集的原因。
然后找到对应的偏移是 244
然后检测保护情况,可以发现只开了 NX
所以 ROP
即可。
这里采用无 ASLR
利用,使用 libc
中的 rop gadgets
通过下面两个形成的链即可。
1 | ROPgadget --binary ./lib/libc.so.0 --only 'mov|blx' |
然后尝试找 libc
的基地址,但是直接 vmmap
啥都看不到,所以我们通过 strcmp
手动计算。
在 idx
中打开 libc.so.0
找到 strcmp
的偏移 0x3e010
。
你也可以使用
pwntools
中的ELF
来查看符号表,一样的。
然后得到 libc_base = 0xff58c000
于是可以构建一个 exp
如下
1 | #!/usr/bin/python |
这里解释一下,通过调试对栈进行观测后,当程序执行溢出后,会来到 pop_r3
处(pop {r3, pc}
),此时会将栈中两个数据进行出栈,即 puts
会被弹出到达 r3
而 mov_r0
会被弹出到达 pc
;而后就是执行 mov r0, sp; blx r3
于是 r0
会指向当前的栈顶,即 _str
的位置,然后再执行 blx r3
即跳转到 r3
所指向的函数进行执行,而 arm
的传参规则为 r0
是函数的第一个参数,所以成功将 _str
作为 r3
处的 puts
函数的参数传入并执行了。
然后重新开始调试
1 | target remote :1234 |
程序刚溢出后,在退出函数 fromAddressNat
前
然后 puts
函数会压在 r3
中去,然后 pc
会变成 mov r0, sp
这一步之后将会让 r0
和 sp
相等,即 r0
指向栈顶,而栈顶是我们放置好的数据
arm
是r0
就是第一个参数,因此通过上述操作,r0
已经指向了栈顶我们放置的数据,即我们已经完成了函数参数的布置。而此时指令 blx r3
的执行即将控制流跳转到 r3
所指向的函数进行执行,即我们这里设定的 puts
函数,同时将返回地址保存到了 lr
中。
于是再 ni
一步,我们将看到 puts
函数执行完毕,在终端打印了我们希望打印的 abcd
在上面成功后,只需要简单将 puts
函数替换成 system
函数即可,但是我们可以看到,其实是不成功的,这里也和之前的那个一样,据说是由于 qemu
模拟不健全,无法执行 system("/bin/sh")
。
如下图所示,可以看到,参数 /bin/sh
已经成功放置在 r0
处,同时函数 system
也已经在 r3
处待命。虽然一切如预期,应该会成功,但是程序就是不会成功。
然后 c
程序就崩溃了。
0x04 内存布局变化图
最终在调试过程中的一些关键位置时的内存变化图如下
0x05 exp
这个能打印,但是 system
跑不起来。
1 | #!/usr/bin/python |