/*************************************************************************** * output.cc -- Handles the Nmap output system. This currently involves * * console-style human readable output, XML output, Script | #include #include /* Workaround for lack of namespace std on HP-UX 11.00 */ namespace std {}; using namespace std; extern NmapOps o; static const char *logtypes[LOG_NUM_FILES]=LOG_NAMES; /* Used in creating skript kiddie style output. |<-R4d! */ static void skid_output(char *s) { int i; for (i=0; s[i]; i++) /* We need a 50/50 chance here, use a random number */ if ((get_random_u8() & 0x01) == 0) /* Substitutions commented out are not known to me, but maybe look nice */ switch(s[i]) { case 'A': s[i]='4'; break; /* case 'B': s[i]='8'; break; case 'b': s[i]='6'; break; case 'c': s[i]='k'; break; case 'C': s[i]='K'; break; */ case 'e': case 'E': s[i]='3'; break; case 'i': case 'I': s[i]="!|1"[get_random_u8() % 3]; break; /* case 'k': s[i]='c'; break; case 'K': s[i]='C'; break;*/ case 'o': case 'O': s[i]='0'; break; case 's': case 'S': if (s[i+1] && !isalnum((int) s[i+1])) s[i] = 'z'; else s[i] = '$'; break; case 'z': s[i]='s'; break; case 'Z': s[i]='S'; break; } else { if (s[i] >= 'A' && s[i] <= 'Z' && (get_random_u8() % 3 == 0)) { s[i] += 'a'-'A'; /* 1/3 chance of lower-case */ } else if (s[i] >= 'a' && s[i] <= 'z' && (get_random_u8() % 3 == 0)) { s[i] -= 'a'-'A'; /* 1/3 chance of upper-case */ } } } /* Remove all "\nSF:" from fingerprints */ static char* xml_sf_convert (const char* str) { char *temp = (char *) safe_malloc(strlen(str) + 1); char *dst = temp, *src = (char *)str; char *ampptr = 0; int charcount = 0; while(*src && charcount < 2035) { /* 2048 - 14 */ if (strncmp(src, "\nSF:", 4) == 0) { src += 4; continue; } /* Needed so "&something;" is not truncated midway */ if (*src == '&') { ampptr = dst; } else if (*src == ';') { ampptr = 0; } *dst++ = *src++; charcount++; } if (ampptr != 0) { *ampptr = '\0'; } else { *dst = '\0'; } return temp; } // Creates an XML element for the information given in // serviceDeduction. It will be 0-length if none is neccessary. // returns 0 for success. static int getServiceXMLBuf(struct serviceDeductions *sd, char *xmlbuf, unsigned int xmlbuflen) { string versionxmlstring = ""; char rpcbuf[128]; char *xml_product = NULL, *xml_version = NULL, *xml_extrainfo = NULL; char *xml_hostname = NULL, *xml_ostype = NULL, *xml_devicetype = NULL; char *xml_servicefp = NULL, *xml_servicefp_temp = NULL; if (xmlbuflen < 1) return -1; xmlbuf[0] = '\0'; if (!sd->name && !sd->service_fp) return 0; if (sd->product) { xml_product = xml_convert(sd->product); versionxmlstring += " product=\""; versionxmlstring += xml_product; free(xml_product); xml_product = NULL; versionxmlstring += '\"'; } if (sd->version) { xml_version = xml_convert(sd->version); versionxmlstring += " version=\""; versionxmlstring += xml_version; free(xml_version); xml_version = NULL; versionxmlstring += '\"'; } if (sd->extrainfo) { xml_extrainfo = xml_convert(sd->extrainfo); versionxmlstring += " extrainfo=\""; versionxmlstring += xml_extrainfo; free(xml_extrainfo); xml_extrainfo = NULL; versionxmlstring += '\"'; } if (sd->hostname) { xml_hostname = xml_convert(sd->hostname); versionxmlstring += " hostname=\""; versionxmlstring += xml_hostname; free(xml_hostname); xml_hostname = NULL; versionxmlstring += '\"'; } if (sd->ostype) { xml_ostype = xml_convert(sd->ostype); versionxmlstring += " ostype=\""; versionxmlstring += xml_ostype; free(xml_ostype); xml_ostype = NULL; versionxmlstring += '\"'; } if (sd->devicetype) { xml_devicetype = xml_convert(sd->devicetype); versionxmlstring += " devicetype=\""; versionxmlstring += xml_devicetype; free(xml_devicetype); xml_devicetype = NULL; versionxmlstring += '\"'; } if (sd->service_fp) { xml_servicefp_temp = xml_convert(sd->service_fp); xml_servicefp = xml_sf_convert(xml_servicefp_temp); versionxmlstring += " servicefp=\""; versionxmlstring += xml_servicefp; free(xml_servicefp_temp); xml_servicefp_temp = NULL; free(xml_servicefp); xml_servicefp = NULL; versionxmlstring += '\"'; } if (o.rpcscan && sd->rpc_status == RPC_STATUS_GOOD_PROG) { Snprintf(rpcbuf, sizeof(rpcbuf), " rpcnum=\"%li\" lowver=\"%i\" highver=\"%i\" proto=\"rpc\"", sd->rpc_program, sd->rpc_lowver, sd->rpc_highver); } else rpcbuf[0] = '\0'; Snprintf(xmlbuf, xmlbuflen, "", sd->name? sd->name : "unknown", versionxmlstring.c_str(), (sd->service_tunnel == SERVICE_TUNNEL_SSL)? "tunnel=\"ssl\" " : "", (sd->dtype == SERVICE_DETECTION_TABLE)? "table" : "probed", sd->name_confidence, rpcbuf); return 0; } /* Print a detailed list of Nmap interfaces and routes to normal/skiddy/stdout output */ int print_iflist(void) { int numifs = 0, numroutes = 0; struct interface_info *iflist; struct sys_route *routes; pcap_if_t *p_ifaces, *p_iface_iter; NmapOutputTable *Tbl = NULL; iflist = getinterfaces(&numifs); int i; /* First let's handle interfaces ... */ if (numifs == 0) { log_write(LOG_PLAIN, "INTERFACES: NONE FOUND(!)\n"); } else { int devcol=0, shortdevcol=1, ipcol=2, typecol = 3, upcol = 4, maccol = 5; Tbl = new NmapOutputTable( numifs+1, 6 ); Tbl->addItem(0, devcol, false, "DEV", 3); Tbl->addItem(0, shortdevcol, false, "(SHORT)", 7); Tbl->addItem(0, ipcol, false, "IP/MASK", 7); Tbl->addItem(0, typecol, false, "TYPE", 4); Tbl->addItem(0, upcol, false, "UP", 2); Tbl->addItem(0, maccol, false, "MAC", 3); for(i=0; i < numifs; i++) { Tbl->addItem(i+1, devcol, false, iflist[i].devfullname); Tbl->addItemFormatted(i+1, shortdevcol, false, "(%s)", iflist[i].devname); Tbl->addItemFormatted(i+1, ipcol, false, "%s/%d", inet_ntop_ez(&(iflist[i].addr), sizeof(iflist[i].addr)), iflist[i].netmask_bits); if (iflist[i].device_type == devt_ethernet) { Tbl->addItem(i+1, typecol, false, "ethernet"); Tbl->addItemFormatted(i+1, maccol, false, "%02X:%02X:%02X:%02X:%02X:%02X", iflist[i].mac[0], iflist[i].mac[1], iflist[i].mac[2], iflist[i].mac[3], iflist[i].mac[4], iflist[i].mac[5]); } else if (iflist[i].device_type == devt_loopback) Tbl->addItem(i+1, typecol, false, "loopback"); else if (iflist[i].device_type == devt_p2p) Tbl->addItem(i+1, typecol, false, "point2point"); else Tbl->addItem(i+1, typecol, false, "other"); Tbl->addItem(i+1, upcol, false, (iflist[i].device_up? "up" : "down")); } log_write(LOG_PLAIN, "************************INTERFACES************************\n"); log_write(LOG_PLAIN, "%s\n", Tbl->printableTable(NULL)); log_flush_all(); delete Tbl; } /* Display windows device names */ if((p_ifaces = getpcapinterfaces()) != NULL && numifs > 0) { Tbl = new NmapOutputTable(numifs+1, 2); Tbl->addItem(0, 0, false, "DEV"); Tbl->addItem(0, 1, false, "WINDEVICE"); i = numifs; { //the previous interface that we printed to screen char * lastface=""; //Now print off all the interfaces, we check to make sure we dont print any duplicate interfaces. //In the case of an interface alias, iflist will have a double entry but p_iface_iter //will only have one. So we check to make sure we only print out one copy of each hardware interface. for(p_iface_iter = p_ifaces; p_iface_iter != NULL && i >= 1; i--) { if(strcmp(lastface,iflist[i-1].devname)!=0){ Tbl->addItem(i, 0, false, iflist[i-1].devname); Tbl->addItem(i, 1, false, p_iface_iter->name); p_iface_iter = p_iface_iter->next; lastface = iflist[i-1].devname; } } } log_write(LOG_PLAIN, "%s\n", Tbl->printableTable(NULL)); log_flush_all(); delete Tbl; pcap_freealldevs(p_ifaces); } /* OK -- time to handle routes */ routes = getsysroutes(&numroutes); u32 mask_nbo; u16 nbits; struct in_addr ia; if (numroutes == 0) { log_write(LOG_PLAIN, "ROUTES: NONE FOUND(!)\n"); } else { int dstcol=0, devcol=1, gwcol=2; Tbl = new NmapOutputTable( numroutes+1, 3 ); Tbl->addItem(0, dstcol, false, "DST/MASK", 8); Tbl->addItem(0, devcol, false, "DEV", 3); Tbl->addItem(0, gwcol, false, "GATEWAY", 7); for(i=0; i < numroutes; i++) { mask_nbo = htonl(routes[i].netmask); addr_mtob(&mask_nbo, sizeof(mask_nbo), &nbits); assert(nbits <= 32); ia.s_addr = routes[i].dest; Tbl->addItemFormatted(i+1, dstcol, false, "%s/%d", inet_ntoa(ia), nbits); Tbl->addItem(i+1, devcol, false, routes[i].device->devfullname); if (routes[i].gw.s_addr != 0) Tbl->addItem(i+1, gwcol, true, inet_ntoa(routes[i].gw)); } log_write(LOG_PLAIN, "**************************ROUTES**************************\n"); log_write(LOG_PLAIN, "%s\n", Tbl->printableTable(NULL)); log_flush_all(); delete Tbl; } return 0; } /* Fills in namebuf (as long as there is space in buflen) with the Name nmap normal output will use to describe the port. This takes into account to confidence level, any SSL tunneling, etc. Truncates namebuf to 0 length if there is no room.*/ static void getNmapServiceName(struct serviceDeductions *sd, int state, char *namebuf, int buflen) { char *dst = namebuf; int lenremaining = buflen; int len; if (buflen < 1) return; if (sd->service_tunnel == SERVICE_TUNNEL_SSL) { if (lenremaining < 5) goto overflow; strncpy(dst, "ssl/", lenremaining); dst += 4; lenremaining -= 4; } if (sd->name && (sd->service_tunnel != SERVICE_TUNNEL_SSL || sd->dtype == SERVICE_DETECTION_PROBED)) { if (o.servicescan && state == PORT_OPEN && sd->name_confidence <= 5) len = Snprintf(dst, lenremaining, "%s?", sd->name); else len = Snprintf(dst, lenremaining, "%s", sd->name); } else { len = Snprintf(dst, lenremaining, "%s", "unknown"); } if (len > lenremaining || len < 0) goto overflow; dst += len; lenremaining -= len; if (lenremaining < 1) goto overflow; *dst = '\0'; return; overflow: *namebuf = '\0'; } /* Prints the familiar Nmap tabular output showing the "interesting" ports found on the machine. It also handles the Machine/Greppable output and the XML output. It is pretty ugly -- in particular I should write helper functions to handle the table creation */ void printportoutput(Target *currenths, PortList *plist) { char protocol[4]; char rpcinfo[64]; char rpcmachineinfo[64]; char portinfo[64]; char xmlbuf[2560]; char grepvers[256]; char grepown[64]; char *p; const char *state; char serviceinfo[64]; char *name=NULL; int i; int first = 1; struct protoent *proto; Port *current; char hostname[1200]; struct serviceDeductions sd; NmapOutputTable *Tbl = NULL; int portcol = -1; // port or IP protocol # int statecol = -1; // port/protocol state int servicecol = -1; // service or protocol name int versioncol = -1; int reasoncol = -1; // int ownercol = -1; // Used for ident scan int colno = 0; unsigned int rowno; int numrows; int numignoredports = plist->numIgnoredPorts(); vector saved_servicefps; log_write(LOG_XML, ""); int prevstate = PORT_UNKNOWN; int istate; while ((istate = plist->nextIgnoredState(prevstate)) != PORT_UNKNOWN) { log_write(LOG_XML, "\n", statenum2str(istate), plist->getStateCounts(istate)); print_xml_state_summary(plist, istate); log_write(LOG_XML, "\n"); prevstate = istate; } if (numignoredports == plist->numports) { if (numignoredports == 0) { log_write(LOG_PLAIN, "0 ports scanned on %s\n", currenths->NameIP(hostname, sizeof(hostname))); } else { log_write(LOG_PLAIN, "%s %d scanned %s on %s %s ", (numignoredports == 1)? "The" : "All", numignoredports, (numignoredports == 1)? "port" : "ports", currenths->NameIP(hostname, sizeof(hostname)), (numignoredports == 1)? "is" : "are"); if (plist->numIgnoredStates() == 1) { log_write(LOG_PLAIN, statenum2str(plist->nextIgnoredState(PORT_UNKNOWN))); } else { prevstate = PORT_UNKNOWN; while ((istate = plist->nextIgnoredState(prevstate)) != PORT_UNKNOWN) { if (prevstate != PORT_UNKNOWN) log_write(LOG_PLAIN, " or "); log_write(LOG_PLAIN, "%s (%d)", statenum2str(istate), plist->getStateCounts(istate)); prevstate = istate; } } if(o.reason) print_state_summary(plist, STATE_REASON_EMPTY); log_write(LOG_PLAIN, "\n"); } log_write(LOG_MACHINE,"Host: %s (%s)\tStatus: Up", currenths->targetipstr(), currenths->HostName()); log_write(LOG_XML, "\n"); return; } if (o.verbose > 1 || o.debugging) { time_t tm_secs, tm_sece; struct tm *tm; char tbufs[128]; tm_secs = currenths->StartTime(); tm_sece = currenths->EndTime(); tm = localtime(&tm_secs); if (strftime(tbufs, sizeof(tbufs), "%Y-%m-%d %H:%M:%S %Z", tm) <= 0) fatal("Unable to properly format host start time"); log_write(LOG_PLAIN,"Scanned at %s for %lds\n", tbufs, tm_sece - tm_secs); } log_write(LOG_PLAIN,"Interesting %s on %s:\n", (o.ipprotscan)? "protocols" : "ports", currenths->NameIP(hostname, sizeof(hostname))); log_write(LOG_MACHINE,"Host: %s (%s)", currenths->targetipstr(), currenths->HostName()); /* Show line like: Not shown: 3995 closed ports, 514 filtered ports if appropriate (note that states are reverse-sorted by # of ports) */ prevstate = PORT_UNKNOWN; while ((istate = plist->nextIgnoredState(prevstate)) != PORT_UNKNOWN) { if (prevstate == PORT_UNKNOWN) log_write(LOG_PLAIN, "Not shown: "); else log_write(LOG_PLAIN, ", "); char desc[32]; if (o.ipprotscan) Snprintf(desc, sizeof(desc), (plist->getStateCounts(istate) == 1)? "protocol" : "protocols"); else Snprintf(desc, sizeof(desc), (plist->getStateCounts(istate) == 1)? "port" : "ports"); log_write(LOG_PLAIN, "%d %s %s", plist->getStateCounts(istate), statenum2str(istate), desc); prevstate = istate; } if (prevstate != PORT_UNKNOWN) log_write(LOG_PLAIN, "\n"); if(o.reason) print_state_summary(plist, STATE_REASON_FULL); /* OK, now it is time to deal with the service table ... */ colno = 0; portcol = colno++; statecol = colno++; servicecol = colno++; if(o.reason) reasoncol = colno++; /* if (o.identscan) ownercol = colno++; */ if (o.servicescan || o.rpcscan) versioncol = colno++; numrows = plist->numports - numignoredports; #ifndef NOLUA int scriptrows = 0; if(plist->numscriptresults > 0) scriptrows = plist->numscriptresults; numrows += scriptrows; #endif assert(numrows > 0); numrows++; // The header counts as a row Tbl = new NmapOutputTable(numrows, colno); // Lets start with the headers if (o.ipprotscan) Tbl->addItem(0, portcol, false, "PROTOCOL", 8); else Tbl->addItem(0, portcol, false, "PORT", 4); Tbl->addItem(0, statecol, false, "STATE", 5); Tbl->addItem(0, servicecol, false, "SERVICE", 7); if (versioncol > 0) Tbl->addItem(0, versioncol, false, "VERSION", 7); if(reasoncol > 0) Tbl->addItem(0, reasoncol, false, "REASON", 6); /* if (ownercol > 0) Tbl->addItem(0, ownercol, false, "OWNER", 5); */ log_write(LOG_MACHINE,"\t%s: ", (o.ipprotscan)? "Protocols" : "Ports" ); rowno = 1; if (o.ipprotscan) { current = NULL; while( (current=plist->nextPort(current, IPPROTO_IP, 0))!=NULL ) { if (!plist->isIgnoredState(current->state)) { if (!first) log_write(LOG_MACHINE,", "); else first = 0; if(o.reason) Tbl->addItem(rowno, reasoncol, true, port_reason_str(current->reason)); state = statenum2str(current->state); proto = nmap_getprotbynum(htons(current->portno)); Snprintf(portinfo, sizeof(portinfo), "%s", proto?proto->p_name: "unknown"); Tbl->addItemFormatted(rowno, portcol, false, "%d", current->portno); Tbl->addItem(rowno, statecol, true, state); Tbl->addItem(rowno, servicecol, true, portinfo); log_write(LOG_MACHINE,"%d/%s/%s/", current->portno, state, (proto)? proto->p_name : ""); log_write(LOG_XML, "portno, state, reason_str(current->reason.reason_id, SINGULAR), current->reason.ttl); if(current->reason.ip_addr.s_addr) log_write(LOG_XML, " reason_ip=\"%s\"", inet_ntoa(current->reason.ip_addr)); log_write(LOG_XML, "/>"); if (proto && proto->p_name && *proto->p_name) log_write(LOG_XML, "\n", proto->p_name); log_write(LOG_XML, "\n"); rowno++; } } } else { current = NULL; while( (current=plist->nextPort(current, TCPANDUDP, 0))!=NULL ) { if (!plist->isIgnoredState(current->state)) { if (!first) log_write(LOG_MACHINE,", "); else first = 0; strcpy(protocol,(current->proto == IPPROTO_TCP)? "tcp": "udp"); Snprintf(portinfo, sizeof(portinfo), "%d/%s", current->portno, protocol); state = statenum2str(current->state); current->getServiceDeductions(&sd); if (sd.service_fp && saved_servicefps.size() <= 8) saved_servicefps.push_back(sd.service_fp); if (o.rpcscan) { switch(sd.rpc_status) { case RPC_STATUS_UNTESTED: rpcinfo[0] = '\0'; strcpy(rpcmachineinfo, ""); break; case RPC_STATUS_UNKNOWN: strcpy(rpcinfo, "(RPC (Unknown Prog #))"); strcpy(rpcmachineinfo, "R"); break; case RPC_STATUS_NOT_RPC: rpcinfo[0] = '\0'; strcpy(rpcmachineinfo, "N"); break; case RPC_STATUS_GOOD_PROG: name = nmap_getrpcnamebynum(sd.rpc_program); Snprintf(rpcmachineinfo, sizeof(rpcmachineinfo), "(%s:%li*%i-%i)", (name)? name : "", sd.rpc_program, sd.rpc_lowver, sd.rpc_highver); if (!name) { Snprintf(rpcinfo, sizeof(rpcinfo), "(#%li (unknown) V%i-%i)", sd.rpc_program, sd.rpc_lowver, sd.rpc_highver); } else { if (sd.rpc_lowver == sd.rpc_highver) { Snprintf(rpcinfo, sizeof(rpcinfo), "(%s V%i)", name, sd.rpc_lowver); } else Snprintf(rpcinfo, sizeof(rpcinfo), "(%s V%i-%i)", name, sd.rpc_lowver, sd.rpc_highver); } break; default: fatal("Unknown rpc_status %d", sd.rpc_status); break; } Snprintf(serviceinfo, sizeof(serviceinfo), "%s%s%s", (sd.name)? sd.name : ((*rpcinfo)? "" : "unknown"), (sd.name)? " " : "", rpcinfo); } else { getNmapServiceName(&sd, current->state, serviceinfo, sizeof(serviceinfo)); rpcmachineinfo[0] = '\0'; } Tbl->addItem(rowno, portcol, true, portinfo); Tbl->addItem(rowno, statecol, false, state); Tbl->addItem(rowno, servicecol, true, serviceinfo); if(o.reason) Tbl->addItem(rowno, reasoncol, true, port_reason_str(current->reason)); /* if (current->owner) Tbl->addItem(rowno, ownercol, true, current->owner); */ if (*sd.fullversion) Tbl->addItem(rowno, versioncol, true, sd.fullversion); // How should we escape illegal chars in grepable output? // Well, a reasonably clean way would be backslash escapes // such as \/ and \\ . // But that makes it harder to pick // out fields with awk, cut, and such. So I'm gonna use the // ugly hat (fitting to grepable output) or replacing the '/' // character with '|' in the version and owner fields. Strncpy(grepvers, sd.fullversion, sizeof(grepvers) / sizeof(*grepvers)); p = grepvers; while((p = strchr(p, '/'))) { *p = '|'; p++; } if (!current->owner) *grepown = '\0'; else { Strncpy(grepown, current->owner, sizeof(grepown) / sizeof(*grepown)); p = grepown; while((p = strchr(p, '/'))) { *p = '|'; p++; } } if (!sd.name) serviceinfo[0] = '\0'; else { p = serviceinfo; while((p = strchr(p, '/'))) { *p = '|'; p++; } } log_write(LOG_MACHINE,"%d/%s/%s/%s/%s/%s/%s/", current->portno, state, protocol, grepown, serviceinfo, rpcmachineinfo, grepvers); log_write(LOG_XML, "", protocol, current->portno); log_write(LOG_XML, "reason.reason_id, SINGULAR), current->reason.ttl); if(current->reason.ip_addr.s_addr) log_write(LOG_XML, " reason_ip=\"%s\"", inet_ntoa(current->reason.ip_addr)); log_write(LOG_XML, "/>"); if (current->owner && *current->owner) { log_write(LOG_XML, "", current->owner); } if (getServiceXMLBuf(&sd, xmlbuf, sizeof(xmlbuf)) == 0) if (*xmlbuf) log_write(LOG_XML, "%s", xmlbuf); rowno++; #ifndef NOLUA if(o.script) { ScriptResults::iterator ssr_iter; for( ssr_iter = current->scriptResults.begin(); ssr_iter != current->scriptResults.end(); ssr_iter++) { char* xml_id= xml_convert((*ssr_iter).id); char* xml_scriptoutput= xml_convert((*ssr_iter).output); log_write(LOG_XML, "