吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4613|回复: 39
收起左侧

[分享] 通杀检测基于白文件patch黑代码的免杀技术的后门

  [复制链接]
huoji120 发表于 2024-8-3 20:07
使用论坛附件上传样本压缩包时必须使用压缩密码保护,压缩密码:52pojie,否则会导致论坛被杀毒软件等误报,论坛有权随时删除相关附件和帖子!
病毒分析分区附件样本、网址谨慎下载点击,可能对计算机产生破坏,仅供安全人员在法律允许范围内研究,禁止非法用途!
禁止求非法渗透测试、非法网络攻击、获取隐私等违法内容,即使对方是非法内容,也应向警方求助!
几年没发帖了,要是帖子有问题请版主帮忙改改,实在是不会用discuz了,markdown的还不支持图片上传..太蛋疼了
前言
patch免杀技术的木马! 是不是很高级,其实就是10年前的一个kali上的工具,后门工厂的二开!!!!那会夹的shellcode是metasploit. 具体自己谷歌搜索 kali 后门工厂
这玩意号称免杀一切, VT全绿,那么真的没有办法解决吗?让我们从头开始
杀毒软件困境
2020年这种木马首次被key08公开的时候我就写了一句,杀毒软件的所谓的机器学习/深度学习模型,完全失去作用了,杀毒软件从18年开始疯狂流行的NGAV概念也已经到头了,原因很简单,这种东西,AI完全无法识别,除了数据特征问题,还有非常多的问题,这需要了解杀毒软件工作原理。
具体传送门请看:
[2021]杀毒软件查杀技术
https://key08.com/index.php/2021/09/27/1349.html
[2023]现代AI杀毒引擎原理+部分代码
https://key08.com/index.php/2023/07/19/1764.html
总之,此类木马让杀毒软件陷入了困境与绝境。但是不着急,杀毒软件也在进化,而此类白patch黑也仅仅局限于找了一个漏洞,仅此而已

致命缺陷工作原理
此类技术工作原理基本上跟4年前相同,找一个比较大的白程序,然后打补丁,换成自己的shellcode。如先知论坛这个大哥的帖子:
image.png
记一次Patch exe 文件实现的静态免杀
https://xz.aliyun.com/t/15096
重点来了,shellcode要怎么访问API列表?

答案是GS寄存器,通过GS寄存器访问PEB访问到LDR![2022]填鸭式shellcode编写教程 (一)
https://key08.com/index.php/2022/09/07/1551.html
[2020]GS寄存器/fs寄存器
https://key08.com/index.php/2020/12/13/810.html
检测方案
聪明的你已经想到,扫描代码中的GS访问! 如
mov rax,gs:[0x30]
很好,这已经成功一半,还有一半是,我们不能直接这样做静态扫描,因为GS寄存器的长度与指令是不固定的,此外直接检测GS也会造成很大的误报,比如某些VEH和SEH或者获得栈大小/pid/tid的函数就是会访问gs的,误报很大,所以我们需要做模式匹配
开始检测
介绍
我们最终目的是检测ldr的访问,甚至是可以更进一步,检测API调用也不是问题。这个留给后人搜集函数列表由于我不想跟IDA一样追踪控制流,我就做了一个比较简单的基于capstone的统计int3和ret的”乞丐版”函数检测
[C++] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
auto buildFunctionMaps(pe64* pe) -> std::vector<std::shared_ptr<_functionDetail>> {
    std::vector<std::shared_ptr<_functionDetail>> functionList;
    cs_insn* insn = nullptr;
    size_t disasmCount = 0;
 
    do {
 
        auto textSection = pe->get_section(".text");
        const auto codeAddressInMemory = reinterpret_cast<uint64_t>(
            pe->get_buffer()->data() + textSection->VirtualAddress);
 
        disasmCount =
            cs_disasm(capstone_handle,
                reinterpret_cast<const uint8_t*>(codeAddressInMemory),
                textSection->Misc.VirtualSize, 0, 0, &insn);
        if (disasmCount == 0) {
            break;
        }
        std::vector<std::string> backTrackCodeList;
        bool isEnterFunction = false;
        bool isFirst = true;
        size_t currentFunctionSize = 0;
        uint64_t currentFuncAddress = 0;
        size_t offset = 0;
 
        for (size_t index = 0; index < disasmCount; index++) {
            const auto code = insn[index];
            const auto codeMnemonic = std::string(code.mnemonic);
            const auto opCode = std::string(code.op_str);
            if (backTrackCodeList.size() > 3) {
                backTrackCodeList.erase(backTrackCodeList.begin());
            }
            backTrackCodeList.push_back(codeMnemonic);
            if ((codeMnemonic != "int3" && codeMnemonic != "nop") &&
                ((backTrackCodeList.size() > 2) &&
                    (backTrackCodeList[0] == "int3" ||
                        backTrackCodeList[0] == "nop") &&
                    (backTrackCodeList[1] == "int3" ||
                        backTrackCodeList[1] == "nop") &&
                    (backTrackCodeList[2] == "int3" ||
                        backTrackCodeList[2] == "nop")) &&
                isEnterFunction == false) {
                // printf("进入函数 开始地址: %llx\n", codeAddressInMemory + offset);
                 // printf("address: 0x%llx | size: %d code: %s %s \n",
                 //         code.address, code.size, code.mnemonic, code.op_str);
                currentFuncAddress = codeAddressInMemory + offset;
                isEnterFunction = true;
                backTrackCodeList.clear();
            }
            else if ((codeMnemonic == "int3" || codeMnemonic == "nop") &&
                ((backTrackCodeList.size() > 2) &&
                    (backTrackCodeList[0] != "int3" &&
                        backTrackCodeList[0] != "nop")) &&
                isEnterFunction) {
                //printf("退出函数 结束地址: %llx 当前大小: %d \n", codeAddressInMemory + code.address, currentFuncAddress - codeAddressInMemory);
 
                auto func = _functionDetail{ .start_address = currentFuncAddress,
                                .end_address = codeAddressInMemory + code.address,
                                .size = (codeAddressInMemory + code.address) - currentFuncAddress };
                functionList.push_back(std::make_shared<_functionDetail>(func));
                //printf("退出函数 结束地址: %llx 当前大小: %d \n", func.end_address, func.size);
 
                isFirst = false;
                isEnterFunction = false;
                currentFunctionSize = 0;
                currentFuncAddress = 0;
            }
            currentFunctionSize += code.size;
            offset += code.size;
        }
        if (isFirst) {
            functionList.push_back(
                std::make_shared<_functionDetail>(_functionDetail{
                    .start_address = static_cast<uint64_t>(codeAddressInMemory),
                    .end_address = static_cast<uint64_t>(
                        codeAddressInMemory + textSection->Misc.VirtualSize),
                    .size = textSection->Misc.VirtualSize }));
        }
    } while (false);
    cs_free(insn, disasmCount);
    return functionList;
}

符号执行
有了函数列表,我们就可以做符号执行,寻找出哪些函数里面有GS寄存器被访问的影子
[C++] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
super_huoji_tracker::super_huoji_tracker(uint64_t startAddr, size_t sizeOfCode, uint64_t current_function_rva)
{
    if (cs_open(CS_ARCH_X86, CS_MODE_64, &capstone_handle_i) != CS_ERR_OK) {
        __debugbreak();
    }
    cs_option(capstone_handle_i, CS_OPT_DETAIL, CS_OPT_ON);
    cs_option(capstone_handle_i, CS_OPT_SKIPDATA, CS_OPT_ON);
 
    do
    {
        disasmCount =
            cs_disasm(capstone_handle_i,
                reinterpret_cast<const uint8_t*>(startAddr),
                sizeOfCode, 0, 0, &insn);
        if (disasmCount == 0) {
            break;
        }
        for (size_t index = 0; index < disasmCount; index++) {
            const auto code = insn[index];
            this->ins_list.push_back(std::make_shared<cs_insn>(code));
        }
    } while (false);
    this->current_function_rva = current_function_rva;
}
别在意大小写问题,这段代码是我从我的VMP还原项目抠出来的:
[C++] 纯文本查看 复制代码
1
2
3
4
5
6
7
8
9
auto super_huoji_tracker::get_next_ins() -> std::shared_ptr<cs_insn> {
    if (this->ins_ip >= this->ins_list.size()) {
        return nullptr;
    }
    const auto result = this->ins_list[this->ins_ip];
    this->ins_ip++;
    this->ins_ip_address = result->address;
    return result;
}

[2023]VMP还原day3:模式匹配寻找VIP/VSP和Flow Entry
https://key08.com/index.php/2023/02/20/1706.html
模式匹配
有了符号执行后,我们只需要做到找出哪些寄存器访问了gs,并且这些寄存器是不是访问了peb,并且访问了peb后是不是访问了ldr,还可以更进一步,但是现在就够了,基本上就能确定是恶意的shellcode在做坏事了
[C++] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
auto super_huoji_tracker::track_gs_access() -> void
{
    //const auto matched_gs_access = match_code([&](cs_insn* instruction) {}, [&](cs_insn* instruction) {}, {}, {});
    const auto isGsRegAccess = match_code([&](cs_insn* instruction) {
        //@todo: other access gs reg code...
        if (instruction->id != X86_INS_MOV && instruction->id != X86_INS_MOVZX) {
            return false;
        }
 
        if (instruction->detail->x86.operands[1].mem.segment != X86_REG_GS) {
            return false;
        }
        /*
            gs:[0x30] TEB
            gs:[0x40] Pid
            gs:[0x48] Tid
            gs:[0x60] PEB
            gs:[0x68] LastError
        */
        if (instruction->detail->x86.operands[1].mem.disp != 0x30 && instruction->detail->x86.operands[1].mem.disp != 0x60) {
            return false;
        }
        return true;
    }, [&](cs_insn* instruction) {}, {}, {});
    if (isGsRegAccess == false) {
        return;
    }
    const auto currentIns = this->ins_list[this->ins_ip - 1].get();
    const auto gsAccessReg = currentIns->detail->x86.operands[0].reg;
    x86_reg ldrAccessReg;
    bool isPebAccess = false;
    if (currentIns->detail->x86.operands[1].mem.disp == 0x30) {
        //从TEB访问的PEB->ldr
        isPebAccess = match_code([&](cs_insn* instruction) {
            //@todo: other access gs reg code...
            if (instruction->id != X86_INS_MOV && instruction->id != X86_INS_MOVZX) {
                return false;
            }
 
            if (instruction->detail->x86.operands[1].mem.base != gsAccessReg) {
                return false;
            }
            if (instruction->detail->x86.operands[1].mem.disp != 0x60) {
                return false;
            }
            ldrAccessReg = instruction->detail->x86.operands[0].reg;
            return true;
        }, [&](cs_insn* instruction) {}, {}, {});
    }
    else {
        //直接访问的GS->peb
        isPebAccess = true;
        ldrAccessReg = gsAccessReg;
    }
    if (isPebAccess == false){
        return;
    }
    //访问了PEB的ldr
    const auto isPebLdrAccess = match_code([&](cs_insn* instruction) {
        //@todo: other access gs reg code...
        if (instruction->id != X86_INS_MOV && instruction->id != X86_INS_MOVZX) {
            return false;
        }
        if (instruction->detail->x86.operands[1].mem.base != ldrAccessReg) {
            return false;
        }
        if (instruction->detail->x86.operands[1].mem.disp != 0x18) {
            return false;
        }
        return true;
        }, [&](cs_insn* instruction) {}, {}, {});
    if (isPebLdrAccess == false) {
        return;
    }
    printf("mawlare function detected at address: 0x%llx by gs access peb->ldr \n", this->current_function_rva);
    this->print_asm(currentIns);
}
熵分析没错,当有了函数后,我们可以把代码熵的函数颗粒度精细到函数,假定大于0.7的函数就是混淆的shellcode:
[C++] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
auto calculateEntropy(void* data, size_t size) -> double {
    if (data == nullptr || size == 0) {
        return 0.0;
    }
 
    unsigned char* byteData = static_cast<unsigned char*>(data);
    std::unordered_map<unsigned char, size_t> frequencyMap;
 
    // 计算每个字节的频率
    for (size_t i = 0; i < size; ++i) {
        frequencyMap[byteData[i]]++;
    }
 
    double entropy = 0.0;
    for (const auto& pair : frequencyMap) {
        double probability = static_cast<double>(pair.second) / size;
        entropy -= probability * std::log2(probability);
    }
 
    return entropy;
}

效果
模式匹配检测:
121424491.png
检测:
image.png
52破解上的大呼不可战胜的马:

三年了,还是VT全绿,它到底凭什么?
https://www.52pojie.cn/thread-1900852-1-1.html


总结
进一步
这些都并不是最好的方法,最好的方法是搞fuzz常见的语义分支分析,要用到LLVM做IR 有点麻烦 懒得写了 反正写POC。如果分支覆盖率不足10% 大概率就是这种白夹黑(其实IDA写插件应该就能追出来)
edr的重要性
EDR从来就不会遇到这个问题,因为EDR看文件视角都是一样的不可信文件。而杀毒软件则会完全拉闸。2024年了,该使用EDR了,杀毒软件已经有诸多案例表明,没有办法解决高级威胁(指APT/黑产灰产)
源码
一如既往的:
https://github.com/huoji120/white_patch_detect

免费评分

参与人数 16吾爱币 +19 热心值 +13 收起 理由
allspark + 1 + 1 用心讨论,共获提升!
海水很咸 + 1 + 1 谢谢@Thanks!
peiki + 1 + 1 热心回复!
hizlez + 1 热心回复!
ailisiyyds + 1 + 1 谢谢@Thanks!
Carinx + 1 用心讨论,共获提升!
1462613925 + 1 + 1 我很赞同!
danshiyuan + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
a33463 + 1 用心讨论,共获提升!
moyinya + 1 + 1 用心讨论,共获提升!
HZ62091 + 1 + 1 谢谢@Thanks!
x410823 + 1 + 1 谢谢@Thanks!
Issacclark1 + 1 谢谢@Thanks!
myweb1996 + 3 + 1 我很赞同!
alexsanda + 1 + 1 谢谢@Thanks!
yAYa + 3 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Hmily 发表于 2024-8-14 16:19
如何实现发帖时图文混排效果:
https://www.52pojie.cn/misc.php? ... &id=29&messageid=36
我帮你把传上去的图贴进去了,但好像还缺少,请自行按上面的方法上传插入吧,markdown也是同样的方法插入,不局限于discuz的代码。
taigan 发表于 2024-8-3 20:53
hcl788 发表于 2024-8-3 22:33
winddu 发表于 2024-8-4 01:32
向大佬学习~
romobin 发表于 2024-8-4 02:49
好帖,  学习了
TheShe1314502 发表于 2024-8-4 02:51
学习学习
你好,再见 发表于 2024-8-4 05:20
原来是做鸭鸭的大佬,厉害厉害
exiaowe 发表于 2024-8-4 09:53
搜下了,漏洞攻击+1
Lty20000423 发表于 2024-8-4 09:58
不明觉厉,感谢分享
xueyinglantian 发表于 2024-8-4 11:22
看着就很厉害,谢谢up主。谢谢分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-3-26 18:44

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表