(void(*)()exec)()的理解

最近开始看一些免杀了,发现shellcode加载器里面有这个代码,现在看懂了拿出来记一下

看一个最简单的shellcode加载器

1
2
3
4
5
6
7
int main(){
unsigned char buf[] = "shellcode";
void* exec = VirtualAlloc(0, sizeof buf, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(exec, buf, sizeof buf);
((void(*)())exec)();
return 0;
}

干脆就所有代码都看一下吧反正就这么几行

看懂这个加载器只需要知道两个函数就行,VirtualAlloc,memcpy,还有最后一句话就行

0x01 VirtualAlloc

这个是一个开辟内存的函数,要让shellcode执行起来需要先把shellcode放到内存中,这个函数就可以开一块内存

先看一下需要的参数

1
2
3
4
5
6
LPVOID VirtualAlloc(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);

lpAddress 分配内存区域的地址,如果为null将有系统来分配

dwSize 要分配或者保留的区域大小,这个参数以字节为单位

flAllocationType 这个数值有三个可选择MEM_COMMIT ,MEM_RESERVE,MEM_RESET,具体的区别不能很明确的说出来,一般都是用MEM_COMMIT

flProtect指定了被分配区域的访问保护方式,其实就是选择可读可写可执行的权限

参数 数值 权限
PAGE_READONLY 0x02 只读
PAGE_EXECUTE 0x10 可执行,不可读不可写
PAGE_EXECUTE_READ 0x20 可执行,只读
PAGE_READWRITE 0x04 可读可写不可执行
PAGE_EXECUTE_READWRITE 0x40 可读可写可执行

这里的每个参数都是有一个数值的,是用宏定义,可以Ctrl+左键查看,就算函数面直接写数值也是可以的

上面的不是很全,就把常用的写了一下,具体的可以在这里看

https://baike.baidu.com/item/VirtualAlloc

现在应该就可以理解这句话了

1
void* exec = VirtualAlloc(0, sizeof buf, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

第一个参数传0也就是null,第二个传shellcode的长度表开始开多大的内存,第三个就这么写吧,最后一个就是shellcode这段内存设置可读可写可执行

这个函数最后返回一个指针,指向分配的首地址

0x02 memcpy

这函数就是把shellcode放到刚开的内存中

第一个参数是存储复制内容的地址,强制转换为void*指针

第二个参数是要复制到第一个参数里面的数据,强制转换为void* 指针

第三个参数是要被复制的字节数

1
memcpy(exec, buf, sizeof buf);

把buf里面的shellcode放入exec中

0x03 (void(*)()exec)()的理解

这个其实很好理解,首先要知道C语言的强制类型转换

1
2
float n = 3.14;
(int)n;

C语言的强制类型转换就是在要转换的参数前面加上 (类型)

现在再了解一下函数指针

1
2
3
4
5
6
7
8
void hello(){
printf("helloworld");
}
int main(){
hello(); //简化调用
(*hello)(); //标准调用
return 0;
}

hello是一个返void类型的函数,hello的声明是· void (*hello)()

看到了声明就可以得到这个函数类型的类型转换符了 void(*)()

然后加上前面的强制类型转换

1
(  (void(*)())  exec)();

这句代码的意思就是把exec强制转换为一个返回void类型的函数指针然后执行,就成功执行了shellcode