hiwifi极路由开启隐藏ssh探秘
一、引言
手里有台极路由,型号HC5861,感觉做工不错,全铝合金外壳,散热功能良好,功能挺多,支持插件,官方宣称的去视频无广告功能、出国加速等也是收到外界一致好评。
可惜正是由于某些插件的原因,导致极路由遇上了它有史以来最大的危机,至于后来虽然经过一些改变,但最终由于某些原因,失去了用户口碑,以至于走下坡路,下图是插件功能的界面,已经无法使用了,同时官网也被其他服务商使用。
二、初探ssh
虽然极路由官网已经无法连接了,但是玩家们还是有很多方式进行设备的改良,如更换固件,但极路由的固件与普通的固件格式是不一致的,所以通常玩家们先获取root权限,通过更换通用BootLoader,如Breed,然后再进行刷机操作。那么如何获取ssh就是首先要解决的问题,当初官网能够使用的情况下,只需要绑定小极账号,申请开发者模式即可使用。
可是现在官网已经无法连接了,想要使用这个方式不太容易,经过互联网搜索,有大神做了一个开启ssh的工具http://www.hiwifi.wtf/ ,免费提供给用户使用,真的很感谢!
根据网站的说明,很快就能开启ssh
最终能够获取到root权限。
三、再探ssh
获取到root权限之后,我们就能拿到设备内的文件系统的内容,通过搜索关键字local-ssh,可以定位到一些文件,其中有nginx的配置文件vh.tw.conf
分析配置文件,可以知晓处理逻辑在local_ssh.lua中
使用文本编辑器打开lua文件,显示为乱码,通过起始的字符串LuaQ可以确定lua代码为编译后的lua,那么需要找到对应的lua版本进行反汇编。
通过搜索liblua.so字符串可以知道lua的版本为5.1.5
经过多次测试,在github上能够找到对应luadec反编译工具,使用对应的安装方式进行安装
git clone https://github.com/HandsomeYingyan/luadec-openwrt.git
cd luadec-openwrt
cd lua-5.1
make linux
sudo make install
cd ../luadec
make LUAVER=5.1
sudo cp luadec /usr/local/bin/
首先试试local_ssh.lua是否能够正常反编译,可以看出来能够正常反编译,虽然里面有一些错误,但基本不影响逻辑的分析:
$ luadec local_ssh.lua
......
local l_0_0 = require("hiwifi.json")
local l_0_1 = require("openapi.utils.utils")
local l_0_2 = string
local l_0_3 = tostring
module("luci.local_ssh.local_ssh")
dispatcher = function(l_1_0)
-- function num : 0_0 , upvalues : l_0_1, l_0_3, l_0_2, l_0_0
local l_1_1 = ""
local l_1_2 = {}
if not l_1_0 then
l_1_0 = {}
end
if l_1_0.method == "get" then
l_1_2.data = (l_0_1.exec_cmd)("sudo /usr/lib/lua/luci/local_ssh/local_ssh_util.lua get")
else
-- DECOMPILER ERROR at PC32: Unhandled construct in 'MakeBoolean' P1
do
if not (l_0_1.exec_cmd)("sudo /usr/lib/lua/luci/local_ssh/local_ssh_util.lua valid " .. l_0_3(l_1_0.data)) then
local l_1_3 = l_1_0.method ~= "valid" or l_1_0.data == nil or ""
end
-- DECOMPILER ERROR at PC34: Confused about usage of register: R3 in 'UnsetPending'
do
local l_1_4 = l_0_3(l_1_3)
if (l_0_2.find)(l_1_4, "Success:") ~= nil then
l_1_2.code = Unknown_Type_Error
l_1_2.data = l_1_4
else
l_1_2.code = Unknown_Type_Error
l_1_2.data = l_1_4
end
if l_1_0.method == "stop" then
l_1_2.data = (l_0_1.exec_cmd)("sudo /usr/lib/lua/luci/local_ssh/local_ssh_util.lua stop")
else
l_1_2.code = Unknown_Type_Error
l_1_2.data = "Error: Method does not exist"
end
l_1_1 = (l_0_0.encode)(l_1_2)
return l_1_1
end
end
end
end
通过分析可以知道主要的逻辑实际上在另外一个lua脚本local_ssh_util.lua,那么使用同样的方式反编译:
$ luadec local_ssh_util.lua
......
local l_0_0 = require("tw")
local l_0_1 = require("auth")
local l_0_2 = require("socket")
local l_0_3 = require("luci.util")
local l_0_4 = require("nixio")
local l_0_5 = require("luci.http.protocol")
local l_0_6 = (math.floor)((l_0_2.gettime)() * Unknown_Type_Error)
local l_0_7 = (l_0_0.get_mac)()
local l_0_8 = "ssh"
local l_0_9 = ""
local l_0_10 = tostring(l_0_7) .. "," .. l_0_8 .. "," .. tostring(l_0_6)
local l_0_11 = ""
local l_0_12 = "/tmp/local_ssh_ms"
local l_0_13 = (io.open)(l_0_12, "r")
local l_0_14 = Unknown_Type_Error
local l_0_15 = arg[Unknown_Type_Error]
do
local l_0_16 = arg[Unknown_Type_Error] or ""
local l_0_17, l_0_18 = , nil
if not l_0_13:read("*n") then
l_0_13:close()
-- DECOMPILER ERROR at PC71: Overwrote pending register: R14 in 'AssignReg'
l_0_13:close()
-- DECOMPILER ERROR at PC83: Overwrote pending register: R13 in 'AssignReg'
if l_0_15 == "get" then
l_0_13:write(l_0_6)
l_0_13:close()
-- DECOMPILER ERROR at PC106: Overwrote pending register: R11 in 'AssignReg'
print(l_0_11)
;
(os.exit)(Unknown_Type_Error)
else
-- DECOMPILER ERROR at PC136: Overwrote pending register: R11 in 'AssignReg'
-- DECOMPILER ERROR at PC141: Overwrote pending register: R11 in 'AssignReg'
if l_0_15 == "valid" then
if (l_0_1.lua_hmac_sha1_with_uuid)(l_0_10, (string.len)(l_0_10)) or l_0_17 == l_0_11 then
(os.execute)("/etc/init.d/dropbear restart;hwf-at 10 /usr/lib/lua/luci/local_ssh/local_ssh_util.lua check_connected")
;
(os.remove)(l_0_12)
if l_0_18 == nil then
print("Error: port file does not exist")
;
(os.exit)(Unknown_Type_Error)
end
-- DECOMPILER ERROR at PC169: Overwrote pending register: R18 in 'AssignReg'
if nil == nil then
print("Error: port does not exist")
;
(os.exit)(Unknown_Type_Error)
end
l_0_18:close()
-- DECOMPILER ERROR at PC183: Confused about usage of register: R18 in 'UnsetPending'
print("Success: ssh port is " .. nil)
;
(os.exit)(Unknown_Type_Error)
else
print("Error: valid token error")
;
(os.exit)(Unknown_Type_Error)
end
else
if l_0_15 == "stop" then
local l_0_19 = nil
local l_0_20 = nil
l_0_20:close()
if ((io.popen)("pidof dropbear | wc -w")):read("*n") <= Unknown_Type_Error then
(os.execute)("/etc/init.d/dropbear stop")
else
;
(os.execute)("hwf-at 5 /usr/lib/lua/luci/local_ssh/local_ssh_util.lua stop")
end
else
do
if l_0_15 == "check_connected" then
local l_0_21 = nil
local l_0_22 = nil
l_0_22:close()
if ((io.popen)("pidof dropbear | wc -w")):read("*n") <= Unknown_Type_Error then
(os.execute)("hwf-at 10 /usr/lib/lua/luci/local_ssh/local_ssh_util.lua check_connected")
else
;
(os.execute)("hwf-at 180 /usr/lib/lua/luci/local_ssh/local_ssh_util.lua stop")
end
else
do
print("Error: method does not exist")
;
(os.exit)(Unknown_Type_Error)
end
end
end
end
end
end
end
end
通过浏览器抓取开启ssh的请求包,可以知道method=get的时候会生成一串字符串local token,需要将它发送到官方客服,然后通过这段字符串进行计算得到cloud token,当method=valid的时候,输入token进行校验,就能开启dropbear,也就是ssh服务了。通过简单的逻辑分析,总结如下:
method值 | 作用 |
get | 写入当前时间戳存入/tmp/local_ssh_ms,并且生成local token字符串 |
valid | 校验cloud token,如果正确则重新启动ssh服务 |
stop | 停止ssh服务 |
check_connected | 检查ssh服务状态,实际上这个method是无法调用到的 |
那么method=valid的校验方法就是关键,然而反编译的结果有一些不完整,整个逻辑无法连贯起来,那么就得分析原始的lua字节码了:
$ luac -l local_ssh_util.lua
1 [-] GETGLOBAL 0 -1 ; require
2 [-] LOADK 1 -2 ; "tw"
3 [-] CALL 0 2 2
4 [-] GETGLOBAL 1 -1 ; require
5 [-] LOADK 2 -3 ; "auth"
6 [-] CALL 1 2 2
7 [-] GETGLOBAL 2 -1 ; require
8 [-] LOADK 3 -4 ; "socket"
9 [-] CALL 2 2 2
10 [-] GETGLOBAL 3 -1 ; require
11 [-] LOADK 4 -5 ; "luci.util"
12 [-] CALL 3 2 2
13 [-] GETGLOBAL 4 -1 ; require
14 [-] LOADK 5 -6 ; "nixio"
15 [-] CALL 4 2 2
16 [-] GETGLOBAL 5 -1 ; require
17 [-] LOADK 6 -7 ; "luci.http.protocol"
18 [-] CALL 5 2 2
19 [-] GETGLOBAL 6 -8 ; math
20 [-] GETTABLE 6 6 -9 ; "floor"
21 [-] GETTABLE 7 2 -10 ; "gettime"
22 [-] CALL 7 1 2
23 [-] MUL 7 7 -11 ; - 1000
24 [-] CALL 6 2 2
25 [-] GETTABLE 7 0 -12 ; "get_mac"
26 [-] CALL 7 1 2
27 [-] LOADK 8 -13 ; "ssh"
28 [-] LOADK 9 -14 ; ""
29 [-] GETGLOBAL 10 -15 ; tostring
30 [-] MOVE 11 7
31 [-] CALL 10 2 2
32 [-] LOADK 11 -16 ; ","
33 [-] MOVE 12 8
34 [-] LOADK 13 -16 ; ","
35 [-] GETGLOBAL 14 -15 ; tostring
36 [-] MOVE 15 6
37 [-] CALL 14 2 2
38 [-] CONCAT 10 10 14
39 [-] LOADK 11 -14 ; ""
40 [-] LOADK 12 -17 ; "/tmp/local_ssh_ms"
41 [-] GETGLOBAL 13 -18 ; io
42 [-] GETTABLE 13 13 -19 ; "open"
43 [-] MOVE 14 12
44 [-] LOADK 15 -20 ; "r"
45 [-] CALL 13 3 2
46 [-] LOADK 14 -21 ; 0
47 [-] GETGLOBAL 15 -22 ; arg
48 [-] GETTABLE 15 15 -23 ; 1
49 [-] GETGLOBAL 16 -22 ; arg
50 [-] GETTABLE 16 16 -24 ; 2
51 [-] TEST 16 0 1
52 [-] JMP 1 ; to 54
53 [-] LOADK 16 -14 ; ""
54 [-] LOADNIL 17 18
55 [-] EQ 1 13 -25 ; - nil
56 [-] JMP 9 ; to 66
57 [-] SELF 19 13 -26 ; "read"
58 [-] LOADK 21 -27 ; "*n"
59 [-] CALL 19 3 2
60 [-] TESTSET 14 19 1
61 [-] JMP 1 ; to 63
62 [-] LOADK 14 -21 ; 0
63 [-] SELF 19 13 -28 ; "close"
64 [-] CALL 19 2 1
65 [-] JMP 9 ; to 75
66 [-] GETGLOBAL 19 -18 ; io
67 [-] GETTABLE 19 19 -19 ; "open"
68 [-] MOVE 20 12
69 [-] LOADK 21 -29 ; "a"
70 [-] CALL 19 3 2
71 [-] MOVE 13 19
72 [-] LOADK 14 -21 ; 0
73 [-] SELF 19 13 -28 ; "close"
74 [-] CALL 19 2 1
75 [-] EQ 0 15 -30 ; - "get"
76 [-] JMP 39 ; to 116
77 [-] GETGLOBAL 19 -31 ; assert
78 [-] GETGLOBAL 20 -18 ; io
79 [-] GETTABLE 20 20 -19 ; "open"
80 [-] MOVE 21 12
81 [-] LOADK 22 -32 ; "w"
82 [-] CALL 20 3 0
83 [-] CALL 19 0 2
84 [-] MOVE 13 19
85 [-] SELF 19 13 -33 ; "write"
86 [-] MOVE 21 6
87 [-] CALL 19 3 1
88 [-] SELF 19 13 -28 ; "close"
89 [-] CALL 19 2 1
90 [-] GETTABLE 19 1 -34 ; "lua_hmac_sha1_with_uuid"
91 [-] MOVE 20 10
92 [-] GETGLOBAL 21 -35 ; string
93 [-] GETTABLE 21 21 -36 ; "len"
94 [-] MOVE 22 10
95 [-] CALL 21 2 0
96 [-] CALL 19 0 2
97 [-] TESTSET 11 19 1
98 [-] JMP 1 ; to 100
99 [-] LOADK 11 -14 ; ""
100 [-] GETTABLE 19 4 -37 ; "bin"
101 [-] GETTABLE 19 19 -38 ; "b64encode"
102 [-] MOVE 20 10
103 [-] LOADK 21 -16 ; ","
104 [-] MOVE 22 11
105 [-] CONCAT 20 20 22
106 [-] CALL 19 2 2
107 [-] MOVE 11 19
108 [-] GETGLOBAL 19 -39 ; print
109 [-] MOVE 20 11
110 [-] CALL 19 2 1
111 [-] GETGLOBAL 19 -40 ; os
112 [-] GETTABLE 19 19 -41 ; "exit"
113 [-] LOADK 20 -23 ; 1
114 [-] CALL 19 2 1
115 [-] JMP 137 ; to 253
116 [-] EQ 0 15 -42 ; - "valid"
117 [-] JMP 82 ; to 200
118 [-] GETGLOBAL 19 -15 ; tostring
119 [-] MOVE 20 7
120 [-] CALL 19 2 2
121 [-] LOADK 20 -16 ; ","
122 [-] MOVE 21 8
123 [-] LOADK 22 -16 ; ","
124 [-] GETGLOBAL 23 -15 ; tostring
125 [-] ADD 24 14 -23 ; - 1
126 [-] CALL 23 2 2
127 [-] CONCAT 10 19 23
128 [-] GETTABLE 19 1 -34 ; "lua_hmac_sha1_with_uuid"
129 [-] MOVE 20 10
130 [-] GETGLOBAL 21 -35 ; string
131 [-] GETTABLE 21 21 -36 ; "len"
132 [-] MOVE 22 10
133 [-] CALL 21 2 0
134 [-] CALL 19 0 2
135 [-] TESTSET 11 19 1
136 [-] JMP 1 ; to 138
137 [-] LOADK 11 -14 ; ""
138 [-] GETTABLE 19 4 -37 ; "bin"
139 [-] GETTABLE 19 19 -38 ; "b64encode"
140 [-] MOVE 20 11
141 [-] CALL 19 2 2
142 [-] MOVE 11 19
143 [-] EQ 0 16 11
144 [-] JMP 47 ; to 192
145 [-] GETGLOBAL 19 -40 ; os
146 [-] GETTABLE 19 19 -43 ; "execute"
147 [-] LOADK 20 -44 ; "/etc/init.d/dropbear restart;hwf-at 10 /usr/lib/lua/luci/local_ssh/local_ssh_util.lua check_connected"
148 [-] CALL 19 2 1
149 [-] GETGLOBAL 19 -40 ; os
150 [-] GETTABLE 19 19 -45 ; "remove"
151 [-] MOVE 20 12
152 [-] CALL 19 2 1
153 [-] GETGLOBAL 19 -18 ; io
154 [-] GETTABLE 19 19 -46 ; "popen"
155 [-] LOADK 20 -47 ; "s=`netstat -lntp|grep dropbear | grep '0.0.0.0' | grep -v '127.0.0.1'|awk '{print $4}'`; echo ${s##*:}"
156 [-] CALL 19 2 2
157 [-] MOVE 17 19
158 [-] EQ 0 17 -25 ; - nil
159 [-] JMP 7 ; to 167
160 [-] GETGLOBAL 19 -39 ; print
161 [-] LOADK 20 -48 ; "Error: port file does not exist"
162 [-] CALL 19 2 1
163 [-] GETGLOBAL 19 -40 ; os
164 [-] GETTABLE 19 19 -41 ; "exit"
165 [-] LOADK 20 -23 ; 1
166 [-] CALL 19 2 1
167 [-] SELF 19 17 -26 ; "read"
168 [-] LOADK 21 -27 ; "*n"
169 [-] CALL 19 3 2
170 [-] MOVE 18 19
171 [-] EQ 0 18 -25 ; - nil
172 [-] JMP 7 ; to 180
173 [-] GETGLOBAL 19 -39 ; print
174 [-] LOADK 20 -49 ; "Error: port does not exist"
175 [-] CALL 19 2 1
176 [-] GETGLOBAL 19 -40 ; os
177 [-] GETTABLE 19 19 -41 ; "exit"
178 [-] LOADK 20 -23 ; 1
179 [-] CALL 19 2 1
180 [-] SELF 19 17 -28 ; "close"
181 [-] CALL 19 2 1
182 [-] GETGLOBAL 19 -39 ; print
183 [-] LOADK 20 -50 ; "Success: ssh port is "
184 [-] MOVE 21 18
185 [-] CONCAT 20 20 21
186 [-] CALL 19 2 1
187 [-] GETGLOBAL 19 -40 ; os
188 [-] GETTABLE 19 19 -41 ; "exit"
189 [-] LOADK 20 -23 ; 1
190 [-] CALL 19 2 1
191 [-] JMP 61 ; to 253
192 [-] GETGLOBAL 19 -39 ; print
193 [-] LOADK 20 -51 ; "Error: valid token error"
194 [-] CALL 19 2 1
195 [-] GETGLOBAL 19 -40 ; os
196 [-] GETTABLE 19 19 -41 ; "exit"
197 [-] LOADK 20 -23 ; 1
198 [-] CALL 19 2 1
199 [-] JMP 53 ; to 253
200 [-] EQ 0 15 -52 ; - "stop"
201 [-] JMP 21 ; to 223
202 [-] GETGLOBAL 19 -18 ; io
203 [-] GETTABLE 19 19 -46 ; "popen"
204 [-] LOADK 20 -53 ; "pidof dropbear | wc -w"
205 [-] CALL 19 2 2
206 [-] SELF 20 19 -26 ; "read"
207 [-] LOADK 22 -27 ; "*n"
208 [-] CALL 20 3 2
209 [-] SELF 21 19 -28 ; "close"
210 [-] CALL 21 2 1
211 [-] LE 0 20 -23 ; - 1
212 [-] JMP 5 ; to 218
213 [-] GETGLOBAL 21 -40 ; os
214 [-] GETTABLE 21 21 -43 ; "execute"
215 [-] LOADK 22 -54 ; "/etc/init.d/dropbear stop"
216 [-] CALL 21 2 1
217 [-] JMP 35 ; to 253
218 [-] GETGLOBAL 21 -40 ; os
219 [-] GETTABLE 21 21 -43 ; "execute"
220 [-] LOADK 22 -55 ; "hwf-at 5 /usr/lib/lua/luci/local_ssh/local_ssh_util.lua stop"
221 [-] CALL 21 2 1
222 [-] JMP 30 ; to 253
223 [-] EQ 0 15 -56 ; - "check_connected"
224 [-] JMP 21 ; to 246
225 [-] GETGLOBAL 19 -18 ; io
226 [-] GETTABLE 19 19 -46 ; "popen"
227 [-] LOADK 20 -53 ; "pidof dropbear | wc -w"
228 [-] CALL 19 2 2
229 [-] SELF 20 19 -26 ; "read"
230 [-] LOADK 22 -27 ; "*n"
231 [-] CALL 20 3 2
232 [-] SELF 21 19 -28 ; "close"
233 [-] CALL 21 2 1
234 [-] LE 0 20 -23 ; - 1
235 [-] JMP 5 ; to 241
236 [-] GETGLOBAL 21 -40 ; os
237 [-] GETTABLE 21 21 -43 ; "execute"
238 [-] LOADK 22 -57 ; "hwf-at 10 /usr/lib/lua/luci/local_ssh/local_ssh_util.lua check_connected"
239 [-] CALL 21 2 1
240 [-] JMP 12 ; to 253
241 [-] GETGLOBAL 21 -40 ; os
242 [-] GETTABLE 21 21 -43 ; "execute"
243 [-] LOADK 22 -58 ; "hwf-at 180 /usr/lib/lua/luci/local_ssh/local_ssh_util.lua stop"
244 [-] CALL 21 2 1
245 [-] JMP 7 ; to 253
246 [-] GETGLOBAL 19 -39 ; print
247 [-] LOADK 20 -59 ; "Error: method does not exist"
248 [-] CALL 19 2 1
249 [-] GETGLOBAL 19 -40 ; os
250 [-] GETTABLE 19 19 -41 ; "exit"
251 [-] LOADK 20 -23 ; 1
252 [-] CALL 19 2 1
253 [-] RETURN 0 1
有了汇编的基础,lua字节码还是比较容易看懂的,关键逻辑如下:
local token生成方法,其中时间戳存入/tmp/local_ssh_ms文件:
mac地址,ssh,timestamp时间戳,hmac哈希认证(mac地址,ssh,timestamp时间戳)
cloud token生成方法:
hmac哈希认证(local token的timestamp时间戳+1)
其中hmac哈希认证的密码为uuid的sha1值,具体的细节可以逆向/usr/lib/libauth.so的hmac_sha1_with_uuid函数。
三、终探ssh
根据逻辑我们可以编写代码来实现自动开启ssh功能,以下是python3代码展示,如果成功,则会显示“result >> Success: ssh port is 22”
import base64,hashlib,hmac,json,urllib.request
def urlopen(url):
r = urllib.request.urlopen(url)
j = json.loads(r.read())
return j
def get_hmac_sha1(message, key):
result = hmac.new(key, message, hashlib.sha1).digest()
return base64.b64encode(result).decode()
def sha1(data):
return hashlib.sha1(data).digest()
if __name__ == '__main__':
local_token = urlopen("http://www.4006024680.com/local-ssh/api?method=get")["data"]
print("local token:" + local_token)
mac, ssh, t, hmacstr = base64.b64decode(local_token).split(b",",3)
message = "{},ssh,{}".format(mac.decode(),int(t)+1).encode()
uuid = urlopen("http://www.4006024680.com/cgi-bin/turbo/proxy/router_info")["data"]["uuid"]
print("uuid:" + uuid)
key = sha1(uuid.encode())
h = get_hmac_sha1(message, key)
print("cloud token:" + h)
print("result >> " + urlopen("http://www.4006024680.com/local-ssh/api?method=valid&data=" + h)["data"])
当然,为了方便大家,也可以使用本站的在线工具进行操作:
四、参考链接
https://baijiahao.baidu.com/s?id=1607853490223049939
It's awesome to visit this web page and reading the views of all friends concerning this paragraph, while I am also keen of getting know-how.
向博主的极客精神致敬,即使是厂家倒闭了的硬件,也能让他重新发光发热。
感谢支持