freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Fscan源码结构学习和混淆免杀
2024-10-29 19:59:19
所属地 海外

最近过了一遍go语言的语法,在尝试写工具时感觉还是欠缺很多东西,所以打算看看其他开源项目,首当其冲一定是Fscan(若有错误,恳请指正)

一 入口和http初始化

在main.go中启动入口

1730126066_671fa0f272880bdd68e50.png!small?1730126066893

common.Flag为标准命令行解析

1730126131_671fa133dd021d25b2f13.png!small?1730126132339

common.Parse进行对参数的解析

1730126237_671fa19def818733e0503.png!small?1730126238257


  1. ParseUser():解析用户名。

    • 如果Username变量不为空,将其按逗号分割成用户名列表。
    • 如果Userfile变量不为空,读取文件中的用户名并添加到用户名列表中。
    • 去除用户名列表中的重复项,并将结果赋值给Userdict字典的每个键。
  2. ParsePass(Info *HostInfo):解析密码和哈希值。

    • 如果Password变量不为空,将其按逗号分割成密码列表,并去重。
    • 如果Passfile变量不为空,读取文件中的密码并添加到密码列表中。
    • 如果Hashfile变量不为空,读取文件中的哈希值,并检查长度是否为32,如果不是则打印错误信息。
    • 如果URL变量不为空,将其按逗号分割成URL列表,并去重。
    • 如果UrlFile变量不为空,读取文件中的URL并添加到URL列表中。
    • 如果PortFile变量不为空,读取文件中的端口并合并到Ports变量中。
  3. ParseInput(Info *HostInfo):解析输入参数。

    • 检查是否提供了主机信息,如果没有则打印错误信息并退出。
    • 设置暴力破解线程数的默认值。
    • 根据TmpSave变量的值设置是否保存结果。
    • 设置默认端口和添加额外端口。
    • 添加额外的用户名和密码,并去重。
    • 处理Socks5代理和普通代理的设置,包括格式检查和错误处理。
    • 检查哈希值的长度是否正确,并进行十六进制解码。
  4. ParseScantype(Info *HostInfo):解析扫描类型。

    • 根据Scantype变量的值,设置对应的端口。
    • 如果Scantype不是"all",并且Ports是默认值,根据扫描类型选择相应的端口。
    • 打印扫描类型和对应的端口信息。

然后就是 Plugins.Scan() 方法,首先对host进行解析Ip,然后走到lib.Inithttp

1730127277_671fa5ada4c300b199197.png!small?1730127278016


这段代码的主要作用是初始化两个HTTP客户端:Client和ClientNoRedirect。Client是一个普通的HTTP客户端,而ClientNoRedirect在发送请求时不会跟随重定向。这两个客户端都使用了自定义的http.Transport配置,包括拨号器、最大连接数、TLS配置等


func Inithttp() {
	//common.Proxy = "http://127.0.0.1:8080"
	if common.PocNum == 0 {
		common.PocNum = 20
	}
	if common.WebTimeout == 0 {
		common.WebTimeout = 5
	}
	err := InitHttpClient(common.PocNum, common.Proxy, time.Duration(common.WebTimeout)*time.Second)
	if err != nil {
		panic(err)
	}
}

func InitHttpClient(ThreadsNum int, DownProxy string, Timeout time.Duration) error {
	//定义了一个用于建立网络连接的函数
	type DialContext = func(ctx context.Context, network, addr string) (net.Conn, error)

	//net.Dialer结构体实例dialer,用于配置网络连接的参数
	dialer := &net.Dialer{
		Timeout:   dialTimout,
		KeepAlive: keepAlive,
	}

	//http.Transport结构体实例tr,用于配置HTTP传输层
	tr := &http.Transport{
		DialContext:         dialer.DialContext,
		MaxConnsPerHost:     5,                                                                   //每个主机的最大连接数为5
		MaxIdleConns:        0,                                                                   //最大空闲连接数为0
		MaxIdleConnsPerHost: ThreadsNum * 2,                                                      //每个主机的最大空闲连接数为ThreadsNum的两倍
		IdleConnTimeout:     keepAlive,                                                           //空闲连接的超时时间
		TLSClientConfig:     &tls.Config{MinVersion: tls.VersionTLS10, InsecureSkipVerify: true}, //设置TLS客户端配置,最低TLS版本为1.0,并且跳过证书验证
		TLSHandshakeTimeout: 5 * time.Second,                                                     //TLS握手超时时间为5秒
		DisableKeepAlives:   false,                                                               //不禁用连接保持
	}

	//检查是否存在Socks5代理
	if common.Socks5Proxy != "" {
		dialSocksProxy, err := common.Socks5Dailer(dialer) //创建一个Socks5代理的拨号器
		if err != nil {
			return err
		}
		if contextDialer, ok := dialSocksProxy.(proxy.ContextDialer); ok {
			tr.DialContext = contextDialer.DialContext
		} else {
			return errors.New("Failed type assertion to DialContext")
		}
	} else if DownProxy != "" {
		if DownProxy == "1" { //代理为 1 ,使用HTTP 代理
			DownProxy = "http://127.0.0.1:8080"
		} else if DownProxy == "2" { //代理为 2 ,使用socks5 代理
			DownProxy = "socks5://127.0.0.1:1080"
		} else if !strings.Contains(DownProxy, "://") {
			DownProxy = "http://127.0.0.1:" + DownProxy
		}
		if !strings.HasPrefix(DownProxy, "socks") && !strings.HasPrefix(DownProxy, "http") {
			return errors.New("no support this proxy")
		}
		u, err := url.Parse(DownProxy)
		if err != nil {
			return err
		}
		tr.Proxy = http.ProxyURL(u) //设置tr的代理为解析后的URL
	}

	//创建一个http.Client实例Client
	Client = &http.Client{
		Transport: tr,
		Timeout:   Timeout,
	}
	//创建一个http.Client实例ClientNoRedirect,用于处理不跟随重定向的请求
	ClientNoRedirect = &http.Client{
		Transport:     tr,
		Timeout:       Timeout,
		CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, //设置ClientNoRedirect的重定向检查函数,使其不跟随重定向
	}
	return nil
}

二 主机和端口探测

继续向下看

如果Noping是false (默认为false)或者 Scantype 为 icmp时进入CheckLive中使用icmp或ping进行存活探测

1730127361_671fa601c094d84798cb3.png!small?1730127362587

CheckLive函数,首先创建一个channel,用来接受存活IP

func CheckLive(hostslist []string, Ping bool) []string {
	//chanHosts是一个缓冲通道,用于传递IP地址给后台goroutine
	chanHosts := make(chan string, len(hostslist))
	go func() {
		for ip := range chanHosts {
			//从chanHosts通道中读取IP地址,并检查每个IP地址是否已经存在于ExistHosts映射中。如果不存在,并且该IP地址也在hostslist中,那么将其标记为存活
			if _, ok := ExistHosts[ip]; !ok && IsContain(hostslist, ip) {
				ExistHosts[ip] = struct{}{}
				if common.Silent == false {
					if Ping == false {
						fmt.Printf("(icmp) Target %-15s is alive\n", ip)
					} else {
						fmt.Printf("(ping) Target %-15s is alive\n", ip)
					}
				}
				//存活的IP地址添加到AliveHosts列表中,并调用livewg.Done()
				AliveHosts = append(AliveHosts, ip)
			}
			livewg.Done()
		}
	}()

然后判断ICMP还是Ping的方式来检测存活

if Ping == true {
		//使用ping探测
		RunPing(hostslist, chanHosts)
	} else {
		//优先尝试监听本地icmp,批量探测
		conn, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
		if err == nil {
			RunIcmp1(hostslist, conn, chanHosts)
		} else {
			common.LogError(err)
			//尝试无监听icmp探测
			fmt.Println("trying RunIcmp2")
			conn, err := net.DialTimeout("ip4:icmp", "127.0.0.1", 3*time.Second)
			defer func() {
				if conn != nil {
					conn.Close()
				}
			}()
			if err == nil {
				RunIcmp2(hostslist, chanHosts)
			} else {
				common.LogError(err)
				//使用ping探测
				fmt.Println("The current user permissions unable to send icmp packets")
				fmt.Println("start ping")
				RunPing(hostslist, chanHosts)
			}
		}
	}

Ping探测

直接通过系统命令进行检测

1730131284_671fb554cd19a1886c2be.png!small?1730131285299

监听本地Icmp方式探测

通过makemsg方法构造了一个Icmp包,如果返回不为空,则存活

func RunIcmp1(hostslist []string, conn *icmp.PacketConn, chanHosts chan string) {
	endflag := false
	go func() {
		for {
			if endflag == true {
				return
			}
			msg := make([]byte, 100)
			_, sourceIP, _ := conn.ReadFrom(msg)
			if sourceIP != nil {
				livewg.Add(1)
				chanHosts <- sourceIP.String()
			}
		}
	}()

	for _, host := range hostslist {
		dst, _ := net.ResolveIPAddr("ip", host)
		IcmpByte := makemsg(host)
		conn.WriteTo(IcmpByte, dst)
	}
	//根据hosts数量修改icmp监听时间
	start := time.Now()
	for {
		if len(AliveHosts) == len(hostslist) {
			break
		}
		since := time.Since(start)
		var wait time.Duration
		switch {
		case len(hostslist) <= 256:
			wait = time.Second * 3
		default:
			wait = time.Second * 6
		}
		if since > wait {
			break
		}
	}
	endflag = true
	conn.Close()
}

1730131966_671fb7fecb4cbec8cbe73.png!small?1730131967218


无监听icmp探测

func RunIcmp2(hostslist []string, chanHosts chan string) {
	num := 1000
	if len(hostslist) < num {
		num = len(hostslist)
	}
	var wg sync.WaitGroup
	limiter := make(chan struct{}, num)
	for _, host := range hostslist {
		wg.Add(1)
		limiter <- struct{}{}
		go func(host string) {
			//icmpalive 函数检查主机是否存活
			if icmpalive(host) {
				livewg.Add(1)
				chanHosts <- host
			}
			<-limiter
			wg.Done()
		}(host)
	}
	wg.Wait()
	close(limiter)
}

func icmpalive(host string) bool {
	startTime := time.Now()
	//尝试使用 ICMP 协议连接到目标主机,超时时间设置为 6 秒。
	conn, err := net.DialTimeout("ip4:icmp", host, 6*time.Second)
	if err != nil {
		return false
	}
	defer conn.Close()
	//设置连接的死线(deadline),即连接必须在指定时间内完成,否则超时
	if err := conn.SetDeadline(startTime.Add(6 * time.Second)); err != nil {
		return false
	}
	msg := makemsg(host)
	//连接发送 ICMP 请求数据包
	if _, err := conn.Write(msg); err != nil {
		return false
	}

	receive := make([]byte, 60)
	if _, err := conn.Read(receive); err != nil {
		return false
	}
	return true
}

端口探测

分为NoPortScan和PortScan

1730175411_672061b350ee6cfa63058.png!small?1730175411512

PortScan代码

创建两个通道:Addrs用于存储要扫描的地址,results用于接收扫描结果,通过在PortConnect调用common.WrapperTcpWithTimeout 进行连接测试是否开放

func PortScan(hostslist []string, ports string, timeout int64) []string {
	var AliveAddress []string
	probePorts := common.ParsePort(ports)
	if len(probePorts) == 0 {
		fmt.Printf("[-] parse port %s error, please check your port format\n", ports)
		return AliveAddress
	}
	noPorts := common.ParsePort(common.NoPorts)
	if len(noPorts) > 0 {
		temp := map[int]struct{}{}
		for _, port := range probePorts {
			temp[port] = struct{}{}
		}

		for _, port := range noPorts {
			delete(temp, port)
		}

		var newDatas []int
		for port := range temp {
			newDatas = append(newDatas, port)
		}
		probePorts = newDatas
		sort.Ints(probePorts)
	}
	workers := common.Threads
	Addrs := make(chan Addr, 100)
	results := make(chan string, 100)
	var wg sync.WaitGroup

	//接收结果
	go func() {
		for found := range results {
			AliveAddress = append(AliveAddress, found)
			wg.Done()
		}
	}()

	//多线程扫描
	for i := 0; i < workers; i++ {
		go func() {
			for addr := range Addrs {
				PortConnect(addr, results, timeout, &wg)
				wg.Done()
			}
		}()
	}

	//添加扫描目标
	for _, port := range probePorts {
		for _, host := range hostslist {
			wg.Add(1)
			Addrs <- Addr{host, port}
		}
	}
	wg.Wait()
	close(Addrs)
	close(results)
	return AliveAddress
}

func PortConnect(addr Addr, respondingHosts chan<- string, adjustedTimeout int64, wg *sync.WaitGroup) {
	host, port := addr.ip, addr.port
	conn, err := common.WrapperTcpWithTimeout("tcp4", fmt.Sprintf("%s:%v", host, port), time.Duration(adjustedTimeout)*time.Second)
	if err == nil {
		defer conn.Close()
		address := host + ":" + strconv.Itoa(port)
		result := fmt.Sprintf("%s open", address)
		common.LogSuccess(result)
		wg.Add(1)
		respondingHosts <- address
	}
}

common.WrapperTcpWithTimeout

func WrapperTcpWithTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
	//创建一个定制化的网络连接,设置超时时间
	d := &net.Dialer{Timeout: timeout}
	return WrapperTCP(network, address, d)
}

func WrapperTCP(network, address string, forward *net.Dialer) (net.Conn, error) {
	//get conn
	var conn net.Conn
	if Socks5Proxy == "" {
		var err error
		//指定使用tcp4和address连接
		conn, err = forward.Dial(network, address)
		if err != nil {
			return nil, err
		}
	} else {
		//走代理的方式
		dailer, err := Socks5Dailer(forward)
		if err != nil {
			return nil, err
		}
		conn, err = dailer.Dial(network, address)
		if err != nil {
			return nil, err
		}
	}
	return conn, nil
}

三 漏洞扫描

然后开始漏扫逻辑

1730176948_672067b4c7100a161e84f.png!small?1730176949047

通过AddScan调度,然后ScanFunc进行反射执行PluginList中获取的方法

func ScanFunc(name *string, info *common.HostInfo) {
defer func() {
if err := recover(); err != nil {
fmt.Printf("[-] %v:%v scan error: %v\n", info.Host, info.Ports, err)
}
}()
//reflect.ValueOf 获取 PluginList 字典中对应 *name 键的值的反射值
f := reflect.ValueOf(PluginList[*name])
//创建一个反射值的切片 in
in := []reflect.Value{reflect.ValueOf(info)}
f.Call(in)
}

1730184123_672083bbb78db0cbeb98a.png!small?1730184123912

这里可以看下mysql的插件(只是一个简单的爆破,如果二开的话可以添加更多功能)

1730202911_6720cd1f64369bcc5e4e4.png!small?1730202911888


Web扫描

所有的扫描中,主要部分其实是webtitle的扫描,看一下这webtitle方法,调用了GOWebTitle和InfoCheck

方法

func WebTitle(info *common.HostInfo) error {
	//webpoc类型
	if common.Scantype == "webpoc" {
		WebScan.WebScan(info)
		return nil
	}
	err, CheckData := GOWebTitle(info)
	info.Infostr = WebScan.InfoCheck(info.Url, &CheckData)

	if !common.NoPoc && err == nil {
		WebScan.WebScan(info)
	} else {
		errlog := fmt.Sprintf("[-] webtitle %v %v", info.Url, err)
		common.LogError(errlog)
	}
	return err
}

GOWebTitle函数中做了一个url的拼接,如果端口不是80或者443,还会调用GetProtocol来获取协议类型,之后通过geturl 对url进行访问和解析,获取到resp的body和header信息存储到CheckData结构中,并且对重定向和访问400错误做了响应处理

func GOWebTitle(info *common.HostInfo) (err error, CheckData []WebScan.CheckDatas) {
	if info.Url == "" {
		switch info.Ports {
		case "80":
			info.Url = fmt.Sprintf("http://%s", info.Host)
		case "443":
			info.Url = fmt.Sprintf("https://%s", info.Host)
		default:
			host := fmt.Sprintf("%s:%s", info.Host, info.Ports)
			protocol := GetProtocol(host, common.Timeout)
			info.Url = fmt.Sprintf("%s://%s:%s", protocol, info.Host, info.Ports)
		}
	} else {
		if !strings.Contains(info.Url, "://") {
			host := strings.Split(info.Url, "/")[0]
			protocol := GetProtocol(host, common.Timeout)
			info.Url = fmt.Sprintf("%s://%s", protocol, info.Url)
		}
	}
err, result, CheckData := geturl(info, 1, CheckData)
if err != nil && !strings.Contains(err.Error(), "EOF") {
return
}

GetProtocol函数(发起tls连接,如果连接成功或者含有特定报错信息则判断为https,感觉自己写工具会用到)

func GetProtocol(host string, Timeout int64) (protocol string) {
protocol = "http"
//如果端口是80或443,跳过Protocol判断
if strings.HasSuffix(host, ":80") || !strings.Contains(host, ":") {
return
} else if strings.HasSuffix(host, ":443") {
protocol = "https"
return
}

//func WrapperTcpWithTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
// //创建一个定制化的网络连接,设置超时时间
// d := &net.Dialer{Timeout: timeout}
// return WrapperTCP(network, address, d)
//}
socksconn, err := common.WrapperTcpWithTimeout("tcp", host, time.Duration(Timeout)*time.Second)
if err != nil {
return
}
conn := tls.Client(socksconn, &tls.Config{MinVersion: tls.VersionTLS10, InsecureSkipVerify: true})
defer func() {
if conn != nil {
defer func() {
if err := recover(); err != nil {
common.LogError(err)
}
}()
conn.Close()
}
}()
conn.SetDeadline(time.Now().Add(time.Duration(Timeout) * time.Second))
err = conn.Handshake()
if err == nil || strings.Contains(err.Error(), "handshake failure") {
protocol = "https"
}
return protocol
}

回到webtitle 看看WebScan.InfoCheck函数

通过正则去匹配Rule中的各种规则,放到infoname数组中

func InfoCheck(Url string, CheckData *[]CheckDatas) []string {
	var matched bool
	var infoname []string

	for _, data := range *CheckData {
		for _, rule := range info.RuleDatas {
			if rule.Type == "code" {
				//通过正则匹配
				matched, _ = regexp.MatchString(rule.Rule, string(data.Body))
			} else {
				matched, _ = regexp.MatchString(rule.Rule, data.Headers)
			}
			if matched == true {
				infoname = append(infoname, rule.Name)
			}
		}
		//flag, name := CalcMd5(data.Body)

		//if flag == true {
		//	infoname = append(infoname, name)
		//}
	}

	infoname = removeDuplicateElement(infoname)

	if len(infoname) > 0 {
		result := fmt.Sprintf("[+] InfoScan %-25v %s ", Url, infoname)
		common.LogSuccess(result)
		return infoname
	}
	return []string{""}
}

1730187721_672091c91ad6096aa6d73.png!small?1730187721321

继续走到WebScan.WebScan

POC扫描

首先初始化poc,在CheckInfoPoc寻找相关poc,放到pocinfo结构体(Target和PocName ),然后进入Execute()

Poc的结构

1730199059_6720be13c9d96b6f52f01.png!small?1730199060196

// 确保一个操作只被执行一次,常用于初始化操作
var once sync.Once
var AllPocs []*lib.Poc

func WebScan(info *common.HostInfo) {
	//使用 once 变量调用 Do 方法,并传入 initpoc 函数
	once.Do(initpoc)
	var pocinfo = common.Pocinfo
	buf := strings.Split(info.Url, "/")
	//协议、主机和端口重新组合成一个字符串
	pocinfo.Target = strings.Join(buf[:3], "/")

	//pocinfo.PocName 不为空,表示已经指定了要执行的漏洞检查名称
	if pocinfo.PocName != "" {
		Execute(pocinfo)
	} else {
		for _, infostr := range info.Infostr {
			pocinfo.PocName = lib.CheckInfoPoc(infostr)
			Execute(pocinfo)
		}
	}
}

initpoc()中对pocs目录下的yaml进行load

func initpoc() {
	if common.PocPath == "" {
		entries, err := Pocs.ReadDir("pocs")
		if err != nil {
			fmt.Printf("[-] init poc error: %v", err)
			return
		}
		for _, one := range entries {
			path := one.Name()
			if strings.HasSuffix(path, ".yaml") || strings.HasSuffix(path, ".yml") {
				if poc, _ := lib.LoadPoc(path, Pocs); poc != nil {
					AllPocs = append(AllPocs, poc)
				}
			}
		}

LoadPoc通过yaml.Unmarshal从yaml中解析出Poc

func LoadPoc(fileName string, Pocs embed.FS) (*Poc, error) {
p := &Poc{}
yamlFile, err := Pocs.ReadFile("pocs/" + fileName)

if err != nil {
fmt.Printf("[-] load poc %s error1: %v\n", fileName, err)
return nil, err
}
err = yaml.Unmarshal(yamlFile, p)
if err != nil {
fmt.Printf("[-] load poc %s error2: %v\n", fileName, err)
return nil, err
}
return p, err
}

回到WebScan往下然后开始Execute()检测,这个代码就有点复杂了

1730197085_6720b65d44ca2d16f7005.png!small?1730197085774

1730197258_6720b70acfb548e1f5158.png!small?1730197259245

func executePoc(oReq *http.Request, p *Poc) (bool, error, string) {
	c := NewEnvOption()
	c.UpdateCompileOptions(p.Set)

在executePoc中首先执行了NewEnvOption(),创建并配置了一个自定义的 CEL 环境选项

1730196207_6720b2ef7cc4f9c12a785.png!small?1730196208174

UpdateCompileOptions()中继续声明变量

1730197427_6720b7b37d502db427d4e.png!small?1730197427874

创建env环境,然后解析http请求

env, err := NewEnv(&c)
if err != nil {
fmt.Printf("[-] %s environment creation error: %s\n", p.Name, err)
return false, err, ""
}
req, err := ParseRequest(oReq)
if err != nil {
fmt.Printf("[-] %s ParseRequest error: %s\n", p.Name, err)
return false, err, ""
}

当item.Value值不是newReverse() 默认走到evalset,evalset函数通过替换模板变量来动态构建攻击载荷,大概是评估测试结果(求明白的大佬可以教一下我,这个地方的evalset调用我不是特别清楚作用)

variableMap := make(map[string]interface{})
	defer func() { variableMap = nil }()
	variableMap["request"] = req
	for _, item := range p.Set {
		k, expression := item.Key, item.Value
		if expression == "newReverse()" {
			if !common.DnsLog {
				return false, nil, ""
			}
			variableMap[k] = newReverse()
			continue
		}
		err, _ = evalset(env, variableMap, k, expression)
		if err != nil {
			fmt.Printf("[-] %s evalset error: %v\n", p.Name, err)
		}
	}

1730198728_6720bcc84f1dac0235c09.png!small?1730198728823

然后就会在evalset中调用Evaluate执行env中定义的函数

1730198766_6720bcee1d746af4d4dbb.png!small?1730198766271


接下来是正式的poc尝试,clusterpoc函数取ymal中的规则,调用clustersend执行

1730199212_6720beac912c68a5c0f39.png!small?1730199212843

下面是clusterpoc的代码解释

func clustersend(oReq *http.Request, variableMap map[string]interface{}, req *Request, env *cel.Env, rule Rules) (bool, error) {
	// 遍历variableMap中的每个键值对,将变量替换到rule的Headers、Path和Body中
	for k1, v1 := range variableMap {
		_, isMap := v1.(map[string]string)
		if isMap {
			continue // 如果值是一个map,则跳过此次循环
		}
		value := fmt.Sprintf("%v", v1) // 将值格式化为字符串
		// 遍历rule的Headers,替换其中的变量
		for k2, v2 := range rule.Headers {
			if strings.Contains(v2, "{{"+k1+"}}") {
				rule.Headers[k2] = strings.ReplaceAll(v2, "{{"+k1+"}}", value)
			}
		}
		// 替换rule.Path和rule.Body中的变量
		rule.Path = strings.ReplaceAll(strings.TrimSpace(rule.Path), "{{"+k1+"}}", value)
		rule.Body = strings.ReplaceAll(strings.TrimSpace(rule.Body), "{{"+k1+"}}", value)
	}

	// 根据oReq的URL Path设置req.Url.Path
	if oReq.URL.Path != "" && oReq.URL.Path != "/" {
		req.Url.Path = fmt.Sprint(oReq.URL.Path, rule.Path)
	} else {
		req.Url.Path = rule.Path
	}
	// 替换Path中的空格为%20
	req.Url.Path = strings.ReplaceAll(req.Url.Path, " ", "%20")

	// 创建新的http请求
	newRequest, err := http.NewRequest(rule.Method, fmt.Sprintf("%s://%s%s", req.Url.Scheme, req.Url.Host, req.Url.Path), strings.NewReader(rule.Body))
	if err != nil {
		return false, err // 如果创建请求失败,返回错误
	}
	// 复制原始请求的Header到新请求
	newRequest.Header = oReq.Header.Clone()
	// 设置rule中定义的Headers
	for k, v := range rule.Headers {
		newRequest.Header.Set(k, v)
	}
	// 发送请求并获取响应
	resp, err := DoRequest(newRequest, rule.FollowRedirects)
	newRequest = nil
	if err != nil {
		return false, err // 如果请求失败,返回错误
	}
	// 将响应存入variableMap
	variableMap["response"] = resp

	// 如果rule定义了Search规则,执行搜索
	if rule.Search != "" {
		result := doSearch(rule.Search, GetHeader(resp.Headers)+string(resp.Body))
		if result != nil && len(result) > 0 { // 如果正则匹配成功
			for k, v := range result {
				variableMap[k] = v
			}
		} else {
			return false, nil // 如果正则匹配失败,返回false
		}
	}

	// 使用CEL环境评估rule.Expression
	out, err := Evaluate(env, rule.Expression, variableMap)
	if err != nil {
		if strings.Contains(err.Error(), "Syntax error") {
			fmt.Println(rule.Expression, err) // 打印语法错误信息
		}
		return false, err // 如果评估表达式失败,返回错误
	}
	// 如果表达式结果为false,不继续执行后续rule
	if fmt.Sprintf("%v", out) == "false" {
		return false, err // 如果最后一步执行失败,返回false
	}
	return true, err // 返回评估结果和错误(如果有)
}

这里更多的代码就暂且不看了,对于cel表达式这块还不是特别清楚,大致结构已经清晰了

四 Fscan的混淆编译

顺便将go语言fscan的混淆方式也记录一下

主要是garble混淆

go install mvdan.cc/garble@latest
garble -literals build main.go

命令
garble -tiny -literals -seed=random build -ldflags="-w -s" main.go

正常编译

1730200898_6720c5421e3b44cdbe0a8.png!small?1730200898477

混淆编译

1730201557_6720c7d57991b25cc8fd7.png!small?1730201557678

其余免杀方法:

exe签名 工具:

GitHub - langsasec/Sign-Sacker: Sign-Sacker(签名掠夺者):一款数字签名复制器,可将其他官方exe中数字签名,图标,详细信息复制到没有签名的exe中,作为免杀,权限维持,伪装的一种小手段。

# 渗透测试 # 网络安全 # web安全 # 系统安全 # 网络安全技术
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录