文章总结: 本文详细介绍了使用garble工具对fscan进行源码修改与混淆以实现免杀的方法。主要步骤包括配置Go环境、修改模块路径、修复Cassandra.go结构体冲突、去除flag用法提示及国际化字符串特征,最终编译生成可绕过火绒查杀的exe文件,并建议结合VMP或UPX加壳增强效果。 综合评分: 88 文章分类: 免杀,内网渗透,安全工具
做个”脚本小子”–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 {
maxConcurrent := Common.ModuleThreadNum if maxConcurrent <= 0 { maxConcurrent = 10 } if maxConcurrent > len(credentials) { maxConcurrent = len(credentials) }
var wg sync.WaitGroup resultChan := make(chan *CassandraScanResult, 1) workChan := make(chan CassandraCredential, maxConcurrent)
scanCtx, scanCancel := context.WithCancel(ctx) defer scanCancel()
for i := 0; i < maxConcurrent; i++ { wg.Add(1) go func() { defer wg.Done() for cred := range workChan { select { case <-scanCtx.Done(): return default: result := tryCassandraCredential( scanCtx, info, cred, timeoutSeconds, maxRetries, ) if result.Success { select { case resultChan <- result: scanCancel() default: } return } } } }() }
go func() { for i, cred := range credentials { select { case <-scanCtx.Done(): break default: Common.LogDebug(fmt.Sprintf( "[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password, )) workChan <- cred } } close(workChan) }()
go func() { wg.Wait() close(resultChan) }()
select { case result := <-resultChan: return result case <-ctx.Done(): return nil }}
// =======================// 核心:连接与验证(已修复 garble 问题)// =======================
func tryCassandraCredential( ctx context.Context, info *Common.HostInfo, credential CassandraCredential, timeoutSeconds int64, maxRetries int,) *CassandraScanResult {
var lastErr error
for retry := 0; retry < maxRetries; retry++ { select { case <-ctx.Done(): return &CassandraScanResult{ Success: false, Error: ctx.Err(), Credential: credential, } default: connCtx, cancel := context.WithTimeout( ctx, time.Duration(timeoutSeconds)*time.Second, )
success, err := CassandraConn( connCtx, info, credential.Username, credential.Password, ) cancel()
if success { return &CassandraScanResult{ Success: true, IsAnonymous: credential.Username == "" && credential.Password == "", Credential: credential, } }
lastErr = err } }
return &CassandraScanResult{ Success: false, Error: lastErr, Credential: credential, }}
func CassandraConn( ctx context.Context, info *Common.HostInfo, user, pass string,) (bool, error) {
cluster := gocql.NewCluster(info.Host) cluster.Port, _ = strconv.Atoi(info.Ports) cluster.Timeout = time.Duration(Common.Timeout) * time.Second cluster.ConnectTimeout = cluster.Timeout cluster.ProtoVersion = 4 cluster.Consistency = gocql.One
if user != "" || pass != "" { cluster.Authenticator = gocql.PasswordAuthenticator{ Username: user, Password: pass, } }
sessionChan := make(chan cassandraSessionResult, 1)
go func() { session, err := cluster.CreateSession() select { case <-ctx.Done(): if session != nil { session.Close() } case sessionChan <- cassandraSessionResult{session, err}: } }()
var session *gocql.Session select { case result := <-sessionChan: if result.err != nil { return false, result.err } session = result.session case <-ctx.Done(): return false, ctx.Err() }
defer session.Close()
queryChan := make(chan cassandraQueryResult, 1)
go func() { var tmp string err := session.Query( "SELECT peer FROM system.peers", ).WithContext(ctx).Scan(&tmp)
if err != nil { err = session.Query( "SELECT now() FROM system.local", ).WithContext(ctx).Scan(&tmp) }
select { case <-ctx.Done(): case queryChan <- cassandraQueryResult{err == nil, err}: } }()
select { case result := <-queryChan: return result.success, result.err case <-ctx.Done(): return false, ctx.Err() }}
// =======================// 保存结果// =======================
func saveCassandraSuccess( info *Common.HostInfo, target string, credential CassandraCredential, isAnonymous bool,) {
Common.LogSuccess(fmt.Sprintf( "Cassandra %s 成功 (%s)", target, map[bool]string{true: "匿名", false: "弱口令"}[isAnonymous], ))
Common.SaveResult(&Common.ScanResult{ Time: time.Now(), Type: Common.VULN, Target: info.Host, Status: "vulnerable", })}
尝试编译
现在将fscan文件夹丢到linux上面,将grable移动到fscan目录下
./garble -tiny -literals -seed=random build -ldflags="-w -s" main.go
没有报错并且main文件可以运行,那就证明我们修改的内容没有影响到编译
开始进一步修改,因为我们的目的是生成一个免杀的exe文件
去提示特征
定位
先删掉Parse/go里面的flag.Usage()和”flag”
修改i18n.go
将LangZH,LangEN,LangJA,LangRU行全替换为空,注意留下图中框出的两行,直接点那个XX就可以排除
注意检查所有函数里的内容是否为空没有
往往需要再手动删除这些
再次尝试编译
确认我们的修改不影响编译
编译exe版本
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 \./garble -tiny -literals -seed=random \build -ldflags="-w -s" -o abc.exe main.go
现在火绒已经看不出风险了,那我们可以进一步加强的,
如使用vmp 加壳或者upx加壳
[TIPS]也可以使用garble进一步混淆他,当然记得看看能不能用,混淆太过不一定可以使用的
没有做到完美,可以多试试其他混淆,看看其他参数的效果
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:kingman安全 lawliet lawliet《做个”脚本小子”–fscan.exe的免杀篇》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论