mirror of
https://github.com/ivre/masscanned.git
synced 2025-10-02 06:38:21 +00:00
Merge pull request #19 from p-l-/enh-rpc
Proto/RPC: support RPC over UDP
This commit is contained in:
commit
1874bc5d61
3 changed files with 122 additions and 53 deletions
|
@ -37,13 +37,14 @@ mod ghost;
|
||||||
use ghost::GHOST_PATTERN_SIGNATURE;
|
use ghost::GHOST_PATTERN_SIGNATURE;
|
||||||
|
|
||||||
mod rpc;
|
mod rpc;
|
||||||
use rpc::RPC_CALL;
|
use rpc::{RPC_CALL_TCP, RPC_CALL_UDP};
|
||||||
|
|
||||||
const PROTO_HTTP: usize = 1;
|
const PROTO_HTTP: usize = 1;
|
||||||
const PROTO_STUN: usize = 2;
|
const PROTO_STUN: usize = 2;
|
||||||
const PROTO_SSH: usize = 3;
|
const PROTO_SSH: usize = 3;
|
||||||
const PROTO_GHOST: usize = 4;
|
const PROTO_GHOST: usize = 4;
|
||||||
const PROTO_RPC: usize = 5;
|
const PROTO_RPC_TCP: usize = 5;
|
||||||
|
const PROTO_RPC_UDP: usize = 6;
|
||||||
|
|
||||||
struct TCPControlBlock {
|
struct TCPControlBlock {
|
||||||
proto_state: usize,
|
proto_state: usize,
|
||||||
|
@ -90,8 +91,13 @@ fn proto_init() -> Smack {
|
||||||
SmackFlags::ANCHOR_BEGIN,
|
SmackFlags::ANCHOR_BEGIN,
|
||||||
);
|
);
|
||||||
smack.add_pattern(
|
smack.add_pattern(
|
||||||
RPC_CALL,
|
RPC_CALL_TCP,
|
||||||
PROTO_RPC,
|
PROTO_RPC_TCP,
|
||||||
|
SmackFlags::ANCHOR_BEGIN | SmackFlags::WILDCARDS,
|
||||||
|
);
|
||||||
|
smack.add_pattern(
|
||||||
|
RPC_CALL_UDP,
|
||||||
|
PROTO_RPC_UDP,
|
||||||
SmackFlags::ANCHOR_BEGIN | SmackFlags::WILDCARDS,
|
SmackFlags::ANCHOR_BEGIN | SmackFlags::WILDCARDS,
|
||||||
);
|
);
|
||||||
smack.compile();
|
smack.compile();
|
||||||
|
@ -141,7 +147,8 @@ pub fn repl<'a>(
|
||||||
PROTO_STUN => stun::repl(data, masscanned, &mut client_info),
|
PROTO_STUN => stun::repl(data, masscanned, &mut client_info),
|
||||||
PROTO_SSH => ssh::repl(data, masscanned, &mut client_info),
|
PROTO_SSH => ssh::repl(data, masscanned, &mut client_info),
|
||||||
PROTO_GHOST => ghost::repl(data, masscanned, &mut client_info),
|
PROTO_GHOST => ghost::repl(data, masscanned, &mut client_info),
|
||||||
PROTO_RPC => rpc::repl(data, masscanned, &mut client_info),
|
PROTO_RPC_TCP => rpc::repl_tcp(data, masscanned, &mut client_info),
|
||||||
|
PROTO_RPC_UDP => rpc::repl_udp(data, masscanned, &mut client_info),
|
||||||
_ => {
|
_ => {
|
||||||
debug!("id: {}", id);
|
debug!("id: {}", id);
|
||||||
None
|
None
|
||||||
|
|
|
@ -22,8 +22,11 @@ use crate::client::ClientInfo;
|
||||||
use crate::Masscanned;
|
use crate::Masscanned;
|
||||||
|
|
||||||
// last fragment (1 bit) + fragment len (31 bits) / length XID (random) / message type: call (0) / RPC version (0-255) / Program: Portmap (99840 - 100095) / Program version (*, random versions used, see below) / / Procedure: ??? (0-255)
|
// last fragment (1 bit) + fragment len (31 bits) / length XID (random) / message type: call (0) / RPC version (0-255) / Program: Portmap (99840 - 100095) / Program version (*, random versions used, see below) / / Procedure: ??? (0-255)
|
||||||
pub const RPC_CALL: &[u8; 28] =
|
pub const RPC_CALL_TCP: &[u8; 28] =
|
||||||
b"********\x00\x00\x00\x00\x00\x00\x00*\x00\x01\x86*****\x00\x00\x00*";
|
b"********\x00\x00\x00\x00\x00\x00\x00*\x00\x01\x86*****\x00\x00\x00*";
|
||||||
|
// UDP: last fragment and fragment len are missing
|
||||||
|
pub const RPC_CALL_UDP: &[u8; 24] =
|
||||||
|
b"****\x00\x00\x00\x00\x00\x00\x00*\x00\x01\x86*****\x00\x00\x00*";
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum RpcState {
|
enum RpcState {
|
||||||
|
@ -363,19 +366,10 @@ fn build_repl(pstate: ProtocolState, client_info: &ClientInfo) -> Vec<u8> {
|
||||||
};
|
};
|
||||||
resp.append(&mut specif_resp);
|
resp.append(&mut specif_resp);
|
||||||
}
|
}
|
||||||
let length: u32 = resp.len().try_into().unwrap();
|
resp
|
||||||
let mut final_resp = Vec::<u8>::new();
|
|
||||||
for i in 0..4 {
|
|
||||||
match i {
|
|
||||||
0 => final_resp.push(get_nth_byte(length, i) | 0x80),
|
|
||||||
_ => final_resp.push(get_nth_byte(length, i)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
final_resp.append(&mut resp);
|
|
||||||
final_resp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn repl<'a>(
|
pub fn repl_tcp<'a>(
|
||||||
data: &'a [u8],
|
data: &'a [u8],
|
||||||
_masscanned: &Masscanned,
|
_masscanned: &Masscanned,
|
||||||
client_info: &ClientInfo,
|
client_info: &ClientInfo,
|
||||||
|
@ -383,6 +377,38 @@ pub fn repl<'a>(
|
||||||
let mut pstate = ProtocolState::new();
|
let mut pstate = ProtocolState::new();
|
||||||
rpc_parse(&mut pstate, data);
|
rpc_parse(&mut pstate, data);
|
||||||
// warn!("RPC {:#?}", pstate);
|
// warn!("RPC {:#?}", pstate);
|
||||||
|
let resp = match pstate.state {
|
||||||
|
RpcState::End => Some(build_repl(pstate, client_info)),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
match resp {
|
||||||
|
Some(mut resp) => {
|
||||||
|
let length: u32 = resp.len().try_into().unwrap();
|
||||||
|
let mut final_resp = Vec::<u8>::new();
|
||||||
|
for i in 0..4 {
|
||||||
|
match i {
|
||||||
|
0 => final_resp.push(get_nth_byte(length, i) | 0x80),
|
||||||
|
_ => final_resp.push(get_nth_byte(length, i)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
final_resp.append(&mut resp);
|
||||||
|
Some(final_resp)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn repl_udp<'a>(
|
||||||
|
data: &'a [u8],
|
||||||
|
_masscanned: &Masscanned,
|
||||||
|
client_info: &ClientInfo,
|
||||||
|
) -> Option<Vec<u8>> {
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
pstate.state = RpcState::Xid;
|
||||||
|
pstate.last_frag = true;
|
||||||
|
pstate.frag_len = data.len().try_into().unwrap();
|
||||||
|
rpc_parse(&mut pstate, data);
|
||||||
|
// warn!("RPC {:#?}", pstate);
|
||||||
match pstate.state {
|
match pstate.state {
|
||||||
RpcState::End => Some(build_repl(pstate, client_info)),
|
RpcState::End => Some(build_repl(pstate, client_info)),
|
||||||
_ => None,
|
_ => None,
|
||||||
|
@ -427,7 +453,26 @@ mod tests {
|
||||||
assert!(pstate.verif_flavor == 0);
|
assert!(pstate.verif_flavor == 0);
|
||||||
assert!(pstate.verif_data.len() == 0);
|
assert!(pstate.verif_data.len() == 0);
|
||||||
let resp = build_repl(pstate, &CLIENT_INFO);
|
let resp = build_repl(pstate, &CLIENT_INFO);
|
||||||
assert!(resp == b"\x80\x00\x00\x20\x72\xfe\x1d\x13\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x04");
|
assert!(resp == b"\x72\xfe\x1d\x13\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x04");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_probe_nmap_udp() {
|
||||||
|
let mut pstate = ProtocolState::new();
|
||||||
|
pstate.state = RpcState::Xid;
|
||||||
|
rpc_parse(&mut pstate, b"\x72\xfe\x1d\x13\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\x86\xa0\x00\x01\x97\x7c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00");
|
||||||
|
assert!(matches!(pstate.state, RpcState::End));
|
||||||
|
assert!(pstate.xid == 0x72fe1d13);
|
||||||
|
assert!(pstate.rpc_version == 2);
|
||||||
|
assert!(pstate.program == 100000);
|
||||||
|
assert!(pstate.prog_version == 104316);
|
||||||
|
assert!(pstate.procedure == 0);
|
||||||
|
assert!(pstate.creds_flavor == 0);
|
||||||
|
assert!(pstate.creds_data.len() == 0);
|
||||||
|
assert!(pstate.verif_flavor == 0);
|
||||||
|
assert!(pstate.verif_data.len() == 0);
|
||||||
|
let resp = build_repl(pstate, &CLIENT_INFO);
|
||||||
|
assert!(resp == b"\x72\xfe\x1d\x13\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x04");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -447,7 +492,7 @@ mod tests {
|
||||||
assert!(pstate.verif_flavor == 0);
|
assert!(pstate.verif_flavor == 0);
|
||||||
assert!(pstate.verif_data.len() == 0);
|
assert!(pstate.verif_data.len() == 0);
|
||||||
let resp = build_repl(pstate, &CLIENT_INFO);
|
let resp = build_repl(pstate, &CLIENT_INFO);
|
||||||
assert!(resp == b"\x80\x00\x00\x20\x72\xfe\x1d\x13\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x04");
|
assert!(resp == b"\x72\xfe\x1d\x13\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x04");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -475,7 +520,7 @@ mod tests {
|
||||||
assert!(pstate.verif_flavor == 0);
|
assert!(pstate.verif_flavor == 0);
|
||||||
assert!(pstate.verif_data.len() == 0);
|
assert!(pstate.verif_data.len() == 0);
|
||||||
let resp = build_repl(pstate, &CLIENT_INFO);
|
let resp = build_repl(pstate, &CLIENT_INFO);
|
||||||
assert!(resp == b"\x80\x00\x00\x20\x72\xfe\x1d\x13\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x04");
|
assert!(resp == b"\x72\xfe\x1d\x13\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x04");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
from socket import AF_INET6
|
from socket import AF_INET6
|
||||||
from subprocess import check_call
|
from subprocess import check_call
|
||||||
import struct
|
import struct
|
||||||
|
@ -1223,40 +1224,45 @@ def test_ipv4_tcp_ghost():
|
||||||
|
|
||||||
@test
|
@test
|
||||||
def test_rpc_nmap():
|
def test_rpc_nmap():
|
||||||
with NamedTemporaryFile(delete=False) as xml_result:
|
for scan in "SU":
|
||||||
check_call(
|
with NamedTemporaryFile(delete=False) as xml_result:
|
||||||
[
|
check_call(
|
||||||
"nmap",
|
[
|
||||||
"-n",
|
"nmap",
|
||||||
"-vv",
|
"-n",
|
||||||
"-oX",
|
"-vv",
|
||||||
"-",
|
"-oX",
|
||||||
IPV4_ADDR,
|
"-",
|
||||||
"-sSV",
|
IPV4_ADDR,
|
||||||
"-p",
|
f"-s{scan}V",
|
||||||
"111",
|
"-p",
|
||||||
"--script",
|
"111",
|
||||||
"rpcinfo,rpc-grind",
|
"--script",
|
||||||
],
|
"rpcinfo,rpc-grind",
|
||||||
stdout=xml_result,
|
],
|
||||||
|
stdout=xml_result,
|
||||||
|
)
|
||||||
|
with NamedTemporaryFile(delete=False, mode="w") as json_result:
|
||||||
|
DBNmap(output=json_result).store_scan(xml_result.name)
|
||||||
|
os.unlink(xml_result.name)
|
||||||
|
with open(json_result.name) as fdesc:
|
||||||
|
results = [json.loads(line) for line in fdesc]
|
||||||
|
os.unlink(json_result.name)
|
||||||
|
assert len(results) == 1, f"Expected 1 result, got {len(results)}"
|
||||||
|
result = results[0]
|
||||||
|
assert len(result["ports"]) == 1, f"Expected 1 port, got {len(result['ports'])}"
|
||||||
|
port = result["ports"][0]
|
||||||
|
assert port["port"] == 111 and port["protocol"] == (
|
||||||
|
"tcp" if scan == "S" else "udp"
|
||||||
)
|
)
|
||||||
with NamedTemporaryFile(delete=False, mode="w") as json_result:
|
assert port["service_name"] in {"rpcbind", "nfs"}
|
||||||
DBNmap(output=json_result).store_scan(xml_result.name)
|
assert port["service_extrainfo"] in {"RPC #100000", "RPC #100003"}
|
||||||
os.unlink(xml_result.name)
|
assert (
|
||||||
with open(json_result.name) as fdesc:
|
len(port["scripts"]) == 1
|
||||||
results = [json.loads(line) for line in fdesc]
|
), f"Expected 1 script, got {len(port['scripts'])}"
|
||||||
os.unlink(json_result.name)
|
script = port["scripts"][0]
|
||||||
assert len(results) == 1, f"Expected 1 result, got {len(results)}"
|
assert script["id"] == "rpcinfo", "Expected rpcinfo script, not found"
|
||||||
result = results[0]
|
assert len(script["rpcinfo"]) == 1
|
||||||
assert len(result["ports"]) == 1, f"Expected 1 port, got {len(result['ports'])}"
|
|
||||||
port = result["ports"][0]
|
|
||||||
assert port["port"] == 111 and port["protocol"] == "tcp"
|
|
||||||
assert port["service_name"] in {"rpcbind", "nfs"}
|
|
||||||
assert port["service_extrainfo"] in {"RPC #100000", "RPC #100003"}
|
|
||||||
assert len(port["scripts"]) == 1, f"Expected 1 script, got {len(port['scripts'])}"
|
|
||||||
script = port["scripts"][0]
|
|
||||||
assert script["id"] == "rpcinfo", "Expected rpcinfo script, not found"
|
|
||||||
assert len(script["rpcinfo"]) == 1
|
|
||||||
|
|
||||||
|
|
||||||
@test
|
@test
|
||||||
|
@ -1276,6 +1282,17 @@ def test_rpcinfo():
|
||||||
for i in range(2, 5):
|
for i in range(2, 5):
|
||||||
assert i in found, f"Missing version {i} in {found}"
|
assert i in found, f"Missing version {i} in {found}"
|
||||||
os.unlink(rpcout.name)
|
os.unlink(rpcout.name)
|
||||||
|
with NamedTemporaryFile(delete=False) as rpcout:
|
||||||
|
check_call(["rpcinfo", "-u", IPV4_ADDR, "100000"], stdout=rpcout)
|
||||||
|
with open(rpcout.name) as fdesc:
|
||||||
|
found = []
|
||||||
|
expr = re.compile("^program 100000 version ([0-9]) ready and waiting$")
|
||||||
|
for line in fdesc:
|
||||||
|
found.append(int(expr.search(line.strip()).group(1)))
|
||||||
|
assert len(found) == 3, f"Expected three versions, got {found}"
|
||||||
|
for i in range(2, 5):
|
||||||
|
assert i in found, f"Missing version {i} in {found}"
|
||||||
|
os.unlink(rpcout.name)
|
||||||
|
|
||||||
|
|
||||||
def test_all():
|
def test_all():
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue