通過逆向分析挖掘Facebook Gameroom中的安全漏洞
去年年底,我受邀參加了Facebook的Bountycon活動,這是一個受邀者才能參加的應用安全會議,其中有一個現場黑客環節。雖然參與者可以提交任何Facebook產品內的漏洞,但Facebook邀請我們關注Facebook游戲。由于之前測試過Facebook的產品,所以我知道這將是一個高難度的挑戰。這些年來,他們的安全控制已經越來越嚴格——即使像跨站腳本攻擊這樣的簡單漏洞也很難找到,這也是為什么他們肯為這些漏洞付出非常高的賞金的原因。因此,頂級白帽黑客往往會從第三方軟件的角度來挖掘Facebook的安全漏洞,比如Orange Tsai著名的MobileIron MDM漏洞。
鑒于我的時間有限(由于某些原因,我遲到了),所以,我決定不進行全面的漏洞研究,而專注于對Facebook Gaming的訪問控制進行簡單的安全審計。然而,正如人們所期望的那樣,移動和網頁應用的安全性都很好。經過一番折騰,我發現了Facebook Gameroom程序,它是一個用于玩Facebook游戲的Windows本地客戶端。于是,我就踏上了將攻勢攻擊性逆向分析應用于本地桌面應用程序的啟蒙之旅。
關于Facebook Gameroom
您是不是還沒聽說過Facebook Gameroom——這很正常,我之前也沒聽說過。實際上,Gameroom發布于2016年11月,被譽為Steam的強勁對手,支持Unity、Flash和最近的HTML5游戲。然而,近年來,隨著流媒體的崛起,Facebook已經將注意力轉向其移動和網絡平臺。事實上,Gameroom計劃在今年6月退役。幸運的是,在我挖到其中的安全漏洞時,它還沒有退出市場。
我注意到的第一件事是,安裝Gameroom時,無需提升任何的權限。它似乎是一個分步安裝的程序:首先,通過一個小型安裝程序從網絡上下載額外的文件,也就是說,它的安裝程序不是一體式的。事實上,我很快就找到了安裝目錄,即C:\Users\< USERNAME >\AppData\Local\Facebook\Games由于大多數用戶級應用程序都位于該C:\Users\< USERNAME >\AppData文件夾中,因此我很快在處找到了安裝目錄。該文件夾包含許多.dll文件以及幾個可執行文件。一些事情對我很重要:
1. Gameroom自帶的7zip可執行文件(7z.exe和7z.dll),可能已經過時,而且很容易受到攻擊。
2. Gameroom將用戶會話數據存儲在Cookies SQLite數據庫中,這對攻擊者來說是一個非常有吸引力的目標。
3. Gameroom包含了CefSharp庫(CefSharp.dll),經過進一步研究,發現這是一個用于C#的嵌入式Chromium瀏覽器。
第三點表明,Gameroom是用.NET框架編寫的。我們知道,.NET框架可以將程序被編譯成通用中間語言(CIL)代碼,而不是機器代碼,也就是說,這些代碼可以在通用語言運行時應用程序虛擬機中運行。這樣做有許多好處,比如可以提高.NET應用程序的互操作性和可移植性。然而,由于這些應用程序被編譯為CIL而不是純機器代碼,因此,將這些應用程序反編譯回接近源碼的過程也要容易得多。
對于.NET程序集,DNSpy是事實上的標準。逆向工程師可以用DNSpy輕松地調試和分析.NET應用程序,包括對它們進行實時修改。于是,我把FacebookGameroom.exe拖拽到DNSpy中,開始進行分析。
挖掘含有漏洞的函數
首先,我們開始查找易受攻擊的函數,如不安全的反序列化函數等。限于篇幅,這里就不對反序列化攻擊進行詳細介紹了,簡單來說,這涉及到將數據類型轉換為易于傳輸的格式,然后再轉換回來,如果處理不當,這可能會導致嚴重的漏洞。例如,微軟就曾警告不要使用BinaryFormatter,因為BinaryFormatter是不安全的,也不能使其安全。
不幸的是,在我搜索“Deserialize”字符串時,BinaryFormatter竟然冒了出來。

然而,我還需要找到易受攻擊的代碼路徑。為此,我右擊搜索結果,選擇“Analyze”,然后沿著“Used By”鏈找到了Gameroom使用BinaryFormatter.Deserialize的地方。

最終,這讓我找到了System.Configuration.ApplicationSettingsBase.GetPreviousVersion(string)和System.Configuration.ApplicationSettingsBase.GetPropertyValue(string)函數。Gameroom在啟動時使用了反序列化函數來檢索其應用程序的相關設置——但這些設置是從哪里來的呢?通過考察安裝文件夾,我發現了fbgames.settings,這是一個序列化的blob。因此,如果我在這個文件中注入一個惡意的反序列化payload,就可以獲得代碼執行權限。不過,在此之前,我還需要找到一個反序列化的gadget。根據已知的反序列化gadget列表,我展開了進一步的搜索,發現Gameroom使用的是WindowsIdentity類。
在此基礎上,我研究出了一個代碼執行漏洞的概念驗證過程:
1. 使用ysoserial反序列化攻擊工具,通過命令ysoserial.exe -f BinaryFormatter -g WindowsIdentity -o raw -c "calc" -t > fbgames.settings生成了相應的代碼執行payload。
2. 接下來,我將fbgames.settings復制到C:\Users\
3. 最后,打開Facebook Gameroom,計算器就彈了出來!
雖然獲得代碼執行權限是件非常令人興奮的事情,但在與Facebook團隊進一步討論后,我們一致認為這不符合他們的威脅模型。因為Gameroom是作為用戶級應用執行的,所以沒有機會升級權限。此外,由于覆蓋文件需要某種程度的訪問權限(例如,需要通過審批才能被公開列出的惡意Facebook游戲),所以沒有可行的遠程攻擊載體。
在本地應用程序構成的不同威脅環境中,我學到了一個重要的教訓——在深入研究代碼級漏洞之前,最好先尋找可行的遠程攻擊載體。
規劃探索路徑
您是否有點擊郵件中的鏈接就神奇地啟動了Zoom的經驗?這背后到底發生了什么?其實很簡單,這只是使用了一個自定義的URI方案,它允許你像網絡上的其他鏈接一樣打開應用程序。例如,Zoom注冊了zoommtg: URI方案,來解析像下面這樣的鏈接: zoommtg:zoom.us/join?confno=123456789&pwd=xxxx&zc=0&browser=chrome&uname=Betty。
同樣,我注意到Gameroom使用了自定義URI方案,以便可以在單擊Web瀏覽器中的鏈接后自動打開Gameroom。搜索完代碼后,我發現Gameroom會通過FacebookGames\Program.cs來檢查fbgames:URI方案:
- private static void OnInstanceAlreadyRunning()
- {
- Uri uri = ArgumentHelper.GetLaunchScheme() ?? new Uri("fbgames://");
- if (SchemeHelper.GetSchemeType(uri) == SchemeHelper.SchemeType.WindowsStartup)
- {
- return;
- }
- NativeHelpers.BroadcastArcadeScheme(uri);
- }
即使Gameroom已經用fbgames://URI打開,它還是會繼續通過SchemeHelper類對其進行解析:
- public static SchemeHelper.SchemeType GetSchemeType(Uri uri)
- {
- if (uri == (Uri) null)
- return SchemeHelper.SchemeType.None;
- string host = uri.Host;
- if (host == "gameid")
- return SchemeHelper.SchemeType.Game;
- if (host == "launch_local")
- return SchemeHelper.SchemeType.LaunchLocal;
- return host == "windows_startup" ? SchemeHelper.SchemeType.WindowsStartup : SchemeHelper.SchemeType.None;
- }
- public static string GetGameSchemeId(Uri uri)
- {
- if (SchemeHelper.GetSchemeType(uri) != SchemeHelper.SchemeType.Game)
- return (string) null;
- string str = uri.AbsolutePath.Substring(1);
- int num = str.IndexOf('/');
- int length = num == -1 ? str.Length : num;
- return str.Substring(0, length);
- }
如果URI含有gameid主機,它將用SchemeHelper.SchemeType.Game.Game來進行解析。如果它使用的是launch_local主機,則會用 SchemeHelper.SchemeType.LaunchLocal來進行解析。于是,我從最有希望的launch_local路徑開始,追蹤到了FacebookGames.SchemeHelper.GenLocalLaunchFile(Uri):
- public static async Task
- {
- string result;
- if (SchemeHelper.GetSchemeType(uri) != SchemeHelper.SchemeType.LaunchLocal || uri.LocalPath.Length <= 1)
- {
- result = null;
- }
- else if (!(await new XGameroomCanUserUseLocalLaunchController().GenResponse()).CanUse)
- {
- result = null;
- }
- else
- {
- string text = uri.LocalPath.Substring(1);
- result = ((MessageBox.Show(string.Format("Are you sure you want to run file\n\"{0}\"?", text), "Confirm File Launch", MessageBoxButtons.YesNo) == DialogResult.Yes) ? text : null);
- }
- return result;
- }
遺憾的是,即使我可以通過像fbgames://launch_local/C:/evilapp.exe這樣的URI來啟動系統中的任何任意文件(正如Facebook所記錄的那樣),但仍然會被確認對話框所阻止。我試圖用格式字符串和非標準輸入繞過這個對話框,但并沒有得手。
于是,我將目光轉向gameid路徑,該路徑會根據URI中的游戲ID打開Facebook URL。例如,如果您想在Gameroom中啟動Words With Friends,可以在瀏覽器中訪問fbgame://gameid/168378113211268,這時Gameroom就會在本地應用窗口中打開https://apps.facebook.com/168378113211268。
然而,我發現GetGameSchemeId會從URI中提取ID,并將其添加到app.facebook.com URL中,但是,它并沒有驗證slug是否為有效的ID。因此,攻擊者可以將本地應用程序窗口重定向到Facebook上的任何其他頁面。
- public static string GetGameSchemeId(Uri uri)
- {
- if (SchemeHelper.GetSchemeType(uri) != SchemeHelper.SchemeType.Game)
- return (string) null;
- string str = uri.AbsolutePath.Substring(1);
- int num = str.IndexOf('/');
- int length = num == -1 ? str.Length : num;
- return str.Substring(0, length);
- }
例如,fbgame://gameid/evilPage會把Gameroom窗口重定向到https://apps.facebook.com/evilPage。
但是,我怎么才能在Gameroom中重定向到攻擊者控制的代碼呢?這里有多種方法,其中包括濫用app.facebook.com上的開放式重定向漏洞。不幸的是,當時我手頭一個這樣的漏洞也沒有。另一種方法是重定向到允許將自定義代碼嵌入iframe的Facebook頁面或廣告。
這時,我遇到了一個障礙。我需要重新修改GetGameSchemeId的代碼,因為它只取URI路徑中的第一個slug,所以fbgame://gameid/evilPage/app/123456會將原生應用窗口引導到https://apps.facebook.com/evilPage,并會丟棄/app/123456。
幸運的是,我還可以借助于其他的代碼gadget。另外,在Gameroom中使用的Chrome瀏覽器版的確有點陳舊:63.0.3239.132——而當時的版本是86.0.4240.75。因此,它并不支持新版本的Facebook Pages。經典的Facebook Pages版本會接受一個sk參數,這樣,https://apps.facebook.com/evilPage?sk=app_123456就會生成一個自定義標簽頁,其中攻擊者控制的代碼位于https://apps.facebook.com/evilPage/app/123456!
但是,怎樣才能在我們的自定義方案中注入額外的查詢參數呢?別忘了,Gameroom會丟棄第一個URL slug之后的所有內容,包括查詢參數。果真是這樣嗎?回顧FacebookGames/SchemeHelper.cs,我發現了GetCanvasParamsFromQuery:
- public static IDictionary
- {
- if (uri == (Uri) null)
- return (IDictionary
- string stringToUnescape;
- if (!UriHelper.GetUrlParamsFromQuery(uri.ToString()).TryGetValue("canvas_params", out stringToUnescape))
- return (IDictionary
- string str = Uri.UnescapeDataString(stringToUnescape);
- try
- {
- return JsonConvert.DeserializeObject<IDictionary
- }
- catch
- {
- return (IDictionary
- }
- }
在傳遞自定義URI之前,GetCanvasParamsFromQuery會尋找查詢參數canvas_params,并將其序列化為JSON字典,并將其轉換為新的URL,并用作查詢參數。
這讓我想到了最終的payload方案——fbgames://gameid/evilPage?canvas_params={"sk": "app_123456"}將被Gameroom解析成本地應用瀏覽器窗口中的https://apps.facebook.com/evilPage/app/123456,然后執行我的自定義的JavaScript代碼。
如前所述,原生應用的威脅環境與Web應用有很大區別。通過將嵌入式Chrome原生窗口重定向到攻擊者控制的Javascript代碼,攻擊者可以繼續在3年前的嵌入式Chromium瀏覽器上執行已知的漏洞。雖然完整的漏洞利用代碼尚未公開發布,但我仍然可以利用CVE-2018-6056的概念驗證代碼,通過類型混淆漏洞使Chrome引擎崩潰。
另外,攻擊者可以創建本質上是合法的原生MessageBoxes的彈出框來執行釣魚攻擊,或者嘗試讀取緩存的憑證文件。幸運的是,與集成Node.JS APIs的Electron應用程序不同,CefSharp限制了API的訪問。然而,它仍然容易受到Chromium和第三方庫漏洞的影響。
小結
Facebook將本文介紹的安全漏洞評為高危,并隨即修復了該漏洞。同時,我藉此榮升Bountycon排行榜的前十名。雖然Gameroom即將退出市場,但它仍然給我留下了攻擊性逆向分析的美好回憶(和實踐機會)。對于應用逆向工程的新手來說,Electron、CefSharp和其他基于瀏覽器的框架是一個很好的練手工具,可以用于測試跨站腳本攻擊和開放式重定向等類Web漏洞,同時,還可以利用桌面應用特有的代碼執行手段。
本文翻譯自:https://spaceraccoon.dev/applying-offensive-reverse-engineering-to-facebook-gameroom如若轉載,請注明原文地址。