Skip to content

Commit

Permalink
Merge pull request #21 from praetorian-rad/jdwp-update
Browse files Browse the repository at this point in the history
Add JDWP service detection.
  • Loading branch information
praetorian-thendrickson committed Sep 4, 2023
2 parents 61fcd6d + 57de26f commit 1e17f02
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 2 deletions.
1 change: 0 additions & 1 deletion pkg/plugins/services/dns/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ func (p *UDPPlugin) Type() plugins.Protocol {
}

func (p TCPPlugin) Run(conn net.Conn, timeout time.Duration, target plugins.Target) (*plugins.Service, error) {

isDNS, err := CheckDNS(conn, timeout)
if err != nil {
return nil, err
Expand Down
191 changes: 191 additions & 0 deletions pkg/plugins/services/jdwp/jdwp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// 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)
info.VMName = string(vmName)

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
}

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
}
23 changes: 23 additions & 0 deletions pkg/plugins/services/jdwp/jdwp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// 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"
)

// tested locally against a Java process with JDWP enabled
func TestJDWP(_ *testing.T) {
}
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 @@ const (
ProtoIMAPS = "imaps"
ProtoIPMI = "ipmi"
ProtoIPSEC = "ipsec"
ProtoJDWP = "jdwp"
ProtoKafka = "kafka"
ProtoLDAP = "ldap"
ProtoLDAPS = "ldaps"
Expand Down Expand Up @@ -496,3 +497,13 @@ func (e ServiceIPMI) Type() string { return ProtoIPMI }
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"`
VMName string `json:"VMName"`
}

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
1 change: 0 additions & 1 deletion pkg/scan/simple_scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ func setupPlugins() {

// UDP Scan of the target
func (c *Config) UDPScanTarget(target plugins.Target) (*plugins.Service, error) {

// first check the default port mappings for TCP / TLS
for _, plugin := range sortedUDPPlugins {
ip := target.Address.Addr().String()
Expand Down

0 comments on commit 1e17f02

Please sign in to comment.