Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JDWP service detection. #21

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 192 additions & 0 deletions pkg/plugins/services/jdwp/jdwp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// Copyright 2022 Praetorian Security, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package jdwp

import (
"bytes"
"encoding/binary"
"net"
"time"

"github.com/praetorian-inc/fingerprintx/pkg/plugins"
utils "github.com/praetorian-inc/fingerprintx/pkg/plugins/pluginutils"
)

type JDWPPlugin struct{}

const JDWP = "jdwp"

var (
commonJDWPPorts = map[int]struct{}{
3999: {},
5000: {},
5005: {},
8000: {},
8453: {},
8787: {},
8788: {},
9001: {},
18000: {},
}
)

type JDWPPacket struct {
Length uint32
ID uint32
Flags byte
CommandSet byte
Command byte
}

func init() {
plugins.RegisterPlugin(&JDWPPlugin{})
}

func DetectJDWPVersion(conn net.Conn, timeout time.Duration) (*plugins.ServiceJDWP, error) {
info := plugins.ServiceJDWP{}

versionRequest := JDWPPacket{
Length: 0x0B,
ID: 0x01,
Flags: 0x00,
CommandSet: 0x01,
Command: 0x01,
}

versionBuf := new(bytes.Buffer)
err := binary.Write(versionBuf, binary.BigEndian, versionRequest)
if err != nil {
return nil, err
}

response, err := utils.SendRecv(conn, versionBuf.Bytes(), timeout)
if err != nil {
return nil, err
}
if len(response) < 11 {
return nil, nil
}

var versionResponse JDWPPacket
responseBuf := bytes.NewBuffer(response)
err = binary.Read(responseBuf, binary.BigEndian, &versionResponse)
if err != nil {
return nil, err
}

if versionResponse.Length != (uint32(len((response)))) {
return nil, err
}

var descriptionLength uint32
err = binary.Read(responseBuf, binary.BigEndian, &descriptionLength)
if err != nil {
return nil, err
}
description := make([]byte, descriptionLength)
err = binary.Read(responseBuf, binary.BigEndian, &description)
if err != nil {
return nil, err
}

var jdwpMajor int32
err = binary.Read(responseBuf, binary.BigEndian, &jdwpMajor)
if err != nil {
return nil, err
}
var jdwpMinor int32
err = binary.Read(responseBuf, binary.BigEndian, &jdwpMinor)
if err != nil {
return nil, err
}

var vmVersionLength uint32
err = binary.Read(responseBuf, binary.BigEndian, &vmVersionLength)
if err != nil {
return nil, err
}
vmVersion := make([]byte, vmVersionLength)
err = binary.Read(responseBuf, binary.BigEndian, &vmVersion)
if err != nil {
return nil, err
}

var vmNameLength uint32
err = binary.Read(responseBuf, binary.BigEndian, &vmNameLength)
if err != nil {
return nil, err
}
vmName := make([]byte, vmNameLength)
err = binary.Read(responseBuf, binary.BigEndian, &vmName)
if err != nil {
return nil, err
}

info.Description = string(description)
info.JdwpMajor = jdwpMajor
info.JdwpMinor = jdwpMinor
info.VmVersion = string(vmVersion)
praetorian-thendrickson marked this conversation as resolved.
Show resolved Hide resolved
info.VmName = string(vmName)
praetorian-thendrickson marked this conversation as resolved.
Show resolved Hide resolved

return &info, nil
}

func (p *JDWPPlugin) Run(conn net.Conn, timeout time.Duration, target plugins.Target) (*plugins.Service, error) {
requestBytes := []byte{
// ascii "JDWP-Handshake"
0x4a, 0x44, 0x57, 0x50, 0x2d, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65,
}

response, err := utils.SendRecv(conn, requestBytes, timeout)
if err != nil {
return nil, err
}
if len(response) == 0 {
return nil, nil
}

if !bytes.Equal(requestBytes, response) {
return nil, nil
}

info, err := DetectJDWPVersion(conn, timeout)
if err != nil {
return nil, err
}

if info == nil {
return plugins.CreateServiceFrom(target, nil, false, "", plugins.TCP), nil
}

return plugins.CreateServiceFrom(target, info, false, info.VmVersion, plugins.TCP), nil
praetorian-thendrickson marked this conversation as resolved.
Show resolved Hide resolved

Check failure on line 174 in pkg/plugins/services/jdwp/jdwp.go

View workflow job for this annotation

GitHub Actions / golangci-lint

[golangci] reported by reviewdog 🐶 unnecessary trailing newline (whitespace) Raw Output: pkg/plugins/services/jdwp/jdwp.go:174: unnecessary trailing newline (whitespace) }
praetorian-thendrickson marked this conversation as resolved.
Show resolved Hide resolved
}

func (p *JDWPPlugin) PortPriority(port uint16) bool {
_, ok := commonJDWPPorts[int(port)]
return ok
}

func (p *JDWPPlugin) Name() string {
return JDWP
}

func (p *JDWPPlugin) Type() plugins.Protocol {
return plugins.TCP
}

func (p *JDWPPlugin) Priority() int {
return 500
}
22 changes: 22 additions & 0 deletions pkg/plugins/services/jdwp/jdwp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2022 Praetorian Security, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package jdwp

import (
"testing"
)

func TestJDWP(t *testing.T) {

Check failure on line 21 in pkg/plugins/services/jdwp/jdwp_test.go

View workflow job for this annotation

GitHub Actions / golangci-lint

[golangci] reported by reviewdog 🐶 unused-parameter: parameter 't' seems to be unused, consider removing or renaming it as _ (revive) Raw Output: pkg/plugins/services/jdwp/jdwp_test.go:21:15: unused-parameter: parameter 't' seems to be unused, consider removing or renaming it as _ (revive) func TestJDWP(t *testing.T) { ^
praetorian-thendrickson marked this conversation as resolved.
Show resolved Hide resolved
praetorian-thendrickson marked this conversation as resolved.
Show resolved Hide resolved
}
11 changes: 11 additions & 0 deletions pkg/plugins/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
ProtoIMAPS = "imaps"
ProtoIPMI = "ipmi"
ProtoIPSEC = "ipsec"
ProtoJDWP = "jdwp"
ProtoKafka = "kafka"
ProtoLDAP = "ldap"
ProtoLDAPS = "ldaps"
Expand Down Expand Up @@ -496,3 +497,13 @@
type ServiceRsync struct{}

func (e ServiceRsync) Type() string { return ProtoRsync }

type ServiceJDWP struct {
Description string `json:"description"`
JdwpMajor int32 `json:"jdwpMajor"`
JdwpMinor int32 `json:"jdwpMinor"`
VmVersion string `json:"vmVersion"`

Check failure on line 505 in pkg/plugins/types.go

View workflow job for this annotation

GitHub Actions / golangci-lint

[golangci] reported by reviewdog 🐶 var-naming: struct field VmVersion should be VMVersion (revive) Raw Output: pkg/plugins/types.go:505:2: var-naming: struct field VmVersion should be VMVersion (revive) VmVersion string `json:"vmVersion"` ^
praetorian-thendrickson marked this conversation as resolved.
Show resolved Hide resolved
VmName string `json:"vmName"`

Check failure on line 506 in pkg/plugins/types.go

View workflow job for this annotation

GitHub Actions / golangci-lint

[golangci] reported by reviewdog 🐶 var-naming: struct field VmName should be VMName (revive) Raw Output: pkg/plugins/types.go:506:2: var-naming: struct field VmName should be VMName (revive) VmName string `json:"vmName"` ^
praetorian-thendrickson marked this conversation as resolved.
Show resolved Hide resolved
}

func (e ServiceJDWP) Type() string { return ProtoJDWP }
1 change: 1 addition & 0 deletions pkg/scan/plugin_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
_ "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/imap"
_ "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/ipmi"
_ "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/ipsec"
_ "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/jdwp"
_ "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/kafka/kafkaNew"
_ "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/kafka/kafkaOld"
_ "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/ldap"
Expand Down
Loading