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端走一下流程, 发现可以分为两步, 第一步先上传文件然后跳转到一个确认加固页面, 第二步点击确认加固.通过抓包可以看到文件上传的接口请求.

  1. 上传文件

    经过测试, 该接口的返回数据如下.这个msg字段的数据很可疑, 我们继续往下看.

    {
    	"code":0,
    	"modelId":0,
    	"msg":"583142"
    }
    

    upload接口请求完成之后紧跟着又请求了一个addapk的接口, 请求参数中多了一个appId的字段, 而这个值跟post接口返回的msg是一模一样的, 看来这个msg就是appId.这个值要存起来, 后续还会用到.最后这个接口的数据返回是重定向的确认加固页面.

  2. 开始加固

    接下来在确认加固页面点击加固按钮之后又抓到了一个接口请求.从这个接口的参数中可以看到又多了一个不确定的参数, 是一个token. 找了一下也没有请求其他的接口了, 最后在addapk的返回数据(确定加固页面的源码)中找到了这个token字段. 既然找到了token在哪就好处理了, 直接用正则把它的值摘出来就可以了.

    $.ajax( {
        data : {
            authenticityToken:'e085497b0562a74216f57b51544eb3646a4ba981'
        },
        type : "post",
        url : '/home/hideheadertip',
        success : function(data) {
        }
    });
    

    接口请求成功后返回如下数据, 梆梆后台开始对apk进行加固.这个时候web端应用列表中可以看到刚才上传的应用.

    {
    	"code":0,
    	"modelId":0,
    	"msg":"添加成功!"
    }
    

抓包分析完了流程之后就可以开始用脚本实现了.这个阶段比登录要复杂不少, 牵扯到了三个接口的调用, 并且接口之间的数据还都是互相依赖的.

  1. 上传文件

    父类中的上传文件接口是通过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]
    
  2. 开始加固

    在第一步中已经拿到了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

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值