写个Python脚本来登录小米路由器

这个脚本写起来难度并不是很大,博主还是一步步的分析下,这样思路会比较清晰,下次遇到类似系统脚本写起来也更快速。好了,一起来分析分析。

首先看下小米路由器的登录界面

screen.png - 大小: 100.22 KB - 尺寸:  x  - 点击打开新窗口浏览全图
可以看到只需要输入密码即可登录,博主这里为了演示,设置了简单的登录密码12345678,使用firebug记录登录的请求。当然,这里也可以使用Burp拦截请求,只不过有点大材小用了,来,我们尝试登录一下,看下做了哪些请求,使用正确的密码进行登录,post到服务端的数据如图所示
post.png - 大小: 30.33 KB - 尺寸: 678 x 216 - 点击打开新窗口浏览全图
从图上很明显的可以看到,post了4个参数到这个地址:http://192.168.65.1/cgi-bin/luci/api/xqsystem/login 进行登录操作。

参数 username 的值为admin,这个很明显就是一个内置的用户名,也就是默认的用户名,所以页面只需要输入密码就可以正常登录。

参数 password 一眼看上去应该是个MD5加密后的密文

参数 logtype 的值为2,这个应该也是系统默认内置的一个登录类型

参数 nonce 看起来应该是 mac地址加时间戳加随机数的组合

登录成功之后的响应如图

success.png - 大小: 17.88 KB - 尺寸: 638 x 136 - 点击打开新窗口浏览全图

上面对参数含义的推测不一定准确,为了了解真正的含义,博主决定去代码里面一探究竟,查看页面源代码,找到了处理登录的方法

  function loginHandle ( e ) {
        e.preventDefault();
        var formObj = document.rtloginform;
        var pwd = $( '#password' ).val();
        if ( pwd == '') {
            return;
        }
        var nonce = Encrypt.init();
        var oldPwd = Encrypt.oldPwd( pwd );
        var param = {
            username: 'admin',
            password: oldPwd,
            logtype: 2,
            nonce: nonce
        };
        $.pub('loading:start');
        var url = '/cgi-bin/luci/api/xqsystem/login';
            $.post( url, param, function( rsp ) {
                $.pub('loading:stop');
                var rsp = $.parseJSON( rsp );
                if ( rsp.code == 0 ) {
                    var redirect,
                        token = rsp.token;
                    if ( /action=wan/.test(location.href) ) {
                        redirect = buildUrl('wan', token);
                    } else if ( /action=lannetset/.test(location.href) ) {
                        redirect = buildUrl('lannetset', token);
                    } else {
                        redirect = rsp.url;
                    }
                    window.location.href = redirect;
                } else if ( rsp.code == 403 ) {
                    window.location.reload();
                } else {
                    pwdErrorCount ++;
                    var errMsg = '密码错误';
                    if (pwdErrorCount >= 4) {
                        errMsg = '多次密码错误,将禁止继续尝试';
                    }
                    Valid.fail( document.getElementById('password'), errMsg, false);
                    $( formObj )
                    .addClass( 'shake animated' )
                    .one( 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function(){
                        $('#password').focus();
                        $( this ).removeClass('shake animated');
                    } );
                }
            });
    }

很容易就能明白代码的意思,博主稍微精简下,登录地址为

/cgi-bin/luci/api/xqsystem/login
参数为
 var param = {
            username: 'admin',
            password: oldPwd,
            logtype: 2,
            nonce: nonce
        };
password参数对应到oldPwd,找到oldPwd的生成方法
 var oldPwd = Encrypt.oldPwd( pwd );
而nonce的生成方法也很容易找到
var nonce = Encrypt.init();
这两个参数都是由
Encrypt

这个类里面的方法生成的,找到这个类即可。而在登录的方法中还有个登录次数的限制,很显然,这个登录的次数也只是js变量控制的,并不是后端限制,很鸡肋,依然可以无限次登录。

找到上面两个参数的生成方法

var Encrypt = {
    key: 'a2ffa5c9be07488bbb04a3a47d3c5f6a',
    iv: '64175472480004614961023454661220',
    nonce: null,
    init: function(){
        var nonce = this.nonceCreat();
        this.nonce = nonce;
        return this.nonce;
    },
    nonceCreat: function(){
        var type = 0;
        var deviceId = '00:88:65:3d:bd:22';
        var time = Math.floor(new Date().getTime() / 1000);
        var random = Math.floor(Math.random() * 10000);
        return [type, deviceId, time, random].join('_');
    },
    oldPwd : function(pwd){
        return CryptoJS.SHA1(this.nonce + CryptoJS.SHA1(pwd + this.key).toString()).toString();
    },
    newPwd: function(pwd, newpwd){
        var key = CryptoJS.SHA1(pwd + this.key).toString();
        key = CryptoJS.enc.Hex.parse(key).toString();
        key = key.substr(0, 32);
        key = CryptoJS.enc.Hex.parse(key);
        var password = CryptoJS.SHA1(newpwd + this.key).toString();
        var iv = CryptoJS.enc.Hex.parse(this.iv);
        var aes = CryptoJS.AES.encrypt(
                password,
                key,
                {iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }
            ).toString();
        return aes;
    }
};
也是相当简单,值得注意的是这里生成的密码并不是MD5,而是由CryptoJS 这个库生成的可逆的密文,这跟我上面的推测有些差异,,nonce的生成在这段代码里面
var type = 0;
        var deviceId = '00:88:65:3d:bd:22';
        var time = Math.floor(new Date().getTime() / 1000);
        var random = Math.floor(Math.random() * 10000);
        return [type, deviceId, time, random].join('_');
类型+设备的mac地址+时间戳+10000以内的随机数,中间用下划线隔开。

搞明白每个参数的生成方法后模拟登录写起来可就简单多了。

利用requests模块进行登录,这里重点需要说明下密码的生成过程

CryptoJS.SHA1(this.nonce + CryptoJS.SHA1(pwd + this.key).toString()).toString();

密码+后端返回的一个key进行一次SHA1加密,转换成字符串之后与nonce相加再进行一个SHA1加密,最后进行一次字符串的转换就的到最终的密文。

好了,分析到这我们都已经清楚整个参数生成的原理,现在就要开始写Python了,首先需要知道要用哪些模块

运行脚本的时候需要直接带参数,所以需要sys模块

生成时间戳需要time模块

发送http请求需要requests模块

生成随机数需要random模块

接收服务端返回的数据并解析需要json模块

正则匹配获取key需要re模块

SHA1加密需要pycrypto模块

博主这里是Linux环境,自带的Python缺少requests模块,需要安装,具体安装方法不再累述

关于pycrypto模块的使用,可以参考链接 http://pythonhosted.org/pycrypto/

好了,下面开始脚本的编写,首先需要做的就是获取那个key,因为生成密码的时候要用到,这个key在登录页面初始化的时候就已经生成了,所以直接使用requests的get方法取回页面进行匹配就可以得到key,代码如下

host = sys.argv[1]
homeRequest = requests.get('http://'+ host +'/cgi-bin/luci/web/home')
key = re.findall(r'key: \'(.*)\',',homeRequest.text)[0]

这里的host直接由参数获得,请求回来的数据使用re进行匹配得到key,接着生成nonce,要用到time模块和random模块。

观察nonce生成规则发现所需要的mac地址真是本机的mac地址而不是路由器的mac,推测是在连接路由器的时候路由器就已经获取到的,登录页面初始化的时候会返回获取到的本机地址,所以直接从页面上抓取mac地址就可以了,同样适用re模块进行匹配

mac = re.findall(r'deviceId = \'(.*)\';',homeRequest.text)[0]
接下来就把获取到的mac地址带入拼接就可以生成nonce参数
nonce = "0_"+ mac +"_"+ str(int(time.time())) +"_"+str(random.randint(1000,10000))
有了nonce和key,那么生成密码的密文也就比较容易,两次SHA1加密就可以了,代码也很简单
pwdtext = sys.argv[2]
	
pwd = SHA.new()
pwd.update(pwdtext+key)
hexpwd1 = pwd.hexdigest()

pwd2 = SHA.new()
pwd2.update(nonce+hexpwd1)
hexpwd2 = pwd2.hexdigest()
原始明文密码直接由参数获得,密码生成之后进行param的组合,一般博主直接用一个json对象来把参数集合在一块,就像这样
data = {
	"logtype":2,
	"nonce":nonce,
	"password":hexpwd2,
	"username":"admin"
	}
好了,参数都有了,咱们直接传过去吧,记得是post哦,代码如下
response = requests.post(url=aimurl,data=data,timeout = 5)
resjson = json.loads(response.content)
	
if resjson['code'] == 0:
	print 'Login Success! Token is '+resjson['token']
else:
	print 'Login Failed! Code is '+str(resjson['code'])
对于返回的数据,之前查看过是json格式,那么直接使用json模块进行解析即可,code返回0则为成功,会得到一个token,code为其他则失败,来看下运行截图

登录成功

ls.png - 大小: 9.99 KB - 尺寸: 523 x 111 - 点击打开新窗口浏览全图
登录失败
lf.png - 大小: 8.94 KB - 尺寸: 528 x 117 - 点击打开新窗口浏览全图
好了,到这咱们就已经成功实现了使用Python进行快速登录小米路由器的功能,下次咱们接着写,如何使用Python来对小米路由器进行设置和重启等。

登录的完整脚本在这 https://github.com/kbdancer/Milogin

补充

更多功能的脚本在 http://www.92ez.com/?action=show&id=23405

如果您觉得文章有帮助到您,请到 https://www.92ez.com/index.php?action=show&id=23403 进行打赏/捐赠,谢谢!
如果您觉得文章有帮助到您,请 使劲戳这里 进行打赏/捐赠,谢谢!
本文链接:https://www.92ez.com/?action=show&id=23373
提示:技术文章有一定的时效性,请先确认是否适用你当前的系统环境。

上一篇: Sony Ericsson E16i (W8)Walkman 音乐手机 刷机安卓 2.3.5
下一篇: windows下安装Python

访客评论
#1
回复 CE.BB.CAT 2017-08-20, 5:07 PM
要不要再启动的时候检查一下参数是否完整?不完整就输出Usage然后退出
发表评论

评论内容 (必填):