├── README.md ├── images ├── iShot_2022-10-17_11.19.16.png ├── iShot_2022-10-17_11.21.45.png ├── iShot_2022-10-17_11.22.42.png ├── iShot_2022-10-17_11.28.07.png └── iShot_2022-10-17_11.40.01.png ├── ios.js ├── macos.js └── mininprogram_cloudfunctions_request.py /README.md: -------------------------------------------------------------------------------- 1 | ## 微信小程序云函数抓包 2 | ![](images/iShot_2022-10-17_11.19.16.png) 3 | 4 | 在对微信小程序进行安全测试时,发现小程序采用了云函数开发方式作为中转,而采用云函数方式与服务器请求过程中采用微信mmtls传输通信,我们无法进行抓包修改数据。 5 | ![](images/iShot_2022-10-17_11.28.07.png) 6 | 当一个小程序云函数的请求数据经过腾讯自己构造mmtls加密传输后,若要想解密->修改->加密这个过程并不容易,甚至需要对APP深入逆向分析协议算法才能解密出明文,参考大佬之前阿里mpass抓包方法[MpaasPentestTool](https://github.com/cnmsec/MpaasPentestTool)在微信小程序云函数请求送入加密函数之前将明文数据hook住,再其上最修改,再送入加密函数,便可以达到修改数据包的目的。 7 | 8 | ### 分析函数 9 | 目前经过分析 在macos 小程序中-[WAJSEventHandler_operateWXDatarequestDataWithAppID:data:]为云函数发起请求前明文数据且可修改 10 | ![](images/iShot_2022-10-17_11.21.45.png) 11 | -[WAJSEventHandler_operateWXData endWithResult:]为云函数返回后解密数据 12 | ![](images/iShot_2022-10-17_11.22.42.png) 13 | 14 | 15 | ### 启动方式 16 | 17 | ``` 18 | python miniprogram_cloudfunctions_request.py -t mac -p 11539 19 | python miniprogram_cloudfunctions_request.py -t ios 20 | ``` 21 | 22 | ## 截图验证 23 | 24 | ![](images/iShot_2022-10-17_11.40.01.png) 25 | 26 | 27 | ### TODO 28 | 29 | * 1.安卓微信 云函数请求 抓包 30 | * 2.windows 云函数请求 抓包 31 | 32 | 33 | 34 | ### 参考资料 35 | https://github.com/cnmsec/MpaasPentestTool 36 | -------------------------------------------------------------------------------- /images/iShot_2022-10-17_11.19.16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tea0o/miniprogram_cloudfunctions_tool/90d8c884367563ed2e25989e725a7182be143a6b/images/iShot_2022-10-17_11.19.16.png -------------------------------------------------------------------------------- /images/iShot_2022-10-17_11.21.45.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tea0o/miniprogram_cloudfunctions_tool/90d8c884367563ed2e25989e725a7182be143a6b/images/iShot_2022-10-17_11.21.45.png -------------------------------------------------------------------------------- /images/iShot_2022-10-17_11.22.42.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tea0o/miniprogram_cloudfunctions_tool/90d8c884367563ed2e25989e725a7182be143a6b/images/iShot_2022-10-17_11.22.42.png -------------------------------------------------------------------------------- /images/iShot_2022-10-17_11.28.07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tea0o/miniprogram_cloudfunctions_tool/90d8c884367563ed2e25989e725a7182be143a6b/images/iShot_2022-10-17_11.28.07.png -------------------------------------------------------------------------------- /images/iShot_2022-10-17_11.40.01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tea0o/miniprogram_cloudfunctions_tool/90d8c884367563ed2e25989e725a7182be143a6b/images/iShot_2022-10-17_11.40.01.png -------------------------------------------------------------------------------- /ios.js: -------------------------------------------------------------------------------- 1 | var NSData = ObjC.classes.NSData; 2 | var NSString = ObjC.classes.NSString; 3 | function str(s) { 4 | return Memory.allocUtf8String(s); 5 | } 6 | function nsstr(str) { 7 | return ObjC.classes.NSString.stringWithUTF8String_(Memory.allocUtf8String(str)); 8 | } 9 | /* NSString -> NSData */ 10 | function nsstr2nsdata(nsstr) { 11 | return nsstr.dataUsingEncoding_(4); 12 | } 13 | 14 | /* NSData -> NSString */ 15 | function nsdata2nsstr(nsdata) { 16 | return ObjC.classes.NSString.alloc().initWithData_encoding_(nsdata, 4); 17 | } 18 | console.log("-----------------IOS微信小程序注入完成-----------"); 19 | try{ 20 | Interceptor.attach(ObjC.classes.WAJSEventHandler_operateWXData['- requestDataWithAppID:data:isImportant:keepAlive:'].implementation,{ 21 | onEnter: function(args) { 22 | var d = new ObjC.Object(args[3]); 23 | var s = nsdata2nsstr(d); 24 | console.log("请求包:"+s.toString()); 25 | send({type: 'REQ', data: s.toString()}) 26 | var op = recv('NEW_REQ', function(val) { 27 | var s = val.payload; 28 | var ns = ObjC.classes.NSString.stringWithString_(s); 29 | var new_s= nsstr2nsdata(ns); 30 | args[3] = new_s; 31 | }); 32 | op.wait() 33 | 34 | }, 35 | onLeave: function(retval) { 36 | 37 | } 38 | 39 | }); 40 | Interceptor.attach(ObjC.classes.WAJSEventHandler_operateWXData['- endWithResult:'].implementation,{ 41 | onEnter: function(args) { 42 | const dict = new ObjC.Object(args[2]); 43 | var respond_data=dict.objectForKey_('data').objectForKey_('data'); 44 | send({type: 'RESP', data: respond_data.toString()}); 45 | console.log("-------------------------------------"); 46 | console.log("返回包:"+ respond_data.toString()); 47 | var op = recv('NEW_RESP', function(val) { 48 | var new_data = val.payload; 49 | var repond_data = ObjC.classes.NSMutableDictionary.alloc().init(); 50 | var repond_data_1 =ObjC.classes.NSMutableDictionary.alloc().init(); 51 | const enumerator = dict.keyEnumerator(); 52 | let key; 53 | const enumerator1 = dict.objectForKey_('data').keyEnumerator(); 54 | while ((key = enumerator1.nextObject()) !== null) { 55 | if(key =="data"){ 56 | repond_data_1.setObject_forKey_(nsstr(new_data),key); 57 | }else{ 58 | repond_data_1.setObject_forKey_(dict.objectForKey_('data').objectForKey_(key),key); 59 | } 60 | } 61 | while ((key = enumerator.nextObject()) !== null) { 62 | if(key =="data"){ 63 | repond_data.setObject_forKey_(repond_data_1,key); 64 | }else{ 65 | repond_data.setObject_forKey_(dict.objectForKey_(key),key); 66 | } 67 | } 68 | args[2]=repond_data; 69 | }); 70 | op.wait(); 71 | }, 72 | onLeave: function(retval) { 73 | } 74 | 75 | }); 76 | } 77 | catch(err){ 78 | console.log("[!] Exception2: " + err.message); 79 | } -------------------------------------------------------------------------------- /macos.js: -------------------------------------------------------------------------------- 1 | var NSData = ObjC.classes.NSData; 2 | var NSString = ObjC.classes.NSString; 3 | function str(s) { 4 | return Memory.allocUtf8String(s); 5 | } 6 | function nsstr(str) { 7 | return ObjC.classes.NSString.stringWithUTF8String_(Memory.allocUtf8String(str)); 8 | } 9 | /* NSString -> NSData */ 10 | function nsstr2nsdata(nsstr) { 11 | return nsstr.dataUsingEncoding_(4); 12 | } 13 | /* NSData -> NSString */ 14 | function nsdata2nsstr(nsdata) { 15 | return ObjC.classes.NSString.alloc().initWithData_encoding_(nsdata, 4); 16 | } 17 | console.log("-----------------mac微信小程序注入完成-----------"); 18 | try{ 19 | Interceptor.attach(ObjC.classes.WAJSEventHandler_operateWXData['- requestDataWithAppID:data:'].implementation,{ 20 | onEnter: function(args) { 21 | var d = new ObjC.Object(args[3]); 22 | var s = nsdata2nsstr(d); 23 | console.log("请求包:"+s.toString()); 24 | send({type: 'REQ', data: s.toString()}) 25 | var op = recv('NEW_REQ', function(val) { 26 | var s = val.payload; 27 | var ns = ObjC.classes.NSString.stringWithString_(s); 28 | var new_s= nsstr2nsdata(ns); 29 | args[3] = new_s; 30 | }); 31 | op.wait() 32 | 33 | }, 34 | onLeave: function(retval) { 35 | 36 | } 37 | 38 | }); 39 | Interceptor.attach(ObjC.classes.WAJSEventHandler_operateWXData['- endWithResult:'].implementation,{ 40 | onEnter: function(args) { 41 | const dict = new ObjC.Object(args[2]); 42 | var respond_data=dict.objectForKey_('data').objectForKey_('data'); 43 | send({type: 'RESP', data: respond_data.toString()}); 44 | console.log("-------------------------------------"); 45 | console.log("返回包:"+ respond_data.toString()); 46 | var op = recv('NEW_RESP', function(val) { 47 | var new_data = val.payload; 48 | var repond_data = ObjC.classes.NSMutableDictionary.alloc().init(); 49 | var repond_data_1 =ObjC.classes.NSMutableDictionary.alloc().init(); 50 | const enumerator = dict.keyEnumerator(); 51 | let key; 52 | const enumerator1 = dict.objectForKey_('data').keyEnumerator(); 53 | while ((key = enumerator1.nextObject()) !== null) { 54 | if(key =="data"){ 55 | repond_data_1.setObject_forKey_(nsstr(new_data),key); 56 | }else{ 57 | repond_data_1.setObject_forKey_(dict.objectForKey_('data').objectForKey_(key),key); 58 | } 59 | } 60 | while ((key = enumerator.nextObject()) !== null) { 61 | if(key =="data"){ 62 | repond_data.setObject_forKey_(repond_data_1,key); 63 | }else{ 64 | repond_data.setObject_forKey_(dict.objectForKey_(key),key); 65 | } 66 | } 67 | args[2]=repond_data; 68 | }); 69 | op.wait(); 70 | }, 71 | onLeave: function(retval) { 72 | } 73 | 74 | }); 75 | } 76 | catch(err){ 77 | console.log("[!] Exception2: " + err.message); 78 | } -------------------------------------------------------------------------------- /mininprogram_cloudfunctions_request.py: -------------------------------------------------------------------------------- 1 | from threading import Thread 2 | from http.server import HTTPServer, BaseHTTPRequestHandler 3 | import sys 4 | import requests 5 | import frida 6 | import sys, getopt 7 | ECHO_PORT = 28080 8 | BURP_PORT = 8080 9 | script=None 10 | 11 | class global_script(): 12 | script=None 13 | @staticmethod 14 | def init_script(self,con): 15 | print(type(con)) 16 | self.script=con() 17 | 18 | class RequestHandler(BaseHTTPRequestHandler): 19 | def do_REQUEST(self): 20 | content_length = int(self.headers.get('content-length', 0)) 21 | self.send_response(200) 22 | self.end_headers() 23 | self.wfile.write(self.rfile.read(content_length)) 24 | do_RESPONSE = do_REQUEST 25 | 26 | 27 | def echo_server_thread(): 28 | print('start echo server at port {}'.format(ECHO_PORT)) 29 | server = HTTPServer(('', ECHO_PORT), RequestHandler) 30 | server.serve_forever() 31 | 32 | def on_message(message, data): 33 | if message['type'] == 'send': 34 | payload = message['payload'] 35 | _type, data = payload['type'], payload['data'] 36 | if _type == 'REQ': 37 | data = str(data) 38 | r = requests.request('REQUEST', 'http://127.0.0.1:{}/'.format(ECHO_PORT), 39 | proxies={'http': 'http://127.0.0.1:{}'.format(BURP_PORT)}, 40 | data=data.encode('utf-8')) 41 | script.post({'type': 'NEW_REQ', 'payload': r.text}) 42 | elif _type == 'RESP': 43 | r = requests.request('RESPONSE', 'http://127.0.0.1:{}/'.format(ECHO_PORT), 44 | proxies={'http': 'http://127.0.0.1:{}'.format(BURP_PORT)}, 45 | data=data.encode('utf-8')) 46 | script.post({'type': 'NEW_RESP', 'payload': r.text}) 47 | def start(argv): 48 | global script 49 | t = Thread(target=echo_server_thread) 50 | t.daemon = True 51 | t.start() 52 | hook_type="" 53 | mini_pid="" 54 | 55 | try: 56 | opts, args = getopt.getopt(argv,"ht:p:",["type=","pid="]) 57 | except getopt.GetoptError: 58 | print("python miniprogram_cloudfunctions_request.py -t mac -p 11539") 59 | print("python miniprogram_cloudfunctions_request.py -t ios ") 60 | sys.exit(2) 61 | for opt, arg in opts: 62 | if opt == '-h': 63 | print("python miniprogram_cloudfunctions_request.py -t mac -p 11539") 64 | print("python miniprogram_cloudfunctions_request.py -t ios ") 65 | sys.exit() 66 | elif opt in ("-t", "--type"): 67 | hook_type = arg 68 | elif opt in ("-p", "--pid"): 69 | mini_pid = arg 70 | if(hook_type=="ios"): 71 | session = frida.get_usb_device().attach('微信') # IOS 移动端 72 | with open("ios.js") as f: 73 | script=session.create_script(f.read()) 74 | script.on('message', on_message) 75 | script.load() 76 | sys.stdin.read() 77 | elif(hook_type=="mac"): 78 | session = frida.attach(int(mini_pid)) # macos电脑端 ps -ef |grep Mini 79 | #session = frida.attach(21831) # macos电脑端 ps -ef |grep Mini 80 | with open("macos.js") as f: 81 | script=session.create_script(f.read()) 82 | script.on('message', on_message) 83 | script.load() 84 | sys.stdin.read() 85 | 86 | else: 87 | print("python miniprogram_cloudfunctions_request.py -t mac -p 11539") 88 | print("python miniprogram_cloudfunctions_request.py -t ios ") 89 | print("安卓、windows 微信小程序暂未支持") 90 | sys.exit() 91 | 92 | if __name__=="__main__": 93 | start(sys.argv[1:]) --------------------------------------------------------------------------------