安全矩阵

 找回密码
 立即注册
搜索
查看: 1061|回复: 0

Fscan 自实现loader免杀规避

[复制链接]

26

主题

26

帖子

88

积分

注册会员

Rank: 2

积分
88
发表于 2024-4-7 18:07:38 | 显示全部楼层 |阅读模式
请您仔细阅读以下声明:
您在AtomsTeam查看信息以及使用AtomsTeam服务,表明您对以下内容的接受:
AtomsTeam提供程序(方法)可能带有攻击性,仅供安全研究与实验性教学之用。
用户将其信息做其他用途,由用户承担全部法律及连带责任,AtomsTeam不承担任何法律及连带责任。
与此同时,希望你能遵守如下的规范,因为这能帮助你走的更远:
1.不在没有直接或间接授权的情况下,对公网的任何设施进行安全检测。
2.在测试的过程中,不做任何可能导致业务遇到干扰的动作。
3.任何的测试中,不查看与下载任何敏感的数据。
4.发现漏洞后,第一时间通过企业SRC进行报告。
5.不在目标站点使用后门类工具,如需必要的测试,请获取目标网站官方授权,测试可通过替代的方案(如webshell替换为phpinfo页面等)。

0x01 基本思路

从github上拉取fscan源代码去除特征后编译成DLL文件

通过loader.exe加载DLL进行免杀

采用如上最朴素的操作后可暂时过部分杀软,但几天后便会报毒

于是进行一系列tricks

0x02 dll去除特征+混淆
首先拉取github上fscan的源代码



简单搜索可以看到存在的特征

"github.com/shadow1ng/fscan/Plugins"  "github.com/shadow1ng/fscan/common"

这里从mian.go处可以看到golang直接从github上拉取的common和plugins,所以我们更改为使用本地的包



将fscan目录下的common 和plugins文件夹移动到golang安装目录下的src目录下




然后将main.go中导入包改为如下即可

import (
"Plugins"
"common"
"fmt"
"time"
)
同时可以发现两文件夹下文件大多都含有特征

github.com/shadow1ng/fscan/common
于是通过如下python脚本分别对两个文件夹内容进行批量替换

import os
def replace_imports(dir, old, new):
  for root, dirs, files in os.walk(dir):
      for file in files:
          if file.endswith('.go'):
              file_path = os.path.join(root, file)
              with open(file_path, 'r', encoding='utf-8') as file:
                  content = file.read()

              content = content.replace(old, new)

              with open(file_path, 'w', encoding='utf-8') as file:
                  file.write(content)


directory = 'C:\\Program Files\\Go\\bin\\src\\common'#要替换的文件目录

old_word = 'github.com/shadow1ng/fscan/common'#要替换的文字
new_word = 'common'#替换后的文字

replace_imports(directory, old_word ,new_word)
替换成功后可以看到已经没有原来的特征



接下来使用golang把fscan编译成dll文件,编译时需要golang具有GCO环境,需要提前配置好,配置过程可搜索Go语言的CGO环境搭建这里不再赘述,将main.go改为如下,主要是设置原来的将原来的mian函数置空,同时建立新的导出函数newmain()



package main

import "C"
/*
#include <stdlib.h>
*/
import (
"Plugins"
"common"
"fmt"
"time"
)

//export newmain
func newmain() {

start := time.Now()
var Info common.HostInfo
common.Flag(&Info)
common.Parse(&Info)
Plugins.Scan(Info)
t := time.Now().Sub(start)
fmt.Printf("
  • 扫描结束,耗时: %s\n", t)
    }

    func main() {

    }
    在配置好环境后使用下列命令进行编译即可

    go build -ldflags "-s -w" -buildmode=c-shared -o a.dll
    同时如果配合garble食用效果更佳

    gradle build -buildmode=c-shared -o a.dll
    0x03 loader根据参数调用指定dll
    采用loader进行直接加载dll时发现只能短暂免杀,loader过一段时间就会被检测到

    于是这里采用由参数传递给loader.exe要加载的dll,通过传入的第一个参数控制要加载的dll



    0x03 loader 自实现getprocess调用dll
    这里我们需要loader调用dll中的newmain导出函数,忽略掉第一个传递进来的参数(因为第一个传递的参数是要加载的dll的名称)

    首先自实现getprocess获取到newmain函数,代码如下



    至此进行编译即可,同时要保证dll和loader的位数一致,这里go的位数设为386,c的位数x86,完整代码如下

    #include <stdio.h>
    #include <iostream>
    #include <Windows.h>
    #include <winternl.h>
    #include <shellapi.h>
    using namespace std;
    typedef void (*DllEntryPointType)( char* argv[]);
    PVOID GetPAT(HMODULE handle, LPCSTR Name) {

        PBYTE pBase = (PBYTE)handle;
        PIMAGE_DOS_HEADER pdosHader = (PIMAGE_DOS_HEADER)pBase;
        if (pdosHader->e_magic != IMAGE_DOS_SIGNATURE) {
            return NULL;
        }

        PIMAGE_NT_HEADERS pImageNtHeaders = (PIMAGE_NT_HEADERS)(pBase + pdosHader->e_lfanew);
        if (pImageNtHeaders->Signature != IMAGE_NT_SIGNATURE) {
            return NULL;
        }
        IMAGE_OPTIONAL_HEADER imageoptiopn = pImageNtHeaders->OptionalHeader;

        PIMAGE_EXPORT_DIRECTORY pimageexport = (PIMAGE_EXPORT_DIRECTORY)(pBase + imageoptiopn.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
        PDWORD FunctionNameArray = (PDWORD)(pBase + pimageexport->AddressOfNames);
        PDWORD FunctionAddressArray = (PDWORD)(pBase + pimageexport->AddressOfFunctions);

        PWORD ordinArray = (PWORD)(pBase + pimageexport->AddressOfNameOrdinals);

        for (DWORD i = 0; i < pimageexport->NumberOfFunctions; i++) {
            CHAR* pFunctionName = (CHAR*)(pBase + FunctionNameArray);
            PVOID functionAddress = (PVOID)(pBase + FunctionAddressArray[ordinArray]);

            if (strcmp(Name, pFunctionName) == 0) {
                return functionAddress;
            }
        }
        return NULL;
    }
    int main(int argc, char* argv[]) {
        int dllArgc = argc - 2;
        char** dllArgv = new char* [argc - 2];
        for (int i = 0; i < argc - 1; ++i) {
            dllArgv = argv[i + 1];
        }
        dllArgv[argc - 1] = NULL;
        HMODULE PP;
        PP = LoadLibrary(argv[1]);//加载dll


        if (PP != NULL) {;

            DllEntryPointType newmain = (DllEntryPointType)GetPAT(PP, "newmain");
            newmain(dllArgv);
            FreeLibrary(PP);// 卸载 dll

        }
        return 0;
    }
    0x04测试效果
    因为第一个参数的是dll名称的传递问题,因为对于golang的不熟练,这里有一个遗留问题没有解决 ,fscan需要再原来多添加一个无用的参数进行调用,即此处3处需要多一个无实际作用的参数,即atoms可以是随机的字符



    静态扫描效果







  • 本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?立即注册

    x
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    小黑屋|安全矩阵

    GMT+8, 2024-11-27 21:06 , Processed in 0.013924 second(s), 19 queries .

    Powered by Discuz! X4.0

    Copyright © 2001-2020, Tencent Cloud.

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