Last week I was in Barcelona helping some colleagues when a client called asking for a list of “running” clients in his network. We had a VPN connection to this net and the customer itself said that “it didn’t need an accurate list, just to have an idea” so we agreed that a simple ICMP scan would be enough.
This looked like the perfect ocassion to show off my Nmap-fu and I started my Backtrack 4 virtual machine. “Don’t worry, I’ll do a simple NMAP Ping scan…”
# nmap -sP 192.168.x.x/24
To my surprise (and shame) every single IP Address appeared listed as “UP“.
FAIL.
“This can’t be. Hmmm… Do this customer have any kind of IPS/IDS solution in place?”
“Quite probably…”
My first thought was that some kind of IPS recognized the “Ping sweep” and was answering on behalf of the clients, saying all of them are up. Security by obscurity…
I tried it again at a lower pace, using the -T Nmap parameter but I got the same results. Apparently this had something to do with some packet characteristics.
At the end we used a small windows program, Angry IP Scanner, which worked like a charm.
We didn’t have neither enough info about the network infrastructure from this customer nor time to research this funny behaviour but once back in Munich I started to play with Snort and Nmap.
Snort detects this type of scan from Nmap. Here’s an excerpt of the rule:
@ snort – icmp.rules:
alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP PING NMAP"; dsize:0; itype:8; reference:arachnids,162; classtype:attempted-recon; sid:469; rev:3;)
Let’s see this in action.
Note: The parameter “–send-ip” specifies that I don’t want to use ARP queries as host detection method.
@ BT4 – Nmap PING sweep
root@bt:~# nmap -v -n -sP –send-ip 192.168.88.139
Starting Nmap 5.21 ( http://nmap.org ) at 2010-04-04 08:34 EDT
Initiating Ping Scan at 08:34
Scanning 192.168.88.139 [4 ports]
Completed Ping Scan at 08:34, 0.00s elapsed (1 total hosts)
Nmap scan report for 192.168.88.139
Host is up (0.0018s latency).
MAC Address: 00:0C:29:80:BB:D4 (VMware)
Read data files from: /usr/share/nmap
Nmap done: 1 IP address (1 host up) scanned in 0.07 seconds
Raw packets sent: 4 (152B) | Rcvd: 1 (28B)
root@ubuntu:~# tail -f /var/log/snort/alert
[...]
[**] [1:469:3] ICMP PING NMAP [**]
[Classification: Attempted Information Leak] [Priority: 2]
04/04-05:34:47.622963 192.168.88.132 -> 192.168.88.139
ICMP TTL:50 TOS:0x0 ID:3270 IpLen:20 DgmLen:28
Type:8 Code:0 ID:6629 Seq:0 ECHO
[Xref => http://www.whitehats.com/info/IDS162]
I captured the icmp packets from Nmap with tcpdump and it confirmed my suspicion: ICMP ECHO packets sent from Nmap have a null data section
This is not cool (evident signature) and I thought that even when probably there was a parameter to avoid this behaviour, it would be nice to override this in the code. So I started diving into NMAP *complex* code… here is what I found.
——– Carlos in Nmap Land ———–
@ targets.cc (massping)
static void massping(Target *hostbatch[], int num_hosts, struct scan_lists *ports) {
[...]
ultra_scan(targets, ports, PING_SCAN, &group_to);
}
@ scan_engine.cc
void ultra_scan(vector<Target *> &Targets, struct scan_lists *ports, … ) {
[...]
doAnyPings(USI);
}
doAnyPings – calls sendPingProbe(USI, hss)
3347 static void sendPingProbe(UltraScanInfo *USI, HostScanStats *hss) {
3348 if (o.debugging > 1) {
3349 char tmpbuf[64];
3350 log_write(LOG_PLAIN, “Ultrascan PING SENT to %s [%s]\n”, hss->target->targetipstr(),
3351 probespec2ascii(&hss->target->pingprobe, tmpbuf, sizeof(tmpbuf)));
3352 }
3353 if (hss->target->pingprobe.type == PS_CONNECTTCP) {
3354 sendConnectScanProbe(USI, hss, hss->target->pingprobe.pd.tcp.dport, 0,
3355 hss->nextPingSeq(true));
3356 } else if (hss->target->pingprobe.type == PS_TCP || hss->target->pingprobe.type == PS_UDP
3357 || hss->target->pingprobe.type == PS_SCTP || hss->target->pingprobe.type == PS_PROTO
3358 || hss->target->pingprobe.type == PS_ICMP) {
3359 sendIPScanProbe(USI, hss, &hss->target->pingprobe, 0, hss->nextPingSeq(true)); <—————— !!!!
3360 } else if (hss->target->pingprobe.type == PS_ARP) {
3361 sendArpScanProbe(USI, hss, 0, hss->nextPingSeq(true));
3362 } else if (USI->scantype == RPC_SCAN) {
3363 assert(0); /* TODO: fill out */
[...]
3025 static UltraProbe *sendIPScanProbe(UltraScanInfo *USI, HostScanStats *hss,
3026 const probespec *pspec, u8 tryno, u8 pingseq) {
[...]
3224 } else if (pspec->type == PS_ICMP) {
3225 u16 icmp_ident;
3226
3227 /* Some hosts do not respond to ICMP requests if the identifier is 0. */
3228 do {
3229 icmp_ident = get_random_u16();
3230 } while (icmp_ident == 0);
3231
3232 for(decoy = 0; decoy < o.numdecoys; decoy++) {
3233 packet = build_icmp_raw(&o.decoys[decoy], hss->target->v4hostip(),
3234 o.ttl, ipid, IP_TOS_DEFAULT, false,
3235 o.ipoptions, o.ipoptionslen,
3236 0, icmp_ident, pspec->pd.icmp.type, pspec->pd.icmp.code,
3237 o.extra_payload, o.extra_payload_length,
3238 &packetlen);
[...]
@ tcpip.h
550 u8 *build_icmp_raw(const struct in_addr *source, const struct in_addr *victim,
551 int ttl, u16 ipid, u8 tos, bool df,
552 u8* ipopt, int ipoptlen,
553 u16 seq, unsigned short id, u8 ptype, u8 pcode,
554 char *data, u16 datalen, u32 *packetlen);
@ NmapOps.h — were the command line parameters are parsed… If I had just started here!
243 extra_payload_length = 0;
244 extra_payload = NULL;
It looks like this parameter is alwasy NULL (0) as long as we don’t specify a –data-length parameter.
Finally I found:
@ nmap.cc
903 o.extra_payload_length = atoi(optarg); <—- it parses the command line argument
904 if (o.extra_payload_length < 0) {
905 fatal(“data-length must be greater than 0″);
906 } else if (o.extra_payload_length > 0) {
907 o.extra_payload = (char *) safe_malloc(o.extra_payload_length);
908 get_random_bytes(o.extra_payload, o.extra_payload_length);
909 }
So I just did a “dirty hack” to override the parsing of the parameter and set a payload size of 40 bytes even if no parameter is passed.
901 o.debugging++;
902 // } else if (optcmp(long_options[option_index].name, “data-length”) == 0) {
903 // o.extra_payload_length = atoi(optarg);
904 } else if (1) {
905 o.extra_payload_length = 40;
906 // if (o.extra_payload_length < 0) {
907 // fatal(“data-length must be greater than 0″);
908 // } else if (o.extra_payload_length > 0) {
909 o.extra_payload = (char *) safe_malloc(o.extra_payload_length);
910 get_random_bytes(o.extra_payload, o.extra_payload_length);
911 }
912 } else if (optcmp(long_options[option_index].name, “send-eth”) == 0) {
913 o.sendpref = PACKET_SEND_ETH_STRONG;
[...]
recompile and it’s done!
And this is what happens kids, when somebody ist bored at home and without internet access
