freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

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

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安全 # 系统安全 # 网络安全技术
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录