1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-09 14:11:29 +00:00
Files
nmap/scripts/fingerfix.cc

592 lines
18 KiB
C++

#include "nbase.h"
#include "nmap.h"
#include "osscan.h"
#include "MACLookup.h"
#include "fingerlib.h"
// attribute value length
#define AVLEN 128
void usage() {
printf("Usage: fingerdiff [filename:lineno]\n"
"If you pass a filename (e.g. ../nmap-os-db) and line number, that print will be merged with whatever you type as fingerprint data when prompted.\n\n");
exit(1);
}
typedef enum {
STR, DECNUM, HEXNUM
} SortAs;
static void sort_and_merge(struct AVal *result, char values[][AVLEN], int num, SortAs sortas) {
// sort and merge input values like 4,12,8 to 4|8|12. The sorting is
// firstly based on their length, and secondly based on their ascii
// order.
assert(num > 0);
int i, j;
char tmp[AVLEN];
int offset;
bool lt;
unsigned int val1, val2;
int base;
/* printf("MERGING %d values: ", num);
for (i = 0; i < num; i++) {
printf("%s ", values[i]);
}
printf("\n");
*/
// sort
for(i = 0; i < num; i++) {
for(j = 1; j < num - i; j++) {
lt = false;
if(sortas == STR) {
// sort as string
// string with less length is regarded as "smaller"
if((strlen(values[j-1]) > strlen(values[j])) ||
(strlen(values[j-1]) == strlen(values[j]) && strcmp(values[j-1], values[j]) > 0)) {
lt = true;
}
} else {
// sort as number
if(sortas == DECNUM) base = 10;
else if(sortas == HEXNUM) base = 16;
val1 = strtol(values[j-1], NULL, base);
val2 = strtol(values[j], NULL, base);
if(val1 > val2) lt = true;
}
if(lt) {
// printf("swap %s and %s\n", values[j-1], values[j]);
strcpy(tmp, values[j-1]);
strcpy(values[j-1], values[j]);
strcpy(values[j], tmp);
}
}
}
// merge.
offset = 0;
for(i = 0; i < num; i++) {
if(i > 0 && strcmp(values[i], tmp) == 0) {
// this is a duplicated value;
continue;
}
strcpy(tmp, values[i]);
offset += snprintf(result->value + offset, AVLEN - offset, "%s|", values[i]);
if(offset >= AVLEN) {
printf("[WARN] Attribute %s is too long and has been truncated\n", result->attribute);
return;
}
}
result->value[offset-1] = '\0'; // remove the final '|'
}
static void merge_gcd(struct AVal *result, char values[][AVLEN], int num) {
// When fingerfix gets a GCD like 0x32, it changes it to "GCD=<67".
// That seems kindof bogus. The GCD is only likely to be 0x32 or
// (in rare cases by coincidence) a small multiple of that. So I
// think it should give "GCD=32|64|96|C8|FA|12C". For the common
// case of GCD=1, still saying GCD=<7 is desirable because that is
// shorter than GCD=1|2|3|4|5|6 .
assert(num > 0);
const unsigned int LIM = 7;
int i, j;
char *p, *q, *endptr;
unsigned int val;
unsigned int curlim = 0;
bool haslim = false;
int newValueNum = 0;
char newValues[128][AVLEN];
// first let's find the limit
for(i = 0; i < num; i++) {
p = values[i];
q = strchr(p, '<');
if(q) {
val = strtol(q + 1, &endptr, 16);
if (q != p || *endptr) {
printf("[WARN] Invalid value (%s) occurs in attribute SEQ.GCD\n", p);
continue;
}
if(curlim < val) {
curlim = val;
haslim = true;
}
}
}
// Normally the limit should be the same with the LIM, since any limit
// exist in the fingerprint is supposed to be generated by this
// tool. Thus if it is not the case, print a warning.
if(haslim && curlim != LIM) {
printf("[WARN] Odd limit (%X) occurs in attribute SEQ.GCD", curlim);
if(curlim < LIM) curlim = LIM;
}
for(i = 0; i < num; i++) {
p = values[i];
q = strchr(p, '<');
if(q) continue; // let's skip those limit strings
val = strtol(p, &endptr, 16);
if (*endptr) {
printf("[WARN] Invalid value (%s) occurs in attribute SEQ.GCD\n", p);
continue;
} else if(val == 0) {
printf("[WARN] Zero value occurs in attribute SEQ.GCD\n");
}
if(haslim) {
if(val <= curlim) continue;
} else if(val <= LIM) {
curlim = LIM;
haslim = true;
continue;
}
for(j = 1; j < 7; j++)
snprintf(newValues[newValueNum++], AVLEN, "%X", val * j);
}
if(newValueNum == 0 && haslim) {
snprintf(result->value, AVLEN, "<%X", curlim);
} else if(newValueNum > 0 && !haslim) {
sort_and_merge(result, newValues, newValueNum, HEXNUM);
} else if(newValues > 0 && haslim) {
struct AVal semiresult;
int offset;
semiresult.attribute = "GCD";
sort_and_merge(&semiresult, newValues, newValueNum, HEXNUM);
// insert the limit string to the front of it
offset = snprintf(result->value, AVLEN, "<%X|%s", curlim, semiresult.value);
if(offset>=AVLEN) {
printf("[WARN] SEQ.GCD is too long and has been truncated\n");
}
} else {
result->value[0] = '\0';
}
}
/* The "R" tests designate whether a response was
received. These can be tricky because packets (probe
or response) may have been dropped on the network
between the source and target host. So in a merge
between an "N" and a "Y", the result is simply "Y".
But if we are given an "R=N|Y" (which would have been
done manually), we preserve it rather than dropping the
N. */
static void merge_response_element(const char *testname, struct AVal *result,
char values[][AVLEN], int num) {
bool yesrequired = false;
bool norequired = false;
bool foundno = false;
int i;
assert(num > 0);
// look at the values
for(i = 0; i < num; i++) {
if (strcmp(values[i], "Y") == 0)
yesrequired = true;
else if (strcmp(values[i], "N|Y") == 0 || strcmp(values[i], "Y|N") == 0)
yesrequired = norequired = true;
else if (strcmp(values[i], "N") == 0)
foundno = true;
else fatal("[ERRO] Bogus R value \"%s\" discuvered (should be N, Y, or N|Y)", values[i]);
}
// Now decide on the results
if (yesrequired && norequired)
Strncpy(result->value, "N|Y", AVLEN);
else if (yesrequired) {
Strncpy(result->value, "Y", AVLEN);
if (foundno)
printf("[WARN] Ignoring test %s.%s \"N\" value because it was found \"Y\" in another instance\n", testname, result->attribute);
}
else
Strncpy(result->value, "N", AVLEN);
return;
}
static void merge_sp_or_isr(struct AVal *result, char values[][AVLEN], int num) {
// Fingerfix should expand elements based on observed deviation.
// So if a fingerprint comes in with SP=0x9C (and that is the only
// SEQ line), that means that all 3 Nmap tries had SP=0x9C and so it
// may be OK to just use 0x9C in the reference fingerprint
// generated, or maybe expand it by 1 like 0x9B-0x9D. But if the
// fingerprint has 3 seq lines (which Nmap does when any elements in
// it differ) and they show SP values of 0x9C, 0x84, and 0xA7, you
// may want to handle them as: 1) You see 0x9C. Now your low and
// high values are 0x9c 2) You see 0x84. This makes for a low of
// 0x84 and a high of 0x9C you've seen. But you may want to double
// the size of this new range, so it is now 0x78-0xA8. Note that
// since your previous low and high values were equal, you expand in
// both directions rather than just expanding in one direction. But
// once you have developed a range, you'll start expanding only in
// the direction of the outlying value. 3) You see 0xA7. This fits
// into your range so you ignore it. 4) Suppose you then see 0xAA.
// This is 3 higher than the top of your range, so you double that
// and extend your upper range by 6 to 0x78-0xAD. It is important
// to be careful not to keep expanding already-expanded values. So
// if someone passes 0x78-0xAD in a reference FP to fingerfix, don't
// double that range again. But if you then see 0x77, you would
// change your range to 0x76-0xAD. This way we keep tight
// fingerprints where the SP doesn't differ so much, but we avoid
// having to constantly tweak fingerprints which differ
// dramatically.
assert(num > 0);
int i;
int low, high, val1, val2;
char *p, *q, *r, buf[AVLEN];
result->value[0] = '\0';
for(i = 0; i < num; i++) {
strncpy(buf, values[i], AVLEN);
p = strchr(buf, '-');
if(p) {
// an interval
*p = '\0';
val1 = (int)strtol(buf, &q, 16);
val2 = (int)strtol(p+1, &r, 16);
if(*q || *r || val1 >= val2) {
printf("[WARN] Invalid value (%s) occurs in attribute SEQ.%s\n", values[i], result->attribute);
return;
}
// do not expand an interval
if(i == 0) {
low = val1;
high = val2;
} else {
if(val1 < low) low = val1;
if(val2 > high) high = val2;
}
} else {
// a value
val1 = (int)strtol(buf, &p, 16);
if(*p) {
printf("[WARN] Invalid value (%s) occurs in attribute SEQ.%s\n", values[i], result->attribute);
return;
}
if(!val1) {
// a zero sp/isr value, this should
printf("[WARN] Zero value occurs in attribute SEQ.%s. A constant ISN sequence?\n", result->attribute);
}
if(i == 0) {
low = high = val1;
} else {
if(low == high && val1 != low) {
// expand it in both directions
if(val1 < low) {
low = val1 - (low - val1) / 2;
high = high + (high - val1) / 2;
} else {
low = low - (val1 - low) / 2;
high = high + (val1 - high) / 2;
}
if(low < 0) low = 0;
} else if(val1 < low) {
// expand in the left direction
low = val1 - (low - val1);
if(low < 0) low = 0;
} else if(val1 > high) {
// expand in the right direction
high = val1 + (val1 - high);
}
}
}
}
if(low == high && low == 0) {
snprintf(result->value, AVLEN, "0");
return;
}
if(low == high) {
// expanded it a little
low = low - 1;
if(low < 0) low = 0;
high = high + 1;
}
snprintf(result->value, AVLEN, "%X-%X", low, high);
}
int main(int argc, char *argv[]) {
FingerPrint *observedFP;
FingerPrint *resultFP;
FingerPrint *resultFPLine, *observedFPLine;
char observedFPString[8192];
char observedFPString2[8192];
char sourcefile[MAXPATHLEN];
int sourceline=-1;
char *p, *endptr;
FILE *fp;
int done = 0;
char line[512];
int i;
char resultTemplate[] = {"SEQ(SP=%GCD=%ISR=%TI=%II=%SS=%TS=)\n"
"OPS(O1=%O2=%O3=%O4=%O5=%O6=)\n"
"WIN(W1=%W2=%W3=%W4=%W5=%W6=)\n"
"ECN(R=%DF=%T=%TG=%W=%O=%CC=%Q=)\n"
"T1(R=%DF=%T=%TG=%S=%A=%F=%RD=%Q=)\n"
"T2(R=%DF=%T=%TG=%W=%S=%A=%F=%O=%RD=%Q=)\n"
"T3(R=%DF=%T=%TG=%W=%S=%A=%F=%O=%RD=%Q=)\n"
"T4(R=%DF=%T=%TG=%W=%S=%A=%F=%O=%RD=%Q=)\n"
"T5(R=%DF=%T=%TG=%W=%S=%A=%F=%O=%RD=%Q=)\n"
"T6(R=%DF=%T=%TG=%W=%S=%A=%F=%O=%RD=%Q=)\n"
"T7(R=%DF=%T=%TG=%W=%S=%A=%F=%O=%RD=%Q=)\n"
"U1(DF=%T=%TG=%TOS=%IPL=%UN=%RIPL=%RID=%RIPCK=%RUCK=%RUL=%RUD=)\n"
"IE(DFI=%T=%TG=%TOSI=%CD=%SI=%DLI=)\n"
};
if (argc != 1 && argc != 2)
usage();
observedFPString2[0] = '\0';
if (argc == 2) {
Strncpy(sourcefile, argv[1], sizeof(sourcefile));
p = strchr(sourcefile, ':');
if (!p) fatal("[ERRO] Filename must be followed by a colon and then line number");
*p++ = '\0';
if (!*p) fatal("[ERRO] Bad command-line arg");
sourceline = strtol(p, &endptr, 10);
if (*endptr) {
error("could not parse line number (trailing garbage?)");
}
fp = fopen(sourcefile, "r");
done = 0; i = 1;
while(i < sourceline) {
if (fgets(line, sizeof(line), fp) == NULL)
fatal("[ERRO] Failed to read to line %d of %s", sourceline, sourcefile);
i++;
}
if (readFP(fp, observedFPString2, sizeof(observedFPString2)) == -1)
fatal("[ERRO] Failed to read in supposed fingerprint in %s line %d\n", sourcefile, sourceline);
fclose(fp);
printf("Read in referenceFP:\n%s\n", observedFPString2);
}
observedFPString[0] = '\0';
printf("Enter the fingerprint(s) you want to fix, followed by a blank or single-dot line:\n");
if (readFP(stdin, observedFPString, sizeof(observedFPString)) == -1)
fatal("[ERRO] Failed to read in supposed fingerprint from stdin\n");
/* Now merge the observedFP and observedFP2 (if any). We want the
file version to come first as that makes a difference for things
like SEQ.SP and SEQ.ISR merging */
if (*observedFPString2) {
strncat(observedFPString2, observedFPString, sizeof(observedFPString2));
Strncpy(observedFPString, observedFPString2, sizeof(observedFPString));
}
observedFP = parse_single_fingerprint(observedFPString);
if (!observedFP) fatal("[ERRO] failed to parse the observed fingerprint you entered\n");
// printf("%s", fp2ascii(observedFP));
resultFP = parse_single_fingerprint(resultTemplate);
// printf("%s", fp2ascii(resultFP));
bool foundline;
struct AVal *resultAV, *observedAV, *tmpAV;
char values[16][AVLEN];
int avnum;
for(resultFPLine = resultFP; resultFPLine; resultFPLine = resultFPLine->next) {
// step 1:
//
// Check if this line is missing in the input fingerprint. If yes,
// replace the result line with a R=N.
foundline = false;
for(observedFPLine = observedFP; observedFPLine; observedFPLine = observedFPLine->next) {
if(observedFPLine->name && strcmp(resultFPLine->name, observedFPLine->name) == 0) {
// Found the corresponding line. If it doesn't begin with a
// R=N, then we take it as a good line.
if(observedFPLine->results &&
!(strcmp(observedFPLine->results->attribute, "R") == 0 &&
strcmp(observedFPLine->results->value, "N") == 0)) {
foundline = true;
break;
}
}
}
if(!foundline) {
// replace the fingerprint line with a R=N
free(resultFPLine->results);
tmpAV = (struct AVal *) safe_zalloc(sizeof(struct AVal));
tmpAV->attribute = "R";
strcpy(tmpAV->value, "N");
resultFPLine->results = tmpAV;
continue;
}
// step 2:
//
// For each AVal of this fingerprint line, merge all the
// counterpart values appeared in the input fingerprint.
for(resultAV = resultFPLine->results; resultAV; resultAV = resultAV->next) {
avnum = 0;
for(observedFPLine = observedFP; observedFPLine; observedFPLine = observedFPLine->next) {
if(strcmp(resultFPLine->name, observedFPLine->name) == 0) {
for(observedAV = observedFPLine->results; observedAV; observedAV = observedAV->next) {
// printf("Found %s.%s value: %s\n", observedFPLine->name, observedAV->attribute, observedAV->value);
if(strcmp(resultAV->attribute, observedAV->attribute) == 0) {
// check if we have stored the same attribute value if
// not, store it
bool stored;
char *p, *q;
p = observedAV->value;
do {
stored = false;
q = strchr(p, '|');
if(q) *q = '\0';
for(i = 0; i < avnum; i++) {
if(strcmp(values[i], p) == 0) {
stored = true;
break;
}
}
if(!stored) {
strcpy(values[avnum++], p);
}
if(q) p = q + 1;
else break;
} while (p && *p);
}
}
}
}
if(avnum == 0) {
// no value for this attribute, now handle the next attribute
continue;
}
// now we get all the values for this attribute, it's time to
// merge them. let's first handle some special attributes.
if(strcmp(resultFPLine->name, "SEQ") == 0 && strcmp(resultAV->attribute, "GCD") == 0) {
// SEQ.GCD
merge_gcd(resultAV, values, avnum);
} else if(strcmp(resultFPLine->name, "SEQ") == 0 &&
(strcmp(resultAV->attribute, "SP") == 0 ||
strcmp(resultAV->attribute, "ISR") == 0)) {
// SEQ.SP or SEQ.ISR
merge_sp_or_isr(resultAV, values, avnum);
} else if (strcmp(resultAV->attribute, "R") == 0) {
/* The "R" tests designate whether a response was
received. These can be tricky because packets (probe
or response) may have been dropped on the network
between the source and target host. So in a merge
between an "N" and a "Y", the result is simply "Y".
But if we are given an "R=N|Y" (which would have been
done manually), we preserve it rather than dropping the
N. */
merge_response_element(resultFPLine->name, resultAV, values, avnum);
} else {
// common merge
sort_and_merge(resultAV, values, avnum, STR);
}
}
// step 3:
// handle some special cases:
// o remove SEQ.SS if it is null
// o make up the TTL and TTL guess stuff
struct AVal *av1, *av2;
// remove SEQ.SS
if(strcmp(resultFPLine->name, "SEQ") == 0) {
av1 = resultFPLine->results;
while(av1) {
if(strcmp(av1->attribute, "SS") == 0 && strlen(av1->value) == 0) {
if(av1 == resultFPLine->results) {
resultFPLine->results = av1->next;
break;
} else {
av2->next = av1->next;
break;
}
}
av2 = av1;
av1 = av1->next;
}
}
// TTL and TTL Guess
av1 = NULL;
av2 = NULL;
if(strcmp(resultFPLine->name, "ECN") == 0 ||
strcmp(resultFPLine->name, "T1") == 0 ||
strcmp(resultFPLine->name, "T2") == 0 ||
strcmp(resultFPLine->name, "T3") == 0 ||
strcmp(resultFPLine->name, "T4") == 0 ||
strcmp(resultFPLine->name, "T5") == 0 ||
strcmp(resultFPLine->name, "T6") == 0 ||
strcmp(resultFPLine->name, "T7") == 0 ||
strcmp(resultFPLine->name, "U1") == 0 ||
strcmp(resultFPLine->name, "IE") == 0) {
for(tmpAV = resultFPLine->results; tmpAV; tmpAV = tmpAV->next ) {
if(strcmp(tmpAV->attribute, "T") == 0) {
av1 = tmpAV;
} else if(strcmp(tmpAV->attribute, "TG") == 0) {
av2 = tmpAV;
}
}
assert(av1&&av2);
if(strlen(av1->value) == 0 && strlen(av2->value) > 0) {
strcpy(av1->value, av2->value);
} else if(strlen(av2->value) == 0 && strlen(av1->value) > 0) {
strcpy(av2->value, av1->value);
}
}
}
// whew, we finally complete the job, now spit it out.
printf("\nADJUSTED FINGERPRINT:\n");
// OS Name
if(observedFP->OS_name) {
printf("Fingerprint %s\n", observedFP->OS_name);
} else {
// print an empty fingerprint
printf("Fingerprint\n");
}
// Class
if(observedFP->num_OS_Classifications > 0) {
for(i = 0; i<observedFP->num_OS_Classifications; i++) {
printf("Class %s | %s |",
observedFP->OS_class[i].OS_Vendor,
observedFP->OS_class[i].OS_Family);
if(observedFP->OS_class[i].OS_Generation) {
printf(" %s |", observedFP->OS_class[i].OS_Generation);
} else {
printf("|");
}
printf(" %s\n", observedFP->OS_class[i].Device_Type);
}
} else {
// print a empty class line
printf("Class\n");
}
// Fingerprint
printf("%s", fp2ascii(resultFP));
return 0;
}