这篇文章介绍app渗透的一点简单方法
0x00 前言 在做移动安全的app渗透或者说移动app的漏洞挖掘时,往往会碰到一种情况:好不容易绕过了app的反抓包机制,通过burp抓到了app传输的数据包,这时想对这部分数据做一些爆破、篡改之类的测试,却发现关键数据进行了加密处理,那么这时就不得不首先解决一下数据解密截取的问题。
0x01 环境搭建 (一).抓包环境 首先是抓包环境,需要针对app的反抓包机制做一些绕过,这不是本篇文章讨论重点,因此在另一篇文章中做介绍。
(二).frida框架 其次是我们做hook操作,需要依赖一些hook框架,来帮助我们更好的完成操作,这里我选用的是frida框架:简单安装教程如下:
1)首先安装python3环境,之后使用pip工具安装frida框架:
pip3 install frida(默认安装最新版) 当然可以指定版本安装: pip3 install frida==14.2.18 frida-tools==9.2.4 !!注意frida与frida-tools的版本对应关系,可以去github查找
2)再安装frida-server到手机(如果是模拟器注意安装包的选取),下载后解压传输安装赋权启用即可
frida-server14.2.18
1 2 3 4 5 adb push xxxfdserverxxx /data/local/tmp/fs14218 cd /data/local/tmp chmod 777 fs14218 ./fs14218 //启动
之后另起一个cmd窗口,输入命令查看手机上的信息:
如果能看到如下一类信息,即安装成功。
(三).反编译工具 jadx-gui 直接下载打开即可食用
jadx-gui下载地址
0x03 apk逆向&&明文抓取 (零)思路介绍 首先配置好代理,抓包看一下:
可以看到,app传输的数据都经过了一些未知的复杂加密处理,sign字段看过去类似是经过摘要算法得到的签名值之类,那么这里如果我们想要对data、timestamp等字段做一个测试,显然我们是不能直接修改的(可以更改一下看看效果)
既然这里不是一些简单的base64编码之类的处理,不能采取直接解码,又一眼看不出这里的加密方式(密码算法逆向),那么此时我们的思路还有什么呢?
可以想象一下数据的传输过程:
明文数据–>app调用加密算法进行加密—>app调用摘要算法计算消息摘要—>app发送请求,传输加密数据—->……..
一眼看过去,可操作的攻击思路是不是就清晰了?
1.可以对加密算法进行逆向,完全掌控加密过程
2.对明文数据在进行加密之前进行截取,并做更改
这篇文章就介绍第二种攻击思路:
对app进行反编译,找到明文传输路径,通过hook方式在加密操作之前,对app的明文数据进行篡改操作,这样的篡改操作在加密与摘要之前,在app的验证机制看来完全合法。接下来实操尝试一下:
(一)反编译及寻找可疑方法调用栈 打开jadx-gui,将得到的app安装包丢进去反编译一下(有壳的先砸壳处理一下):
如何寻找可疑方法呢?这里介绍一种最常用的简单方法:
回头看一下我们抓到的包:
可以看到一些关键字,那么我们就直接在反编译的代码中去检索这些关键字,大概率就可以找到关键位置(如果源码做了混淆,就得采用一些其他办法,这里不做讨论):搜索一下sign关键字:
有所发现,一一进行查看即可,有经验的话,可能可以猜到一些特征:sign是网络请求中的字段等等…随意联想。
经过检查,找到最具可疑性的目标:
出现了请求包中出现的sign、_ver字段,以及可疑的DATA,继续跟进一下:
原来,DATA就是我们的data,hahah~~~
回头追踪sign字段:发现与e.c.a.i.c.a()方法相关,跟过去看一下
阅读代码,找到源头,原来sign值就是将传入的数据+一定的密钥通过encrypt()方法处理得到的,那么这里就可以作为我们的切入点,hook验证一下看看我们的猜测对不对,数据是不是真的经过了这里:
脚本如下:
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 Java .perform (function ( ) { var HashMap = Java .use ('java.util.HashMap' ); HashMap .put .implementation = function (key, value ) { var keyStr = key ? key.toString () : '' ; if (keyStr === 'data' || keyStr === 'sign' ) { console .log ('Key:' , keyStr); console .log ('Value:' , value); console .log ('Call Stack:' , Java .use ('android.util.Log' ).getStackTraceString (Java .use ('java.lang.Exception' ).$new())); } return this .put .call (this , key, value); }; });
使用frida注入脚本:
首先在手机开启frida服务器:
之后执行命令,注入脚本:
1 2 3 frida -U -f 包名 -l hook脚本 --no-pause 如果此时app在前台运行,可以选择-UF模式 frida -UF -l hook脚本
刷新一下app试试:
果然,看到了相应的数据,证明我们的猜测无误,同时脚本也打印出了调用栈。
根据分析调用栈,寻找源头:(找与app包名相关的东西):找到:
e.c.a.i.d.a()方法,追踪到e.c.a.i.c.a()方法,和前面我们的分析吻合。那么这里我们就找到了精准位置
将该方法复制为一段frida代码片段,hook一下看看:
1 2 3 4 5 6 7 let c = Java .use ("e.c.a.i.c" );c["a" ].overload ('java.lang.String' ).implementation = function (str ) { console .log (`c.a is called: str=${str} ` ); let result = this ["a" ](str); console .log (`c.a result=${result} ` ); return result; };
改成简单的hook脚本:1.js
1 2 3 4 5 6 7 8 9 Java .perform (function ( ) { let c = Java .use ("e.c.a.i.c" ); c["a" ].overload ('java.lang.String' ).implementation = function (str ) { console .log (`c.a is called: str=${str} ` ); let result = this ["a" ](str); console .log (`c.a result=${result} ` ); return result; }; });
注入一下看看:方法如上
截取到了想要的数据。切入点无误,开始操作,抓取明文:
(二)编写脚本,抓取明文(被动调用) 前面我们找到了精准位置如下:
因此这里就编写对应的hook脚本与python脚本,提取控制转发明文即可达成我们的目的:
首先简单的只打印看一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Java .perform (function ( ) { var c = Java .use ("e.c.a.i.c" ); var EncryptUtil = Java .use ("com.qq.lib.EncryptUtil" ); c.a .overload ('java.lang.String' ).implementation = function (str ){ console .log ("\n请求加密前明文:\n" ,str); return this .a (str); } EncryptUtil .decrypt .implementation = function (str,str2 ){ console .log ("\n响应解密后明文:\n" ,this .decrypt (str,str2)); return this .decrypt (str,str2); } });
看到,明文已经抓取成功了,接下来就是需要丰富一下脚本功能,把请求与响应转发到我们的burp工具去,方便我们进行改包测试(这里我利用python来实现转发请求,调用hook脚本等功能)
python脚本如下(构建镜像服务器):
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 import argparsefrom threading import Threadfrom http.server import HTTPServer, BaseHTTPRequestHandlerimport sysimport requestsimport fridaECHO_PORT = 28080 BURP_PORT = 8080 class RequestHandler (BaseHTTPRequestHandler ): def do_REQUEST (self ): content_length = int (self.headers.get('content-length' , 0 )) self.send_response(200 ) self.end_headers() self.wfile.write(self.rfile.read(content_length)) do_RESPONSE = do_REQUEST def echo_server_thread (): print ('start echo server at port {}' .format (ECHO_PORT)) server = HTTPServer(('' , ECHO_PORT), RequestHandler) server.serve_forever() t = Thread(target=echo_server_thread) t.daemon = True t.start() parser = argparse.ArgumentParser(description="Frida Python script with command line arguments." ) parser.add_argument("process_name" , help ="The process name you want to attach to." ) parser.add_argument("js_file" , help ="The path to the Frida JS script." ) args = parser.parse_args() session = frida.get_usb_device().attach(args.process_name) with open (args.js_file, "r" , encoding='utf-8' ) as f: js_code = f.read() script = session.create_script(js_code) def on_message (message, data ): if message['type' ] == 'send' : payload = message['payload' ] _type , data = payload['type' ], payload['data' ] if _type == 'REQ' : data = str (data) r = requests.request('REQUEST' , 'http://127.0.0.1:{}/' .format (ECHO_PORT), proxies={'http' : 'http://127.0.0.1:{}' .format (BURP_PORT)}, data=data.encode('utf-8' )) script.post({'type' : 'NEW_REQ' , 'payload' : r.text}) elif _type == 'RESP' : r = requests.request('RESPONSE' , 'http://127.0.0.1:{}/' .format (ECHO_PORT), proxies={'http' : 'http://127.0.0.1:{}' .format (BURP_PORT)}, data=data.encode('utf-8' )) script.post({'type' : 'NEW_RESP' , 'payload' : r.text}) script.on('message' , on_message) script.load() sys.stdin.read()
hook脚本如下:
js
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 Java .perform (function ( ) { var c = Java .use ("e.c.a.i.c" ); var EncryptUtil = Java .use ("com.qq.lib.EncryptUtil" ); var newStr; c.a .overload ('java.lang.String' ).implementation = function (str ) { send ({ type : 'REQ' , data : JSON .stringify (str) }); var newArgs = recv ('NEW_REQ' , function (data ) { newStr = JSON .parse (data.payload ); }); newArgs.wait (); return this .a (newStr); } var newPlaintext; EncryptUtil .decrypt .implementation = function (str, str2 ) { var plaintext = this .decrypt (str, str2); send ({ type : 'RESP' , data : JSON .stringify (plaintext) }); var newResult = recv ('NEW_RESP' , function (data ) { newPlaintext = JSON .parse (data.payload ); }); newResult.wait (); return newPlaintext; } });
记得在burp监听相应的端口:测试效果
启动python脚本:python3 main.py org.tdyoa.mcdfmv(包名为org.tdyoa.mcdfmv) hook.js
burp上接收到python转发过来的响应包,我们进行修改试试:以用户名为例:
原用户名:
改包到新用户名:xiaoheilaoshi123
已经成功篡改,可以开始愉快的测试啦!!!
(三)进阶-主动调用 用了前面的方法,有的小伙伴肯定就要说了,每次测试,我都要去点app,操作手机,好麻烦啊,算了算了。别急,咱们这就解决一下
前面我们知道,我们是通过反编译找到关键函数点,然后用frida进行hook打印寻找调用栈,那么不妨拓展一下省力思路:
能不能找到关键方法,然后脚本直接调用方法,进行hook,这样我们就只需要把app开着放在一边,直接去运行脚本,就可以完成操作?
显然是可以的(就以同一个app中data字段的加密解密方法为例):
编写一下脚本如下(找到该方法相关的类名、密钥等信息,利用python提供的frida、flask库实现frida的功能):
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 import fridaimport sysfrom flask import Flask, request, jsonifyapp = Flask(__name__) device = frida.get_usb_device() session = device.attach("org.tdyoa.mcdfmv" ) @app.route('/decrypt' , methods=['POST' ] ) def decrypt (): encrypted_data = request.json['encrypted_data' ] decrypt_key = request.json['decrypt_key' ] script = session.create_script(""" Java.perform(function() { var EncryptUtil = Java.use('com.qq.lib.EncryptUtil'); var encrypted_data = '%s'; var decrypt_key = '%s'; var decrypted_data = EncryptUtil.decrypt(encrypted_data, decrypt_key); send(decrypted_data); }); """ % (encrypted_data, decrypt_key)) decrypted_data = None def on_message (message, data ): nonlocal decrypted_data if message['type' ] == 'send' : decrypted_data = message['payload' ] script.on('message' , on_message) script.load() script.unload() return jsonify({'decrypted_data' : decrypted_data}) if __name__ == '__main__' : app.run(host='0.0.0.0' , port=5000 )
1 2 3 4 5 6 7 8 9 10 11 12 import requestsurl = "http://localhost:5000/decrypt" headers = {"Content-Type" : "application/json" } data = { "encrypted_data" : "8039C68F5AAE16B87FED41779E66ADC704ACC42028F9D422F97BFAB7D3288972139060B21A850DA4DB0107F6D2165076DCB064BA6C4796B61C4E2AF0F3B02947F41F6D7E6454F6FD06117A714093FDD71A46A017A490E9E51B5D73A403E34F5BFF83A1F2963935B12B04E960BD31540F8773AFB6A6B916FE66531B0D8891B1A0F1B9A0EA4DE7976FC01E3DE4DACEB8BA07D2086F845D2191A06A20626B9C1906B1A7AC02275C9DB8AF88E2D5254F9C7588104E4668C1229365850FA0BB6C2B55CB2DE8D3186C6F5A6DD5E3C1EE4EB8D50E02C7DA6C388D4A0D94C31D180B882E2C05CD15D13F226BD0966BE78FC2942FD23862E92D2A5AC3CF3C818D13C20D37680C3E4B7F42101C84EB795289945107EBBB539B27704A083D3D55B2815FD848F359C9ACE6986E1B751F1041CD9B1DBCF35591DAA4C74974F6A9CD57B4082C15A0FB8FD77A5F9F17B7F80F008D5D18D5D0C4D7D842E5BA5BABDDCB52C7A1E4860ED439F4A97FFBDDFE6BDF7BA82CCA479113A1A2FF59A91E0A4C0304CCB1CF0B64D7307F98C810088D49CB4AF9D06C674B3F8C38DAF82D5D1ABE3D3F08CD29763C7233B55960512D3D16FD4412537872EC6BE1E219B145B6F0116BDCC48C811DCE60CAF080198067138DB7BFC7D3C216B9FACF3D5DCF5734E38A6C99EEF23327CF188089820183E4F739022CB64ABF433A61F1B71F4C00B926E81D52486BF70FFA9A20EAB9381C63143A414391DD9D4A1B49701027F7F7BDCF4A6A108B8212FC463063B6DC42C086292F7A6F9B675805E4064327F8FE0ECDCF8FBCC407858A540FC4D47AA9028CC00C8DB9EB41BB6266187EEEBABB5138C1E83EE25C88A83EB20B0EAD4A7B09D45B9D4DEB44D1887C017B10F985021B2231A5F69053468EC0A45B6411561780D85C12E6E59597846FC4014F2ACB28E17175F952A64EAB5E3C1AA520A3821D6340627EA83448F1505B29D51097577A37F32B2F68D3B3BFC2018B8F5AADC41E20D1D9DA4383D4AEC7BBC6F86AE3602EAE99774EF695532B52E05824E025250F43A70137F56B6B6EBE7B8604FF785039D1F38E1CC10D49DA238F11C50B9BBE9E30FE785A5BFC49E28D3A3025A207A82BEC973E3760915A827428E3BA11B9433D95640EB3FA51394A02D543622F1B815A47C918453A2E6A917B00730F89E28DE31DF9184DAFFEC5737789122AA5A35A7ACA32A289031626CCC59251F890A213C893DBC0B17CAA3EDA18A9B365CA3DD3AC1E49445C4B67F63106BEED0A3346F50E18B7FAAE0600908F15E494C28140E2437282481C0687D31D938514D93CA4FF32F274823A4075982517DB18C8F9ED081452ECA3E35BBA8DE95E77612E3DD8449389B67DE4D631605079B609024CE7F03AB83F7BAAD7FF9DE5DCF6255FF78219FF724F0DBEFA29B4506375FC6383828638AB77B19FA0A0057EF8A84CD09CFB28C239CD11042D52D370FD0FC6C2F284BF4287A5735802FBEDF666601EED5EB9029E1C2448E6393E44536FEC7D178E8BD3843DA4E0B6137C590AD23310F52CD3B8F5AF93081EB3C5D01003AC9D272FECC40C5808EBFC9533421C84C8C5B1C372BF9146107F6B7946B8ECC3D0773AA1211F953B2773E18C06C57949163BADA100A4BC0A3E6A7555914DA70C2537C1EB709C7C54F8BE3C8D2617381BCDEDB9790B1E673EC7B73BBE9AA2B45139EEFC17AC6416215D5B02219968B4DF9311AD8EF3992910932A3B7B72CA9BA5AC66BDDF45DB272D469D08607E4B12201C07BF0EB8FBDA7F41D343ADB8217E289D730EECCA4D84027C9A5AD4619C9326B452AEC88F2C6B1F0A004C7B45594CA5888C022E4975C9E7D337519336D0607AD32BCEE34557DE7374EECC55717AC09139155D7932B19189C48AF9D90C157805B2B97145BE7348637DBA0F3C6A7F91EAEE489FB309CC60041F83EAD4845AC1F2F4EFE06326575602A4715D6E4A296459D5FFB466A5089EC45FEC0158302388A96D725228855A3D85CAC933DBCE2153209069EEF2DE1DA4780A7616492295D81397D581589E13FEAE061D57CCC6D427131E8A7FDB9AEB454F2F7555E598C056A46662E9E0BF96A1B9AA5E09423E6653710A16177AB658F2E14BA9C83639BB64DFF4E4F1CA21143FD2E01B62CAF69D6BC559BAD2B76749919AE6F1648C0E9608B39746A13CFA42F47C9AC52A2198438CFA4283D2B6CFF961D69C40F6DF36DCE6F8DCED811E0197427BDD7F135050A49D004C262D1AECA4417F25A823D95FA921C491853783F56914ABDA14384163640DDC7FA36C2C302A1EF396DF846C3CC8A130583B7C6826771948D60029AA14CCE816D84B113B1E418A2361AA0A4E64BFD5F6DC1EDF9DCC548BC6F7A329D3FABDE9CF1AA6692DF2F67A53D5E951804F769BE31CEA7681F63C8AADCD629460564D31F43843125A35B7CE4EDF8A6D79E15363CF74F955AD6029DD6C0B1EBF8031AC52B502DA90CACF9AEB5FEAD5A19D85216C12F03160CB33F015E461CD2AA6CCFCD68865E53930B544C5364E00A7821B8DE76116D0BD4A3EE231801C0B44C12CC11376BE0437962FD43C677D70D9C39AC2A479550A5A8DFD1C5678964FE3C207AE124AD3D0B406573A72DB2CBDA5421647C3D625D1C29A58395421A186A7BD0B48AA8CD1FD5CCF73DEE9719ACC7837A47B9B0CC436ACAB936872F72F1E65C36FA02CB0B218F1B1D37B552713627F4FE400882185DA042CBD7EB79E90AF628692141704D3B72948536CF62C427477370B65B18D43C9B2D02D6E376F0ABB59916B6C7FBFC3CBD7018BB7F4BE1F91D381A1E059B1D0953FC23DF55A648D50AD4BC44903BCD68E8D6E61090C8DF4A06532DEC45FF9F69CED82B4909563AF336AA339F1684A6AE7D6DBBCD32A897A99049BEBFF8847357DE40A437BE4E49DCF41D2DE7F9804CECE81B7E19F291480F1D0279506020D7960F9563C731F5C23BEC2FE3FC754A8F7D4F6E9C5703A149703ACFCC67068083739EF1E646977FF53F7C11333BEFA0EF7C824D697AA62774BAB7B616ECD2B99A4D3F3D56D01C09F374869B2577E4BACC91539DDE51EB484EEDBF5DFF84E302510E9AB39FB9269511CB8D7788EB0567C5A897F7FDA5D815201DB4CD0C5DEA6836A6C5BC62424640A9869B743D499A865C65D1EF9F9651722D38002DADC41025C1C63B6E3E1022F085A92882E1DC4F23FC9A9A2BF2609E6DF54AFB36F6F136909C7" , "decrypt_key" : "PT0dPVxYXglbDARZXwlfXwwMDApYDgReWV4JXl4ICAtfWAxcJD4WCD8SFRYCFQ==" } response = requests.post(url, json=data, headers=headers) print (response.json())
运行效果如下:
好啦,本篇文章讨论就到这里啦,小伙伴们开始愉快的玩耍吧!!!