这里的加固指的是那种需要把文件上传到第三方网站上, 等它加固完成之后再下载下来的场景.这里就以梆梆加固为例, 通过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()
转载请注明出处:http://blog.csdn.net/l2show/article/details/55803455