作者:墨阳 免责声明:本文仅供学习研究,严禁从事非法活动,任何后果由使用者本人负责。
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 了解更多安全知识
欢迎关注我们的安全公众号,学习更多安全知识!!! 欢迎关注我们的安全公众号,学习更多安全知识!!! 欢迎关注我们的安全公众号,学习更多安全知识!!!
|