mirror of
https://github.com/ivre/masscanned.git
synced 2025-10-02 06:38:21 +00:00
Proto/RPC: support RPC over UDP
This commit is contained in:
parent
f28e0770f6
commit
166f121d76
3 changed files with 122 additions and 53 deletions
|
@ -37,13 +37,14 @@ mod ghost;
|
|||
use ghost::GHOST_PATTERN_SIGNATURE;
|
||||
|
||||
mod rpc;
|
||||
use rpc::RPC_CALL;
|
||||
use rpc::{RPC_CALL_TCP, RPC_CALL_UDP};
|
||||
|
||||
const PROTO_HTTP: usize = 1;
|
||||
const PROTO_STUN: usize = 2;
|
||||
const PROTO_SSH: usize = 3;
|
||||
const PROTO_GHOST: usize = 4;
|
||||
const PROTO_RPC: usize = 5;
|
||||
const PROTO_RPC_TCP: usize = 5;
|
||||
const PROTO_RPC_UDP: usize = 6;
|
||||
|
||||
struct TCPControlBlock {
|
||||
proto_state: usize,
|
||||
|
@ -90,8 +91,13 @@ fn proto_init() -> Smack {
|
|||
SmackFlags::ANCHOR_BEGIN,
|
||||
);
|
||||
smack.add_pattern(
|
||||
RPC_CALL,
|
||||
PROTO_RPC,
|
||||
RPC_CALL_TCP,
|
||||
PROTO_RPC_TCP,
|
||||
SmackFlags::ANCHOR_BEGIN | SmackFlags::WILDCARDS,
|
||||
);
|
||||
smack.add_pattern(
|
||||
RPC_CALL_UDP,
|
||||
PROTO_RPC_UDP,
|
||||
SmackFlags::ANCHOR_BEGIN | SmackFlags::WILDCARDS,
|
||||
);
|
||||
smack.compile();
|
||||
|
@ -141,7 +147,8 @@ pub fn repl<'a>(
|
|||
PROTO_STUN => stun::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_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);
|
||||
None
|
||||
|
|
|
@ -22,8 +22,11 @@ use crate::client::ClientInfo;
|
|||
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)
|
||||
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*";
|
||||
// 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)]
|
||||
enum RpcState {
|
||||
|
@ -363,19 +366,10 @@ fn build_repl(pstate: ProtocolState, client_info: &ClientInfo) -> Vec<u8> {
|
|||
};
|
||||
resp.append(&mut specif_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);
|
||||
final_resp
|
||||
resp
|
||||
}
|
||||
|
||||
pub fn repl<'a>(
|
||||
pub fn repl_tcp<'a>(
|
||||
data: &'a [u8],
|
||||
_masscanned: &Masscanned,
|
||||
client_info: &ClientInfo,
|
||||
|
@ -383,6 +377,38 @@ pub fn repl<'a>(
|
|||
let mut pstate = ProtocolState::new();
|
||||
rpc_parse(&mut pstate, data);
|
||||
// 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 {
|
||||
RpcState::End => Some(build_repl(pstate, client_info)),
|
||||
_ => None,
|
||||
|
@ -427,7 +453,26 @@ mod tests {
|
|||
assert!(pstate.verif_flavor == 0);
|
||||
assert!(pstate.verif_data.len() == 0);
|
||||
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]
|
||||
|
@ -447,7 +492,7 @@ mod tests {
|
|||
assert!(pstate.verif_flavor == 0);
|
||||
assert!(pstate.verif_data.len() == 0);
|
||||
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]
|
||||
|
@ -475,7 +520,7 @@ mod tests {
|
|||
assert!(pstate.verif_flavor == 0);
|
||||
assert!(pstate.verif_data.len() == 0);
|
||||
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]
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from socket import AF_INET6
|
||||
from subprocess import check_call
|
||||
import struct
|
||||
|
@ -1223,40 +1224,45 @@ def test_ipv4_tcp_ghost():
|
|||
|
||||
@test
|
||||
def test_rpc_nmap():
|
||||
with NamedTemporaryFile(delete=False) as xml_result:
|
||||
check_call(
|
||||
[
|
||||
"nmap",
|
||||
"-n",
|
||||
"-vv",
|
||||
"-oX",
|
||||
"-",
|
||||
IPV4_ADDR,
|
||||
"-sSV",
|
||||
"-p",
|
||||
"111",
|
||||
"--script",
|
||||
"rpcinfo,rpc-grind",
|
||||
],
|
||||
stdout=xml_result,
|
||||
for scan in "SU":
|
||||
with NamedTemporaryFile(delete=False) as xml_result:
|
||||
check_call(
|
||||
[
|
||||
"nmap",
|
||||
"-n",
|
||||
"-vv",
|
||||
"-oX",
|
||||
"-",
|
||||
IPV4_ADDR,
|
||||
f"-s{scan}V",
|
||||
"-p",
|
||||
"111",
|
||||
"--script",
|
||||
"rpcinfo,rpc-grind",
|
||||
],
|
||||
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:
|
||||
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"
|
||||
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
|
||||
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
|
||||
|
@ -1276,6 +1282,17 @@ def test_rpcinfo():
|
|||
for i in range(2, 5):
|
||||
assert i in found, f"Missing version {i} in {found}"
|
||||
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():
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue