FollowWeChat公众号:BrotherKCrawler,QQExchangeGroup:808574309,continuetosharetechnicaldrygoodssuchasadvancedcrawler,JS/Androidreverseengineering!Itisdeclaredthatallthecontentinthisarticleisforlearningandcommunicationonly.Thecapturedcontent,sensitiveURLs,anddatainterfaceshavebeendesensitized,andarestrictlyprohibitedfrombeingusedforcommercialorillegalpurposes.Otherwise,allconsequencesarisingtherefromhavenothingtodowiththeauthor.Infringement,pleasecontactmetodeleteimmediately!逆向目标目标:亚航airasia航班状态查询,请求头Authorization参数主页:aHR0cHM6Ly93d3cuYWlyYXNpYS5jb20vZmxpZ2h0c3RhdHVzLw==接口:aHR0cHM6Ly9rLmFwaWFpcmFzaWEuY29tL2ZsaWdodHN0YXR1cy9zdGF0dXMvb2QvdjMv逆向参数:RequestHeaders:authorization:BearereyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI......逆向过程抓包分析来到航班状态查询页面,Enterthedepartureanddestinationatwill,andclicktofindtheflight,forexample,querytheflightfromMacautoKualaLumpur.MFMandKULarethecodesofMacauandKualaLumpurInternationalAirportrespectively.ThequeryinterfaceconsistsofthemostbasicURL+airportcode+date,similarto:https://xxxxxxxxxxx/MFM/KUL/28...,thereisanauthorizationparameterintheRequestHeaders.Throughobservation,itisfoundthatthevalueofthisparameterremainsunchangednomatterwhetherthecookieisclearedorthebrowserischanged.AfterFortesting,itisalsofeasibletodirectlycopythisparameterintothecode,butthistimeourpurposeistohookthisparameterbywritingabrowserplug-inandfindwhereitisgenerated.FordetailedknowledgeaboutHook,itisintroducedindetailinBrotherK’spreviousarticle:HookofJSReverse,eatinghotpotandsinging,suddenlyrobbedbygangsters!Browserplug-insHookbrowserplug-insareactuallycalledbrowserextensions(extensions),whichcanenhancebrowserfunctions,suchasblockingadvertisements,managingbrowseragents,changingtheappearanceofthebrowser,andsoon.由于Hook是通过编写浏览器插件来实现的,所以首先我们要简单了解下如何编写浏览器插件。编写浏览器插件也有相应的规范。以前不同浏览器的插件写法都不是很好。一样的,到现在跟谷歌浏览器插件的写法基本一样,谷歌浏览器插件不仅可以运行在Chrome浏览器上,还可以运行在国内所有带webkit内核的浏览器上,比如360极速浏览器、360安全浏览器、搜狗浏览器、QQ浏览器等。此外,火狐浏览器也被很多人使用。Firefox浏览器插件的开发方式已经改变了很多次,但是从2017年11月下旬开始,插件必须使用WebExtensionsAPIs来构建,其目的也是为了与其他浏览器统一。一般谷歌浏览器插件也可以直接在火狐浏览器上运行,但是火狐浏览器插件需要经过Mozilla签名后才能安装,否则只能暂时安装,调试后插件会消失浏览器,不方便。浏览器插件的开发有简单也有复杂,但是对于我们做爬虫逆向工程的开发者来说,主要是通过插件来hook代码。我们只需要知道一个插件由一个manifest.json和一个JavaScript脚本文件组成就可以了。下面K哥就以本案例中请求头的授权参数为例,带领大家开发一个Hook插件。当然,如果想深入研究浏览器插件的开发,可以参考谷歌浏览器扩展文档和火狐浏览器扩展文档。按照谷歌浏览器插件的开发规范,首先新建一个文件夹,里面有一个manifest.json文件和一个JSHook脚本。当然,如果你想给你的插件配置一个图标,你也可以把图标放在这个文件夹下,图标格式官方推荐为PNG,或者WebKit支持的任何格式,包括BMP、GIF、ICO,和JPEG等。注意:manifest.json文件名不能更改!普通插件目录类似如下结构:JavaScriptHook├─manifest.json//配置文件,文件名不可更改├─icons.png//图标└─javascript_hook.js//Hook脚本,文件名顺便说一句,是manifest.jsonmanifest。json是Chrome扩展中最重要和必不可少的文件。用于配置与扩展相关的所有配置,必须放在根目录下。其中manifest_version、name、version这三个参数必不可少。本例中manifest.json文件的配置如下:(完整配置参考Chromemanifest文件格式){"name":"JavaScriptHook",//插件名称"version":"1.0",//插件版本"description":"JavaScriptHook",//插件描述"manifest_version":2,//清单版本,必须是2或3"icons":{//pluginIcon"16":"/icons.png",//图标路径,不同大小的插件图标也可以是同一张图片"48":"/icons.png","128":"/icons.png"},"content_scripts":[{"matches":[""],//匹配所有地址"js":["javascript_hook.js"],//注入的代码文件名和路径,如果有多个,则依次注入"all_frames":true,//允许在页面的所有框架中嵌入内容脚本"permissions":["tabs"],//权限申请,tabs代表标签"run_at":"document_start"//代码注入时间}]}这里需要注意以下几点:manifest_version:配置清单版本,curren目前只支持2和3,2以后会逐步淘汰,以后也可能会出4或更高的版本。可以在官网查看ManifestV2和ManifestV3的区别。3对隐私和安全要求更高,这里推荐2。content_scripts:Chrome插件中页面脚本注入的一种形式,包括地址匹配(支持正则表达式)、需要注入的JS和CSS脚本、代码注入时间(建议document_start,在网页开始加载时注入),等javascript_hook.jsjavascript_hook.js文件就是Hook代码:varhook=function(){varorg=window.XMLHttpRequest.prototype.setRequestHeader;window.XMLHttpRequest.prototype.setRequestHeader=function(key,value){if(key=='Authorization'){调试器;}returnorg.apply(this,arguments);}}varscript=document.createElement('script');script.textContent='('+hook+')()';(document.head||document.documentElement).appendChild(script);script.parentNode。removeChild(script);XMLHttpRequest.setRequestHeader()是设置HTTP请求头的方法,定义了一个变量org来保存原来的方法,window.XMLHttpRequest.prototype.setRequestHeader这里有一个prototype对象prototype,所有JavaScript对象都会从一个prototype原型对象继承属性和方法,具体请参考菜鸟教程JavaScriptprototype的介绍。程序一旦在requestheader中设置了Authorization,就会进入我们的Hook代码,通过debugger破解,最后将所有参数返回给org,也就是XMLHttpRequest.setRequestHeader()的原始方法,保证正常传输数据。然后创建一个script标签,script标签的内容就是把Hook函数变成一个IIFE自执行函数,然后插入到网页中。至此,我们的浏览器插件就写好了,接下来介绍如何在谷歌浏览器和火狐浏览器中使用。谷歌浏览器,在浏览器地址栏输入chrome://extensions或点击右上角【自定义和控制谷歌浏览器】—>【更多工具】—>【扩展】进入扩展页面,然后选择打开【Developermode]—>[Loadunzipedextension],选择整个Hook插件文件夹(该文件夹应包含manifest.json、javascript_hook.js和icon文件),如下图:Firefox浏览器无法直接安装插件-未经Mozilla签名的插件只能通过调试附加组件来安装。插件的格式必须是.xpi、.jar或.zip,所以我们需要将manifest.json、javascript_hook.js和icon文件打包在一起。我们需要注意的是,打包的时候不要把顶层目录也包含进去,右键全部压缩即可,否则在安装的时候会提示没有包含有效的清单。在浏览器地址栏输入about:addons或者点击右上角【打开应用菜单】->【扩展和主题】,也可以直接使用快捷键Ctrl+Shift+A进入扩展页面进行管理你的扩展目录旁边有个设置按钮,点击选择【DebugAdd-ons】,在临时扩展项下,选择【TemporaryLoadAdd-ons】,选择Hook插件的压缩包。也可以直接在浏览器地址栏输入about:debugging#/runtime/this-firefox直接进入临时扩展页面,如下图:至此,我们完成了浏览器的开发安装Hook插件,返回航班在查询页面,随意输入出发地和目的地,点击找到航班,此时可以看到已经成功断开连接:TamperMonkey插件HookWe前面已经介绍了如何自己写一个浏览器插件,但是不同的浏览器插件的写法总是大同小异。有可能你写的插件不能在其他浏览器上运行,TamperMonkey可以帮助我们解决这个问题。TamperMonkey俗称油猴插件。它本身就是一个浏览器扩展,是最流行的用户脚本。该管理器基本支持所有具有扩展功能的浏览器。实现脚本一次编写,全平台运行。用户可以直接获取他人在GreasyFork、OpenUserJS等平台发布的脚本。它的功能很多,功能也很强大。同样,我们也可以使用TamperMonkey来实现Hook。TamperMonkey可以在各大浏览器扩展商店直接安装,也可以到TamperMonkey官网安装。安装过程这里不再赘述。安装完成后,点击图标添加新脚本,或者点击管理面板,再点击加号新建脚本,写入如下Hook代码://==UserScript==//@nameJavaScriptHook//@namespacehttp://tampermonkey.net///@version0.1//@descriptionJavaScriptHook脚本//@authorKge爬虫//@include*://*airasia.com/*//@iconhttps://profile.csdnimg.cn/1/B/8/3_kdl_csdn//@grantnone//@run-atdocument-start//==/UserScript==(function(){'usestrict';varorg=window.XMLHttpRequest.prototype.setRequestHeader;window.XMLHttpRequest.prototype.setRequestHeader=function(key,value){if(key=='Authorization'){调试器;}returnorg.apply(this,arguments);};})();整个代码的JavaScript部分是一个IIFE,立即执行函数,具体含义不做解释。前面在浏览器插件的开发中提到,重要的是上面那几行注释。不要认为这些只是简单的注释,是可选的。在TamperMonkey中,你可以把这部分当作一个基本的配置选项,每一个都有其特定的含义,完整的配置选项参考TamperMonkey官方文档,常用的配置项如下表所示(特别注意@match、@includeand@run-atoptions):选项含义@name脚本的名字@namespace命名空间,用来区分同名脚本,一般写作者名字或者网址@version脚本版本,油猴的更新脚本会读这个一个版本号@description描述这个脚本是什么@author写这个脚本的作者的名字@match从字符串开头匹配正则表达式,只有匹配到的URL才会执行相应的脚本,例如*匹配所有,https://www.baidu.com/*匹配百度等,可以参考Pythonre模块中的re.match()方法,允许多个实例@include类似于@match,只是匹配URL会执行对应的Script,但是@include不会从字符串开头开始匹配。例如,*://*baidu.com/*匹配百度。具体区别可以参考TamperMonkey官方文档,如果脚本有相应的权限,就可以调用油猴扩展提供的API与浏览器进行交互。如果设置为none,则不使用沙箱环境,脚本直接在网页环境中运行。此时,您不能使用GreaseMonkey扩展API的大部分。如果不指定,油猴会默认添加几个最常用的API@require如果脚本依赖其他JS库,可以使用require命令导入,运行脚本前加载其他库@run-at脚本注入时机,这个选项是能否被hook的关键,有五个可选值:document-start:网页开始的时候;文档正文:当正文出现时;document-end:加载时或加载后执行;document-idle:加载完成后执行,默认选项;context-menu:点击浏览器上下文菜单中的脚本时,一般设置为document-start,返回航班查询页面,启用TamperMonkey脚本。如果配置正确,可以看到我们写的Hook脚本已经打开了,只要输入出发地和目的地,点击找到航班,此时可以看到已经成功断开:参数reverse是否使用浏览器插件或者TamperMonkey来hook,此时hook到达的是请求头Authorization设置的地方,也就是说Authorization的值之前肯定是通过了某个函数或者方法,那么我们跟进开发者工具的CallStack调用栈就能找到这个方法,这个过程和调用栈一样,很考验耐心,很费时间。一般情况下,我们检查每个函数传递的参数是否包含我们的目标参数。如果前一个函数中没有目标参数,但它出现在下一个函数中,那么加密过程很可能是在这两个函数之间进行的。输入上面如果对一个函数进行单步调试,通常可以找到加密后的代码。本例中,我们按照t.getData函数埋下断点进行单步调试。你可以看到t.subscribe和t.call,之所以不在这两个函数处埋断点,是因为循环太多,调试难度大,而且t.getData凭名字判断也很可疑。再次点击登录,来到刚才下断点的地方,F11或者点击向下箭头,进入单步调试的函数,大概调试7步后,来到一个t.getHttpHeader函数,可以看到Authorization的值是“Bearer”+r.accessToken。我们在控制台打印r.accessToken,可以看到是我们想要的值,如下图所示:那么重点就是这个r.accessToken。如果你尝试直接找,你会发现找了很多行都找不到。直接搜索关键字accessToken,发现直接定义在zUnb对象中,可以直接使用,如下图:关于出发地和目的地代码通过JSON传递,很容易find,可以根据实际需要灵活处理,如下图:这个案例本身并不难,直接搜索也可以更快的定位到参数位置,但是这个案例的重点是如何使用浏览器插件-in进行Hook操作,对于一些无法搜索到的参数,或者搜索结果过多,难以定位时,是一个很好的解决方案。完整代码GitHub关注K哥的爬虫,持续分享爬虫相关代码!欢迎加星!https://github.com/kgepachong/下面只是演示了部分关键代码,不能直接运行!完整代码仓库地址:https://github.com/kgepachong...Python示例代码#!/usr/bin/envpython3#-*-coding:utf-8-*-importrequestsstatus_url='脱敏处理,完成了代码遵循GitHub:https://github.com/kgepachong/crawler'defget_flight_status(departure,destination,date):headers={'user-agent':'Mozilla/5.0(WindowsNT10.0;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/92.0.4515.159Safari/537.36','authorization':'脱敏处理,完整代码遵循GitHub:https://github.com/kgepachong/crawler'}complete_url=status_url+department+'/'+destination+'/'+dateresponse=requests.get(url=complete_url,headers=headers)print(response.text)if__name__=='__main__':departure=input('请输入出发代码:')destination=input('请输入目的地代码:')date=input('请输入日期(例如:29/09/2021):')#department='MFM'#destination='KUL'#date='29/09/2021'get_flight_status(d出发地、目的地、日期)