Python 自動化加固流程
這裡的加固指的是那種需要把檔案上傳到第三方網站上, 等它加固完成之後再下載下來的場景.這裡就以梆梆加固為例, 通過Python指令碼將這個過程自動化起來.既然牽扯到第三方的網站, 那麼這個指令碼絕大多數的操作都跟網路請求相關. 簡單設計一下指令碼的結構, 先封一個基類出來對外提供一些基礎的網路操作, 方面以後擴充套件使用.子類根據實際的業務場景對外提供功能方法.
根據梆梆加固的業務流程來拆解指令碼需要做的步驟.大致可以分為五步, 分別是登入, 上傳檔案開始加固, 檢測加固狀態, 下載已加固檔案, 刪除應用.下面就詳細介紹每個步驟如果分析如何實現.
登陸梆梆
使用梆梆加固肯定是需要登陸的, 所以要自動化這個流程首當其衝得就是要模擬登陸, 拿到cookie之後儲存起來, 這樣之後請求介面的時候帶著有登陸資訊的cookie就不會被攔截登入了. 在使用urllib2預設的opener是不支援cookie相關操作的, 所以在父類的初始化方法中初始化cookie庫, 構建出支援cookie自定義的opener, 並將其裝載為全域性的opener.
cookie_jar = cookielib.LWPCookieJar()
self.__cookie_support = urllib2.HTTPCookieProcessor(cookie_jar)
opener = urllib2.build_opener(
self.__cookie_support, urllib2.HTTPHandler)
urllib2.install_opener(opener)
接下來分析一下梆梆的登入流程, 使用chrome抓取登入介面.從圖中可以看到登入介面的url以及post資料的格式. 這裡的賬號和密碼是明文的, randomID經過測試可以設定一個固定的值.
知道了需要什麼資料直接就可以實現子類中的登入方法了.首先將賬號密碼和其他固定的引數先構建出來, 再呼叫父類封裝好的post方法並接受response的返回資訊, 並打印出來.如果返回資料中有”code”:0則代表登入成功.
def login(self, user_name, user_password):
print 'prepare to login bangbang website...'
login_params = {'account': user_name,
'password': user_password,
'randomID' : '0.6358602885605047',
'randomCode': '',
'rememberChecked': 'false',
}
result = self.post(self.__login_url, login_params, self.__header)
print result
if "\"code\":0" in result:
self.__logined = True
print 'login succeed!'
else:
print 'login failed!'
根據列印的結果來看是登入成功的. 返回的資料中除了code代表請求狀態之外, 其他的資料這裡都用不到. 由於父類已經初始化了cookielib, 此時使用者的cookie資訊已經儲存起來了.
{
"code":0,
"enumlastupdate":0,
"modelId":0,
"msg":"https://dev.bangcle.com/Apps/index"
}
login succeed!
上傳檔案開始加固
登入成功之後就要上傳apk檔案讓梆梆對其加固. 先在web端走一下流程, 發現可以分為兩步, 第一步先上傳檔案然後跳轉到一個確認加固頁面, 第二步點選確認加固.通過抓包可以看到檔案上傳的介面請求.
上傳檔案
經過測試, 該介面的返回資料如下.這個msg欄位的資料很可疑, 我們繼續往下看.
{ "code":0, "modelId":0, "msg":"583142" }
upload介面請求完成之後緊跟著又請求了一個addapk的介面, 請求引數中多了一個appId的欄位, 而這個值跟post介面返回的msg是一模一樣的, 看來這個msg就是appId.這個值要存起來, 後續還會用到.最後這個介面的資料返回是重定向的確認加固頁面.
開始加固
接下來在確認加固頁面點選加固按鈕之後又抓到了一個介面請求.從這個介面的引數中可以看到又多了一個不確定的引數, 是一個token. 找了一下也沒有請求其他的介面了, 最後在addapk的返回資料(確定加固頁面的原始碼)中找到了這個token欄位. 既然找到了token在哪就好處理了, 直接用正則把它的值摘出來就可以了.
$.ajax( { data : { authenticityToken:'e085497b0562a74216f57b51544eb3646a4ba981' }, type : "post", url : '/home/hideheadertip', success : function(data) { } });
介面請求成功後返回如下資料, 梆梆後臺開始對apk進行加固.這個時候web端應用列表中可以看到剛才上傳的應用.
{ "code":0, "modelId":0, "msg":"新增成功!" }
抓包分析完了流程之後就可以開始用指令碼實現了.這個階段比登入要複雜不少, 牽扯到了三個介面的呼叫, 並且介面之間的資料還都是互相依賴的.
上傳檔案
父類中的上傳檔案介面是通過poster實現的, 首先通過poster註冊一個opener, 並將之前的cookie相關資訊新增進該opener中.
opener = poster.streaminghttp.register_openers() opener.add_handler(self.__cookie_support)
再使用poster的編碼方法將dir引數資料編碼出包含Content-Length和Content-Type的頭資訊以及包含檔案流的資料引數, 併發出請求.
post_data, post_header = multipart_encode(upload_data) print post_header request = urllib2.Request(upload_url, post_data, post_header) return urllib2.urlopen(request).read()
回到子類的業務流程. 首先判斷一下當前是否是登入狀態, 然後構建檔案上傳的引數, 請求上傳介面, 根據返回值判斷介面成功狀態.
if self.__logined is False: print 'should be logined!' return upload_params = {'name': file_name, 'file': open(file_name, "rb")} result = self.upload(self.__upload_url, upload_params) print result json_result = json.loads(result) if 0 != json_result['code']: print 'upload apk failed!' return print 'upload apk succeed!'
講上傳介面返回的appId儲存起來請求addapk介面, 並且從請求結果中將token資訊摘出來儲存起來.
self.__app_id = json_result['msg'] add_params = {} add_params['appId'] = self.__app_id result = self.get(self.__add_app_url, add_params) self.__auth_token = re.findall(r"authenticityToken:'(.+?)'", result)[0]
開始加固
在第一步中已經拿到了appId和token, 將它們和預設引數拼接起來呼叫complete介面完成檔案的上傳並且開始加固.
complete_params = {'appId': self.__app_id, 'authenticityToken': self.__auth_token, 'appCategoryId': '', 'isCeping': 'true', 'isReinforce': 'true' } result = self.post(self.__complete_app_url, complete_params, self.__header)
檢測加固狀態
在web端開始加固之後會自動跳轉到應用列表頁面, 這時會請求getappinfos介面獲取當前app的資訊. 請求引數沒什麼特別的, 只要登入了這些引數都可以固定起來.
而返回資料中就有很多欄位了, 看下圖中黑色框起來的欄位, 先通過appId找到我們剛才上傳的app, 然後根據自己的理解reinforceStatus這個欄位代表了加固狀態, 8代表的是安檢中.
過一會等加固完成了之後再重新整理一下web頁, 重新找到app資訊. 這次可以看到狀態變成10代表加固完成.
既然有欄位可以判斷加固的狀態, 那就可以通過指令碼輪詢這個介面直到加固完成. 根據以下流程圖完成這部分邏輯並記錄加固狀態.
首先做一些初始化工作:判斷是否登入, 是否上傳成功, 構建預設請求引數. 建立一個60次的迴圈.
check_params = {
'pageSize': '8',
'page': '1',
'param': ''
}
check_count = 0
while check_count <= 60:
check_count += 1
在迴圈中請求getappinfos介面. 反序列化返回資料到物件中, 並根據code狀態決定是否跳過這次迴圈進入下一次迴圈中.
result = self.get(self.__check_status_url, check_params)
json_result = json.loads(result)
if 0 != json_result['code']:
print 'check app reinforce status failed!'
continue
遍歷json物件中的results陣列, 這裡的每一個item就代表一個應用. 從item中的id欄位也就是前面的appId資訊過濾出我們正在操作的應用. 再根據上面對欄位的分析, 判斷reinforceStatus是否完成加固. 如果加固還沒有完成則休眠10s繼續輪詢.
for result_item in json_result['results']:
if str(result_item['id']) != self.__app_id:
continue
if result_item['reinforceStatusCode'] == '10':
print 'reinforce app reinforce succeed!'
self.__reinforced = True
return
if self.__reinforced is False:
print 'sleeping 10s...'
time.sleep(10)
下載加固後文件
在web端等加固完成之後就可以下載加固過的apk包. 這裡抓到了兩個介面呼叫. 第一個是請求引數w為appId的downapkcode介面, 返回資料中有很多欄位, 先不管看第二個介面.
{
"code":0,
"info":{
"code":"d7076e3810ad628875af97b58b6fc9953f95122e",
"model_id":583145,
"sid":"355d0d0f167b4ffbaf8fa8be27de4fa724238509",
"user_id":620615
},
"modelId":0,
"msg":""
}
第二個呼叫的就是下載檔案的介面downloadfile, 該介面的引數除了id是appId, type固定之外都是通過第一個介面返回值確定的.
套路都瞭解了之後就先判斷當前是否是登入狀態, 是否加固完成. 然後根據appId請求介面解析結果, 判斷狀態之後獲得sid, user_id和code欄位值.
download_code_params = {
'appId': self.__app_id,
'hideDownloadNotice': 'false'
}
result = self.post(self.__download_code_url,
download_code_params, self.__header)
print result
json_result = json.loads(result)
集齊引數之後就可以呼叫父類的download方法, 將檔案下載到指定的路徑下.
download_params = {
'id': self.__app_id,
'sid': json_result['info']['sid'],
'user_id': json_result['info']['user_id'],
'code': json_result['info']['code'],
'type': '1'
}
self.download(self.__download_apk_url, download_params, downlaod_path)
刪除應用
為什麼走完上面的流程之後要把應用從梆梆應用列表中刪除呢? 因為根據目前梆梆的業務流程, 相同版本的同一個應用只能加固一次.如果要把加固指令碼持續整合起來可能每天都要加固一次.所以我們要每次加固完成之後就把應用刪除掉, 方便下次加固使用.
刪除應用同樣抓包, 定位到引數為appId和token的介面delById. 這些引數資訊我們都有儲存, 直接拿來呼叫就行了.
同樣先判斷是否登入是否上傳成功, 然後拼裝引數執行post, 最後根據返回結果輸出最終狀態就完成整個指令碼的最後一步.
delete_params = {
'authenticityToken': self.__auth_token,
'appId': self.__app_id
}
result = self.post(self.__delete_app_url, delete_params, self.__header)
彙總
BangBangHttp
類實現了之後在外部就可以通過物件方便得使用加固的功能.也可以將賬號, 密碼, 輸入和輸出這四個屬性作為外部引數傳遞進來, 豐富使用場景.
bang_bang = BangBangHttp()
bang_bang.login('account', 'pwd')
bang_bang.upload_apk('test.apk')
bang_bang.check_reinforce_status()
bang_bang.download_apk('reinforced.apk')
bang_bang.delete_app()