自己動手寫H3C校園網登入客戶端(Linux平臺版)

凝霜發表於2012-04-11

自己動手寫H3C校園網登入客戶端(Linux平臺版)

By 馬冬亮(凝霜  Loki)

一個人的戰爭(http://blog.csdn.net/MDL13412)

        週一晚上的時候,和實驗室的ZL同學提聊到了Android手機使用Wifi連線學校的無線網掉線的問題。由於我們學校的上網登入客戶端僅支援Windows平臺,在其他平臺無法使用,所以,一直以來大家的解決方案就是使用瀏覽器進行登入。在Linux PC上使用網頁登入還是很穩定的,但是一旦使用我的小平板(Android系統)登入的時候,就會在10分鐘以內掉線。其實,早在一年前就想給我的Linux PC寫一個登入的客戶端,但是一直懶得去分析協議,這次經ZL同學一說,決定動手分析一下登入協議,寫一個客戶端給大家用。

        首先交代一下我的開發環境:

                作業系統:Fedora 16(Verne)

                核心版本:3.3.1-3

                瀏覽器:Google Chrome 17.0.963.79

                開發語言:Python(3.2)

                開發工具:Eclipse 3.7.1 (PyDev 2.2.4.2011110216)

                網路分析工具:WireShark 1.6.5

        下面開始分析登入協議,在gnome-terminal終端下使用root許可權執行wireshark(注意:在Fedora上,必須要用root許可權去執行wireshark才能設定網路卡到混雜模式),如下圖所示:


        接下來設定過濾器,點選選單欄上的“Capture”->“Options”,彈出如下圖所示的"Capture Options"對話方塊:


        單擊“Capture Filter”按鈕,彈出過濾器選擇對話方塊,如下圖所示:


        由於我是要分析網路客戶端登入的資料包,所以用IP進行過濾是最佳選擇,我們校園網驗證的伺服器地址為192.168.252.251,將其填入上圖的"Filter string"選項中就可以進行過濾了。然後點選“確定”,在單擊“Start”開始抓包。

        開啟瀏覽器,輸入http://192.168.252.251:8080/portal/index_default.jsp,這個地址是我們進行網頁登入的首頁。此時抓到的資料並不是我們想要的資料,填入帳號和密碼,如下圖所示:


        點選“上線”,開始檢視資料包,經過分析,我找到的關鍵資料包如下圖所示:


        我們在Line-based text data: application/x-www-form-urlencoded這項中看到了如下欄位,userName=mdl_&userPwd=V1hYWVlESkowMDA%3D&isQuickAuth=false&language=Chinese&browserFinalUrl=&userip=null,很明顯userName沒有進行加密,而userPwd被加密了,為了得知加密演算法,我修改了幾次密碼,並對其對映關係進行了分析,對映關係如下:

        AAA000                  QUFBMDAw
        000AAA                  MDAwQUFB
        000000A                MDAwMDAwQQ%3D%3D
        000000AA              MDAwMDAwQUE%3D
        000000AAA            MDAwMDAwQUFB
        000BBB                  MDAwQkJC
        000000B                MDAwMDAwQg%3D%3D
        000000BB              MDAwMDAwQkI%3D
        000000BBB           MDAwMDAwQkJC

        經過分析,這個加密演算法是每3個位元組加密一次,並且將其對映為4位ASCII字元,對於不足三位的用%3D填充,很明顯了,這個是Base64加密。有了加密演算法,還要分析Cookie欄位Cookie: JSESSIONID=3D4B4FBA9E201DFEF973138DF52B5161; hello1=mdl_; hello2=false; hello3=; hello4=\r\n,對於記住密碼和不記住密碼,這個欄位的內容是不同的,經過分析HTTP的流程,我發現Cookie的JSESSIONID是客戶端Notify給伺服器的,那麼就給我們偽造Cookie提供了可能。

        經過分析,hello1欄位是使用者名稱,hello2欄位是是否記住密碼,hello3欄位在記住密碼的時候是一段經過加密的字串,不記住密碼的時候為空,hello4欄位是登入的資費型別,我們學校沒有使用到這個欄位,所以始終為空。

        分析完這個資料包,我發現,帳號和密碼驗證成功後客戶端又向伺服器傳送瞭如下的資料包:


        其中Cookie欄位和上一個資料包一致,Line-based text data: application/x-www-form-urlencoded中language=Chinese&heartbeatCyc=240000&heartBeatTimeoutMaxTime=3&userDevPort=IAG_5000-vlan-02-0000%40vlan&userStatus=99&userip=null&serialNo=-19730&basip=這段後來經過分析,是同時post給本地和伺服器的,客戶端的線上頁面也要接收一份此欄位中的引數。

        接下來,我找到了心跳檢測的資料包,這個是客戶端主動傳送給伺服器的驗證包,如下圖:

           開始的時候我認為只要每隔一段時間向伺服器GET這個資料包的內容,並且保持TCP長連線就可以不掉線了,但是經過驗證,這是不可行的。後來又嘗試模擬瀏覽器的所有行為,但是也沒有成功。最後,想到在一臺機器上如果帳號已經線上,再次傳送的登入請求,伺服器會返回使用者已經線上資訊,並重新設定掉線時間,於是突破點找到了,我每隔1分鐘,向伺服器傳送一次登入請求,終於,可以保證穩定線上了。

        下面將我用Python寫的客戶端程式碼貼出來,給大家做一個參考,為了能最小負擔的移植到其他平臺,我將最初的PyQt4做的介面去掉了,還是使用了純終端的程式:

        這個專案總共分為3個檔案,config.ini儲存使用者的賬戶和密碼,NsINodeLogin.py負責登入並維持線上,NsINodeLogout.py負責下線。

config.ini

[Account]
username:mdl_
password:MYPASSWORD
NsINodeLogout.py
# -*- coding: utf-8 -*-

welcomeInfo = '''
作者:馬冬亮
單位:內蒙古科技大學資訊工程學院ACM程式設計協會
部落格:http://blog.csdn.net/MDL13412
郵箱:mdl2009@vip.qq.com
Q Q:401074567
版權所有 (C) 2012 凝霜.保留所有權利.

使用方法:
修改當前路徑下的config.ini檔案,將使用者名稱和密碼填寫至相應欄位
登入使用NsINodeLogin.py
登出使用NsINodeLogout.py
線上時請不要關閉本程式
'''

import http.client
import base64
import os
from configparser import ConfigParser

requesteaders = {
    'Connection':'keep-alive',
    'Cache-Control':'max-age=0',
    'User-Agent':'Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.79 Safari/535.11',
    'Content-Type':'application/x-www-form-urlencoded',
    'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Encoding':'gzip,deflate,sdch',
    'Accept-Language':'zh-CN,zh;q=0.8',
    'Accept-Charset':'GBK,utf-8;q=0.7,*;q=0.3',
    'Cookie':''
}

if __name__ == '__main__':
    try:
        print(welcomeInfo)
        
        try:
            configFile = ConfigParser()
            configFile.read(filenames=os.getcwd() + '/config.ini', encoding='utf-8')
            username = configFile.get('Account', 'username')
            pwd = configFile.get('Account', 'password')
        except:
            print('載入使用者資訊錯誤')

        logoutBody = 'userName={0}&userPwd={1}&isQuickAuth=false&language=Chinese&browserFinalUrl=&userip=null'
        requesteaders['Cookie'] = 'JSESSIONID=F447CB1C348B7D7AA6C02CBA3ECBF7AF; hello1={0}; hello2=flase; hello3=; hello4='.format(username)
       
        pwd = base64.encodebytes(pwd.encode(encoding='utf_8', errors='strict'))
        pwd = pwd.replace('='.encode(encoding='utf_8', errors='strict'), '%3D'.encode(encoding='utf_8', errors='strict'))
        logoutBody = logoutBody.format(username, pwd)
        logoutBody = logoutBody.replace("b'", "")
        logoutBody = logoutBody.replace("\\n'", "")
        
        while True:
            conn = http.client.HTTPConnection('192.168.252.251:8080')
            conn.request('POST', '/portal/logout.jsp', logoutBody, headers=requesteaders)
            res = conn.getresponse()

            if res.status == 200:
                print('下線成功')
            else:
                print('下線失敗')
    
            break
    except Exception as e:
        print('出錯啦...請檢查網路連線...')

NsINodeLogin.py

# -*- coding: utf-8 -*-

import http.client
import time
import base64
import os
from configparser import ConfigParser

requesteaders = {
    'Connection':'keep-alive',
    'Cache-Control':'max-age=0',
    'User-Agent':'Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.79 Safari/535.11',
    'Content-Type':'application/x-www-form-urlencoded',
    'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Encoding':'gzip,deflate,sdch',
    'Accept-Language':'zh-CN,zh;q=0.8',
    'Accept-Charset':'GBK,utf-8;q=0.7,*;q=0.3',
    'Cookie':''
}

if __name__ == '__main__':
    try:
        os.system('python3 ./NsINodeLogout.py')
        
        try:
            configFile = ConfigParser()
            configFile.read(filenames=os.getcwd() + '/config.ini', encoding='utf-8')
            username = configFile.get('Account', 'username')
            pwd = configFile.get('Account', 'password')
        except:
            print('載入使用者資訊錯誤')

        loginBody = 'userName={0}&userPwd={1}&isQuickAuth=false&language=Chinese&browserFinalUrl=&userip=null'
        onlineBody = 'language=Chinese&heartbeatCyc=240000&heartBeatTimeoutMaxTime=3&userDevPort=IAG_5000-vlan-02-0000%40vlan&userStatus=99&userip=null&serialNo=15500&basip='
        requesteaders['Cookie'] = 'JSESSIONID=F447CB1C348B7D7AA6C02CBA3ECBF7AF; hello1={0}; hello2=flase; hello3=; hello4='.format(username)
       
        pwd = base64.encodebytes(pwd.encode(encoding='utf_8', errors='strict'))
        pwd = pwd.replace('='.encode(encoding='utf_8', errors='strict'), '%3D'.encode(encoding='utf_8', errors='strict'))
        loginBody = loginBody.format(username, pwd)
        loginBody = loginBody.replace("b'", "")
        loginBody = loginBody.replace("\\n'", "")
        
        while True:
            conn = http.client.HTTPConnection('192.168.252.251:8080')
            conn.request('POST', '/portal/login.jsp', loginBody, headers=requesteaders)
            res = conn.getresponse()

            if res.status == 200:
                print('傳送驗證資訊成功')
                data = res.read()
                if -1 == data.find(b'3032'):
                    print('登入資訊正確')
                else:
                    print('請檢查登入資訊')
                    break
            else:
                print('傳送驗證資訊失敗')
                continue
            
            conn = http.client.HTTPConnection('192.168.252.251:8080')
            conn.request('POST', '/portal/online.jsp', onlineBody, headers=requesteaders)
            res = conn.getresponse()
            
            if res.status == 200:
                print('傳送線上資訊成功')
            else:
                print('傳送線上資訊失敗')
                continue

            time.sleep(60)
    except Exception as e:
        print('出錯啦...請檢查網路連線...')
        這個程式因為屬於實驗性的程式碼,沒有進行詳細的錯誤校驗,不過對於校園網的登入來說,已經足夠用了。

相關文章