Proto/RPC: support RPC over UDP

This commit is contained in:
Pierre Lalet 2021-12-22 18:27:02 +01:00
parent f28e0770f6
commit 166f121d76
3 changed files with 122 additions and 53 deletions

View file

@ -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

View file

@ -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,6 +366,23 @@ fn build_repl(pstate: ProtocolState, client_info: &ClientInfo) -> Vec<u8> {
};
resp.append(&mut specif_resp);
}
resp
}
pub fn repl_tcp<'a>(
data: &'a [u8],
_masscanned: &Masscanned,
client_info: &ClientInfo,
) -> Option<Vec<u8>> {
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 {
@ -372,15 +392,21 @@ fn build_repl(pstate: ProtocolState, client_info: &ClientInfo) -> Vec<u8> {
};
}
final_resp.append(&mut resp);
final_resp
Some(final_resp)
}
_ => None,
}
}
pub fn repl<'a>(
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 {
@ -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]

View file

@ -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,6 +1224,7 @@ def test_ipv4_tcp_ghost():
@test
def test_rpc_nmap():
for scan in "SU":
with NamedTemporaryFile(delete=False) as xml_result:
check_call(
[
@ -1232,7 +1234,7 @@ def test_rpc_nmap():
"-oX",
"-",
IPV4_ADDR,
"-sSV",
f"-s{scan}V",
"-p",
"111",
"--script",
@ -1250,10 +1252,14 @@ def test_rpc_nmap():
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["port"] == 111 and port["protocol"] == (
"tcp" if scan == "S" else "udp"
)
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'])}"
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
@ -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():