360 路由器 P2 登陆验证分析

流量分析

由于某些需求,需要伪造个登陆过程,然后就花了点时间分析了下过程。由于我拿到的是板子而且是开发板,我拥有 telnet 接口。于是我 telnet 上去用 tcpdump 抓取了流量。

tcpdump -i any -w target.cap

为什么我不在本地抓本地 web 登录的流量呢? 因为我那个时候在做 App 一些功能测试。我需要抓取全部经过路由器的流量。

然后我需要把 cap 文件拿出来。由于是在板子里面,虽然 DEBUG 版本用于 tftp 可以做简单的文件传输。但是由于需要一个 server 服务器。所以我这里需要在本地搭建一个 tftp server。踩了一个坑..最后用 Python 现有的模块两句话完成。

1
2
3
4
import tftpy

server = tftpy.TftpServer('/tftpboot')
server.listen('0.0.0.0', 69)

然后起 server,紧接着在板子里使用:

tftp -l target.cap -p 192.168.0.4 (192.168.0.4 是我本地的 ip ,也就是这个时候的 server)

拿到流量后用 wireshark 打开分析,我过滤了 HTTP 流呈现的效果如下:

主要是分析验证登陆过程关键地方在 web_login.cgi get_rand_key.cgi

首先登陆的时候,先从 get_rand_key 获取一个 rand_key:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
POST /router/get_rand_key.cgi HTTP/1.1
Referer: http://guanli.luyou.360.cn
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
Host: guanli.luyou.360.cn
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.7.0

HTTP/1.1 200 OK
Server: Boa/0.94
Date: Sat, 21 Dec 2013 12:00:00 GMT
Connection: close
Cache-Control: no-cache
Content-Type: text/plain; charset=UTF-8

{"rand_key":"9332c90abc850ef93f9a600eff5606ba20c9bdd11c07c704a9b8e3faddbfd713"}

这个 rand_key 是直接 post /router/get_rand_key.cgi 就可以直接获取到。

紧接着向 web_login_cgi post 数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /router/web_login.cgi HTTP/1.1
Referer: http://guanli.luyou.360.cn
token_id: 6887b2151fdc73069d8ff84c164b7ced
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 268
Host: guanli.luyou.360.cn
Accept-Encoding: gzip
User-Agent: okhttp/3.7.0

bindLanguage=eyJsYW5ndWFnZSI6IjEifQ%3D%3D&density=420&language=1&language_server=zh&ostype=android&osversion=27&pass=9332c90abc850ef93f9a600eff5606ba5328c9362bc131db6047cfbc481523f9&phonetype=ONEPLUS%20A5010&region=CN&screenx=1080&screeny=2160&user=admin&version=4.2.3HTTP/1.1 200 OK
Set-Cookie: Qihoo_360_login=c0bd8fc56b6bcdb5192cd56d9c437763;path=/
Connection: close
content-type: text/plain; charset=UTF-8

{"success":"1","token_id":"704420fad014228d75b98f9341d79c2f"}

这里的字段会用到一个叫 pass 的:

pass = 9332c90abc850ef93f9a600eff5606ba5328c9362bc131db6047cfbc481523f9

我们会发现 pass 与上面的 rand_key :

9332c90abc850ef93f9a600eff5606ba20c9bdd11c07c704a9b8e3faddbfd713,前32 位一致,后32位不一样。

代码分析

简单分析下代码:

str 是 路由器的管理密码,最后形成的 pass 由 rand_key 的后32位对 str 进行 AES 加密,加密结果再与 rank_key 前32位拼接。

简单的讲就是

1
2
3
4
5
rand_key = '9332c90abc850ef93f9a600eff5606ba20c9bdd11c07c704a9b8e3faddbfd713'
pass = '9332c90abc850ef93f9a600eff5606ba5328c9362bc131db6047cfbc481523f9'

ran_key[:32] == pass[:32]
pass[32:] = EnAES(passwd,rand_key[32:])

Python 模拟登陆过程

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
# coding:utf-8
import requests
from Crypto.Cipher import AES
from pkcs7 import PKCS7Encoder
import base64


# key = '\xf7\x44\x50\xb1\x2a\x1e\x6e\x9e\xae\x36\xd4\x01\xfb\x5d\x48\xc2'

# key = '\x20\xc9\xbd\xd1\x1c\x07\xc7\x04\xa9\xb8\xe3\xfa\xdd\xbf\xd7\x13'


def EnAES(key):
mode = AES.MODE_CBC
iv = '\x33\x36\x30\x6c\x75\x79\x6f\x75\x40\x69\x6e\x73\x74\x61\x6c\x6c' #"360luyou@install".decode('hex')
encryptor = AES.new(key, mode, iv)
encoder = PKCS7Encoder()
text = '******' # password
pad_text = encoder.encode(text)
cipher = encryptor.encrypt(pad_text).encode('hex')
# enc_cipher = base64.b64encode(cipher)

# print enc_cipher
# print type(enc_cipher)

return(str(cipher))

r = requests.post('http://192.168.0.1/router/get_rand_key.cgi')

key = r.content
key = eval(key)

rand_key = key['rand_key']
aes_key = key['rand_key'][32:].decode('hex')

data = 'bindLanguage=eyJsYW5ndWFnZSI6IjEifQ==&density=420&language=1&language_server=zh&ostype=android&osversion=27&pass={}&phonetype=ONEPLUS%20A5010&region=CN&screenx=1080&screeny=2160&user=admin&version=4.2.3'.format(key['rand_key'][:32]+EnAES(aes_key))

header = '...


r = requests.post('http://192.168.0.1/router/web_login.cgi',data =data)
print r.text'