4-特征修改

一、前言

参考文章免杀与 golang 实现 - 先知社区

混淆加密 那一小节的开头我提过,AV/EDR会对木马文件进行特征扫描与匹配,为了规避静态查杀,除了混淆加密外,另一种办法就是本节将介绍的 特征修改

特征修改包括:

  1. 改函数名、改变量名、改包名、改头文件名、改代码逻辑等(常用于源码二开,最有效)

  2. 更改默认端口、修改https特征证书、修改http服务流量特征、域前置、修改密钥、修改默认的硬编码数字等(常用于具有流量通信的工具二开)

  3. 时间戳

  4. 换编译器

  5. 修改Visual Studio的的编译默认选项

  6. 资源修改和加签名

  7. 文件的熵值

在本小节中我只介绍第3点到第7点,至于第1点和第2点,只有具体二开某个工具的时候才会详细介绍。

注意:并不是说用了这些技术就可以提高免杀性,这个要看具体AV/EDR而言的,有时会你用了这些技术反而增加被杀的可能。这句话对后面各章节将要提到的各种免杀技术同样适用,所以我们的目标就是以最少的技术达到免杀效果。

二、修改时间戳

个别杀毒喜欢给最近创建的文件标记为可疑,我们可以修改时间戳提升一点点免杀性,可以使用工具修改一下程序的时间戳

项目地址:GitHub - sorabug/ChangeTimestamp: 一键修改exe、dll的编译时间、创建时间、修改时间和访问时间

该项目需要自己编译源码

  1. 点击ChangeTimestamp.sln,会自动打开VS

  1. 可以选择自己电脑上的NET Framework环境,我选择的是4.8

  1. 编译

看了一下源码,很简单,无后门,放心编译,点击生成即可

  1. 使用方法

.\ChangeTimestamp <filepath> <timestamp>

例子
.\ChangeTimestamp .\beacon.exe 2008-8-8 8:12:31

三、换编译器

大部分黑客早期都一直在使用VS默认的编译器(MSVC)去编写恶意代码导致就连正常的编译的都会报毒了,说的就是你,某数字杀毒软件,真tmd睿智,编写个helloworld程序都给我报毒。

所以就可以换一些相对冷门的编译器,如ClangIntel C++ CompilerMinGW,使用它们编译程序后报毒率大大降低。

它们的特点,详见:几种广泛使用的 C++ 编译器-CSDN博客

这里我将详细介绍ClangMinGW,当然Intel C++ Compiler也挺好的,可以与VS很好的适配,只是需要的空间更大而已。

如何给VS安装Intel C++编译器 官网Get the Intel® oneAPI Base Toolkit

安装教程如何给VS安装Intel C++编译器-腾讯云开发者社区-腾讯云

3.1 Clang

Clang 是 LLVM 项目的一部分,旨在提供一个更现代、更快的编译体验。Clang前端,前端主要负责词法和语法分析,将源代码转化为抽象语法树;LLVM后端,后端则是将已经优化的中间代码转化为针对各自平台的机器代码。clang+llvm构成一个完整的编译器,它们统称为LLVM。

项目地址:GitHub - llvm/llvm-project:LLVM 项目是模块化和可重用的编译器和工具链技术的集合。

使用VS2022可以很方便的下载Clang

  1. 点击工具-获取工具和功能

  1. 在单个组件中搜索"clang",将两个选项都都勾选,安装即可。

这里我已经安装了

  1. 切换编译器

接下来就可以愉快的编译程序了。

3.2 MinGW

MinGW 的目标之一是提供一个轻量级的开发环境,使开发者能够在 Windows 上编写和编译原生的 C/C++ 程序,而无需依赖于大型的集成开发环境(IDE)

MinGW不是很依赖IDE,要与VS适配就要配置CMake,比较麻烦。详见:Visual Studio 2022使用MinGW来编译调试C/C++程序_cmakepresets.json-CSDN博客

如果是想要用MinGW来编译和调式程序的话,可以使用Clion这个IDE。默认安装的C++编译器就是MinGW,各种环境都配好了,开箱即用。越到后面,比如说 第三章-防御规避,就越不推荐使用Clion了,因为会遇到各种各样的问题,比如说找不到库文件。

  1. 新建项目

  1. 直接运行main.cpp

  1. 在一个项目中创建多个main函数

在实际开发中,多个main函数的程序主要用于测试和调式目的。为了在一个项目中创建多个main函数,有两种办法可以达到这个目的,两种方法都需要利用CMake

(1)修改入口点,可以创建多个main文件

①右键项目,点击新建一个文件,命名为 main1.cpp

②配置CMake中的入口点,将main.cpp修改为main1.cpp

③运行

(2)宏定义,一个main文件中包含多个main函数

①编写宏定义

#include <iostream>  
  
#ifdef MAIN  
int main()  
{  
    std::cout << "Hello, World! main" << std::endl;    return 0;  
}  
#endif  
  
#ifdef MAIN1  
int main()  
{  
    std::cout << "Hello, World! main1" << std::endl;    return 0;  
}  
#endif

②配置CMake

cmake_minimum_required(VERSION 3.28)  
project(Test)  
  
set(CMAKE_CXX_STANDARD 17)  
  
add_executable(Test main.cpp)  
  
add_definitions(-DMAIN1)

其中-D表示这是一个宏定义,MAIN1表示宏定义的名称

③运行

3.3 思路

我提供一些思路仅供参考使用。

  1. 如果不想安装CLion,但是安装了Clang和MinGW,可以在VS上用MSVC来调式(毕竟是VS自带的,适配性更好),然后调式完了再换Clang和MinGW进行编译即可。

  2. 如果只安装CLion,不想安装VS也可以,毕竟CLion也非常强大而且跨平台,再搭配 在一个项目中创建多个main函数 就可以在一个项目方便创建多个main函数,而不覆盖之前编写的代码,也省去了创建多个项目。关于这一点还是Golang好啊,一个项目创建多个目录,每个目录可以有一个main函数,这个是Go语言特性决定的。

四、修改Visual Studio的编译选项设置

有一定的免杀效果,这些选项看个人选择来设置

/MD 类型表示运行时库不集成,生成的文件小,程序运行时动态加载对应的 dll/MT 类型表示运行时库集成,生成的文件较大,在链接时将 C/C++ 运行时库集成到程序中。

MTMD 的选择,详见:Visual Studio中MD与MT的区别及运行库类型选择 - 知乎

五、资源修改和加签名

卡巴斯基和某数字杀毒软件具有启发式查杀的功能,对于较小的程序很容易检测出异常。所谓启发式查杀其实是利用病毒程序与正常程序启动或运行时,所做的行为不同来判断是否为病毒程序,多数结合人工智能手段进行判断,一般误判可能性较大

某数字杀毒软件常见的QVM编号

HEUR/Malware.QVM06.Gen   一般情况下加数字签名可过
HEUR/Malware.QVM07.Gen   一般情况下换资源
HEUR/Malware.QVM13.Gen   加壳了
HEUR/Malware.QVM19.Gen   杀壳 (lzz221089提供 )
HEUR/Malware.QVM20.Gen   改变了入口点
HEUR/Malware.QVM27.Gen   输入表
HEUR/Malware.QVM18.Gen   加花
HEUR/Malware.QVM05.Gen  加资源,改入口点

QVM07加资源一般加到2M会报QVM06
再加数字签名,然后再慢慢减资源,这个方法对大部分木马有效果。
QVM06 加数字签名
QVM12杀壳
QVM13杀壳
QVM27杀输入表
QVM19 加aspack
QVM20就加大体积/加aspack压缩 

5.1 Ting_More

使用Ting_More工具进行全自动添加签名和添加资源 工具下载:Ting_More.zip_免费高速下载|百度网盘-分享无限制

.\Ting_More.exe -t .\beacon.exe

其结果输出到了output文件夹中

5.2 ResourceHacker

如果想自己替换资源,可以使用ResourceHacker工具

官网地址:Resource Hacker

如果你下载了Ting_More这个工具,它就自带了ResourceHacker这个工具。使用方法如下

选择一个.ico文件,然后替换

保存

5.3 使用VS添加资源

选择一个.ico文件即可

5.4 Signtool-GUI

如果想自己添加数字签名可以使用微软的SignTool工具,但是SignTool操作太过复杂,可以使用大佬开发的Signtool-GUI,图形化,方便操作。

工具下载: Signtool-GUI Ver1.0 【数字签名制作及签名工具】 - 吾爱破解 - 52pojie.cn

  1. 点击生成证书

  1. 给目标程序签名

没签成功,不懂为什么,算了换命令行来签吧。

signtool.exe sign /f xxx.pfx /t http://timestamp.digicert.com yyy.exe

例子
.\signtool.exe sign /f .\Cert_1735735852.pfx /t http://timestamp.digicert.com .\beacon.exe

5.5 SigThief

有些反病毒供应商优先考虑某些证书颁发机构,而不检查签名是否实际有效

工具与使用说明: GitHub - secretsquirrel/SigThief:窃取签名并一次制作一个无效签名

简单示例,更多示例详见上面给的链接

python .\sigthief.py -i C:\Windows\explorer.exe -t .\beacon.exe -o beacon_sig.exe

六、文件的熵值

熵也被用于检测PE文件病毒, 一般一些合法软件的熵值在 4.8 - 7.2 之间。使用混淆加密和加壳会增加程序的熵值

用Golang编译下面的代码,得到计算文件的熵值的工具。

package main

import (
    "debug/pe"
    "flag"
    "fmt"
    "io"
    "log"
    "math"
    "os"
)

func calculateEntropy(buffer []byte) float64 {
    l := float64(len(buffer))
    m := map[byte]float64{}
    for _, b := range buffer {
        m[b]++
    }

    var hm float64
    for _, c := range m {
        hm += c * math.Log2(c)
    }

    return math.Log2(l) - hm/l
}

func calculateFileEntropy(filename string) {
    fileBuffer, err := os.ReadFile(filename)
    if err != nil {
        log.Fatal(err)
    }

    fileEntropy := calculateEntropy(fileBuffer)

    fmt.Printf("Entropy of \033[36m%s \033[0m as a whole file is: ", filename)

    if fileEntropy >= 5.6 && fileEntropy <= 6.8 {
        fmt.Printf("\033[32m%f\033[0m\n", fileEntropy) // Green - legitimate
    } else if fileEntropy > 7.2 && fileEntropy <= 8.0 {
        fmt.Printf("\033[31m%f\033[0m\n", fileEntropy) // Red - malicious
    } else {
        fmt.Printf("%f\n", fileEntropy)
    }
}

func calculatePESectionEntropy(filename string) {
    file, err := os.Open(filename)
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()

    filePE, err := pe.NewFile(file)
    if err != nil {
        log.Fatal(err)
    }

    calculateFileEntropy(filename)

    fmt.Printf("[i] Parsing \033[36m%s\033[0m 's PE Section Headers ...\n", filename)

    colorIndex := 0
    colors := []string{"\033[33m", "\033[32m", "\033[36m", "\033[35m", "\033[34m"}

    for _, section := range filePE.Sections {
        sectionName := string(section.Name[:])
        sectionSize := section.Size

        switch sectionName {
        case ".text", ".data", ".rdata", ".pdata", ".xdata", ".CRT", ".rsrc", ".reloc":
            sectionContent := make([]byte, sectionSize)
            _, err := file.Seek(int64(section.Offset), 0)
            if err != nil {
                log.Fatal(err)
            }
            _, err = io.ReadFull(file, sectionContent)
            if err != nil {
                log.Fatal(err)
            }

            sectionEntropy := calculateEntropy(sectionContent)

            color := colors[colorIndex]
            colorIndex = (colorIndex + 1) % len(colors)

            fmt.Printf("\t>>> %s%s%s Scored Entropy Of Value: %f\033[0m\n", color, "\""+sectionName+"\"", color, sectionEntropy)
        }
    }

}

func main() {
    filename := flag.String("file", "", "File to calculate entropy")
    flag.Parse()

    if *filename == "" {
        flag.Usage()
        return
    }

    file, err := os.Open(*filename)
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()

    _, err = pe.NewFile(file)
    if err == nil {
        calculatePESectionEntropy(*filename)
    } else {
        calculateFileEntropy(*filename)
    }
}

正常文件

原始无阶段beacon.exe

避免提高熵值的方法

  1. 加.ico图标

  2. 加签名

  3. 加版本信息

  4. shellcode进行base64编码可以降熵,但是用AES、RSA、XOR加密后shellcode的熵值会增加。所以shellcode加密后再base64编码熵值会低一些

  5. 后面会介绍 保护 这一节,里面的介绍的保护手段会提高熵值,所以少用一些保护选项避免提高熵值,这也是我一直强调以最少的技术达到免杀效果的原因。