命名管道与进程令牌窃取

msf中的getsystem也是利用的这个原理,还是非常的有意思。

由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。(本文仅用于交流学习)

命名管道提权

命名管道

我们先了解一下什么是管道,也可以叫做进程间的通信。其是用于通信共享内存的一部分,管道有两端,一端允许进程进行写入,另一端允许进程进行读取。

  • 管道服务器:创建管道的进程。
  • 管道客户端:连接管道的进程。

管道又可以分为匿名管道、命名管道

  • 匿名管道:未命名的单向管道,通常用在父子进程间的传输数据,因此只能用于本地通信,不能用于网络通信。
  • 命名管道:命名的可单向、双向传输的管道,可以用于网络通信。

我们着重看一下命名管道,对于命名管道,每个命名管道都有一个唯一的名称。

  • 管道服务器可以使用CreateNamedPipe函数创建一个命名管道实例,且命名规则必须遵循如下格式\\.\pipe\pipe\pipename,使用ConnectNamedPipe函数等待客户端进程进行连接。
  • 客户端进程使用CreateFile或CallNamedPipe函数连接到命名管道,需要使用如下格式\\ServerName\pipe\PipeName

我们来看看如何编写一个管道服务器:

  • 使用CreateNamedPipe函数创建一个命名管道实例
  • ConnectNamedPipe函数等待客户端连接
  • ReadFile接收客户端发送来的信息

编写服务端与客户端

#include <windows.h>
#include <iostream>

using namespace std;

int main() {
    HANDLE lCreatePipe = CreateNamedPipe("\\\\.\\pipe\\pipe\\testname", PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE, 1, 2048, 2048, 0, NULL);
    if (lCreatePipe == INVALID_HANDLE_VALUE) {
        cout << "CreateNamedPipeA Error" << endl;

        return 1;
    }
    cout << "CreateNamedPipeA Success" << endl;
    BOOL ConnectSuccess = ConnectNamedPipe(lCreatePipe, NULL);
    if (ConnectSuccess == 0) {
        cout << "ConnectNamedPipe Error" << endl;

        return 1;
    }
    cout << "客户端成功连接到服务器" << endl;

    char buf[2048];
    BOOL Read = ReadFile(lCreatePipe, buf, 2048, NULL, NULL);
    if (Read) {
        cout << "Success receive:" << buf << endl;
    }

        return 0;
}

接着我们来看看如何编写客户端的代码

  • CreateFile连接到服务端
  • WriteFile向服务端发送数据
#include <windows.h>
#include <iostream>

using namespace std;

int main() {

    HANDLE ConectionTo = CreateFile("\\\\.\\pipe\\pipe\\testname", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
    if (ConectionTo == INVALID_HANDLE_VALUE) {
        cout << "CreateFileA Error:" << GetLastError() << endl;

        return 1;
    }

    cout << "成功连接到服务端" << endl;

    char message[] = "This is a test";
    DWORD messageLenght = lstrlen(message) * 2;
    BOOL Write = WriteFile(ConectionTo, message, messageLenght, NULL, NULL);
    if (Write == 0) {
        cout << "WriteFile Error" << endl;
        return 1;
    }
    cout << "WriteFile to server success" << endl;

}

14.PNG

模拟命名管道客户端

简单来说就是命名管道服务器线程调用ImpersonateNamedPipeClient函数,当客户端连接到服务端时,系统就会根据客户端的权限授予服务端相同的权限。
具体可参考:
https://learn.microsoft.com/zh-cn/windows/win32/ipc/impersonating-a-named-pipe-client
但是ImpersonateNamedPipeClient函数成功调用是有条件的,只要满足如下之一即可 我们看看第二个条件

15.PNG
当我们以管理员权限运行cmd的时候,我们可以发现其开启了SeImpersonatePrivilege

16.PNG
我们可以让具有system权限程序访问我们,那么这样我们也就具有了system权限,msf中的getsystem也是这个原理
我们可以利用其打开一个cmd窗口来验证是否成功,我们先来看看几个Windows的api

OpenThreadToken
https://learn.microsoft.com/zh-cn/windows/win32/api/processthreadsapi/nf-processthreadsapi-openthreadtoken

17.PNG
DuplicateTokenEx
https://learn.microsoft.com/zh-cn/windows/win32/api/securitybaseapi/nf-securitybaseapi-duplicatetokenex

18.PNG
CreateProcessWithTokenW
调用该函数的进程需要开启SE_IMPERSONATE_NAME特权,管理员进程默认开启 https://learn.microsoft.com/zh-cn/windows/win32/api/winbase/nf-winbase-createprocesswithtokenw

19.PNG
因此思路就很简单了

  • 我们创建好服务端后,利用ImpersonateNamedPipeClient函数进行模拟(客户端也需要访问我们)
  • 模拟成功后,调用OpenThreadToken函数打开服务端线程的令牌
  • 利用DuplicateTokenEx函数复制线程令牌(这一步可有可无)
  • 使用CreateProcessWithTokenW函数打开具有线程令牌(或副本)的程序

20.PNG

进程令牌窃取

其大致原理就是通过从另外一个正在运行的进程中,窃取其令牌,然后对其令牌进行复制,创建一个新的进程。

访问令牌

当用户登录时,系统会将密码与存储在安全数据库中的信息进行比较来验证用户的密码。 如果密码经过验证,系统会生成访问令牌。之后此用户执行的每个进程都有此访问令牌的副本。 访问令牌包含以下信息:

  • 用户帐户的安全标识符(SID)
  • 用户所属组的 SID
  • 标识当前登录会话的登录 SID
  • 用户或用户组持有的特权列表
  • 所有者 SID
  • 主组的 SID
  • 当用户创建安全对象时系统使用的默认DACL ,而无需指定安全描述符
  • 访问令牌的源
  • 令牌是主要令牌还是模拟令牌
  • 限制SID的可选列表
  • 当前模拟级别
  • 其他统计信息

参考:
https://learn.microsoft.com/zh-cn/windows/win32/secauthz/access-tokens

因此我们现在的思路如下:

  • 使用OpenProcess函数打开要窃取的进程,获得句柄
  • 使用OpenProcesToken函数打开与进程关联的访问令牌
  • DuplicateTokenEx复制其令牌
  • CreateProcessWithTokenW使用新的令牌创建进程

这里测试从administrator窃取system进程的令牌,打开一个system权限的cmd

21.PNG
我们这里窃取winlogon进程的令牌,来打开一个新的cmd窗口

22.PNG

  • 发表于 2023-02-20 17:46:56
  • 阅读 ( 7073 )
  • 分类:内网渗透

0 条评论

请先 登录 后评论
事与愿违
事与愿违

9 篇文章

站长统计