mitmproxy的應(yīng)用-代碼生成

需求

很多手機(jī)App都有做任務(wù),得金幣兌換現(xiàn)金的功能
手動做任務(wù)有很多限制;寫代碼自動化運(yùn)行可以消除一些限制
但寫代碼時的敲鍵盤有點(diǎn)費(fèi)時間,這篇文章就是要解決自動生成代碼的問題

運(yùn)行環(huán)境

背景知識: python、mitmproxy、jinja2
python: 3.7.3
python第三方庫: mitmproxy, requests, Jinja2
手機(jī)代理地址: mitmproxy的地址

因?yàn)橛卸鄠€文件,這里貼部分代碼,完整代碼地址:
https://github.com/zhoujie903/LearnPython/tree/master/mitmproxy_addons/gen_code

使用說明

步驟

設(shè)置:
設(shè)置1:設(shè)置self.api_dir: 存放App目錄的父目錄
代碼目錄下的gen_code_mitm.py

class GenCode(object):
    def __init__(self):
        ctx.log.info('__init__')
        # 設(shè)置代碼文件生成的目錄\文件夾
        self.api_dir = '/Users/zhoujie/Desktop/api/'

設(shè)置2: 后面說明...

步驟:

  1. 電腦上運(yùn)行起mitmproxy;手機(jī)設(shè)置代理地址為mitmproxy的地址
  2. 打開手機(jī)App,正常操作:點(diǎn)擊、滑動觸發(fā)相應(yīng)網(wǎng)絡(luò)請求被觸發(fā),這時各個api方法代碼已生成<api-name>.text
  3. 正常結(jié)束mitmproxy,這時在done方法里會生成整體代碼code-<app-name>.py
  4. 微修改code-<app-name>.py并運(yùn)行

步驟說明:
步驟1: 啟動mitmproxy

mitmdump --set session='huawei' -s "gen_code_mitm.py"

session的值在后面說明...

設(shè)置

gen_code_mitm.py并不會自動生成全部網(wǎng)絡(luò)請求的代碼,只會生成已配置好的網(wǎng)絡(luò)請求的代碼

配置如下:

class Api(object):
    ...
class GenCode(object):
    def __init__(self):
        ctx.log.info('__init__')
        ...
        
        # 趣頭條
        # 步驟1: urls里增加用URL path代表的網(wǎng)絡(luò)請求
        urls = [
            r'x/tree-game/',
            Api(r'/app/re/taskCenter/info/v1/get',params_as_all=True),            ,
        ]
        
        # 步驟2: 創(chuàng)建代表App的App對象
        self.qu_tou_tiao = App(urls, 'qu-tou-tiao')
        
        # 百度 - 好看視頻
        urls = [...]      
        self.hao_kan = App(urls, 'hao-kan') 
        
        # 步驟3: 把App添加進(jìn)來
        self.flowfilters = [
            self.qu_tou_tiao, 
            self.hao_kan,
            ...
        ] 
    1. urls里增加用URL path代表的網(wǎng)絡(luò)請求
urls = [
    r'x/tree-game/',
]
    1. 創(chuàng)建代表App的App對象
# 'qu-tou-tiao'中給App取的名字
self.qu_tou_tiao = App(urls, 'qu-tou-tiao')
    1. 把App對象加入到self.flowfilters中,代表要生成這個App的代碼
self.flowfilters = [
    self.qu_tou_tiao, 
]

urls的配置在后面說明...

目錄/文件夾

先約定2個概念:代碼目錄、App目錄
代碼目錄:生成App目錄的代碼、模板等文件的目錄
App目錄:保存自動生成的App代碼文件的目錄

代碼目錄有2類文件:

  • *.py    - 代碼文件 `gen_code_mitm.py`
    
  • *.j2.py - 模板文件
    

App目錄有3類文件:

  • *.py - 代碼文件:sessions.py、code-xxx.py、session_xxx.py
  • *.text - api文件,包含2個版本的代碼文件、響應(yīng)http_resopnse_body,不會被py文件讀取
  • *.json - 暫時無用,不會被py文件讀取
依賴關(guān)系[import關(guān)系]
code-xxx.py
    sessions.py
        session_xxx.py    
  • session_xxx.py: 保存著像賬號ID、cookie、token等用戶登錄數(shù)據(jù)

  • 每個App都有自己的目錄: 比如這里有 今天頭條極速版本['jin-ri-tou-tiao'], 趣頭條['qu-tou-tiao'] 兩個目錄/文件夾

?  api tree
.
├── jin-ri-tou-tiao
│   ├── api_news_feed_v47.text
│   ├── code-jin-ri-tou-tiao.py
│   ├── data-bodys-keys-huawei.json
│   ├── data-bodys-keys-ios.json
│   ├── xxxx.json
│   ├── search_suggest_homepage_suggest.text
│   ├── session_huawei.py
│   ├── session_ios.py
│   ├── session_xiaomi.py
│   └── sessions.py
└── qu-tou-tiao
    ├── app_re_taskcenter_info_v1_get.text
    ├── code-qu-tou-tiao.py
    ├── xxx.json
    ├── session_huawei.py
    ├── session_xiaomi.py
    ├── sessions.py
    └── sign_sign.text
    
2 directories, 40 files
  • 每個App會有多份session_xxx.py:比如session_huawei.py, session_ios.py, session_xiaomi.py; 表示有華為、iPhone、小米三臺手機(jī)的運(yùn)行數(shù)據(jù)

session

這里有一個需求與實(shí)現(xiàn)方案的問題:

需求: 一份代碼[code-xxx.py] - 維護(hù)成本低; 多份賬號數(shù)據(jù)[session_xxx.py] - 批量運(yùn)行, 效率高
問題: 一個網(wǎng)絡(luò)請求的數(shù)據(jù)怎么知道寫入哪個賬號數(shù)據(jù)文件中[session_xxx.py]
解決:

  • 推斷: 從網(wǎng)絡(luò)請求的headers、parameters、query、body中的值來推斷為某個session
  • 指定: 啟動時指定為某個session

先約定1個概念:一次mitmproxy運(yùn)行
一次mitmproxy運(yùn)行: 啟動mitmproxy -> 1或多部手機(jī)操作相同App ->退出\停止mitmproxy[ctrl+c或Q]

方案一: 指定

在啟動時指定--set session='huawei', 則所有數(shù)據(jù)寫入session_huawei.py文件中

mitmdump --set session='huawei' -s "gen_code_mitm.py"

問題: 多部手機(jī)[代表不同賬號]操作相同App時,多個賬號數(shù)據(jù)都寫入到了同一個賬號文件中[session_huawei.py]

最佳實(shí)踐: 一次mitmproxy運(yùn)行只操作一個手機(jī)[一個賬號]

方案二: 推斷
在啟動時不指定session, 代碼自動推斷

mitmdump -s "gen_code_mitm.py"

舉例:
今日頭條極速版有一個api: /search/suggest/homepage_suggest/
在parameters\query有"device_platform": "iphone"字段值, 那就寫入session_ios.py中。

問題:
如果有一個api無法從headers、parameters、query、body等信息中判斷出該寫入哪個文件?

這時會寫入session_default.py文件中,這時一個賬號的數(shù)據(jù)被寫到2個文件中: session_default.py、session_xxx.py, 生成的代碼會被認(rèn)為是2個賬號,無法"正常運(yùn)行"

最佳實(shí)踐: 一次mitmproxy運(yùn)行只操作一個手機(jī)[一個賬號], 且啟動時設(shè)置guess_as_session

mitmdump --set guess_as_session='huawei' -s "gen_code_mitm.py"

guess_as_session不能像session那樣設(shè)定任意值
guess_as_session的取值:ios, huawei, xiaomi [自己手頭只有這些設(shè)備]
若要guess_as_session可以取其它值,需要設(shè)置:

gen_code_mitm.py - class GenCode(object) - __init__方法:

self.guess_session = collections.OrderedDict(
    ios=re.compile(r'iphone|ios', flags=re.IGNORECASE),
    xiaomi=re.compile(r'xiaomi|mi\+5|miui', flags=re.IGNORECASE),
    huawei=re.compile(r'huawei', flags=re.IGNORECASE),
    # 在這里增加其它的自己想要的值
    # meizhu=re.compile(r'meizhu', flags=re.IGNORECASE),
)

urls的配置 - Api

urls = [
    r'x/tree-game/',
    Api(r'/app/re/taskCenter/info/v1/get',params_as_all=True),            ,
]

urls中可以添加2類對象: str, Api

Api類說明

class Api(object):
    def __init__(self, url, f_name='', log='', params_as_all=False, p_as_all_limit=50, body_as_all=False, f_p_enc: set=None, f_b_enc: set=None, f_p_arg: set=None, f_p_kwarg: dict=None, f_b_arg: set=None, f_b_kwarg: dict=None, content_type=''):

參數(shù)說明

  • url - 不需要完整的URL
urls = [
    # 方式01:包含host
    'game-center-new.1sapp.com/x/open/game',
    
    # 方式02:只有path
    '/x/open/game',
]
  • url - 通配: 一個URL生成多個api方法代碼
urls = [
    # 只要寫'score_task/v1/sleep/'路徑前綴, 所有以它為前綴的方法都會生成相應(yīng)代碼
    # 'score_task/v1/sleep/status/',
    # 'score_task/v1/sleep/start/',
    # 'score_task/v1/sleep/stop/',
    # 'score_task/v1/sleep/done_task/',
    'score_task/v1/sleep/',
]
  • url - 通配與匹配順序
urls = [
    # 當(dāng)觸發(fā)'score_task/v1/sleep/done_task/'時,
    # 因?yàn)?score_task/v1/sleep/'先添加并且也匹配
    # 不會再尋找最佳匹配'score_task/v1/sleep/done_task/'
    # 所以會生成默認(rèn)的方法名:score_task_v1_sleep_done_task
    # 而不是:sleep_done_task
    'score_task/v1/sleep/',
    Api('score_task/v1/sleep/done_task/', f_name='sleep_done_task')   
]

最佳實(shí)踐:先具體[長], 后模糊[前綴]

urls = [
    Api('score_task/v1/sleep/done_task/', f_name='sleep_done_task')
    'score_task/v1/sleep/',       
]
  • f_name - 指定方法名
默認(rèn)方法名:
    parse_result = urlparse(request.url)
    url_path = parse_result.path
    function_name = re.sub(r'[./-]', '_', url_path).strip('_').lower()
    
比如:
    urls = ['/score_task/v1/walk/count/']
    觸發(fā)的url為:https://i-hl.snssdk.com/score_task/v1/walk/count/
默認(rèn):
    def score_task_v1_walk_count(self)
    
指定后:    
    urls = [Api('/score_task/v1/walk/count/',f_name='walk_count')]
    def walk_count(self):
  • log - 指定打印的日志信息
默認(rèn):
    def score_task_v1_walk_count(self):
        logging.info('score_task_v1_walk_count')
指定后:
    urls = [Api('/score_task/v1/walk/count/',log='睡覺 - 領(lǐng)金幣')]
    def score_task_v1_walk_count(self):
        logging.info('睡覺 - 領(lǐng)金幣')     
  • params_as_all\body_as_all - 應(yīng)對sign簽名的問題
比如:
    趣頭條的'任務(wù)'界面,會調(diào)用
    http://api.1sapp.com/app/re/taskCenter/info/v1/get
    來獲取任務(wù)列表\進(jìn)度信息
    params有一個字段:"sign": "1ad43dd2b7dbacc62b0e2b98e325483a"
    這個字段和其它字段是一個整體,且我們不能偽造sign的值
    直接把整個params值拿來用
默認(rèn):
    def app_re_taskcenter_info_v1_get(self):
        url = self.urls['app/re/taskcenter/info/v1/get']
        params = self._params_from(url)
指定后:
    urls = [Api(r'/app/re/taskCenter/info/v1/get',params_as_all=True)]
    def app_re_taskcenter_info_v1_get(self, params_as_all):
        url = self.urls['app/re/taskcenter/info/v1/get']
        params = self._params_from(url)
        params = params_as_all

    并會生成一個全局方法
    def app_re_taskcenter_info_v1_get(user: User):
        for item in user.params_as_all['/app/re/taskCenter/info/v1/get']:
            user.app_re_taskcenter_info_v1_get(item)
  • p_as_all_limit - 沒有實(shí)現(xiàn)
  • f_p_arg\f_b_arg - 輸入?yún)?shù)
默認(rèn):
    def actcenter_piggy_videoconfirm(self):
        params = self._params_from(url)     
指定后:
    urls = [Api(r'/actcenter/piggy/videoConfirm', f_p_arg={'tag','count'})]
    def actcenter_piggy_videoconfirm(self, tag, count):
        params = self._params_from(url)
        params['tag'] = tag
        params['count'] = count
  • f_p_enc\f_b_enc - params_as_all 與 f_p_arg 的結(jié)合體
類中方法 與f_p_arg\f_b_arg相同
并會生成一個像params_as_all\body_as_all相似的全局方法
    def v5_user_rewar_video_callback_json(self, p):
        url = self.urls['/v5/user/rewar_video_callback.json']
        data = self._bodys_from(url)
        data['p'] = p
        
def v5_user_rewar_video_callback_json(user: User):
    for item in user.bodys_encry['/v5/user/rewar_video_callback.json']['p']:
        user.v5_user_rewar_video_callback_json(item)    
  • f_p_kwarg\f_b_kwarg - 有默認(rèn)值的輸入?yún)?shù)

未實(shí)現(xiàn)需求

  1. 類似動態(tài)變化可偽造的值,比如時間戳,沒有實(shí)現(xiàn)自動生成
def api_contains_timstamp(self):
    #這句需要自己輸入
    params['ts'] = time.time() 
  1. 同品牌的2臺手機(jī),無法區(qū)分出session

  2. Api收集數(shù)據(jù)沒有限制數(shù)量

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容