郑重声明:文中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用,如果您不同意请关闭该页面!任何人不得将其用于非法用途以及盈利等目的,否则后果自行承担!

前言

在学Go相关的免杀,来提高木马的存活性,看到一些有意思的东西记下来

1

加载DLL

要在Go中加载DLL,可以使用syscall.NewLazyDLLsyscall.LoadLibrary 以及syscall.MustLoadDLL

  • NewLazyDLL返回一个*LazyDLL,懒加载,只在第一次调用其函数时才加载库;
  • LoadLibrary是立即加载DLL库。

创建函数

syscall.NewLazyDLL

package main

import (
"syscall"
"unsafe"
)

func main() {
user32 := syscall.NewLazyDLL("user32.dll")
MessageBoxW := user32.NewProc("MessageBoxW")
MessageBoxW.Call(uintptr(0), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("windows下的第一种调用方式"))), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("ascotbe"))), uintptr(0))
}

syscall.LoadLibrary

package main

import (
"syscall"
"unsafe"
)

const (
MB_YESNOCANCEL = 0x00000003
)

func main() {
user32, _ := syscall.LoadLibrary("user32.dll")
messageBox, _ := syscall.GetProcAddress(user32, "MessageBoxW")
_, _, callErr := syscall.Syscall9(messageBox,
4,
0,
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("第二种调用方式"))),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("ascotbe"))),
MB_YESNOCANCEL,
0, 0, 0, 0, 0)
if callErr != 0 {

}
}

syscall.MustLoadDLL

package main

import (
"syscall"
"unsafe"
)

const (
MB_YESNOCANCEL = 0x00000003
)

var (
user32 = syscall.MustLoadDLL("user32.dll")
MessageBoxW = user32.MustFindProc("MessageBoxW")
)

func main() {

_, _, eeee := MessageBoxW.Call(0,
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("第三种调用方式"))),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("ascotbe"))),
MB_YESNOCANCEL)
if eeee != nil {
}

}

不管调用哪个API,Call的模式都是一样的。

而且syscall.Syscall函数始终返回r1,r2 uintptr,err error, 就最近的实践(windows_amd64)来看,基本可以确定:

  • r1 始终返回 syscall的值;
  • r2 暂且使用;
  • err 返回调用Windows APIGetLastError的结果,这是syscall自动调用的。

而你传入Call中的值必须全部是uintptr,不管你原来的类型是什么

API函数签名

在实际调用DLL函数之前,我们必须要了解一下过程所需要的参数,类型,大小。Microsoft将此描述为Windows API文档的一部分。如CreateJobObjectA的过程签名如下:

HANDLE CreateJobObjectA(
LPSECURITY_ATTRIBUTES lpJobAttributes,
LPCSTR lpName
);

也就是说,CreateJobObjectA需要一个指向LPSECURITY_ATTRIBUTES结构的指针,和一个指向C-String的指针(ASCII编码,技术上是Windows-1252编码 ;它与ASCII兼容)。

C结构与Go结构

在文档中我们可以搜索到,LPSECURITY_ATTRIBUTES是这么定义的:

typedef struct _SECURITY_ATTRIBUTES {
DWORD nLength;
LPVOID lpSecurityDescriptor;
BOOL bInheritHandle;
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;

这时,我们就必须构造一个类似的Go结构来替代它。这时我们可以参考syscallSecurityAttributes的定义。

在Windows API中,我们可以看到,SecurityAttributes是这么定义的:

typedef struct _SECURITY_ATTRIBUTES {
DWORD nLength;
LPVOID lpSecurityDescriptor;
BOOL bInheritHandle;
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;

而Go中SecurityAttributes的定义为:

type SecurityAttributes struct {
Length uint32
SecurityDescriptor uintptr
InheritHandle uint32
}

由此我们大概知道, DWORD对应Go uint32LPVOID (* void)对应uintptrBOOL对应uint32。所以在你不知道用什么类型来表示C中对应的结构时,你可以去看看syscallgo.sys库中找找,或许能有收获。Windows一些参考类型这里也有描述。

然而,了解下面这些常见C类型与Go类型的对应关系会非常有用。

type (
BOOL uint32
BOOLEAN byte
BYTE byte
DWORD uint32
DWORD64 uint64
HANDLE uintptr
HLOCAL uintptr
LARGE_INTEGER int64
LONG int32
LPVOID uintptr
SIZE_T uintptr
UINT uint32
ULONG_PTR uintptr
ULONGLONG uint64
WORD uint16
)

参考文章

https://razeencheng.com/post/breaking-all-the-rules-using-go-to-call-windows-api.html