frida的python库使用 安装配置frida的时候是下载了两个frida的相关库,frida 和 frida-tools 之前使用的 frida-ps 等更多的是用来手工调试的阶段,如果要用代码自动化处理,还需要其他语言的接入,比如python爬虫
后续的一些 frida 算法转发方案和 frida 的rpc也需要用到python,如果进行算法转发的话,就不需要对app的算法进行还原了,通过主动调用让app成为加密的工具
frida可以实时与python进行数据交互,可以把数据发送到python,等待python处理完毕之后,接收返回值,frida继续执行
包名注入 使用 frida.get_usb_device() 来通过 USB 连接,下面写入需要注入的代码,然后调用 load 方法进行注入,最好方式命令行关闭可以使用 sys.stdin.read()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import frida, sysJScode = """ Java.perform(function() { var RequestUtils = Java.use('com.dodonew.online.http.RequestUtil'); RequestUtils.encodeDesMap.overload('java.lang.String', 'java.lang.String', 'java.lang.String').implementation = function(str, key, iv) { console.log("data", str) console.log("key", key) console.log("iv", iv) var retval = this.encodeDesMap(str, key, iv); console.log("retval", retval) return retval; } var Utils = Java.use('com.dodonew.online.util.Utils') Utils.md5.implementation = function(str) { console.log("md5", str) var retval = this.md5(str); console.log("retval", retval) return retval; } }); """ process = frida.get_usb_device().attach('com.dodonew.online' ) script = process.create_script(JScode) script.load() print ("开始运行" )sys.stdin.read()
pid注入 和包名注入差不多,先查一下PID
spawn spawn附加就是和之前js的命令附加差不多,注意这里的代码和上面的相比多了恢复进程这一步
1 2 3 4 5 6 7 8 9 10 11 device = frida.get_usb_device() print ("device:" , device)pid = device.spawn(['com.dodonew.online' ]) process = device.attach(pid) script = process.create_script(JScode) script.load() device.resume(pid) print ("开始运行" )sys.stdin.read()
上面相当于
1 frida -U -f com.dodonew.online
通过非标准端口连接 frida-server 开启的默认端口是 27042,启动 frida-server的时候可以指定端口的
1 frida-server -l 0.0.0.0:8888
这种方法是可以进行多设备连接 hook 的,前提是都正常打开了 frida-server,然后根据IP和端口来对多个设备进行hook。但是要操作多个设备,还是整个 gadget 方便点吧。对模拟器进行更改端口时,需要端口转发,因为模拟器和本机的IP地址是一样的
frida与python的交互 send 使用send进行交互,例如要将md5返回值 send 返回给python,不进行打印了,首先要在 js 中更改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Java .perform (function ( ) { var RequestUtils = Java .use ('com.dodonew.online.http.RequestUtil' ); RequestUtils .encodeDesMap .overload ('java.lang.String' , 'java.lang.String' , 'java.lang.String' ).implementation = function (str, key, iv ) { console .log ("data" , str) console .log ("key" , key) console .log ("iv" , iv) var retval = this .encodeDesMap (str, key, iv); console .log ("retval" , retval) return retval; } var Utils = Java .use ('com.dodonew.online.util.Utils' ) Utils .md5 .implementation = function (str ) { console .log ("md5" , str) var retval = this .md5 (str); send (retval) return retval; } });
send只能接收一个参数,将参数包装成一个字典,如果接收多个参数就会报错,type字段为 error,而且send还需要注册一下 ,第一个参数固定是 message 第二个参数是函数名。
send最重要的作用就是,将js的数据交给python来处理
1 script.on('message', messageTest)
recv send是将js里的数据传递给python,recv是等待python里的数据传递给js,传递的时候则通过 create_script 方法创建的那个 script.post 来传递
recv().wait()
wait()方法会阻塞方法,等待python的返回
通过recv更改一下返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 import frida, sysJScode = """ Java.perform(function() { var RequestUtils = Java.use('com.dodonew.online.http.RequestUtil'); RequestUtils.encodeDesMap.overload('java.lang.String', 'java.lang.String', 'java.lang.String').implementation = function(str, key, iv) { console.log("data", str) console.log("key", key) console.log("iv", iv) var retval = this.encodeDesMap(str, key, iv); console.log("retval", retval) return retval; } var Utils = Java.use('com.dodonew.online.util.Utils') Utils.md5.implementation = function(str) { console.log("md5", str) var retval = this.md5(str); send(retval) recv(function(obj) { console.log("python:", obj.data); retval = obj.data }).wait(); return retval; } }); """ def messageTest (message, data ): if message['type' ] == 'send' : print (u"[*] {0}" .format (message['payload' ])) script.post({ "data" : "7588343d9969bf6e7f9438f7a081d36d" } ) else : print (message) device = frida.get_usb_device() print ("device:" , device)pid = device.spawn(['com.dodonew.online' ]) process = device.attach(pid) script = process.create_script(JScode) script.on('message' , messageTest) script.load() device.resume(pid) print ("开始运行" )sys.stdin.read()
通过执行结果可以看到确实是返回的更改之后的数值
一般来说除非数据特别复杂,不然有点多此一举了,有点麻烦,js中又不是不可以处理一些数据
frida rpc 远程调用 回忆一下主动调用,之前了解到的主动调用的方式有点呆板,只能一次调用,是写死了的。这个JS代码也是注入到程序中的,不能再更改了,现在就可以将他拿到python中来,可以跟服务器,爬虫对接等等,修改和调用就相对自由。
使用 rpc 导出一个接口让python调用,这种方法可以看到提示有点过时了
1 2 3 4 5 6 7 rpc.exports = { rpcfunc: test } result = script.exports.rpcfunc()
看下面的提示,提示要异步操作,这样即可正常调用
1 result = script.exports_sync.rpcfunc()
rpc.exports 中也可以直接写方法,也可以给出写好的方法名,这个是无所谓的
1 2 3 4 5 6 7 8 9 rpc.exports = { rpcfunc : function (data ) { var res = "" ; Java .perform (function ( ) { res = Java .use ('com.dodonew.online.util.Utils' ).md5 (data); }); return res; } }
虽然这样写还是要手动进行调用,但是是再python中,就可以结合爬虫,或者监听什么的操作稍微灵活一些了
frida 算法转发 写一个主动调用加密的算法,跳过算法分析的步骤,再结合之前写过的协议还原,将计算好的发送到服务端,再主动调用解密算法将服务端的数据解密,这样就可以实现将app作为加密机器,完全不用什么操作
利用 rpc 进行算法转发在python中直接调用方法,还可以开放一个本地端口,支持其他的语言和python进行交互
1 2 pip install fastapi pip install uvicorn
get getEchoApi 函数上面的注解的意思是,如果访问到这个路由,就调用下面这个方法,比如
http://127.0.0.1:8888/get 如果发送了这个请求,就会调用方法,后面再加上参数
http://127.0.0.1:8888/get?item_id=100&item_user=13112345678&item_pass=123asad
python代码是可以被导包的,如果这个文件被导入会被先执行一遍,在main中的方法是不在这个时候被执行的
有了这个东西,就可以使用其他语言,通过一个 get 请求来获取加密后的内容了,也是一个将app中的算法拿出来使用的操作,省去算法还原的操作了
post 上面app.get 是get请求app.post 是post请求,面对大量数据(图片)的时候 get 请求长度是不太够的,需要用到post 请求,写post需要用到一个 BaseModel,新建一个calss类
fiddler数据转发 fiddler数据转发:使用fiddler拦截数据包,把数据包发送到指定服务器
fiddler是一个抓包工具,直接从官网下载exe,默认安装即可
半汉化插件:https://www.52pojie.cn/forum.php?mod=viewthread&tid=1853941&highlight=fiddler%BA%BA%BB%AF
在选项这里将 https 中的三个选项勾选上,中间会进行证书安装,全部yes即可
抓包工具配好了,但是要实现数据转发还需要用到 fiddler script 。这个是 fiddler 功能的扩展,用代码去控制 fiddler
找了一下介绍 fiddler script 中的几个方法的文章: https://www.kancloud.cn/ncymkj/development_document/2055244
名称
作用
OnBeforeRequest
每次请求之前调用
OnBeforeResponse
每次响应之前调用
OnBoot
启动时调用
OnShutdown
fiddler关闭时调用
OnAttach
fiddler注册成系统代理时调用
OnDetach
fiddler 取消注册系统代理时调用
Main
在每次fiddler启动时和编译CustomRules.js 脚本时调用。
既然是数据转发那么就需要在 OnBeforeResponse 方法中做点手脚了
这里以抖音为例,尝试拦截一下评论内容
搜索一下 刘招华原型 文本信息
找到了一条请求,根据这条请求筛选 url 和字段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 if (oSession.uriContains ("/monitor_browser/collect/batch" )) { FiddlerObject .log ("POST 请求" ); var html = oSession.GetRequestBodyAsString () FiddlerObject .log ("捕获请求:" + html); if (html.Contains ("resultKeywords" )){ var _xhr = new ActiveXObject ('Microsoft.XMLHTTP' ); var url = 'http://127.0.0.1:8000/sendData' ; var jsonString = oSession.GetResponseBodyAsString (); var requestHeaders = oSession.oRequest .headers .ToString (); var responseHeaders=oSession.oResponse .headers .ToString (); var str='{}' ; var data = Fiddler .WebFormats .JSON .JsonDecode (str); data.JSONObject ["requestHeaders" ]=requestHeaders; data.JSONObject ["responseHeaders" ]=responseHeaders; data.JSONObject ["responseBody" ] = jsonString; data.JSONObject ["url" ] = oSession.fullUrl ; data.JSONObject ["response_code" ] = oSession.responseCode ; if (oSession.oRequest .headers .Exists ("Cookie" )){ data.JSONObject ["requestCookie" ] = oSession.oRequest .headers ['Cookie' ]; }else { data.JSONObject ["requestCookie" ] = 'request no Cookie' ; }; if (oSession.oResponse .headers .Exists ("Cookie" )){ data.JSONObject ["responseCookie" ] = oSession.oResponse .headers ['Cookie' ]; }else { data.JSONObject ["responseCookie" ] = 'response no Cookie' ; }; jsonString = Fiddler .WebFormats .JSON .JsonEncode (data.JSONObject ) FiddlerObject .log (jsonString); _xhr.onreadystatechange =function ( ){ if (_xhr.readyState ==4 ){ FiddlerObject .log (_xhr.responseText ); } }; _xhr.open ('POST' , url, true ); _xhr.send (jsonString); }else { FiddlerObject .alert ("抓取出错!" ); } }
将特定url的数据发送到本机的 8000 端口,然后在使用 fastAPI 开启本地端口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 FiddlerObject .log ("xiaojianbang1" );if (oSession.fullUrl .Contains ("relationalSearch" )){ FiddlerObject .log ("xiaojianbang2" ); var html = oSession.GetResponseBodyAsString () FiddlerObject .log (html); if (html.Contains ("resultKeywords" )){ var _xhr = new ActiveXObject ('Microsoft.XMLHTTP' ); var url = 'http://127.0.0.1:8000/sendData' ; var jsonString = oSession.GetResponseBodyAsString (); var requestHeaders = oSession.oRequest .headers .ToString (); var responseHeaders=oSession.oResponse .headers .ToString (); var str='{}' ; var data = Fiddler .WebFormats .JSON .JsonDecode (str); data.JSONObject ["requestHeaders" ]=requestHeaders; data.JSONObject ["responseHeaders" ]=responseHeaders; data.JSONObject ["responseBody" ] = jsonString; data.JSONObject ["url" ] = oSession.fullUrl ; data.JSONObject ["response_code" ] = oSession.responseCode ; if (oSession.oRequest .headers .Exists ("Cookie" )){ data.JSONObject ["requestCookie" ] = oSession.oRequest .headers ['Cookie' ]; }else { data.JSONObject ["requestCookie" ] = 'request no Cookie' ; }; if (oSession.oResponse .headers .Exists ("Cookie" )){ data.JSONObject ["responseCookie" ] = oSession.oResponse .headers ['Cookie' ]; }else { data.JSONObject ["responseCookie" ] = 'response no Cookie' ; }; jsonString = Fiddler .WebFormats .JSON .JsonEncode (data.JSONObject ) FiddlerObject .log (jsonString); _xhr.onreadystatechange =function ( ){ if (_xhr.readyState ==4 ){ FiddlerObject .log (_xhr.responseText ); } }; _xhr.open ('POST' , url, true ); _xhr.send (jsonString); }else { FiddlerObject .alert ("抓取出错!" ); } }
在 Fiddler 的脚本(如 FiddlerScript 或自定义规则)中,oSession.GetResponseBodyAsString()
和 oSession.GetRequestBodyAsString()
是用于获取 HTTP 消息体内容的方法,oSession.GetResponseBodyAsString()
用于获取服务端返回给客户端 的HTTP响应oSession.GetRequestBodyAsString()
用于获取客户端发送给服务端 的HTTP请求
因此如果获取不到html信息的话可以,换一个API尝试,我对抖音进行测试的时候,使用oSession.GetRequestBodyAsString()
没有获取到body的信息 oSession.GetResponseBodyAsString()
反倒可以