GO工具开发|基于网站API的子域名与IP反查工具(二)

作者:墨阳
免责声明:本文仅供学习研究,严禁从事非法活动,任何后果由使用者本人负责。

0x00 前言

上一篇实现了一个简单的子域名查询和ip反查工具,但是当我们的目标比较多时,一个一个查询体验并不友好,和网站查询没啥区别。这篇文章改造一下小工具。

0x01 实现

1、函数部分修改

首先设想一下输入,可以有两种输入方式,命令行手动输入,适用于目标比较少的。另一种是从文件读取,这里需要写一个读取文件的函数:

func ReadTarget(filename string) []string {
    var tasks []string
    f,_ := os.Open(filename)
    defer f.Close()
    buff := bufio.NewReader(f)
    for i := 1 ; ; i++ {
        //按行读取,结果为[]byte类型
        target,_,err := buff.ReadLine()
        if err != nil && err != io.EOF{
            panic(err)
        }else if err == io.EOF {        //判断读取到最后则退出
            break
        }
        tasks = append(tasks,string(target))
    }
    return tasks
}

如果目标很多,单线程就会很慢,可以设计成并发模式,Start()函数用于开启协程:

func Start(tasks []string)  {
    wg := &sync.WaitGroup{}
    //定义一个通道
    taskChan := make(chan string,len(tasks))
    //开启五个协程
    for i:=1; i<=5; i++{
        go Run(taskChan,wg)
    }
    //将目标遍历出来添加到通道中
    for _,target := range tasks {
        wg.Add(1)
        taskChan <- target
    }
    close(taskChan)
    wg.Wait()
}

Run()函数,开始走程序的流程:


func Run(taskChan chan string,wg *sync.WaitGroup)  {
    for target := range taskChan{
        GetInfo(Request(strings.TrimSpace(target)))        //strings.TrimSpace()去除首尾空格
        wg.Done()
    }
}

因为多目标的原因,在保存结果的时候需要保存到对应的target中,所以我们在Request()函数中增加一个返回值target,并传递到GetInfo()中,其他不变。

func Request(target string) ([]byte,string) {
    start := time.Now()
    client := &http.Client{Timeout: 5*time.Second}
    url := "https://www.dnsgrep.cn/api/query?q="+target+"&token=[申请的token值]"
    resp,err := client.Get(url)
    if err != nil {
        fmt.Println("请求链接失败!",err)
        return nil,target
    }else if resp.Status != "200 OK" {
        fmt.Println("请求链接失败,状态码不为200!")
        return nil,target
    }
    defer resp.Body.Close()

    text,err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("读取响应失败!",err)
        return nil,target
    }
    end := time.Since(start)
    fmt.Printf("请求数据用时:%s\n",end)
    return text,target
}

同样因为多目标的原因,需要新定义一个结构体来存放查询目标和对应的返回信息:

vars包:

package vars

import "sync"

type JsonData struct {
    Status    int       `json:"status"`
    Data      Info      `json:"data"`
}
type Info struct {
    Data    []map[string]string  `json:"data"`
    Count   int
    Type    int
}

type Result struct {
    Target     []string
    ResultData []Info        //定义为Info类型的切片
}

var (
    V         Result        //V变为了Result类型,用于存放总的结果
    Target    string
    FileName  string        //导入target的文件名
    ExcelName string        //输出excel文件的文件名
    Mu        sync.Mutex    //定义一个锁
)

修改一下GetInfo()函数,增加保存到最终结果功能:

func GetInfo(jsonData []byte,target string)  {
    if jsonData == nil {
        fmt.Println("获取数据失败!")
        return
    }
    //局部变量v,用于存放当前target的解析数据
    var v vars.JsonData
    err := json.Unmarshal(jsonData, &v)
    if err != nil {
        fmt.Println("数据解析失败!", err)
        return
    }
    //结构体在多线程读写的时候不是并发安全的,加锁
    vars.Mu.Lock()
    //将target和结果添加到全局变量V也就是Result类型的结构体的对应字段中
    vars.V.Target = append(vars.V.Target,target)
    vars.V.ResultData = append(vars.V.ResultData,v.Data)
    vars.Mu.Unlock()
}

同样输出的函数也需要修改:

func OutPut()  {
    //遍历结果,i用于关联target和信息,r为下标i对应的信息
    for i,r := range vars.V.ResultData{
        fmt.Printf("\n==============目标:%s共检索到%d条数据================\n\n",vars.V.Target[i],len(r.Data))
        tb,err := gotable.Create("domain","value","type","time")
        if err != nil{
            fmt.Println("创建表格失败!",err)
            os.Exit(0)
        }
        tb.AddRows(r.Data)
        tb.PrintTable()
    }
}

在用户指定输出的文件名后,需要检查一下这个文件是否已经存在:

func CheckFileExist(filename string) bool {
    _,err := os.Stat(filename)
    if os.IsNotExist(err) {
        return false
    }else {
        return true
    }
}

输出到文件:

func WriteExcel(filename string)  {
    f := excelize.NewFile()
    //根据target名称创建工作表
    for _,r := range vars.V.Target{
        f.NewSheet(r)
    }
    //写入数据
    var sheetName string
    for i,obj := range vars.V.ResultData{
        sheetName = vars.V.Target[i]            //根据下标i来对应上target和数据,用于指定工作表
        //指定单元格,设置第一行的值
        f.SetCellValue(sheetName, "A1", "Domain")
        f.SetCellValue(sheetName, "B1", "Value")
        f.SetCellValue(sheetName, "C1", "Type")
        f.SetCellValue(sheetName, "D1", "Time")
        //指定工作表、单元格输出数据
        for i,r := range obj.Data{
            num := strconv.Itoa(i+2)
            f.SetCellValue(sheetName, "A"+num, r["domain"])
            f.SetCellValue(sheetName, "B"+num, r["value"])
            f.SetCellValue(sheetName, "C"+num, r["type"])
            f.SetCellValue(sheetName, "D"+num, r["time"])
        }
    }
    //设置工作簿默认工作表,即打开时显示的表
    f.SetActiveSheet(1)
    //根据指定路径保存文件
    if err := f.SaveAs(filename); err != nil {
        fmt.Println("写入文件失败:",err)
        return
    }
    fmt.Printf("写入文件:%s成功!\n",filename)
}

2、util包完整代码

package util

import (
    "bufio"
    "dnsgrep/vars"
    "encoding/json"
    "fmt"
    "github.com/liushuochen/gotable"
    "github.com/xuri/excelize/v2"
    "io"
    "io/ioutil"
    "net/http"
    "os"
    "strconv"
    "strings"
    "sync"
    "time"
)

func ReadTarget(filename string) []string {
    var tasks []string
    f,_ := os.Open(filename)
    defer f.Close()
    buff := bufio.NewReader(f)
    for i := 1 ; ; i++ {
        target,_,err := buff.ReadLine()
        if err != nil && err != io.EOF{
            panic(err)
        }else if err == io.EOF {
            break
        }
        tasks = append(tasks,string(target))
    }
    return tasks
}

func Start(tasks []string)  {
    wg := &sync.WaitGroup{}
    taskChan := make(chan string,50)
    for i:=1; i<5; i++{
        go Run(taskChan,wg)
    }
    for _,target := range tasks {
        wg.Add(1)
        taskChan <- target
    }
    close(taskChan)
    wg.Wait()
}

func Run(taskChan chan string,wg *sync.WaitGroup)  {
    for target := range taskChan{
        GetInfo(Request(strings.TrimSpace(target)))
        wg.Done()
    }
}

func Request(target string) ([]byte,string) {
    start := time.Now()
    client := &http.Client{Timeout: 5*time.Second}
    url := "https://www.dnsgrep.cn/api/query?q="+target+"&token=[申请的token]"
    resp,err := client.Get(url)
    if err != nil {
        fmt.Println("请求链接失败!",err)
        return nil,target
    }else if resp.Status != "200 OK" {
        fmt.Println("请求链接失败,状态码不为200!")
        return nil,target
    }
    defer resp.Body.Close()

    text,err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("读取响应失败!",err)
        return nil,target
    }
    end := time.Since(start)
    fmt.Printf("请求数据用时:%s\n",end)
    return text,target
}

func GetInfo(jsonData []byte,target string)  {
    if jsonData == nil {
        fmt.Println("获取数据失败!")
        return
    }
    var v vars.JsonData
    err := json.Unmarshal(jsonData, &v)
    if err != nil {
        fmt.Println("数据解析失败!", err)
        return
    }
    vars.Mu.Lock()
    vars.V.Target = append(vars.V.Target,target)
    vars.V.ResultData = append(vars.V.ResultData,v.Data)
    vars.Mu.Unlock()
}

func OutPut()  {
    for i,r := range vars.V.ResultData{
        fmt.Printf("\n==============目标:%s共检索到%d条数据================\n\n",vars.V.Target[i],len(r.Data))
        tb,err := gotable.Create("domain","value","type","time")
        if err != nil{
            fmt.Println("创建表格失败!",err)
            os.Exit(0)
        }
        tb.AddRows(r.Data)
        tb.PrintTable()
    }

}

func CheckFileExist(filename string) bool {
    _,err := os.Stat(filename)
    if os.IsNotExist(err) {
        return false
    }else {
        return true
    }
}

func WriteExcel(filename string)  {
    f := excelize.NewFile()
    //根据target名称创建工作表
    for _,r := range vars.V.Target{
        f.NewSheet(r)
    }
    //写入数据
    var sheetName string
    for i,obj := range vars.V.ResultData{
        sheetName = vars.V.Target[i]
        //指定单元格,设置第一行的值
        f.SetCellValue(sheetName, "A1", "Domain")
        f.SetCellValue(sheetName, "B1", "Value")
        f.SetCellValue(sheetName, "C1", "Type")
        f.SetCellValue(sheetName, "D1", "Time")
        //指定工作表、单元格输出数据
        for i,r := range obj.Data{
            num := strconv.Itoa(i+2)
            f.SetCellValue(sheetName, "A"+num, r["domain"])
            f.SetCellValue(sheetName, "B"+num, r["value"])
            f.SetCellValue(sheetName, "C"+num, r["type"])
            f.SetCellValue(sheetName, "D"+num, r["time"])
        }
    }
    //设置工作簿默认工作表,即打开时显示的表
    f.SetActiveSheet(1)
    // 根据指定路径保存文件
    if err := f.SaveAs(filename); err != nil {
        fmt.Println("写入文件失败:",err)
        return
    }
    fmt.Printf("写入文件:%s成功!\n",filename)
}

3、主函数部分

主函数部分也需要添加参数和进行逻辑的调整:

package main

import (
    "dnsgrep/util"
    "dnsgrep/vars"
    "fmt"
    "github.com/urfave/cli"
    "os"
    "strings"
    "time"
)

func main()  {
    start := time.Now()
    app := &cli.App{
        Name: "名字",
        Author: "作者",
        Version: "版本信息",
        Usage: "简介",
        Flags: []cli.Flag{
            &cli.StringFlag{
                Name: "t",
                Value: "",
                Usage: "target,命令格式:-t `baidu.com,127.0.0.1`",
                Destination: &vars.Target,
            },
            &cli.StringFlag{
                Name: "r",
                Value: "",
                Usage: "从文件导入目标,目标文件内一行一个,命令格式:-r `xx.txt`",
                Destination: &vars.FileName,
            },
            &cli.StringFlag{
                Name: "f",
                Value: "",
                Usage: "输出表格名称,命令格式:-f `result.xlsx`",
                Destination: &vars.ExcelName,
            },
        },
        Action: func(c *cli.Context) {
            if !c.IsSet("t") && !c.IsSet("r") {
                fmt.Println("请输入查询目标,-h查看参数!")
                os.Exit(0)
            }
        },
    }
    app.Run(os.Args)
    //检查输出文件是否已经存在
    if vars.ExcelName != ""{
        if util.CheckFileExist(vars.ExcelName){
            fmt.Println("保存的文件名已存在,请重新输入!")
            os.Exit(0)
        }
    }
    //根据输入的target生成最终target切片
    if vars.Target != "" || vars.FileName != "" {
        var tasks []string
        switch  {
        //命令行输入和文件输入同时存在
        case vars.Target != "" && vars.FileName != "":
            tasks = strings.Split(vars.Target,",")            //根据逗号分割字符串,返回切片
            tasks = append(tasks,util.ReadTarget(vars.FileName)...)        //将切片添加到切片中,需要加...来表明这是个切片
        //只有命令行
        case vars.Target != "":
            tasks = strings.Split(vars.Target,",")
        //只有文件
        case vars.FileName != "":
            tasks = util.ReadTarget(vars.FileName)
        }
        fmt.Println("开始查询,待查询数:",len(tasks))
        util.Start(tasks)
        util.OutPut()
    }else {
        os.Exit(0)
    }
    //判断一下是否需要输出到文件
    if vars.ExcelName != ""{
        util.WriteExcel(vars.ExcelName)
    }
    end := time.Since(start)
    fmt.Printf("共用时:%s",end)
}

0x03 运行结果

运行一下,看看是不是我们想要的结果
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

0x04 总结

多任务并发模式的查询脚本就写好了,接下来的修改方向就是可以根据自己需要多添加写网站,这样收集的信息会全一些。初学可能有些地方不完美,望各位大佬不要吐槽我哈哈。
项目地址:https://github.com/MoYang233/subdomain-demo/tree/main/subdomain-demo2

0x05 了解更多安全知识

欢迎关注我们的安全公众号,学习更多安全知识!!!
欢迎关注我们的安全公众号,学习更多安全知识!!!
欢迎关注我们的安全公众号,学习更多安全知识!!!
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_42282189/article/details/121237600
今日推荐