├── README.md ├── connect.go ├── encrypt.go ├── execute.go ├── filecreate.go ├── go.mod ├── go.sum ├── img └── w3wp_powershell.png ├── main.go ├── reg.go └── resolve.go /README.md: -------------------------------------------------------------------------------- 1 | # Detection-Validation 2 | 3 | ## Purpose 4 | 5 | The tool automates the process of simulating malicious process events without need to go through setup of real processes. 6 | 7 | Suppose you want to test w3wp.exe spawning Powershell, you will need to go through iis setup to simulate w3wp.exe events, which is a consuming task if you have many rules to validate. Since detection engines work based simple string matching from telemetry collection tools such as Sysmon or EDR, any binary with the same parent process name, child process name, commandline and path can be used to test the logic, hence no need to setup iis to simulate the behavior. 8 | 9 | ![w3wp_powershell.png](img/w3wp_powershell.png) 10 | 11 | The tool allow you to create a child process with a custom parent, child, commandline and path. In addition to couple of other events such as file create from specific process and path, DNS query, registry, and Process connections. 12 | 13 | ``` 14 | NAME: 15 | Malware Cli - A new cli application 16 | 17 | USAGE: 18 | main.exe [global options] command [command options] [arguments...] 19 | 20 | DESCRIPTION: 21 | Detection validation tool. 22 | The objective is to generate event with specific conditions to validate detection rule. 23 | You can execute commands such as w3wp.exe spawning shell or winword creating file or making DNS queries. 24 | 25 | COMMANDS: 26 | argsfree Accept any commandline 27 | connect Connect to host 28 | download Download file 29 | dnsquery Resolve DNS 30 | execute Execute command with custom commandline and parent process 31 | encrypt encrypt all files in a folder that match a pattern 32 | createfile Create file at a spcific path 33 | reg Add registry key 34 | help, h Shows a list of commands or help for one command 35 | 36 | GLOBAL OPTIONS: 37 | --help, -h show help 38 | ``` 39 | 40 | ## Examples 41 | 42 | **winword.exe spawning cscript.exe** 43 | 44 | ``` 45 | mcli.exe execute --parent winword.exe --command cscript.exe 46 | ``` 47 | 48 | **rundll32.exe making DNS request** 49 | 50 | ``` 51 | mcli.exe dnsquery --binpath c:\temp\rundll32.exe --host malicious.com 52 | ``` 53 | 54 | **w.exe creating file from path C:\temp** 55 | 56 | ``` 57 | mcli.exe createfile --path f.dat --binpath c:\temp\w.exe 58 | ``` 59 | 60 | ## Installation 61 | 62 | 63 | ### Windows (Powershell) 64 | 65 | **Run app to download prerequisites and check execution** 66 | ```powershell 67 | go run . 68 | ``` 69 | **Compile app** 70 | ```powershell 71 | go build -o mcli.exe . 72 | ``` 73 | -------------------------------------------------------------------------------- /connect.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | "log" 8 | "net" 9 | "net/http" 10 | "time" 11 | ) 12 | 13 | func connectToHost(host string, port string) { 14 | timeout := time.Second * 3 15 | conn, err := net.DialTimeout("tcp", host+":"+port, timeout) 16 | if err != nil { 17 | fmt.Println("Connecting error:", err) 18 | } 19 | if conn != nil { 20 | defer conn.Close() 21 | fmt.Println("Opened", net.JoinHostPort(host, port)) 22 | } 23 | } 24 | 25 | func downloadFile(fullURLFile string) { 26 | 27 | file, err := ioutil.TempFile("C:/Users/Public/", "mcli.*.dat") 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | 32 | client := http.Client{ 33 | CheckRedirect: func(r *http.Request, via []*http.Request) error { 34 | r.URL.Opaque = r.URL.Path 35 | return nil 36 | }, 37 | } 38 | 39 | resp, err := client.Get(fullURLFile) 40 | if err != nil { 41 | log.Fatal(err) 42 | } 43 | defer resp.Body.Close() 44 | 45 | size, err := io.Copy(file, resp.Body) 46 | if err != nil { 47 | log.Fatal(err) 48 | } 49 | 50 | defer file.Close() 51 | 52 | fmt.Printf("Downloaded a file %s with size %d", file.Name(), size) 53 | } 54 | -------------------------------------------------------------------------------- /encrypt.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "path/filepath" 8 | 9 | "golang.org/x/crypto/openpgp" 10 | ) 11 | 12 | // EncryptFiles traverses the file system starting from the given root folder 13 | // and encrypts all files that match the given filename pattern. 14 | // Each encrypted file will be prepended with "enc_". 15 | // Files that are larger than 2 MB will be skipped. 16 | func EncryptFiles(rootFolder, filenamePattern string) error { 17 | err := filepath.Walk(rootFolder, func(path string, info os.FileInfo, err error) error { 18 | if err != nil { 19 | return err 20 | } 21 | if info.IsDir() { 22 | return nil 23 | } 24 | if info.Size() > 2*1024*1024 { 25 | return nil 26 | } 27 | matched, err := filepath.Match(filenamePattern, info.Name()) 28 | if err != nil { 29 | return err 30 | } 31 | if matched { 32 | inFile, err := os.Open(path) 33 | if err != nil { 34 | return err 35 | } 36 | defer inFile.Close() 37 | outFile, err := os.Create(filepath.Join(filepath.Dir(path), "enc_"+info.Name())) 38 | if err != nil { 39 | return err 40 | } 41 | defer outFile.Close() 42 | w, err := openpgp.SymmetricallyEncrypt(outFile, []byte("password"), nil, nil) 43 | if err != nil { 44 | return err 45 | } 46 | defer w.Close() 47 | if _, err = io.Copy(w, inFile); err != nil { 48 | return err 49 | } 50 | } 51 | return nil 52 | }) 53 | if err != nil { 54 | return fmt.Errorf("failed to encrypt files: %v", err) 55 | } 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /execute.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "log" 8 | "os" 9 | "os/exec" 10 | "strings" 11 | ) 12 | 13 | func copyBinaryTo(toPath string, binaryName string) string { 14 | 15 | //get path of current running process 16 | srcbinary, err := os.Executable() 17 | if err != nil { 18 | panic(err) 19 | } 20 | 21 | parentBinary := "" 22 | if !strings.HasSuffix(binaryName, ".exe") { 23 | if strings.HasSuffix(toPath, "/") { 24 | 25 | parentBinary = toPath + binaryName + ".exe" 26 | 27 | } else { 28 | 29 | parentBinary = toPath + "/" + binaryName + ".exe" 30 | } 31 | 32 | } else { 33 | parentBinary = toPath + "/" + binaryName 34 | } 35 | 36 | in, err := os.Open(srcbinary) 37 | if err != nil { 38 | log.Println(err) 39 | } 40 | defer in.Close() 41 | out, err := os.Create(parentBinary) 42 | if err != nil { 43 | log.Println(err) 44 | } 45 | defer func() { 46 | cerr := out.Close() 47 | if err == nil { 48 | err = cerr 49 | log.Println(err) 50 | } 51 | }() 52 | if _, err = io.Copy(out, in); err != nil { 53 | log.Println(err) 54 | } 55 | err = out.Sync() 56 | if err != nil { 57 | log.Println(err) 58 | } 59 | 60 | return parentBinary 61 | } 62 | 63 | func prepareCommandArgs(command string, args []string) []string { 64 | c := []string{} 65 | 66 | c = append(c, "execute") 67 | c = append(c, "--command") 68 | c = append(c, command) 69 | 70 | if len(args) > 1 { 71 | 72 | c = append(c, "--arg") 73 | chainedArgs := "" 74 | for _, arg := range args { 75 | chainedArgs = fmt.Sprintf("%s %s", chainedArgs, arg) 76 | } 77 | chainedArgs = strings.TrimPrefix(chainedArgs, " ") 78 | c = append(c, chainedArgs) 79 | 80 | } 81 | 82 | return c 83 | 84 | } 85 | 86 | func execute(command string, args []string) string { 87 | 88 | log.Println("Executing:", command, args) 89 | 90 | cmd := exec.Command(command, args...) 91 | 92 | stdout, err := cmd.StdoutPipe() 93 | 94 | if err != nil { 95 | log.Println(err) 96 | } 97 | if err := cmd.Start(); err != nil { 98 | log.Println(err) 99 | } 100 | var out bytes.Buffer 101 | if _, err := io.Copy(&out, stdout); err != nil { 102 | log.Println(err) 103 | } 104 | if err := cmd.Wait(); err != nil { 105 | log.Println(err) 106 | } 107 | output := out.String() 108 | return output 109 | } 110 | 111 | func ExecuteCommand(commandName string, commandline string, parent string, copyPath string) (string, error) { 112 | 113 | args := strings.Split(commandline, " ") 114 | 115 | if copyPath != "C:/Users/Public" { 116 | _, err := os.Stat(copyPath) 117 | if os.IsNotExist(err) { 118 | if err := os.MkdirAll(copyPath, 0755); err != nil { 119 | log.Println(err) 120 | } 121 | 122 | } 123 | } 124 | 125 | // when passed for first time with custom parent process name 126 | if parent != "" { 127 | 128 | parentBinaryPath := copyBinaryTo(copyPath, parent) 129 | 130 | fullcommand := prepareCommandArgs(commandName, args) 131 | 132 | fmt.Println("outter command:", parentBinaryPath, fullcommand) 133 | 134 | output := execute(parentBinaryPath, fullcommand) 135 | 136 | fmt.Println(output) 137 | 138 | return output, nil 139 | 140 | } else { 141 | 142 | //when executed after the command is prepared with the custom parent 143 | fmt.Println("Inner command:", commandName, args) 144 | output := execute(commandName, args) 145 | fmt.Println(output) 146 | return output, nil 147 | 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /filecreate.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "path/filepath" 8 | ) 9 | 10 | func filewrite(fpath string, binPath string) { 11 | 12 | if binPath != "" { 13 | directory := filepath.Dir(binPath) 14 | fileName := filepath.Base(binPath) 15 | 16 | copyBinaryTo(directory, fileName) 17 | 18 | args := []string{"createfile", "--path", fpath} 19 | output := execute(binPath, args) 20 | fmt.Println(output) 21 | return 22 | } 23 | 24 | _, err := os.Stat(fpath) 25 | if os.IsNotExist(err) { 26 | dir, file := filepath.Split(fpath) 27 | fmt.Println(dir, "file:", file) 28 | if err := os.MkdirAll(dir, 0755); err != nil { 29 | log.Println(err) 30 | } 31 | 32 | file_handle, err := os.Create(fpath) 33 | file_handle.WriteString("Test file\ncreated by https://github.com/alwashali/Detection-Validation") 34 | 35 | if err != nil { 36 | log.Println(err) 37 | } 38 | file_handle.Close() 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/urfave/cli v1.22.12 7 | golang.org/x/crypto v0.8.0 8 | golang.org/x/sys v0.7.0 9 | ) 10 | 11 | require ( 12 | github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect 13 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 2 | github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= 3 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 9 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 10 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 11 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 12 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 13 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 14 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 15 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 16 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 17 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 18 | github.com/urfave/cli v1.22.12 h1:igJgVw1JdKH+trcLWLeLwZjU9fEfPesQ+9/e4MQ44S8= 19 | github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= 20 | golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= 21 | golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= 22 | golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= 23 | golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 24 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 25 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 26 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 27 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 28 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 29 | -------------------------------------------------------------------------------- /img/w3wp_powershell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alwashali/detection-validation/64fdd8d73fcb0c5b4175a8c763a9f49772da25b0/img/w3wp_powershell.png -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | 8 | "github.com/urfave/cli" 9 | ) 10 | 11 | var app = cli.NewApp() 12 | 13 | func init() { 14 | app.Name = "Malware Cli" 15 | 16 | app.Description = `Detection validation tool. 17 | The objective is to generate event with specific conditions to validate detection rule. 18 | You can execute commands such as w3wp.exe spawning shell or winword creating file or making DNS queries.` 19 | 20 | } 21 | 22 | func main() { 23 | app.Commands = []cli.Command{ 24 | { 25 | Name: "argsfree", 26 | 27 | Usage: "Accept any commandline", 28 | 29 | Action: func(c *cli.Context) { 30 | 31 | fmt.Println(os.Args) 32 | 33 | }, 34 | }, 35 | { 36 | Name: "connect", 37 | 38 | Usage: "Connect to host", 39 | Flags: []cli.Flag{ 40 | &cli.StringFlag{ 41 | Name: "host", 42 | Usage: "hostname or IP Address", 43 | Required: true, 44 | }, 45 | &cli.StringFlag{ 46 | Name: "port", 47 | Usage: "port number", 48 | Required: true, 49 | }, 50 | }, 51 | Action: func(c *cli.Context) { 52 | 53 | connectToHost(c.String("host"), c.String("port")) 54 | 55 | }, 56 | }, 57 | { 58 | Name: "download", 59 | 60 | Usage: "Download file", 61 | Flags: []cli.Flag{ 62 | &cli.StringFlag{ 63 | Name: "url", 64 | Usage: "File URL", 65 | Required: true, 66 | }, 67 | }, 68 | Action: func(c *cli.Context) { 69 | 70 | downloadFile(c.String("url")) 71 | 72 | }, 73 | }, 74 | { 75 | Name: "dnsquery", 76 | 77 | Usage: "Resolve DNS", 78 | Flags: []cli.Flag{ 79 | &cli.StringFlag{ 80 | Name: "host", 81 | Usage: "hostname to resolve", 82 | Required: true, 83 | }, 84 | }, 85 | Action: func(c *cli.Context) { 86 | 87 | resolve(c.String("host")) 88 | 89 | }, 90 | }, 91 | { 92 | Name: "execute", 93 | Usage: "Execute command with custom commandline and parent process", 94 | Flags: []cli.Flag{ 95 | 96 | &cli.StringFlag{ 97 | Name: "command", 98 | Usage: "Hostname or IP Address", 99 | Required: true, 100 | }, 101 | &cli.StringFlag{ 102 | Name: "parent", 103 | Usage: "Optinal parent command to execute", 104 | }, 105 | &cli.StringFlag{ 106 | Name: "arg", 107 | Usage: "Command arguments", 108 | }, 109 | &cli.StringFlag{ 110 | Name: "copy", 111 | Usage: "Copy to path before execution", 112 | Value: "C:/Users/Public", 113 | }, 114 | }, 115 | Action: func(c *cli.Context) { 116 | 117 | ExecuteCommand(c.String("command"), c.String("arg"), c.String("parent"), c.String("copy")) 118 | 119 | }, 120 | }, { 121 | Name: "encrypt", 122 | Usage: "encrypt all files in a folder that match a pattern", 123 | Flags: []cli.Flag{ 124 | 125 | &cli.StringFlag{ 126 | Name: "path", 127 | Usage: "Folder path", 128 | Required: true, 129 | }, 130 | &cli.StringFlag{ 131 | Name: "pattern", 132 | Usage: "File name pattern", 133 | }, 134 | }, 135 | Action: func(c *cli.Context) { 136 | 137 | EncryptFiles(c.String("path"), c.String("pattern")) 138 | 139 | }, 140 | }, 141 | { 142 | Name: "createfile", 143 | 144 | Usage: "Create file at a spcific path", 145 | Flags: []cli.Flag{ 146 | &cli.StringFlag{ 147 | Name: "path", 148 | Usage: "full path and file name", 149 | Required: true, 150 | }, 151 | &cli.StringFlag{ 152 | Name: "binpath", 153 | Usage: "Full path of the binary creating the file. Example: C:/temp/binary.exe", 154 | }, 155 | }, 156 | Action: func(c *cli.Context) { 157 | 158 | filewrite(c.String("path"), c.String("binpath")) 159 | 160 | }, 161 | }, 162 | { 163 | Name: "reg", 164 | 165 | Usage: "Add registry key", 166 | Flags: []cli.Flag{ 167 | &cli.StringFlag{ 168 | Name: "keyname", 169 | Usage: "Key name", 170 | Required: true, 171 | }, 172 | &cli.StringFlag{ 173 | Name: "keypath", 174 | Usage: "Key path", 175 | Required: true, 176 | }, 177 | &cli.StringFlag{ 178 | Name: "value", 179 | Usage: "Key value", 180 | }, 181 | &cli.StringFlag{ 182 | Name: "binpath", 183 | Usage: "Full path of the binary creating the file. Example: C:/temp/binary.exe", 184 | }, 185 | &cli.BoolFlag{ 186 | Name: "delete", 187 | Usage: "Delete key", 188 | }, 189 | }, 190 | Action: func(c *cli.Context) { 191 | 192 | AddRegistryKey(c.String("keypath"), c.String("keyname"), c.String("value"), c.String("binpath"), c.Bool("delete")) 193 | 194 | }, 195 | }, 196 | } 197 | 198 | //log.Println("Received arguments: ", os.Args) 199 | if err := app.Run(os.Args); err != nil { 200 | log.Fatal(err) 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /reg.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "path/filepath" 7 | 8 | "golang.org/x/sys/windows/registry" 9 | ) 10 | 11 | func AddRegistryKey(keyPath string, keyName string, keyValue interface{}, binPath string, delete bool) { 12 | if binPath != "" { 13 | directory := filepath.Dir(binPath) 14 | fileName := filepath.Base(binPath) 15 | 16 | copyBinaryTo(directory, fileName) 17 | 18 | args := []string{"reg", "--keyname", keyName, "--keypath", keyPath, "--keyvalue", keyValue.(string)} 19 | output := execute(binPath, args) 20 | fmt.Println(output) 21 | 22 | } 23 | 24 | key, err := registry.OpenKey(registry.CURRENT_USER, keyPath, registry.ALL_ACCESS) 25 | if err != nil { 26 | log.Println(err) 27 | } 28 | defer key.Close() 29 | 30 | if delete { 31 | 32 | if keyValue.(string) != "" { 33 | fmt.Println("Option --value and --delete can't be used together!") 34 | return 35 | } 36 | if err = key.DeleteValue(keyName); err != nil { 37 | log.Println(err) 38 | } 39 | log.Printf("Registry key deleted successfully: %s\\%s = %s\n", keyPath, keyName, keyValue) 40 | return 41 | 42 | } 43 | 44 | if err = key.SetStringValue(keyName, keyValue.(string)); err != nil { 45 | log.Println(err) 46 | } 47 | 48 | log.Printf("Registry key added successfully: %s\\%s = %s\n", keyPath, keyName, keyValue) 49 | 50 | } 51 | -------------------------------------------------------------------------------- /resolve.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "time" 8 | ) 9 | 10 | func resolve(hostname string) { 11 | 12 | r := &net.Resolver{ 13 | PreferGo: true, 14 | Dial: func(ctx context.Context, network, address string) (net.Conn, error) { 15 | d := net.Dialer{ 16 | Timeout: time.Millisecond * time.Duration(10000), 17 | } 18 | return d.DialContext(ctx, network, "8.8.8.8:53") 19 | }, 20 | } 21 | ips, _ := r.LookupHost(context.Background(), hostname) 22 | 23 | for _, ip := range ips { 24 | fmt.Println(ip) 25 | } 26 | 27 | } 28 | --------------------------------------------------------------------------------