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

https://www.cnblogs.com/lsgxeva/p/16376971.html

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

3 评论

留下评论

您的电子邮箱地址不会被公开。 必填项已用*标注