From 4910363cca770a8ae1068c0692e260ad3793cc6b Mon Sep 17 00:00:00 2001 From: M09Ic Date: Mon, 18 May 2026 01:10:12 +0800 Subject: [PATCH 1/2] Update chainreactors dependencies and add CI --- .github/workflows/ci.yml | 47 +++++++++++ .gitignore | 17 ++-- cmd/cmd.go | 19 +++-- cmd/cmd_test.go | 40 ++++++++++ core/generator.go | 1 + core/generator_test.go | 88 +++++++++++++++++++++ core/options.go | 13 ++- core/options_test.go | 56 +++++++++++++ core/run_with_args_test.go | 31 ++++++++ core/runner.go | 18 ++++- core/runner_test.go | 24 ++++++ core/target_test.go | 27 +++++++ example/template/main.go | 6 +- external/grdp/core/io_test.go | 19 +++++ external/grdp/core/rle_test.go | 10 +++ external/grdp/grdp_test.go | 47 +++++++++++ external/grdp/protocol/nla/encode_test.go | 32 ++++++++ external/grdp/protocol/nla/ntlm_test.go | 53 +++++++++++++ external/grdp/protocol/t125/ber/ber.go | 2 +- external/grdp/protocol/t125/ber/ber_test.go | 16 ++++ go.mod | 10 +-- go.sum | 19 +++-- pkg/utils.go | 5 +- pkg/utils_test.go | 17 ++++ plugin/neutron/neutron.go | 5 +- 25 files changed, 583 insertions(+), 39 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 cmd/cmd_test.go create mode 100644 core/generator_test.go create mode 100644 core/options_test.go create mode 100644 core/run_with_args_test.go create mode 100644 core/runner_test.go create mode 100644 core/target_test.go create mode 100644 external/grdp/core/io_test.go create mode 100644 external/grdp/core/rle_test.go create mode 100644 external/grdp/grdp_test.go create mode 100644 external/grdp/protocol/nla/encode_test.go create mode 100644 external/grdp/protocol/nla/ntlm_test.go create mode 100644 external/grdp/protocol/t125/ber/ber_test.go create mode 100644 pkg/utils_test.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..1b23cec --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,47 @@ +name: ci + +on: + push: + branches: + - main + - master + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + basic: + name: basic checks (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - windows-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.20.x' + cache: true + + - name: Download dependencies + run: go mod download + + - name: Test + run: go test -count=1 ./... + + - name: Vet + run: go vet ./... + + - name: Build packages + run: go build ./... diff --git a/.gitignore b/.gitignore index 8d389ec..cd87ee4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,15 +4,14 @@ *.exe *.exe~ *.dll -*.so -*.dylib - -# Test binary, built with `go test -c` -*.test -*.dic -*_test.go -*.txt -*.tar.gz +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test +*.dic +*.txt +*.tar.gz # Output of the go coverage tool, specifically when used with LiteIDE *.out diff --git a/cmd/cmd.go b/cmd/cmd.go index f1c346f..845d878 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -4,7 +4,7 @@ import ( "context" "fmt" "github.com/chainreactors/zombie/core" - "github.com/jessevdk/go-flags" + "io" "io/ioutil" "log" "os" @@ -17,12 +17,17 @@ func init() { var ver = "dev" func Zombie() { - defer os.Exit(0) - err := core.RunWithArgs(context.Background(), os.Args[1:], core.RunOptions{Version: ver}) + os.Exit(Run(os.Args[1:], os.Stdout)) +} + +func Run(args []string, output io.Writer) int { + if output == nil { + output = os.Stdout + } + err := core.RunWithArgs(context.Background(), args, core.RunOptions{Output: output, Version: ver}) if err != nil { - if flagsErr, ok := err.(*flags.Error); !ok || flagsErr.Type != flags.ErrHelp { - fmt.Println(err.Error()) - } - return + fmt.Fprintln(output, err.Error()) + return 1 } + return 0 } diff --git a/cmd/cmd_test.go b/cmd/cmd_test.go new file mode 100644 index 0000000..04cf136 --- /dev/null +++ b/cmd/cmd_test.go @@ -0,0 +1,40 @@ +package cmd + +import ( + "bytes" + "strings" + "testing" +) + +func TestRunReturnsZeroForVersion(t *testing.T) { + var out bytes.Buffer + + if code := Run([]string{"--version"}, &out); code != 0 { + t.Fatalf("expected exit code 0, got %d", code) + } + if strings.TrimSpace(out.String()) != "dev" { + t.Fatalf("unexpected version output: %q", out.String()) + } +} + +func TestRunReturnsNonZeroForInvalidFlag(t *testing.T) { + var out bytes.Buffer + + if code := Run([]string{"--definitely-not-a-flag"}, &out); code == 0 { + t.Fatal("expected non-zero exit code for invalid flag") + } + if !strings.Contains(out.String(), "unknown flag") { + t.Fatalf("expected unknown flag output, got %q", out.String()) + } +} + +func TestRunReturnsNonZeroForMissingInput(t *testing.T) { + var out bytes.Buffer + + if code := Run(nil, &out); code == 0 { + t.Fatal("expected non-zero exit code for missing input") + } + if !strings.Contains(out.String(), "please input ip") { + t.Fatalf("expected missing input output, got %q", out.String()) + } +} diff --git a/core/generator.go b/core/generator.go index 9447c86..de37986 100644 --- a/core/generator.go +++ b/core/generator.go @@ -44,6 +44,7 @@ func NewGeneratorWithFile(filename string) (*Generator, error) { g := &Generator{ C: make(chan string), Filename: filename, + File: f, } g.Word = words.NewWorderWithFile(f) diff --git a/core/generator_test.go b/core/generator_test.go new file mode 100644 index 0000000..d170d72 --- /dev/null +++ b/core/generator_test.go @@ -0,0 +1,88 @@ +package core + +import ( + "os" + "path/filepath" + "testing" + + "github.com/chainreactors/zombie/pkg" +) + +func writeWordsFile(t *testing.T, words string) string { + t.Helper() + + filename := filepath.Join(t.TempDir(), "words.txt") + if err := os.WriteFile(filename, []byte(words), 0600); err != nil { + t.Fatal(err) + } + return filename +} + +func TestNewGeneratorWithFile(t *testing.T) { + g, err := NewGeneratorWithFile(writeWordsFile(t, "admin\nroot\n")) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = g.File.Close() }) + + got := g.RunAsSlice() + if len(got) != 2 || got[0] != "admin" || got[1] != "root" { + t.Fatalf("unexpected generated words: %#v", got) + } +} + +func TestNewGeneratorWithRule(t *testing.T) { + g, err := NewGeneratorWithFile(writeWordsFile(t, "admin\n")) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = g.File.Close() }) + if err := g.SetRuleFile(filepath.Join("..", "templates", "zombie", "rule", "weakpass.rule")); err != nil { + t.Fatal(err) + } + + got := g.RunAsSlice() + if len(got) == 0 { + t.Fatal("expected weakpass rule to generate words") + } +} + +func TestNewGeneratorWithWord(t *testing.T) { + g, err := NewGeneratorWithWord("{?#3}", nil, nil) + if err != nil { + t.Fatal(err) + } + + _ = g +} + +func TestKeyword(t *testing.T) { + if err := pkg.LoadKeyword(); err != nil { + t.Fatal(err) + } + g, err := NewGeneratorWithWord("{@mysql_pwd}", nil, nil) + if err != nil { + t.Fatal(err) + } + + got := g.RunAsSlice() + if len(got) == 0 { + t.Fatal("expected mysql_pwd keyword to produce words") + } +} + +func TestWeakPass(t *testing.T) { + g, err := NewGeneratorWithFile(writeWordsFile(t, "admin\n")) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = g.File.Close() }) + if err := g.SetRuleFile(filepath.Join("..", "templates", "zombie", "rule", "weakpass.rule")); err != nil { + t.Fatal(err) + } + + got := g.RunAsSlice() + if len(got) == 0 { + t.Fatal("expected weakpass rule to generate words") + } +} diff --git a/core/options.go b/core/options.go index 8c2cd76..f29eaae 100644 --- a/core/options.go +++ b/core/options.go @@ -61,16 +61,27 @@ type MiscOptions struct { Strict bool `long:"strict" description:"Bool, strict mode, when finger check pass will brute"` Threads int `short:"t" default:"100" description:"Int, threads"` Timeout int `long:"timeout" default:"5" description:"Int, timeout"` - Mod string `short:"m" default:"clusterbomb" description:"String, clusterbomb/sniper"` + Mod string `short:"m" default:"clusterbomb" description:"String, clusterbomb/pitchfork/sniper"` ListService bool `short:"l" long:"list" description:"Bool, list all service"` Bar bool `long:"bar" description:"Bool, enable bar"` Version bool `long:"version" description:"Bool, show version"` } func (opt *Option) Validate() error { + if opt.Mod == "" { + opt.Mod = ModBomb + } + switch opt.Mod { + case ModBomb, ModPitchFork, ModSniper: + default: + return fmt.Errorf("unsupported mod %q, want clusterbomb, pitchfork, or sniper", opt.Mod) + } if len(opt.IP) == 0 && opt.IPFile == "" && opt.JsonFile == "" && opt.GogoFile == "" && opt.CIDR == nil { return errors.New("please input ip or or file or json file or gogo file") } + if opt.Mod == ModPitchFork && opt.Auth == nil && opt.AuthFile == "" { + return errors.New("pitchfork mode requires auth, please set -a/-A") + } if opt.WeakPassWord && (opt.Password == nil && opt.PasswordFile == "") { return errors.New("use weak-password rule must set password, please set -p/-P") } diff --git a/core/options_test.go b/core/options_test.go new file mode 100644 index 0000000..4484a7f --- /dev/null +++ b/core/options_test.go @@ -0,0 +1,56 @@ +package core + +import ( + "os" + "path/filepath" + "testing" + + "github.com/chainreactors/words" +) + +func TestReadFile(t *testing.T) { + filename := filepath.Join(t.TempDir(), "words.txt") + if err := os.WriteFile(filename, []byte("admin\nroot\n"), 0600); err != nil { + t.Fatal(err) + } + + f, err := os.Open(filename) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + w := words.NewWorderWithFile(f) + w.Run() + got := w.All() + if len(got) != 2 || got[0] != "admin" || got[1] != "root" { + t.Fatalf("unexpected words: %#v", got) + } +} + +func TestOptionValidateRejectsUnsupportedMod(t *testing.T) { + opt := &Option{} + opt.IP = []string{"127.0.0.1"} + opt.ServiceName = "redis" + opt.Mod = "not-a-mode" + + if err := opt.Validate(); err == nil { + t.Fatal("expected unsupported mode to be rejected") + } +} + +func TestOptionValidateRequiresPitchforkAuth(t *testing.T) { + opt := &Option{} + opt.IP = []string{"127.0.0.1"} + opt.ServiceName = "redis" + opt.Mod = ModPitchFork + + if err := opt.Validate(); err == nil { + t.Fatal("expected pitchfork without auth to be rejected") + } + + opt.Auth = []string{"user::pass"} + if err := opt.Validate(); err != nil { + t.Fatalf("expected pitchfork with auth to pass validation: %v", err) + } +} diff --git a/core/run_with_args_test.go b/core/run_with_args_test.go new file mode 100644 index 0000000..319d989 --- /dev/null +++ b/core/run_with_args_test.go @@ -0,0 +1,31 @@ +package core + +import ( + "bytes" + "context" + "strings" + "testing" +) + +func TestRunWithArgsListsServices(t *testing.T) { + var out bytes.Buffer + + if err := RunWithArgs(context.Background(), []string{"-l"}, RunOptions{Output: &out}); err != nil { + t.Fatal(err) + } + if !strings.Contains(out.String(), "support service list") || !strings.Contains(out.String(), "ssh") { + t.Fatalf("unexpected service list output: %q", out.String()) + } +} + +func TestRunWithArgsRejectsUnsupportedMod(t *testing.T) { + var out bytes.Buffer + + err := RunWithArgs(context.Background(), []string{"-i", "127.0.0.1", "-s", "redis", "-m", "not-a-mode"}, RunOptions{Output: &out}) + if err == nil { + t.Fatal("expected unsupported mode to return an error") + } + if !strings.Contains(err.Error(), "unsupported mod") { + t.Fatalf("unexpected error: %v", err) + } +} diff --git a/core/runner.go b/core/runner.go index 7449904..b74bfb3 100644 --- a/core/runner.go +++ b/core/runner.go @@ -51,6 +51,17 @@ func (r *Runner) RunWithContext(ctx context.Context) error { if ctx == nil { ctx = context.Background() } + if r.Mod == "" { + r.Mod = ModBomb + } + switch r.Mod { + case ModSniper, ModBomb, ModPitchFork: + default: + return fmt.Errorf("unsupported mod %q, want clusterbomb, pitchfork, or sniper", r.Mod) + } + if r.Mod == ModPitchFork && r.Auths == nil { + return fmt.Errorf("pitchfork mode requires auth, please set -a/-A") + } go r.OutputHandler() r.Pool, _ = ants.NewPoolWithFunc(r.Threads, func(i interface{}) { task := i.(*pkg.Task) @@ -166,6 +177,9 @@ func (r *Runner) RunWithSniper(ctx context.Context, targets chan *Target) { } func (r *Runner) RunWithPitchfork(ctx context.Context, target chan *Target) { + if r.Auths == nil { + return + } var pairs [][]string for _, auth := range r.Auths.RunAsSlice() { username, password := parseAuthPair(auth) @@ -180,10 +194,12 @@ func (r *Runner) RunWithPitchfork(ctx context.Context, target chan *Target) { default: } targetCtx, cancel := context.WithCancel(ctx) + defer cancel() + pairLoop: for _, pair := range pairs { select { case <-targetCtx.Done(): - break + break pairLoop default: } r.add(&pkg.Task{ diff --git a/core/runner_test.go b/core/runner_test.go new file mode 100644 index 0000000..6f8191d --- /dev/null +++ b/core/runner_test.go @@ -0,0 +1,24 @@ +package core + +import ( + "context" + "testing" +) + +func TestRunnerRunWithContextRejectsUnsupportedMod(t *testing.T) { + runner := &Runner{Option: &Option{}} + runner.Mod = "not-a-mode" + + if err := runner.RunWithContext(context.Background()); err == nil { + t.Fatal("expected unsupported mode to return an error") + } +} + +func TestRunnerRunWithContextRejectsPitchforkWithoutAuth(t *testing.T) { + runner := &Runner{Option: &Option{}} + runner.Mod = ModPitchFork + + if err := runner.RunWithContext(context.Background()); err == nil { + t.Fatal("expected pitchfork without auth to return an error") + } +} diff --git a/core/target_test.go b/core/target_test.go new file mode 100644 index 0000000..432e7d1 --- /dev/null +++ b/core/target_test.go @@ -0,0 +1,27 @@ +package core + +import ( + "fmt" + "github.com/chainreactors/zombie/pkg" + "testing" +) + +func TestTarget_CheckFinger(t1 *testing.T) { + err := pkg.LoadFingers() + if err != nil { + t1.Log(err) + return + } + target := &Target{ + IP: "127.0.0.1", + Port: "6379", + Service: "redis", + } + if open := target.CheckOpen(); open { + fmt.Println("Open") + } + + if matched := target.CheckFinger(); matched { + fmt.Println("Matched") + } +} diff --git a/example/template/main.go b/example/template/main.go index a45adeb..e001ac7 100644 --- a/example/template/main.go +++ b/example/template/main.go @@ -3,7 +3,7 @@ package main import ( "fmt" "github.com/chainreactors/logs" - "github.com/chainreactors/neutron/common" + neutronlogs "github.com/chainreactors/neutron/logs" "github.com/chainreactors/neutron/templates" "github.com/chainreactors/utils/iutils" "github.com/jessevdk/go-flags" @@ -17,8 +17,8 @@ type Option struct { } func init() { - common.NeutronLog = logs.Log - logs.Log.SetLevel(logs.Debug) + neutronlogs.SetLevel(neutronlogs.LevelDebug) + logs.Log.SetLevel(logs.DebugLevel) } func main() { diff --git a/external/grdp/core/io_test.go b/external/grdp/core/io_test.go new file mode 100644 index 0000000..82f8205 --- /dev/null +++ b/external/grdp/core/io_test.go @@ -0,0 +1,19 @@ +package core_test + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/chainreactors/zombie/external/grdp/core" +) + +func TestWriteUInt16LE(t *testing.T) { + buff := &bytes.Buffer{} + core.WriteUInt32LE(66538, buff) + result := hex.EncodeToString(buff.Bytes()) + expected := "ea030100" + if result != expected { + t.Error(result, "not equals to", expected) + } +} diff --git a/external/grdp/core/rle_test.go b/external/grdp/core/rle_test.go new file mode 100644 index 0000000..192edfd --- /dev/null +++ b/external/grdp/core/rle_test.go @@ -0,0 +1,10 @@ +// rle_test.go +package core + +//func TestSum() { +// input := []byte{ +// 192, 44, 200, 8, 132, 200, 8, 200, 8, 200, 8, 200, 8, 0, 19, 132, 232, 8, 12, 50, 142, 66, 77, 58, 208, 59, 225, 25, 1, 0, 0, 0, 0, 0, 0, 0, 132, 139, 33, 142, 66, 142, 66, 142, 66, 208, 59, 4, 43, 1, 0, 0, 0, 0, 0, 0, 0, 132, 203, 41, 142, 66, 142, 66, 142, 66, 208, 59, 96, 0, 1, 0, 0, 0, 0, 0, 0, 0, 132, 9, 17, 142, 66, 142, 66, 142, 66, 208, 59, 230, 27, 1, 0, 0, 0, 0, 0, 0, 0, 132, 200, 8, 9, 17, 139, 33, 74, 25, 243, 133, 14, 200, 8, 132, 200, 8, 200, 8, 200, 8, 200, 8, +// } +// out := decompress(input, 64, 64, 2) +// fmt.Println(out) +//} diff --git a/external/grdp/grdp_test.go b/external/grdp/grdp_test.go new file mode 100644 index 0000000..7963908 --- /dev/null +++ b/external/grdp/grdp_test.go @@ -0,0 +1,47 @@ +package grdp + +import ( + "fmt" + "github.com/chainreactors/zombie/external/grdp/glog" + "testing" +) + +func testrdp(target string) { + domain := "" + username := "administrator" + password := "zaq1@WSX" + //target = "180.102.17.30:3389" + var err error + g := NewClient(target, glog.NONE) + //SSL协议登录测试 + err = g.loginForSSL(domain, username, password) + if err == nil { + fmt.Println("Login Success") + return + } + if err.Error() != "PROTOCOL_RDP" { + fmt.Println("Login Error:", err) + return + } + //RDP协议登录测试 + err = g.loginForRDP(domain, username, password) + if err == nil { + fmt.Println("Login Success") + return + } else { + fmt.Println("Login Error:", err) + return + } +} + +func TestName(t *testing.T) { + targetArr := []string{ + //"50.57.49.172:3389", + //"20.49.22.250:3389", + "192.168.217.166:3389", + } + for _, target := range targetArr { + fmt.Println(target) + testrdp(target) + } +} diff --git a/external/grdp/protocol/nla/encode_test.go b/external/grdp/protocol/nla/encode_test.go new file mode 100644 index 0000000..a0774c2 --- /dev/null +++ b/external/grdp/protocol/nla/encode_test.go @@ -0,0 +1,32 @@ +package nla_test + +import ( + "encoding/hex" + "testing" + + "github.com/chainreactors/zombie/external/grdp/protocol/nla" +) + +func TestNTOWFv2(t *testing.T) { + res := hex.EncodeToString(nla.NTOWFv2("", "", "")) + expected := "f4c1a15dd59d4da9bd595599220d971a" + if res != expected { + t.Error(res, "not equal to", expected) + } + + res = hex.EncodeToString(nla.NTOWFv2("user", "pwd", "dom")) + expected = "652feb8208b3a8a6264c9c5d5b820979" + if res != expected { + t.Error(res, "not equal to", expected) + } +} + +func TestRC4K(t *testing.T) { + key, _ := hex.DecodeString("55638e834ce774c100637f197bc0683f") + src, _ := hex.DecodeString("177d16086dd3f06fa8d594e3bad005b7") + res := hex.EncodeToString(nla.RC4K(key, src)) + expected := "f5ab375222707a492bd5a90705d96d1d" + if res != expected { + t.Error(res, "not equal to", expected) + } +} diff --git a/external/grdp/protocol/nla/ntlm_test.go b/external/grdp/protocol/nla/ntlm_test.go new file mode 100644 index 0000000..dab06ea --- /dev/null +++ b/external/grdp/protocol/nla/ntlm_test.go @@ -0,0 +1,53 @@ +package nla_test + +//func TestNewNegotiateMessage(t *testing.T) { +// ntlm := nla.NewNTLMv2("", "", "") +// negoMsg := ntlm.GetNegotiateMessage() +// buff := &bytes.Buffer{} +// struc.Pack(buff, negoMsg) +// +// result := hex.EncodeToString(buff.Bytes()) +// expected := "4e544c4d535350000100000035820860000000000000000000000000000000000000000000000000" +// +// if result != expected { +// t.Error(result, " not equals to", expected) +// } +//} +// +//func TestNTLMv2_ComputeResponse(t *testing.T) { +// ntlm := nla.NewNTLMv2("", "", "") +// +// ResponseKeyNT, _ := hex.DecodeString("39e32c766260586a9036f1ceb04c3007") +// ResponseKeyLM, _ := hex.DecodeString("39e32c766260586a9036f1ceb04c3007") +// ServerChallenge, _ := hex.DecodeString("adcb9d1c8d4a5ed8") +// ClienChallenge, _ := hex.DecodeString("1a78bed8e5d5efa7") +// Timestamp, _ := hex.DecodeString("a02f44f01267d501") +// ServerName, _ := hex.DecodeString("02001e00570049004e002d00460037005200410041004d004100500034004a00430001001e00570049004e002d00460037005200410041004d004100500034004a00430004001e00570049004e002d00460037005200410041004d004100500034004a00430003001e00570049004e002d00460037005200410041004d004100500034004a00430007000800a02f44f01267d50100000000") +// +// NtChallengeResponse, LmChallengeResponse, SessionBaseKey := ntlm.ComputeResponse(ResponseKeyNT, ResponseKeyLM, ServerChallenge, ClienChallenge, Timestamp, ServerName) +// +// ntChallRespExpected := "4e7316531937d2fc91e7230853844b890101000000000000a02f44f01267d5011a78bed8e5d5efa70000000002001e00570049004e002d00460037005200410041004d004100500034004a00430001001e00570049004e002d00460037005200410041004d004100500034004a00430004001e00570049004e002d00460037005200410041004d004100500034004a00430003001e00570049004e002d00460037005200410041004d004100500034004a00430007000800a02f44f01267d50100000000" +// lmChallRespExpected := "d4dc6edc0c37dd70f69b5c4f05a615661a78bed8e5d5efa7" +// sessBaseKeyExpected := "034009be89a0507b2bd6d28e966e1dab" +// +// if hex.EncodeToString(NtChallengeResponse) != ntChallRespExpected { +// t.Error("NtChallengeResponse incorrect") +// } +// +// if hex.EncodeToString(LmChallengeResponse) != lmChallRespExpected { +// t.Error("LmChallengeResponse incorrect") +// } +// +// if hex.EncodeToString(SessionBaseKey) != sessBaseKeyExpected { +// t.Error("SessionBaseKey incorrect") +// } +//} +// +//func TestSIGNKEY(t *testing.T) { +// exportedSessionKey, _ := hex.DecodeString("be32c3c56ea6683200a35329d67880c3") +// result := hex.EncodeToString(nla.SIGNKEY(exportedSessionKey, true)) +// expected := "79b4f9a4113230f378a0af99f784adae" +// if result != expected { +// t.Error(result, "not equal to", expected) +// } +//} diff --git a/external/grdp/protocol/t125/ber/ber.go b/external/grdp/protocol/t125/ber/ber.go index adaa6bc..3788d23 100644 --- a/external/grdp/protocol/t125/ber/ber.go +++ b/external/grdp/protocol/t125/ber/ber.go @@ -114,7 +114,7 @@ func ReadInteger(r io.Reader) (int, error) { case 3: integer1, _ := core.ReadUInt8(r) integer2, _ := core.ReadUint16BE(r) - return int(integer2) + int(integer1<<16), nil + return int(integer2) + (int(integer1) << 16), nil case 4: num, _ := core.ReadUInt32BE(r) return int(num), nil diff --git a/external/grdp/protocol/t125/ber/ber_test.go b/external/grdp/protocol/t125/ber/ber_test.go new file mode 100644 index 0000000..c757ebc --- /dev/null +++ b/external/grdp/protocol/t125/ber/ber_test.go @@ -0,0 +1,16 @@ +package ber + +import ( + "bytes" + "testing" +) + +func TestReadIntegerThreeBytes(t *testing.T) { + got, err := ReadInteger(bytes.NewReader([]byte{TAG_INTEGER, 0x03, 0x01, 0x02, 0x03})) + if err != nil { + t.Fatal(err) + } + if got != 0x010203 { + t.Fatalf("expected 0x010203, got %#x", got) + } +} diff --git a/go.mod b/go.mod index b00bfbc..51099f2 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,11 @@ go 1.11 require ( github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 github.com/chainreactors/files v0.0.0-20240716182835-7884ee1e77f0 - github.com/chainreactors/fingers v1.1.2-0.20260203043619-4f90dd60c787 - github.com/chainreactors/logs v0.0.0-20241030063019-8ca66a3ee307 - github.com/chainreactors/neutron v0.0.0-20260203032004-95c9e8431214 - github.com/chainreactors/parsers v0.0.0-20250225073555-ab576124d61f - github.com/chainreactors/utils v0.0.0-20250831165528-f06246b0f311 + github.com/chainreactors/fingers v1.2.0 + github.com/chainreactors/logs v0.0.0-20260508055944-c678762ed15c + github.com/chainreactors/neutron v0.0.1 + github.com/chainreactors/parsers v0.0.0-20260516032912-27875cfb3372 + github.com/chainreactors/utils v0.0.0-20260507101628-fd69d955ae21 github.com/chainreactors/words v0.0.0-20241002061906-25d8893158d9 github.com/denisenkom/go-mssqldb v0.9.0 github.com/eclipse/paho.mqtt.golang v1.4.3 diff --git a/go.sum b/go.sum index 28f6876..80dd456 100644 --- a/go.sum +++ b/go.sum @@ -92,20 +92,23 @@ github.com/chainreactors/files v0.0.0-20231123083421-cea5b4ad18a8/go.mod h1:/Xa9 github.com/chainreactors/files v0.0.0-20240716182835-7884ee1e77f0 h1:cU3sGEODXZsUZGBXfnz0nyxF6+37vA+ZGDx6L/FKN4o= github.com/chainreactors/files v0.0.0-20240716182835-7884ee1e77f0/go.mod h1:NSxGNMRWryAyrDzZpVwmujI22wbGw6c52bQOd5zEvyU= github.com/chainreactors/fingers v0.0.0-20240702104653-a66e34aa41df/go.mod h1:l8AO6ZbIL8WQ8PkihCK/MD6Iww/O+LY/osAhRJjThs4= -github.com/chainreactors/fingers v1.1.2-0.20260203043619-4f90dd60c787 h1:y5iwL9f4yWGgju7bXfogIyViV4ArQu6ZjjlKf05zVSw= -github.com/chainreactors/fingers v1.1.2-0.20260203043619-4f90dd60c787/go.mod h1:owlti2VYK2YpnLqdZ1HWACK4upuyFgjDOfSdm36pAjw= +github.com/chainreactors/fingers v1.2.0 h1:x699lshOI8gAsoedk/QQDtpMPA8mXfcKWmUgQ292UbM= +github.com/chainreactors/fingers v1.2.0/go.mod h1:owlti2VYK2YpnLqdZ1HWACK4upuyFgjDOfSdm36pAjw= github.com/chainreactors/logs v0.0.0-20240207121836-c946f072f81f/go.mod h1:6Mv6W70JrtL6VClulZhmMRZnoYpcTahcDTKLMNEjK0o= -github.com/chainreactors/logs v0.0.0-20241030063019-8ca66a3ee307 h1:gZuBqpqwbn9WR8f/vfn8lf6tjeWcM+ajLXAL3SkOtQs= github.com/chainreactors/logs v0.0.0-20241030063019-8ca66a3ee307/go.mod h1:6Mv6W70JrtL6VClulZhmMRZnoYpcTahcDTKLMNEjK0o= -github.com/chainreactors/neutron v0.0.0-20260203032004-95c9e8431214 h1:jCYHc6bfMhmXZYcwlkCLO5HAmNxEfnNHKnph7/Pd6iE= +github.com/chainreactors/logs v0.0.0-20260508055944-c678762ed15c h1:6Net2Mgo/qo6ADFBZJWWScKuMfZ0rbzLqSCVDuLKFdc= +github.com/chainreactors/logs v0.0.0-20260508055944-c678762ed15c/go.mod h1:VrXmYPbNN5AVoo1sc5aeyPVBYqubMdb3KO/tn5rRZpo= github.com/chainreactors/neutron v0.0.0-20260203032004-95c9e8431214/go.mod h1:CktNFh1I1sNwFI9Ml+t0X/uuEoO4eAW5Dmb/cuzV8BY= -github.com/chainreactors/parsers v0.0.0-20250225073555-ab576124d61f h1:SfLaU4TMVjloWhLnbajJWtITX/QH9HzS6mV+uNSMpsc= -github.com/chainreactors/parsers v0.0.0-20250225073555-ab576124d61f/go.mod h1:7rXdYz6jrdjF0WUH1ICcAXKIKKjKmJo2PU8u43V7jkA= +github.com/chainreactors/neutron v0.0.1 h1:3FBtqORuCfAzeo5I+Ur004F1kMDPsuH78bwAGRLCMJ8= +github.com/chainreactors/neutron v0.0.1/go.mod h1:fvrw6Z8gXqYkZVIjl04HBoQ7LW7ptiBcUBXxJZWn0n0= +github.com/chainreactors/parsers v0.0.0-20260516032912-27875cfb3372 h1:tfymgfGmnQGrcRyV0hJ4cghQ0D86RV7/x/qb5khizxU= +github.com/chainreactors/parsers v0.0.0-20260516032912-27875cfb3372/go.mod h1:HQ859e6u07EnwdNRjIUSpiUwzVyy7Oa/edgj9aT5wCg= github.com/chainreactors/utils v0.0.0-20240528085651-ba1b255482c1/go.mod h1:JA4eiQZm+7AsfjXBcIzIdVKBEhDCb16eNtWFCGTxlvs= github.com/chainreactors/utils v0.0.0-20240704062557-662d623b74f4/go.mod h1:JA4eiQZm+7AsfjXBcIzIdVKBEhDCb16eNtWFCGTxlvs= github.com/chainreactors/utils v0.0.0-20240716182459-e85f2b01ee16/go.mod h1:LajXuvESQwP+qCMAvlcoSXppQCjuLlBrnQpu9XQ1HtU= -github.com/chainreactors/utils v0.0.0-20250831165528-f06246b0f311 h1:kyN5csx/tbAJ426vrLswXpOKeVVTVrDpYyAxT1+WrZE= github.com/chainreactors/utils v0.0.0-20250831165528-f06246b0f311/go.mod h1:LajXuvESQwP+qCMAvlcoSXppQCjuLlBrnQpu9XQ1HtU= +github.com/chainreactors/utils v0.0.0-20260507101628-fd69d955ae21 h1:8Vf3BIolSLmZFeK8xYqKBDZi88g2iU9dtVIKohEFPW4= +github.com/chainreactors/utils v0.0.0-20260507101628-fd69d955ae21/go.mod h1:LajXuvESQwP+qCMAvlcoSXppQCjuLlBrnQpu9XQ1HtU= github.com/chainreactors/words v0.0.0-20241002061906-25d8893158d9 h1:BXaPgD1pfBPvFSy9DHZDxW0bHX96ONZT6HW+WbXlZCk= github.com/chainreactors/words v0.0.0-20241002061906-25d8893158d9/go.mod h1:zfz367PUmyaX6oAqV9SktVqyRXKlEh0sel9Wsq9dd2c= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -355,6 +358,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lcvvvv/logs v0.0.3 h1:5cuH/GnJANnUAMHoA8p/3ryTlYzhxdk4cZ0d+YZPgHE= +github.com/lcvvvv/logs v0.0.3/go.mod h1:C94n/NhKNNzhUnWIl2XlDO0tpSPStUXNm4QuVoK1Mo4= github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8= github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc= diff --git a/pkg/utils.go b/pkg/utils.go index 80eb9ed..31c2f1d 100644 --- a/pkg/utils.go +++ b/pkg/utils.go @@ -62,8 +62,9 @@ func RandomUA() string { func SplitUserDomain(user string) (string, string) { var domain string if strings.Contains(user, "/") { - user = strings.Split(user, "/")[1] - domain = strings.Split(user, "/")[0] + parts := strings.SplitN(user, "/", 2) + domain = parts[0] + user = parts[1] } return user, domain } diff --git a/pkg/utils_test.go b/pkg/utils_test.go new file mode 100644 index 0000000..367d416 --- /dev/null +++ b/pkg/utils_test.go @@ -0,0 +1,17 @@ +package pkg + +import "testing" + +func TestSplitUserDomain(t *testing.T) { + user, domain := SplitUserDomain("DOMAIN/user") + if user != "user" || domain != "DOMAIN" { + t.Fatalf("unexpected split result: user=%q domain=%q", user, domain) + } +} + +func TestSplitUserDomainWithoutDomain(t *testing.T) { + user, domain := SplitUserDomain("user") + if user != "user" || domain != "" { + t.Fatalf("unexpected split result: user=%q domain=%q", user, domain) + } +} diff --git a/plugin/neutron/neutron.go b/plugin/neutron/neutron.go index b08815d..42eca57 100644 --- a/plugin/neutron/neutron.go +++ b/plugin/neutron/neutron.go @@ -3,15 +3,14 @@ package neutron import ( "errors" "fmt" - "github.com/chainreactors/logs" - "github.com/chainreactors/neutron/common" + neutronlogs "github.com/chainreactors/neutron/logs" templates "github.com/chainreactors/neutron/templates" "github.com/chainreactors/utils/iutils" "github.com/chainreactors/zombie/pkg" ) func init() { - common.NeutronLog = logs.NewLogger(logs.Level(9)) + neutronlogs.SetLevel(neutronlogs.LevelError) } type NeutronPlugin struct { From acf97484864c2658b1ddcb942bd8ee0c9cd435d8 Mon Sep 17 00:00:00 2001 From: M09Ic Date: Mon, 18 May 2026 01:44:07 +0800 Subject: [PATCH 2/2] Replace archived files dependency --- core/generator.go | 4 ++-- core/options.go | 11 ++++++----- core/options_test.go | 33 +++++++++++++++++++++++++++++++++ core/runner.go | 4 ++-- go.mod | 5 ++--- go.sum | 22 +++++----------------- 6 files changed, 50 insertions(+), 29 deletions(-) diff --git a/core/generator.go b/core/generator.go index de37986..b7e4d3f 100644 --- a/core/generator.go +++ b/core/generator.go @@ -2,7 +2,7 @@ package core import ( "errors" - "github.com/chainreactors/files" + "github.com/chainreactors/utils/fileutils" "github.com/chainreactors/words" "github.com/chainreactors/zombie/pkg" "io/ioutil" @@ -113,7 +113,7 @@ func (g *Generator) SetFile(filename string) error { } func (g *Generator) SetRuleFile(filename string) error { - if files.IsExist(filename) { + if fileutils.IsExist(filename) { g.RuleFilename = filename content, err := ioutil.ReadFile(filename) if err != nil { diff --git a/core/options.go b/core/options.go index f29eaae..fc0c7df 100644 --- a/core/options.go +++ b/core/options.go @@ -4,9 +4,9 @@ import ( "encoding/json" "errors" "fmt" - "github.com/chainreactors/files" "github.com/chainreactors/logs" "github.com/chainreactors/utils" + "github.com/chainreactors/utils/fileutils" "github.com/chainreactors/zombie/pkg" "io/ioutil" "strings" @@ -98,16 +98,17 @@ func (opt *Option) Prepare() (*Runner, error) { var err error var targets []*Target - var file *files.File + var file *fileutils.File var outfunc func(string) if opt.OutputFile != "" { - file, err = files.NewFile(opt.OutputFile, false, false, true) + file, err = fileutils.NewFile(opt.OutputFile, fileutils.ModeAppend, false, false) if err != nil { return nil, err } outfunc = func(s string) { - file.SafeWrite(s) - file.SafeSync() + if err := file.SyncWrite(s); err != nil { + logs.Log.Warn(fmt.Sprintf("write output file failed: %v", err)) + } } } diff --git a/core/options_test.go b/core/options_test.go index 4484a7f..f4aca61 100644 --- a/core/options_test.go +++ b/core/options_test.go @@ -54,3 +54,36 @@ func TestOptionValidateRequiresPitchforkAuth(t *testing.T) { t.Fatalf("expected pitchfork with auth to pass validation: %v", err) } } + +func TestOptionPrepareOutputFileWriter(t *testing.T) { + output := filepath.Join(t.TempDir(), "results.txt") + opt := &Option{} + opt.IP = []string{"127.0.0.1"} + opt.ServiceName = "redis" + opt.OutputFile = output + opt.Mod = ModSniper + + runner, err := opt.Prepare() + if err != nil { + t.Fatal(err) + } + if runner.File == nil { + t.Fatal("expected output file writer") + } + if runner.OutFunc == nil { + t.Fatal("expected output function") + } + + runner.OutFunc("ok\n") + if err := runner.File.Close(); err != nil { + t.Fatal(err) + } + + got, err := os.ReadFile(output) + if err != nil { + t.Fatal(err) + } + if string(got) != "ok\n" { + t.Fatalf("unexpected output file content: %q", string(got)) + } +} diff --git a/core/runner.go b/core/runner.go index b74bfb3..d2af5d1 100644 --- a/core/runner.go +++ b/core/runner.go @@ -3,10 +3,10 @@ package core import ( "context" "fmt" - "github.com/chainreactors/files" "github.com/chainreactors/logs" "github.com/chainreactors/parsers" "github.com/chainreactors/utils" + "github.com/chainreactors/utils/fileutils" "github.com/chainreactors/utils/iutils" "github.com/chainreactors/zombie/pkg" "github.com/panjf2000/ants/v2" @@ -37,7 +37,7 @@ type Runner struct { Targets []*Target Services []string OutputCh chan *pkg.Result - File *files.File + File *fileutils.File OutFunc func(string) FirstOnly bool Pool *ants.PoolWithFunc diff --git a/go.mod b/go.mod index 51099f2..2e0a53d 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,10 @@ go 1.11 require ( github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 - github.com/chainreactors/files v0.0.0-20240716182835-7884ee1e77f0 - github.com/chainreactors/fingers v1.2.0 + github.com/chainreactors/fingers v1.2.1-0.20260517173939-651a8e7ceac0 github.com/chainreactors/logs v0.0.0-20260508055944-c678762ed15c github.com/chainreactors/neutron v0.0.1 - github.com/chainreactors/parsers v0.0.0-20260516032912-27875cfb3372 + github.com/chainreactors/parsers v0.0.0-20260517174207-da1ebd0e7f68 github.com/chainreactors/utils v0.0.0-20260507101628-fd69d955ae21 github.com/chainreactors/words v0.0.0-20241002061906-25d8893158d9 github.com/denisenkom/go-mssqldb v0.9.0 diff --git a/go.sum b/go.sum index 80dd456..b585ec2 100644 --- a/go.sum +++ b/go.sum @@ -87,26 +87,15 @@ github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chainreactors/files v0.0.0-20231102192550-a652458cee26/go.mod h1:/Xa9YXhjBlaC33JTD6ZTJFig6pcplak2IDcovf42/6A= -github.com/chainreactors/files v0.0.0-20231123083421-cea5b4ad18a8/go.mod h1:/Xa9YXhjBlaC33JTD6ZTJFig6pcplak2IDcovf42/6A= -github.com/chainreactors/files v0.0.0-20240716182835-7884ee1e77f0 h1:cU3sGEODXZsUZGBXfnz0nyxF6+37vA+ZGDx6L/FKN4o= -github.com/chainreactors/files v0.0.0-20240716182835-7884ee1e77f0/go.mod h1:NSxGNMRWryAyrDzZpVwmujI22wbGw6c52bQOd5zEvyU= -github.com/chainreactors/fingers v0.0.0-20240702104653-a66e34aa41df/go.mod h1:l8AO6ZbIL8WQ8PkihCK/MD6Iww/O+LY/osAhRJjThs4= -github.com/chainreactors/fingers v1.2.0 h1:x699lshOI8gAsoedk/QQDtpMPA8mXfcKWmUgQ292UbM= -github.com/chainreactors/fingers v1.2.0/go.mod h1:owlti2VYK2YpnLqdZ1HWACK4upuyFgjDOfSdm36pAjw= -github.com/chainreactors/logs v0.0.0-20240207121836-c946f072f81f/go.mod h1:6Mv6W70JrtL6VClulZhmMRZnoYpcTahcDTKLMNEjK0o= -github.com/chainreactors/logs v0.0.0-20241030063019-8ca66a3ee307/go.mod h1:6Mv6W70JrtL6VClulZhmMRZnoYpcTahcDTKLMNEjK0o= +github.com/chainreactors/fingers v1.2.1-0.20260517173939-651a8e7ceac0 h1:lXeukDJg9QQKAFuxLwfmyZzQPnZ0s4UtmoRnvWbhAn8= +github.com/chainreactors/fingers v1.2.1-0.20260517173939-651a8e7ceac0/go.mod h1:dEsa73t8NP7DacLCQOKa+7bKOlAorOyLxXjCMPPn+VU= github.com/chainreactors/logs v0.0.0-20260508055944-c678762ed15c h1:6Net2Mgo/qo6ADFBZJWWScKuMfZ0rbzLqSCVDuLKFdc= github.com/chainreactors/logs v0.0.0-20260508055944-c678762ed15c/go.mod h1:VrXmYPbNN5AVoo1sc5aeyPVBYqubMdb3KO/tn5rRZpo= -github.com/chainreactors/neutron v0.0.0-20260203032004-95c9e8431214/go.mod h1:CktNFh1I1sNwFI9Ml+t0X/uuEoO4eAW5Dmb/cuzV8BY= +github.com/chainreactors/neutron v0.0.0-20260517173800-28516c3539a3/go.mod h1:U8ZyyTVaRvqGY224XAKENQZW4M3pqM9BzMt4phZzAgc= github.com/chainreactors/neutron v0.0.1 h1:3FBtqORuCfAzeo5I+Ur004F1kMDPsuH78bwAGRLCMJ8= github.com/chainreactors/neutron v0.0.1/go.mod h1:fvrw6Z8gXqYkZVIjl04HBoQ7LW7ptiBcUBXxJZWn0n0= -github.com/chainreactors/parsers v0.0.0-20260516032912-27875cfb3372 h1:tfymgfGmnQGrcRyV0hJ4cghQ0D86RV7/x/qb5khizxU= -github.com/chainreactors/parsers v0.0.0-20260516032912-27875cfb3372/go.mod h1:HQ859e6u07EnwdNRjIUSpiUwzVyy7Oa/edgj9aT5wCg= -github.com/chainreactors/utils v0.0.0-20240528085651-ba1b255482c1/go.mod h1:JA4eiQZm+7AsfjXBcIzIdVKBEhDCb16eNtWFCGTxlvs= -github.com/chainreactors/utils v0.0.0-20240704062557-662d623b74f4/go.mod h1:JA4eiQZm+7AsfjXBcIzIdVKBEhDCb16eNtWFCGTxlvs= -github.com/chainreactors/utils v0.0.0-20240716182459-e85f2b01ee16/go.mod h1:LajXuvESQwP+qCMAvlcoSXppQCjuLlBrnQpu9XQ1HtU= -github.com/chainreactors/utils v0.0.0-20250831165528-f06246b0f311/go.mod h1:LajXuvESQwP+qCMAvlcoSXppQCjuLlBrnQpu9XQ1HtU= +github.com/chainreactors/parsers v0.0.0-20260517174207-da1ebd0e7f68 h1:WbG2RrE/a/zDTdXthEqp1Hbxryqrmxvh4ViTAvBsKb8= +github.com/chainreactors/parsers v0.0.0-20260517174207-da1ebd0e7f68/go.mod h1:Y5HpZLDOUm+hPvWfsxvVemPbe5iX0zDpimgdWRI1S/g= github.com/chainreactors/utils v0.0.0-20260507101628-fd69d955ae21 h1:8Vf3BIolSLmZFeK8xYqKBDZi88g2iU9dtVIKohEFPW4= github.com/chainreactors/utils v0.0.0-20260507101628-fd69d955ae21/go.mod h1:LajXuvESQwP+qCMAvlcoSXppQCjuLlBrnQpu9XQ1HtU= github.com/chainreactors/words v0.0.0-20241002061906-25d8893158d9 h1:BXaPgD1pfBPvFSy9DHZDxW0bHX96ONZT6HW+WbXlZCk= @@ -155,7 +144,6 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go. github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= -github.com/expr-lang/expr v1.16.9/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/facebookincubator/flog v0.0.0-20190930132826-d2511d0ce33c/go.mod h1:QGzNH9ujQ2ZUr/CjDGZGWeDAVStrWNjHeEcjJL96Nuk= github.com/facebookincubator/nvdtools v0.1.5 h1:jbmDT1nd6+k+rlvKhnkgMokrCAzHoASWE5LtHbX2qFQ= github.com/facebookincubator/nvdtools v0.1.5/go.mod h1:Kh55SAWnjckS96TBSrXI99KrEKH4iB0OJby3N8GRJO4=