在看雪上看到了这篇文章,特推荐给大家,主要有两点吸引了我,一是文中罗列的方法虽普通,但总结得挺好,可以作为一个科普性的归纳;二是文中结合了代码穿插讲解,加深了知识点地理解。这里重新编排了原文,主要是方便这里看,也可以去原文查看。这里引用,看雪应该不会找我算帐吧。
原文:https://bbs.pediy.com/thread-271207.htm
恶意代码开发
以下内容仅用于安全研究
前言
目的是能过杀软静态扫描并且能执行上线。
开发平台:Windows 10
开发工具:MS Visual Studio 2019
开发语言:C++
杀软检测工作原理
/引用鸡哥的话术/
静态查杀:主要基于hash和特征码,hash可以是文件的hash或导入表之类的hash,特征码可以是是PE头、pdb、全局字符串、互斥体之类的信息。
动态查杀:基于API的监控和沙箱执行,杀软会通过对ntdll的关键API进行hook,实现对程序的API监控。另外可以在内核中注册一系列的回调函数实现对行为的监控。
启发式:多数杀软采用的是基于权重的启发式,就是一套加减分的规则,用于检测程序的潜在恶意行为,如程序在沙盒或模拟器环境运行,在此过程中有操作端口和通讯的函数,并将自身加载到启动项中等上述行为,则很有可能被判定为恶意,另外一些畸形区块也可触发。
杀躲避检测技术
-
多态(重新编译)对抗基于签名的检测。
-
代码流的混淆对抗启发式检测。
-
基于环境的检测对抗沙箱。
-
敏感信息编码或加密对抗基于签名和启发式。
基本知识
生成shellcode
Shellcode 是设计用于运行本地或远程系统 shell 的机器代码片段(因此得名)。它们主要用于利用软件漏洞 – 当攻击者能够控制程序的执行流程时,他需要一些通用有效负载来执行所需的操作(通常是 shell 访问)。这适用于本地开发(例如权限提升)和远程开发(用于在服务器上获得 RCE)。
Shellcode 是一种引导代码,它利用已知的特定于平台的机制来执行特定操作(创建进程、启动 TCP 连接等)。Windows shellcode 通常使用 TEB(线程环境块)和 PEB(进程环境块)来查找加载的系统库(<font color=”red”> kernel32.dll,
kernelbase.dll或ntdll.dll</font>>)的地址,然后“浏览”它们以找到可用于定位其他函数的地址LoadLibrary和GetProcAddress`函数。
使用最快捷的方式生成shellcode,比如Metasploit 或者CobaltStrike:
|
生成的 shellcode 可以作为字符串包含在二进制文件中。char 数组的经典执行涉及将此数组转换为指向函数的指针,如下所示:
|
执行shellcode
执行 shellcode 的实际方式有点不同。我们需要:
-
使用
VirtualAlloc
(或VirtualAllocEx
为远程进程)Windows API 函数分配一个新的内存区域, -
用 shellcode 字节填充它(例如,使用
RtlCopyMemory
基本上是一个memcpy
包装器的函数), -
分别使用
CreateThread
或CreateRemoteThread
函数创建一个新线程。
Shellcode 也可以使用 char 数组来执行函数指针转换,只要 shellcode 所在的内存区域被标记为可执行。
include <Windows.h>
void main() { const char shellcode[] = ” “; PVOID shellcode_exec = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE); RtlCopyMemory(shellcode_exec, shellcode, sizeof shellcode); DWORD threadID; HANDLE hThread = CreateThread(NULL, 0, (PTHREAD_START_ROUTINE)shellcode_exec, NULL, 0,&threadID); WaitForSingleObject(hThread, INFINITE); } |
针对 VirusTotal 的优化
在发布我们的可执行文件之前,我们应该确保从二进制文件中删除一些工件。比如丢弃任何调试符号和信息——这可以通过将构建配置切换到“发布”并禁用调试信息的生成(项目属性中的链接器配置)来实现。
在使用 Visual Studio 时,PDB(程序数据库)路径默认嵌入在二进制文件中。PDB 用于存储调试信息,该文件与可执行文件(或 DLL)本身存储在同一目录中。这条路径可能会泄露一些敏感信息-比如:"C:usersuserDesktopprojectReleaseapp.exe"
.
签名
某些恶意软件检测引擎可能会将未签名的二进制文件标记为可疑。
举个例子:
|
给生成的PE文件添加签名:
|
过沙箱
恶意软件动态分析
可执行文件的动态分析可以由沙箱自动执行,也可以由分析人员手动执行。恶意应用程序通常使用各种方法来对它们正在执行的环境进行指纹识别,并根据情况执行不同的操作。
自动分析在简化的沙盒环境中执行,该环境可能具有某些特定特征,特别是它可能无法模拟真实环境的所有细微差别。手动分析通常在虚拟化环境中执行,并且可能会遇到特定的附加工具(调试器、其他分析软件)。
自动和手动分析都有共同的特点,特别是它们通常在虚拟化环境中执行,如果没有正确配置(强化),可以很容易地检测到。大多数沙箱/分析检测技术都围绕检查特定环境属性(有限资源、指示性设备名称)和工件(特定文件、注册表项的存在)。
然而,有几个针对自动化沙箱的特定检测,以及恶意软件分析师使用的其他特定于虚拟环境的检测。
测试检测
这里,用反向shell来进行测试,因为反向shell有一个回连接的c2地址,也是实际场景中最常用的一种方式。
cs生成二进制文件:
xor加密:
测试上线:
这种形式的木马,VT 沙箱能够在动态分析过程中提取 IP 地址。
检测虚拟化环境
沙箱类的虚拟化操作系统通常都不能 100% 准确地模拟实际执行环境。虚拟化环境的资源有限,可以利用这里点,来做逃避沙箱。
硬件资源
要求处理器至少有 2 个内核、至少 2 GB 的 RAM 和 100 GB 的硬盘驱动器。
} |
设备和供应商名称
在默认 VM 安装中,设备通常具有可预测的名称,例如包含与特定管理程序关联的字符串。我们可以检查硬盘名称、光驱名称、BIOS 版本、计算机制造商和型号名称、图形控制器名称等。可以使用 WMI 查询检索相关信息(检查“名称”、“描述”、“标题”等属性) )。
|
查找典型主机系统中不存在的特定虚拟设备,例如用于来宾主机通信的管道和其他接口:
|
MAC 地址可以指示虚拟环境的存在,因为默认情况下前 3 个字节是制造商标识符。迭代所有可用的网络适配器并将第一个字节与已知值进行比较:
|
虚拟环境中的特征值
通过进程名称判断
以下是Vmware和VirtualBox可能存在的进程,我们可以使用Process32First,Process32Next等WINAPI列举进程并且检测是否存以下内容。
Vmware:
Vmtoolsd.exe
Vmwaretrat.exe
Vmwareuser.exe
Vmacthlp.exe
VirtualBox:
vboxservice.exe
vboxtray.exe
|
使用注册表键值判断
以下是vbox和vmware存在的一些注册表:
|
通过检测指定注册表键值是否存在来判断程序是否在虚拟机中运行:
|
检测硬盘中的文件
下面搜集来了一些vmware和vbox存在的文件特征。可以用多种方法检测文件是否存在,如:WMIC,WINAPI和CMD。
VMware
|
VirtualBox
|
简化代码:
|
检测当前环境运行的进程
枚举所有现有流程和检查典型的分析工具,如Wireshark
,Procmon
,x64dbg
,IDA
等。
|
检测当前环境运行的时间
我们知道,如果我们的PE文件将要被放到沙箱或者虚拟机分析,那么大概率这个测试环境是刚刚启动的,根据这一点,我们可以做一个小功能:
如果当前环境启动时间小于20分钟,便不执行该程序:
|
PE资源加载执行
将shellcode作为资源文件嵌入到源代码中:
使用cs生成一个二进制文件,类型为raw:
在vs中,将其作为资源文件嵌入:
然后导入生成的.bin文件,并设置类型(名称可自定义):
源代码如下:
|
混淆
Shellcode亦或混淆加密
首先想到的是修改shellcode以逃避基于其内容的静态签名。
可以尝试最简单的“加密”——将 ROT13 密码应用于嵌入的 shellcode 的所有字节——所以0x41
变成0x54
、0xFF
变成0x0C
等等。在执行期间,shellcode 将通过0x0D
从每个字节中减去(13)的值来“解密” 。
|
举个栗子:
python加密脚本
|
源代码中解密:
|
关于xor解密,有些杀软会识别源码中的xor解密,标记为可疑:
这里可使用windos 提供的一个 API(现在也会被识别了,提供一个思路):
<font color=”red”>_InterlockedXor8:在由多线程共享的变量上执行原子按位异或 (XOR) 操作。</font>
https://docs.microsoft.com/zh-cn/cpp/intrinsics/interlockedxor-intrinsic-functions?view=msvc-160
1 2 3 4 5 |
|
Shellcode AES混淆加密
AES为分组密码,分组密码也就是把明文分成一组一组的,每组长度相等,每次加密一组数据,直到加密完整个明文。在AES标准规范中,分组长度只能是128位,也就是说,每个分组为16个字节(每个字节8位)。密钥的长度可以使用128位、192位或256位。密钥的长度不同,推荐加密轮数也不同,如下表所示:
AES 密钥长度(32位比特字) 分组长度(32位比特字) 加密轮数
AES-128 4 4 10
AES-192 6 4 12
AES-256 8 4 14
AES有多种加密方式和填充模式。
加密方式如CBC,ECB,CFB等;填充模式有NoPadding、PKCS#7 & PKCS#5、ZerosPadding等。
简单说一下填充模式:
NoPadding
顾名思义,就是不填充。缺点就是只能加密长为128bits倍数的信息,一般不会使用。
PKCS#7 & PKCS#5
缺几个字节就填几个缺的字节数。
ZerosPadding
全部填充0x00
,无论缺多少全部填充0x00
,已经是128bits倍数仍要填充。
C++没有内置的AES加密库,需要在项目中导入封装好的方法来调用,下面是github上的三个开源的AES加密解密项目:
-
谢尔盖贝尔/AES
-
kokke/tiny-AES-c
-
kkAyataka/plusaes
这里以第二个项目为例(因为star最多)。
git clone项目到本地,然后将源文件和头文件导入到项目中,结构如下:
AES加密
这里就以AES-256、CBC、NoPadding为例,写一个加密的功能:
256位就是32byte,为了保证是shellcode是32的倍数,需要在末尾添加 0。同时C++会在字符串最后加个 作为结尾符,所以我们需要加密的实际长度应该是<font color =”red”><b>sizeof(shellcode) -1</b></font>,如下:
;
|
AES解密
|
分离
顾名思义,将加载器和shellcode分开,执行加载器的时候再讲shellcode加载进内存,一般是将shellcode放到远程服务器上。
|
注入
用到的分析工具
进程监测工具:Overview – Process Hacker (sourceforge.io)
远线程注入
关于CreateRemoteThread远线程注入,实际上有三个主要步骤需要实现:
-
VirtualAllocEx() – 在外部进程的虚拟地址空间内分配内存。
-
WriteProcessMemory() – 将shellcode写入分配的内存。
-
CreateRemoteThread() – 让外部进程在另一个线程中执行shellcode。
NULL,
|
监控内存
用Visual Stdio在 VirtuallAllocEx处下断点,找到申请内存的基地址:
用Process Hacker找调试进程,并找到此地址,可以看到此处是RWX可读可写可执行的内存块,且数据都为0:
然后F10往下走一步,写入数据:
点击Re-read,数据已经写入:
再往下走,便会创建线程弹出notepad.exe
APC注入
主要API:QueueUserAPC function (processthreadsapi.h) – Win32 apps | Microsoft Docs ,将用户模式异步过程调用(APC) 对象添加到指定线程的 APC 队列中。它主要的优势在于可以绕过Sysmon。
QueueUserAPC 是一个异步过程调用。
异步:是指程序发起了调用或者请求,无需等待返回结果,程序可以接着执行,等调用或者请求结束之后再通知程序拿到结果,不影响程序的执行。
给出实例:
-
CreateProcess() 创建一个要注入的进程
-
VIrtualAllocEx() 在上一步创建的进程中,开辟一段虚拟的内存空间
-
WriteProcessMemory() 将shellcode写入
-
QueueUserAPC 注入
|
隐藏导入表函数
在PE结构中,存在一个导入表,导入表中声明了这个PE文件会载入哪些模块,同时每个模块的结构中又会指向模块中的一些函数名称,直接调用API会很容易被PE解析器发现我们的意图,尝试使用隐函数的方式隐藏导入表中的信息。
GetProcAddress | Microsoft Docs 这个API在Kernel32.dll中被导出,主要功能是从一个加载的模块中获取函数的地址。
一个最简单的shellcode加载器,可以是这样的:
用LoadPE查看导入表:
现在通过函数指针的方式做一些优化:
(
|
重新生成,再次打开:
最后
综合一下这几个小项目,得到一个能过大部分杀软静态扫描的项目:
参考链接:
https://github.com/kokke/tiny-AES-c
http://payloads.online/
https://outflank.nl/blog/2019/06/19/red-team-tactics-combining-direct-system-calls-and-srdi-to-bypass-av-edr/
原文始发于微信公众号(MicroPest):Soloz的静态恶意代码免杀