---[ Phrack Magazine Volume 7, Issue 51 September 01, 1997, article 10 of 17 -------------------------[ Scanning for RPC Services --------[ halflife Remote Procedure Language is a specification for letting procedures be executable on remote machines. It is defined in rfc1831. It has a number of good traits, and if you run SunOS or Solaris, you are almost required to make use of it to some degree. Unfortunately, there are vulnerabilities in some RPC services that have caused many machines to be penetrated. Many administrators block access to portmapper (port 111) in an effort to deny external users access to their weak RPC services. Unfortunately, this is completely inadequate. This article details how trivial it is to do a scan for specific RPC program numbers. The scan can be performed relatively quickly, and in many cases will not be logged. First, a little information about RPC itself; when I refer to RPC, I am only referring to ONC RPC, and not DCE RPC. RPC is a query/reply-based system. You send an initial query with the program number you are interested in, the procedure number, any arguments, authentication, and other needed parameters. In response, you get whatever the procedure returns, and some indication of the reason for the failure if it failed. Since RPC was designed to be portable, all arguments must be translated into XDR. XDR is a data encoding language that superficially reminds me a little bit of Pascal (at least, as far as strings are concerned). If you want more information on XDR, it is defined in rfc1832. As you probably surmised by now, RPC programs are made up of various procedures. There is one procedure that always exists, it is procedure 0. This procedure accepts no arguments, and it does not return any value (think void rpcping(void)). This is how we will determine if a given port holds a given program, we will call the ping procedure! So now we have a basic idea on how to determine if a given port is running a given RPC program number. Next we need to determine which UDP ports are listening. This can be done a number of ways, but the way I am using is to connect() to the port and try write data. If nothing is there, we will (hopefully) get a PORT_UNREACH error in errno, in which case we know there is nothing on that port. In the given code, we do a udp scan, and for every listening udp port, we try to query the ping procedure of the program number we are scanning for. If we get a positive response, the program number we are looking for exists on that port and we exit. <++> RPCscan/Makefile CC=gcc PROGNAME=rpcscan CFLAGS=-c build: checkrpc.o main.o rpcserv.o udpcheck.o $(CC) -o $(PROGNAME) checkrpc.o main.o rpcserv.o udpcheck.o checkrpc.o: $(CC) $(CFLAGS) checkrpc.c main.o: $(CC) $(CFLAGS) main.c rpcserv.o: $(CC) $(CFLAGS) rpcserv.c udpcheck.o: $(CC) $(CFLAGS) udpcheck.c clean: rm -f *.o $(PROGNAME) <--> <++> RPCscan/checkrpc.c #include #include #include #include #include #include #include extern struct sockaddr_in *saddr; int check_rpc_service(long program) { int sock = RPC_ANYSOCK; CLIENT *client; struct timeval timeout; enum clnt_stat cstat; timeout.tv_sec = 10; timeout.tv_usec = 0; client = clntudp_create(saddr, program, 1, timeout, &sock); if(!client) return -1; timeout.tv_sec = 10; timeout.tv_usec = 0; cstat = RPC_TIMEDOUT; cstat = clnt_call(client, 0, xdr_void, NULL, xdr_void, NULL, timeout); if(cstat == RPC_TIMEDOUT) { timeout.tv_sec = 10; timeout.tv_usec = 0; cstat = clnt_call(client, 0, xdr_void, NULL, xdr_void, NULL, timeout); } clnt_destroy(client); close(sock); if(cstat == RPC_SUCCESS) return 1; else if(cstat == RPC_PROGVERSMISMATCH) return 1; else return 0; } <--> <++> RPCscan/main.c #include #include #include int check_udp_port(char *, u_short); int check_rpc_service(long); long get_rpc_prog_number(char *); #define HIGH_PORT 5000 #define LOW_PORT 512 main(int argc, char **argv) { int i,j; long prog; if(argc != 3) { fprintf(stderr, "%s host program\n", argv[0]); exit(0); } prog = get_rpc_prog_number(argv[2]); if(prog == -1) { fprintf(stderr, "invalid rpc program number\n"); exit(0); } printf("Scanning %s for program %d\n", argv[1], prog); for(i=LOW_PORT;i <= HIGH_PORT;i++) { if(check_udp_port(argv[1], i) > 0) { if(check_rpc_service(prog) == 1) { printf("%s is on port %u\n", argv[2], i); exit(0); } } } } <--> <++> RPCscan/rpcserv.c #include #include #include #include #include #include long get_rpc_prog_number(char *progname) { struct rpcent *r; int i=0; while(progname[i] != '\0') { if(!isdigit(progname[i])) { setrpcent(1); r = getrpcbyname(progname); endrpcent(); if(!r) return -1; else return r->r_number; } i++; } return atoi(progname); } <--> <++> RPCscan/udpcheck.c #include #include #include #include #include #include #include #include #include #include #include #include extern int h_errno; struct sockaddr_in *saddr = NULL; int check_udp_port(char *hostname, u_short port) { int s, i, sr; struct hostent *he; fd_set rset; struct timeval tv; if(!saddr) { saddr = malloc(sizeof(struct sockaddr_in)); if(!saddr) return -1; saddr->sin_family = AF_INET; saddr->sin_addr.s_addr = inet_addr(hostname); if(saddr->sin_addr.s_addr == INADDR_NONE) { sethostent(1); he = gethostbyname(hostname); if(!he) { herror("gethostbyname"); exit(1); } if(he->h_length <= sizeof(saddr->sin_addr.s_addr)) bcopy(he->h_addr, &saddr->sin_addr.s_addr, he->h_length); else bcopy(he->h_addr, &saddr->sin_addr.s_addr, sizeof(saddr->sin_addr.s_addr)); endhostent(); } } saddr->sin_port = htons(port); s = socket(AF_INET, SOCK_DGRAM, 0); if(s < 0) { perror("socket"); return -1; } i = connect(s, (struct sockaddr *)saddr, sizeof(struct sockaddr_in)); if(i < 0) { perror("connect"); return -1; } for(i=0;i < 3;i++) { write(s, "", 1); FD_ZERO(&rset); FD_SET(s, &rset); tv.tv_sec = 5; tv.tv_usec = 0; sr = select(s+1, &rset, NULL, NULL, &tv); if(sr != 1) continue; if(read(s, &sr, sizeof(sr)) < 1) { close(s); return 0; } else { close(s); return 1; } } close(s); return 1; } <--> ----[ EOF