From 56f59de131e1bc2ff71053daa936844640f1aa96 Mon Sep 17 00:00:00 2001 From: dmiller Date: Fri, 11 Nov 2022 18:44:19 +0000 Subject: [PATCH] Optimizations for IPv4 OS scan Since the number, names, and preferred order of OS detection tests are known, we can use fixed indices to put each test/value type in a predictable location. Previously, we would insert the tests in an arbitrary order, sort them, and then use strcmp() in each comparison to ensure the sort order holds and any missing tests are skipped over. Keeping test names in one location (MatchPoints) saves memory and keeps the string pool small, which improves performance by reducing lookups and making existing lookups faster. Using a dedicated class (FingerPrintDef) for MatchPoints avoids calling strtol() to obtain the points value for every comparison. --- osscan.cc | 555 ++++++++++++++++++++++++++--------------------------- osscan.h | 115 +++++++++-- osscan2.cc | 448 ++++++++++++------------------------------ osscan2.h | 42 ++-- 4 files changed, 504 insertions(+), 656 deletions(-) diff --git a/osscan.cc b/osscan.cc index 97ce5d006..a3f505b77 100644 --- a/osscan.cc +++ b/osscan.cc @@ -78,6 +78,88 @@ extern NmapOps o; +template void ShortStr<_MaxStrLen>::setStr(const char *in) { + const char *end = in; + while (end - in < _MaxStrLen && *++end); + setStr(in, end); + trunc = trunc || *end; +} +template void ShortStr<_MaxStrLen>::setStr(const char *in, const char *end) { + assert(end > in && in != NULL); + int len = end - in; + len = MIN(len, _MaxStrLen); + + int i = 0; + for (; i < len; i++) { + char c = in[i]; + if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z')) { + str[i] = c; + } + else + break; + } + str[i] = '\0'; + trunc = i < (end - in); +} + +const char *FingerPrintDef::test_attrs[NUM_FPTESTS][FP_MAX_TEST_ATTRS] = { + /* SEQ */ {"SP", "GCD", "ISR", "TI", "CI", "II", "SS", "TS"}, + /* OPS */ {"O1", "O2", "O3", "O4", "O5", "O6"}, + /* WIN */ {"W1", "W2", "W3", "W4", "W5", "W6"}, + /* ECN */ {"R", "DF", "T", "TG", "W", "O", "CC", "Q"}, + /* T1 */ {"R", "DF", "T", "TG", "S", "A", "F", "RD", "Q"}, + /* T2 */ {"R", "DF", "T", "TG", "W", "S", "A", "F", "O", "RD", "Q"}, + /* T3 */ {"R", "DF", "T", "TG", "W", "S", "A", "F", "O", "RD", "Q"}, + /* T4 */ {"R", "DF", "T", "TG", "W", "S", "A", "F", "O", "RD", "Q"}, + /* T5 */ {"R", "DF", "T", "TG", "W", "S", "A", "F", "O", "RD", "Q"}, + /* T6 */ {"R", "DF", "T", "TG", "W", "S", "A", "F", "O", "RD", "Q"}, + /* T7 */ {"R", "DF", "T", "TG", "W", "S", "A", "F", "O", "RD", "Q"}, + /* U1 */ {"R", "DF", "T", "TG", "IPL", "UN", "RIPL", "RID", "RIPCK", "RUCK", "RUD"}, + /* IE */ {"R", "DFI", "T", "TG", "CD"} + }; + +FingerPrintDef::FingerPrintDef() { + TestDefs.reserve(NUM_FPTESTS); + int i = 0; + FPstr name; +#define ADD_TEST_DEF(_Name) \ + i = ID2INT(_Name); \ + name = FPstr(#_Name); \ + TestDefs.push_back(FingerTestDef(name, test_attrs[i])); \ + assert(TestDefs[i].name == name); \ + TestIdx.insert(std::make_pair(name, _Name)); + + ADD_TEST_DEF(SEQ); + ADD_TEST_DEF(OPS); + ADD_TEST_DEF(WIN); + ADD_TEST_DEF(ECN); + ADD_TEST_DEF(T1); + ADD_TEST_DEF(T2); + ADD_TEST_DEF(T3); + ADD_TEST_DEF(T4); + ADD_TEST_DEF(T5); + ADD_TEST_DEF(T6); + ADD_TEST_DEF(T7); + ADD_TEST_DEF(U1); + ADD_TEST_DEF(IE); + + assert(FingerPrintDef::INVALID == INT2ID(NUM_FPTESTS)); + assert(TestDefs.size() == NUM_FPTESTS); + assert(TestIdx.size() == NUM_FPTESTS); +}; + +FingerTestDef::FingerTestDef(const FPstr &n, const char *a[]) + : name(n), numAttrs(0) { + hasR = (0 == strcmp(a[0], "R")); + Attrs.reserve(FP_MAX_TEST_ATTRS); + while (numAttrs < FP_MAX_TEST_ATTRS && a[numAttrs] != NULL) { + Attr attr(a[numAttrs]); + Attrs.push_back(attr); + AttrIdx.insert(std::make_pair(attr.name, numAttrs)); + numAttrs++; + } +} + FingerPrintDB::FingerPrintDB() : MatchPoints(NULL) { } @@ -85,7 +167,6 @@ FingerPrintDB::~FingerPrintDB() { std::vector::iterator current; if (MatchPoints != NULL) { - MatchPoints->erase(); delete MatchPoints; } for (current = prints.begin(); current != prints.end(); current++) { @@ -94,9 +175,35 @@ FingerPrintDB::~FingerPrintDB() { } } -FingerTest::FingerTest(bool allocResults) : name(NULL), results(NULL) { - if (allocResults) - this->results = new std::vector; +bool FingerPrintDef::parseTestStr(const char *str, const char *end) { + const char *p = str; + const char *q = strchr_p(p, end, '('); + if (!q) + return false; + + std::map::iterator t_i = TestIdx.find(FPstr(p, q)); + if (t_i == TestIdx.end()) + return false; + + FingerTestDef &test = getTestDef(t_i->second); + p = q + 1; + while ((q = strchr_p(p, end, '='))) { + std::map::iterator a_i = test.AttrIdx.find(FPstr(p, q)); + if (a_i == test.AttrIdx.end()) + return false; + Attr &attr = test.Attrs[a_i->second]; + + p = q + 1; + errno = 0; + attr.points = strtol(p, NULL, 10); + if (errno != 0 || attr.points <= 0) + return false; + + if (NULL == (p = strchr_p(q, end, '%'))) + break; + p++; + } + return true; } void FingerTest::erase() { @@ -106,18 +213,9 @@ void FingerTest::erase() { } } -void FingerPrint::sort() { - unsigned int i; - - for (i = 0; i < tests.size(); i++) - std::stable_sort(tests[i].results->begin(), tests[i].results->end()); - std::stable_sort(tests.begin(), tests.end()); -} - void FingerPrint::erase() { - for (std::vector::iterator t = this->tests.begin(); - t != this->tests.end(); t++) { - t->erase(); + for (int i=0; i < NUM_FPTESTS; i++) { + tests[i].erase(); } } @@ -186,69 +284,45 @@ static bool expr_match(const char *val, const char *expr) { case, you may also pass in the group name (SEQ, T1, etc) to have that extra info printed. If you pass 0 for verbose, you might as well pass NULL for testGroupName as it won't be used. */ -static int AVal_match(const FingerTest *reference, const FingerTest *fprint, const FingerTest *points, +static int AVal_match(const FingerTest &reference, const FingerTest &fprint, const FingerTestDef &points, unsigned long *num_subtests, unsigned long *num_subtests_succeeded, int shortcut, int verbose) { - std::vector::const_iterator current_ref, prev_ref; - std::vector::const_iterator current_fp, prev_fp; - std::vector::const_iterator current_points; int subtests = 0, subtests_succeeded=0; - int pointsThisTest = 1; - char *endptr; + if (!reference.results || !fprint.results) + return 0; - /* We rely on AVals being sorted by attribute. */ - prev_ref = reference->results->end(); - prev_fp = fprint->results->end(); - current_ref = reference->results->begin(); - current_fp = fprint->results->begin(); - current_points = points->results->begin(); - while (current_ref != reference->results->end() - && current_fp != fprint->results->end()) { - int d; + const std::vector &pointsV = points.Attrs; - /* Check for sortedness. */ - if (prev_ref != reference->results->end()) - assert(*prev_ref < *current_ref); - if (prev_fp != fprint->results->end()) - assert(*prev_fp < *current_fp); + const std::vector &refV = *reference.results; + assert(refV.size() == points.numAttrs); - d = strcmp(current_ref->attribute, current_fp->attribute); - if (d == 0) { - for (; current_points != points->results->end(); current_points++) { - if (strcmp(current_ref->attribute, current_points->attribute) == 0) - break; + const std::vector &fpV = *fprint.results; + assert(refV.size() == points.numAttrs); + + for (size_t i = 0; i < points.numAttrs; i++) { + const char *current_ref = refV[i]; + const char *current_fp = fpV[i]; + const Attr &aDef = pointsV[i]; + if (current_ref == NULL || current_fp == NULL) + continue; + int pointsThisTest = aDef.points; + if (pointsThisTest < 0) + fatal("%s: Got bogus point amount (%d) for test %s.%s", __func__, pointsThisTest, points.name.str, aDef.name.str); + subtests += pointsThisTest; + + if (expr_match(current_fp, current_ref)) { + subtests_succeeded += pointsThisTest; + } else { + if (shortcut) { + if (num_subtests) + *num_subtests += subtests; + return 0; } - if (current_points == points->results->end()) - fatal("%s: Failed to find point amount for test %s.%s", __func__, reference->name ? reference->name : "", current_ref->attribute); - errno = 0; - pointsThisTest = strtol(current_points->value, &endptr, 10); - if (errno != 0 || *endptr != '\0' || pointsThisTest < 0) - fatal("%s: Got bogus point amount (%s) for test %s.%s", __func__, current_points->value, reference->name ? reference->name : "", current_ref->attribute); - subtests += pointsThisTest; - - if (expr_match(current_fp->value, current_ref->value)) { - subtests_succeeded += pointsThisTest; - } else { - if (shortcut) { - if (num_subtests) - *num_subtests += subtests; - return 0; - } - if (verbose) - log_write(LOG_PLAIN, "%s.%s: \"%s\" NOMATCH \"%s\" (%d %s)\n", reference->name, - current_ref->attribute, current_fp->value, - current_ref->value, pointsThisTest, (pointsThisTest == 1) ? "point" : "points"); - } - } - - if (d <= 0) { - prev_ref = current_ref; - current_ref++; - } - if (d >= 0) { - prev_fp = current_fp; - current_fp++; + if (verbose) + log_write(LOG_PLAIN, "%s.%s: \"%s\" NOMATCH \"%s\" (%d %s)\n", points.name.str, + aDef.name.str, current_fp, + current_ref, pointsThisTest, (pointsThisTest == 1) ? "point" : "points"); } } if (num_subtests) @@ -265,55 +339,22 @@ static int AVal_match(const FingerTest *reference, const FingerTest *fprint, con accuracy (between 0 and 1) is returned). If MatchPoints is not NULL, it is a special "fingerprints" which tells how many points each test is worth. */ double compare_fingerprints(const FingerPrint *referenceFP, const FingerPrint *observedFP, - const FingerPrint *MatchPoints, int verbose) { - std::vector::const_iterator current_ref, prev_ref; - std::vector::const_iterator current_fp, prev_fp; - std::vector::const_iterator current_points; + const FingerPrintDef *MatchPoints, int verbose) { unsigned long num_subtests = 0, num_subtests_succeeded = 0; - unsigned long new_subtests, new_subtests_succeeded; assert(referenceFP); assert(observedFP); - /* We rely on tests being sorted by name. */ - prev_ref = referenceFP->tests.end(); - prev_fp = observedFP->tests.end(); - current_ref = referenceFP->tests.begin(); - current_fp = observedFP->tests.begin(); - current_points = MatchPoints->tests.begin(); - while (current_ref != referenceFP->tests.end() - && current_fp != observedFP->tests.end()) { - int d; + for (int i = 0; i < NUM_FPTESTS; i++) { + const FingerTest ¤t_ref = referenceFP->tests[i]; + const FingerTest ¤t_fp = observedFP->tests[i]; + const FingerTestDef &points = MatchPoints->getTestDef(INT2ID(i)); - /* Check for sortedness. */ - if (prev_ref != referenceFP->tests.end()) - assert(strcmp(prev_ref->name, current_ref->name) < 0); - if (prev_fp != observedFP->tests.end()) - assert(strcmp(prev_fp->name, current_fp->name) < 0); + unsigned long new_subtests = 0, new_subtests_succeeded = 0; - d = strcmp(current_ref->name, current_fp->name); - if (d == 0) { - new_subtests = new_subtests_succeeded = 0; - for (; current_points != MatchPoints->tests.end(); current_points++) { - if (strcmp(current_ref->name, current_points->name) == 0) - break; - } - if (current_points == MatchPoints->tests.end()) - fatal("%s: Failed to locate test %s in MatchPoints directive of fingerprint file", __func__, current_ref->name); - - AVal_match(&*current_ref, &*current_fp, &*current_points, - &new_subtests, &new_subtests_succeeded, 0, verbose); - num_subtests += new_subtests; - num_subtests_succeeded += new_subtests_succeeded; - } - - if (d <= 0) { - prev_ref = current_ref; - current_ref++; - } - if (d >= 0) { - prev_fp = current_fp; - current_fp++; - } + AVal_match(current_ref, current_fp, points, + &new_subtests, &new_subtests_succeeded, 0, verbose); + num_subtests += new_subtests; + num_subtests_succeeded += new_subtests_succeeded; } assert(num_subtests_succeeded <= num_subtests); @@ -347,7 +388,6 @@ void match_fingerprint(const FingerPrint *FP, FingerPrintResultsIPv4 *FPR, assert(accuracy_threshold >= 0 && accuracy_threshold <= 1); FP_copy = *FP; - FP_copy.sort(); FPR->overall_results = OSSCAN_SUCCESS; @@ -356,7 +396,6 @@ void match_fingerprint(const FingerPrint *FP, FingerPrintResultsIPv4 *FPR, acc = compare_fingerprints(*current_os, &FP_copy, DB->MatchPoints, 0); - /* error("Comp to %s: %li/%li=%f", o.reference_FPs1[i]->OS_name, num_subtests_succeeded, num_subtests, acc); */ if (acc >= FPR_entrance_requirement || acc == 1.0) { state = 0; @@ -510,10 +549,8 @@ void WriteSInfo(char *ostr, int ostrlen, bool isGoodFP, null-terminated. Returns the number of bytes written, excluding the terminator. */ static int test2str(const FingerTest *test, char *s, const size_t n) { - std::vector::const_iterator av; char *p; char *end; - size_t len; if (n == 0) return 0; @@ -521,40 +558,29 @@ static int test2str(const FingerTest *test, char *s, const size_t n) { p = s; end = s + n - 1; - len = strlen(test->name); - if (p + len > end) + std::vector &results = *test->results; + p += Snprintf(p, n, "%s(", test->getTestName()); + if (p > end) goto error; - memcpy(p, test->name, len); - p += len; - if (p + 1 > end) - goto error; - *p++ = '('; - - for (av = test->results->begin(); av != test->results->end(); av++) { - if (av != test->results->begin()) { - if (p + 1 > end) - goto error; - *p++ = '%'; - } - len = strlen(av->attribute); - if (p + len > end) +assert(results.size() == test->def->numAttrs); + for (u8 i = 0; i < results.size(); i++) { + if (results[i] == NULL) + continue; + p += Snprintf(p, end - p, "%s=%s%%", test->getAValName(i), results[i]); + if (p > end) goto error; - memcpy(p, av->attribute, len); - p += len; - if (p + 1 > end) - goto error; - *p++ = '='; - len = strlen(av->value); - if (p + len > end) - goto error; - memcpy(p, av->value, len); - p += len; } - if (p + 1 > end) + // overwrite last '%' with ')' + if (*(p - 1) == '%') + *(p - 1) = ')'; + // if there were no results and there is space for it, close parenthesis + else if (*(p - 1) == '(' && p < end) + *p++ = ')'; + // otherwise, something went wrong. + else goto error; - *p++ = ')'; *p = '\0'; @@ -566,101 +592,94 @@ error: return -1; } -static std::vector *str2AVal(const char *str, const char *end) { - int i = 1; - int count = 1; +bool FingerTest::str2AVal(const char *str, const char *end) { + assert(results); + assert(def); const char *q = str, *p=str; - std::vector *AVs = new std::vector; - - if (!*str || str == end) - return AVs; - - /* count the AVals */ - while ((q = strchr_p(q, end, '%'))) { - count++; - q++; + if (!def->hasR && 0 == strncmp("R=N", str, end - str)) { + return true; } + u8 count = def->numAttrs; + std::vector &AVs = *results; - AVs->reserve(count); - for (i = 0; i < count; i++) { - struct AVal av; - + for (u8 i = 0; i < count && p < end; i++) { q = strchr_p(p, end, '='); if (!q) { - fatal("Parse error with AVal string (%s) in nmap-os-db file", str); + error("Parse error with AVal string (%s) in nmap-os-db file", str); + return false; + } + std::map::const_iterator idx = def->AttrIdx.find(FPstr(p, q)); + if (idx == def->AttrIdx.end() || AVs[idx->second] != NULL) { + error("Parse error with AVal string (%s) in nmap-os-db file", str); + return false; } - av.attribute = string_pool_substr(p, q); p = q+1; - if (i < count - 1) { - q = strchr_p(p, end, '%'); - if (!q) { - fatal("Parse error with AVal string (%s) in nmap-os-db file", str); - } - av.value = string_pool_substr(p, q); - } else { - av.value = string_pool_substr(p, end); + q = strchr_p(p, end, '%'); + if (!q) { + q = end; } + if (p != q) // empty? use NULL + AVs[idx->second] = string_pool_substr(p, q); p = q + 1; - AVs->push_back(av); } - - return AVs; + return true; } -/* Compare two AVal chains literally, without evaluating the value of either one - as an expression. This is used by mergeFPs. Unlike with AVal_match, it is - always the case that test_match_literal(a, b) == test_match_literal(b, a). */ -static bool test_match_literal(const FingerTest *a, const FingerTest *b) { - std::vector::const_iterator ia, ib; +void FingerTest::setAVal(const char *attr, const char *value) { + u8 idx = def->AttrIdx.at(attr); + assert(idx < results->size()); + (*results)[idx] = value; +} - for (ia = a->results->begin(), ib = b->results->begin(); - ia != a->results->end() && ib != b->results->end(); - ia++, ib++) { - if (strcmp(ia->attribute, ib->attribute) != 0) - return false; - } - if (ia != a->results->end() || ib != b->results->end()) - return false; +const char *FingerTest::getAValName(u8 index) const { + return def->Attrs.at(index).name; +} - return true; +const char *FingerTest::getAVal(const char *attr) { + if (!results) + return NULL; + + u8 idx = def->AttrIdx.at(attr); + return results->at(idx); } /* This is a less-than relation predicate that establishes the preferred order of tests when they are displayed. Returns true if and only if the test a should come before the test b. */ -static bool FingerTest_lessthan(const FingerTest* a, const FingerTest* b) { - /* This defines the order in which test lines should appear. */ - const char *TEST_ORDER[] = { - "SEQ", "OPS", "WIN", "ECN", - "T1", "T2", "T3", "T4", "T5", "T6", "T7", - "U1", "IE" - }; - unsigned int i; - int ia, ib; +struct FingerTestCmp { + bool operator()(const FingerTest* a, const FingerTest* b) { + if (a->id != b->id) + return a->id < b->id; + if (a->results == NULL) { + return b->results != NULL; + } + else if (b->results == NULL) { + return false; + } + const std::vector &av_a = *a->results; + size_t numtests = av_a.size(); + const std::vector &av_b = *b->results; + assert(av_b.size() == numtests); - /* The indices at which the test names were found in the list. -1 means "not - found." */ - ia = -1; - ib = -1; - /* Look up the test names in the list. */ - for (i = 0; i < sizeof(TEST_ORDER) / sizeof(*TEST_ORDER); i++) { - if (ia == -1 && strcmp(a->name, TEST_ORDER[i]) == 0) - ia = i; - if (ib == -1 && strcmp(b->name, TEST_ORDER[i]) == 0) - ib = i; - /* Once we've found both tests we can stop searching. */ - if (ia != -1 && ib != -1) - break; + for (size_t i = 0; i < numtests; i++) { + if (av_a[i] == NULL) { + if (av_b[i] == NULL) + continue; + else + return true; + } + else if (av_b[i] == NULL) { + return false; + } + int cmp = strcmp(av_a[i], av_b[i]); + if (cmp == 0) + continue; + else + return cmp < 0; + } + return false; } - /* If a test name was not found, it probably indicates an error in another - part of the code. */ - if (ia == -1) - fatal("%s received an unknown test name \"%s\".\n", __func__, a->name); - if (ib == -1) - fatal("%s received an unknown test name \"%s\".\n", __func__, b->name); - - return ia < ib; -} +}; /* Merges the tests from several fingerprints into a character string representation. Tests that are identical between more than one fingerprint @@ -675,58 +694,22 @@ const char *mergeFPs(FingerPrint *FPs[], int numFPs, bool isGoodFP, static char wrapstr[10240]; char *p; - int i; char *end = str + sizeof(str) - 1; /* Last byte allowed to write into */ - std::list tests; - std::list::iterator iter; - std::vector::iterator ft; + std::set tests; + std::set::iterator iter; if (numFPs <= 0) return "(None)"; else if (numFPs > 32) return "(Too many)"; - /* Copy the tests from each fingerprint into a flat list. */ - for (i = 0; i < numFPs; i++) { - for (ft = FPs[i]->tests.begin(); ft != FPs[i]->tests.end(); ft++) - tests.push_back(&*ft); - } - /* Put the tests in the proper order and ensure that tests with identical names are contiguous. */ - tests.sort(FingerTest_lessthan); - - /* Delete duplicate tests to ensure that all the tests are unique. One test is - a duplicate of the other if it has the same name as the first and the two - results lists match. */ - for (iter = tests.begin(); iter != tests.end(); iter++) { - std::list::iterator tmp_i, next; - tmp_i = iter; - tmp_i++; - while (tmp_i != tests.end() && strcmp((*iter)->name, (*tmp_i)->name) == 0) { - next = tmp_i; - next++; - if (test_match_literal(*iter, *tmp_i)) { - /* This is a duplicate test. Remove it. */ - tests.erase(tmp_i); - } - tmp_i = next; - } - } - - /* A safety check to make sure that no tests were lost in merging. */ - for (i = 0; i < numFPs; i++) { - for (ft = FPs[i]->tests.begin(); ft != FPs[i]->tests.end(); ft++) { - for (iter = tests.begin(); iter != tests.end(); iter++) { - if (strcmp((*iter)->name, ft->name) == 0 && test_match_literal(*iter, &*ft)) { - break; - } - } - if (iter == tests.end()) { - char buf[200]; - test2str(&*ft, buf, sizeof(buf)); - fatal("The test %s was somehow lost in %s.\n", buf, __func__); - } + for (int i = 0; i < numFPs; i++) { + for (int j = 0; j < NUM_FPTESTS; j++) { + const FingerTest &ft = FPs[i]->tests[j]; + if (ft.id != FingerPrintDef::INVALID) + tests.insert(&ft); } } @@ -794,16 +777,18 @@ const char *mergeFPs(FingerPrint *FPs[], int numFPs, bool isGoodFP, const char *fp2ascii(const FingerPrint *FP) { static char str[2048]; - std::vector::const_iterator iter; char *p = str; if (!FP) return "(None)"; - for (iter = FP->tests.begin(); iter != FP->tests.end(); iter++) { + for (int j = 0; j < NUM_FPTESTS; j++) { + const FingerTest &ft = FP->tests[j]; + if (ft.id == FingerPrintDef::INVALID) + continue; int len; - len = test2str(&*iter, p, sizeof(str) - (p - str)); + len = test2str(&ft, p, sizeof(str) - (p - str)); if (len == -1) break; p += len; @@ -887,7 +872,7 @@ static void parse_cpeline(FingerPrint *FP, const char *thisline, const char *lin which some partial fingerpritns are OK. */ /* This function is not currently used by Nmap, but it is present here because it is used by fingerprint utilities that link with Nmap object files. */ -FingerPrint *parse_single_fingerprint(const char *fprint) { +FingerPrint *parse_single_fingerprint(const FingerPrintDB *DB, const char *fprint) { int lineno = 0; const char *p, *q; const char *thisline, *nextline; @@ -938,15 +923,16 @@ FingerPrint *parse_single_fingerprint(const char *fprint) { parse_cpeline(FP, thisline, nextline, lineno); } else if ((q = strchr_p(thisline, nextline, '('))) { - FingerTest test; - test.name = string_pool_substr(thisline, q); + FingerTest test(FPstr(thisline, q), *DB->MatchPoints); p = q+1; q = strchr_p(p, nextline, ')'); if (!q) { fatal("Parse error on line %d of fingerprint: %.*s\n", lineno, (int)(nextline - thisline), thisline); } - test.results = str2AVal(p, q); - FP->tests.push_back(test); + if (!test.str2AVal(p, q)) { + fatal("Parse error on line %d of fingerprint: %.*s\n", lineno, (int)(nextline - thisline), thisline); + } + FP->setTest(test); } else { fatal("Parse error on line %d of fingerprint: %.*s\n", lineno, (int)(nextline - thisline), thisline); } @@ -985,6 +971,7 @@ top: fparse: if (strncmp(line, "Fingerprint", 11) == 0) { parsingMatchPoints = false; + current = new FingerPrint; } else if (strncmp(line, "MatchPoints", 11) == 0) { if (DB->MatchPoints) fatal("Found MatchPoints directive on line %d of %s even though it has previously been seen in the file", lineno, fname); @@ -994,11 +981,8 @@ fparse: continue; } - current = new FingerPrint; - if (parsingMatchPoints) { - current->match.OS_name = NULL; - DB->MatchPoints = current; + DB->MatchPoints = new FingerPrintDef(); } else { DB->prints.push_back(current); p = line + 12; @@ -1016,9 +1000,9 @@ fparse: fatal("Parse error on line %d of fingerprint: %s", lineno, line); current->match.OS_name = cp_strndup(p, q - p + 1); - } - current->match.line = lineno; + current->match.line = lineno; + } /* Now we read the fingerprint itself */ while (fgets(line, sizeof(line), fp)) { @@ -1030,34 +1014,37 @@ fparse: q = strchr(line, '\n'); - if (!strncmp(line, "Fingerprint ",12)) { + if (0 == strncmp(line, "Fingerprint ",12)) { goto fparse; + } else if (parsingMatchPoints) { + if (!DB->MatchPoints->parseTestStr(line, q)) { + fatal("Parse error in MatchPoints on line %d of nmap-os-db file: %s", lineno, line); + } } else if (strncmp(line, "Class ", 6) == 0) { parse_classline(current, line, q, lineno); } else if (strncmp(line, "CPE ", 4) == 0) { parse_cpeline(current, line, q, lineno); } else { - FingerTest test; p = line; q = strchr(line, '('); if (!q) { error("Parse error on line %d of nmap-os-db file: %s", lineno, line); goto top; } - test.name = string_pool_substr(p, q); + FingerTest test(FPstr(p, q), *DB->MatchPoints); p = q+1; q = strchr(p, ')'); if (!q) { error("Parse error on line %d of nmap-os-db file: %s", lineno, line); goto top; } - test.results = str2AVal(p, q); - current->tests.push_back(test); + if (!test.str2AVal(p, q)) { + error("Parse error on line %d of nmap-os-db file: %s", lineno, line); + goto top; + } + current->setTest(test); } } - /* This sorting is important for later comparison of FingerPrints and - FingerTests. */ - current->sort(); } fclose(fp); diff --git a/osscan.h b/osscan.h index 30f72c24c..bc462567b 100644 --- a/osscan.h +++ b/osscan.h @@ -67,6 +67,7 @@ #include #include +#include class Target; class FingerPrintResultsIPv4; @@ -90,14 +91,74 @@ enum dist_calc_method { /********************** STRUCTURES ***********************************/ -struct AVal { - const char *attribute; - const char *value; - AVal() : attribute(NULL), value(NULL) {} +#define NUM_FPTESTS 13 + // T2-T7 and U1 have 11 attributes each +#define FP_MAX_TEST_ATTRS 11 + // RIPCK +#define FP_MAX_NAME_LEN 5 - bool operator<(const AVal& other) const { - return strcmp(attribute, other.attribute) < 0; +// Short alphanumeric strings. +template +struct ShortStr { + char str[_MaxStrLen+1]; + bool trunc; + ShortStr() : trunc(false) {memset(str, 0, sizeof(str));} + ShortStr(const char *s) { setStr(s); } + ShortStr(const char *s, const char *e) { setStr(s, e); } + void setStr(const char *in); + void setStr(const char *in, const char *end); + // Helpers for type conversion + operator const char *() const {return this->str;} + operator char *() {return this->str;} + bool operator==(const ShortStr &other) const { + return (!trunc && !other.trunc + && strncmp(str, other.str, _MaxStrLen) == 0); } + bool operator!=(const ShortStr &other) const { + return (trunc || other.trunc + || strncmp(str, other.str, _MaxStrLen) != 0); + } + bool operator<(const ShortStr &other) const { + return (trunc < other.trunc || strncmp(str, other.str, _MaxStrLen) < 0); + } +}; + +typedef ShortStr FPstr; + +struct Attr { + FPstr name; + int points; + Attr() : name(), points(0) {} + Attr(const char *n) : name(n), points(0) {} +}; + +struct FingerTestDef { + FPstr name; + u8 numAttrs; + bool hasR; + std::map AttrIdx; + std::vector Attrs; + + FingerTestDef() : name(), numAttrs(0), hasR(false) {} + FingerTestDef(const FPstr &n, const char *a[]); +}; + +#define ID2INT(_i) static_cast(_i) +#define INT2ID(_i) static_cast(_i) +class FingerPrintDef { + public: + enum TestID { SEQ, OPS, WIN, ECN, T1, T2, T3, T4, T5, T6, T7, U1, IE, INVALID }; + static const char *test_attrs[NUM_FPTESTS][FP_MAX_TEST_ATTRS]; + FingerPrintDef(); + bool parseTestStr(const char *str, const char *end); + FingerTestDef &getTestDef(TestID id) { return TestDefs[ID2INT(id)]; } + const FingerTestDef &getTestDef(TestID id) const { return TestDefs[ID2INT(id)]; } + int getTestIndex(FPstr testname) { return ID2INT(TestIdx.at(testname)); } + TestID str2TestID(FPstr testname) { return TestIdx.at(testname); } + + private: + std::map TestIdx; + std::vector TestDefs; }; struct OS_Classification { @@ -124,30 +185,46 @@ struct FingerMatch { } }; +struct FingerPrintDB; struct FingerTest { - const char *name; - std::vector *results; - FingerTest(bool allocResults=false); + FingerPrintDef::TestID id; + const FingerTestDef *def; + std::vector *results; + FingerTest() : id(FingerPrintDef::INVALID), def(NULL), results(NULL) {} + FingerTest(const FPstr &testname, FingerPrintDef &Defs) { + id = Defs.str2TestID(testname); + def = &Defs.getTestDef(id); + results = new std::vector(def->numAttrs, NULL); + } + FingerTest(FingerPrintDef::TestID testid, FingerPrintDef &Defs) + : id(testid), results(NULL) { + def = &Defs.getTestDef(id); + results = new std::vector(def->numAttrs, NULL); + } ~FingerTest() { - // name is allocated from string_pool // results must be freed manually } - bool operator<(const FingerTest& other) const { - return strcmp(name, other.name) < 0; - } void erase(); + bool str2AVal(const char *str, const char *end); + void setAVal(const char *attr, const char *value); + const char *getAVal(const char *attr); + const char *getAValName(u8 index) const; + const char *getTestName() const { return def->name.str; } }; struct FingerPrint { FingerMatch match; - std::vector tests; - void sort(); + FingerTest tests[NUM_FPTESTS]; void erase(); + void setTest(const FingerTest &test) { + tests[ID2INT(test.id)] = test; + } }; + /* This structure contains the important data from the fingerprint database (nmap-os-db) */ struct FingerPrintDB { - FingerPrint *MatchPoints; + FingerPrintDef *MatchPoints; std::vector prints; FingerPrintDB(); @@ -162,7 +239,7 @@ const char *fp2ascii(const FingerPrint *FP); non-null fingerprint is returned, the user is in charge of freeing it when done. This function does not require the fingerprint to be 100% complete since it is used by scripts such as scripts/fingerwatch for - which some partial fingerpritns are OK. */ + which some partial fingerprints are OK. */ FingerPrint *parse_single_fingerprint(const char *fprint_orig); /* These functions take a file/db name and open+parse it, returning an @@ -176,10 +253,10 @@ void free_fingerprint_file(FingerPrintDB *DB); /* Compares 2 fingerprints -- a referenceFP (can have expression attributes) with an observed fingerprint (no expressions). If verbose is nonzero, differences will be printed. The comparison - accuracy (between 0 and 1) is returned). If MatchPoints is not NULL, it is + accuracy (between 0 and 1) is returned). MatchPoints is a special "fingerprints" which tells how many points each test is worth. */ double compare_fingerprints(const FingerPrint *referenceFP, const FingerPrint *observedFP, - const FingerPrint *MatchPoints, int verbose); + const FingerPrintDef *MatchPoints, int verbose); /* Takes a fingerprint and looks for matches inside the passed in reference fingerprint DB. The results are stored in in FPR (which diff --git a/osscan2.cc b/osscan2.cc index f9c9f87ac..05c204757 100644 --- a/osscan2.cc +++ b/osscan2.cc @@ -134,42 +134,36 @@ static struct scan_performance_vars perf; * Miscellaneous functions * ******************************************************************************/ -/* Fill in a struct AVal with a value based on the IP ID sequence generation +/* Return a value based on the IP ID sequence generation class (one of the IPID_SEQ_* constants). If ipid_seqclass is such that the - test result should be omitted, the function returns NULL and doesn't modify - *av. Otherwise, it returns av after filling in the information. */ -static struct AVal *make_aval_ipid_seq(struct AVal *av, const char *attribute, - int ipid_seqclass, u32 ipids[NUM_SEQ_SAMPLES], + test result should be omitted, the function returns NULL. */ +static const char *make_aval_ipid_seq(int ipid_seqclass, u32 ipids[NUM_SEQ_SAMPLES], HostOsScanStats *hss) { switch (ipid_seqclass) { case IPID_SEQ_CONSTANT: - av->value = hss->target->FPR->cp_hex(ipids[0]); + return hss->target->FPR->cp_hex(ipids[0]); break; case IPID_SEQ_INCR_BY_2: case IPID_SEQ_INCR: - av->value = "I"; + return "I"; break; case IPID_SEQ_BROKEN_INCR: - av->value = "BI"; + return "BI"; break; case IPID_SEQ_RPI: - av->value = "RI"; + return "RI"; break; case IPID_SEQ_RD: - av->value = "RD"; + return "RD"; break; case IPID_SEQ_ZERO: - av->value = "Z"; + return "Z"; break; default: /* Signal to omit test result. */ return NULL; break; } - - av->attribute = attribute; - - return av; } @@ -1034,12 +1028,6 @@ HostOsScanStats::~HostOsScanStats() { FPtests[i] = NULL; } } - for (i = 0; i < 6; i++) { - if (TOps_AVs[i]) - free(TOps_AVs[i]); - if (TWin_AVs[i]) - free(TWin_AVs[i]); - } while (!probesToSend.empty()) { delete probesToSend.front(); @@ -1156,10 +1144,6 @@ void HostOsScanStats::initScanStats() { } } for (i = 0; i < 6; i++) { - if (TOps_AVs[i]) - free(TOps_AVs[i]); - if (TWin_AVs[i]) - free(TWin_AVs[i]); TOps_AVs[i] = NULL; TWin_AVs[i] = NULL; } @@ -2029,10 +2013,6 @@ void HostOsScan::makeFP(HostOsScanStats *hss) { assert(hss); int i; - struct AVal AV; - std::vector::iterator it; - - int ttl; if (!hss->FP_TSeq) makeTSeqFP(hss); @@ -2051,35 +2031,31 @@ void HostOsScan::makeFP(HostOsScanStats *hss) { /* We create a Resp (response) attribute with value of N (no) because it is important here to note whether responses were or were not received */ - hss->FPtests[i] = new FingerTest(true); - AV.attribute = "R"; - AV.value = "N"; - hss->FPtests[i]->results->push_back(AV); - hss->FPtests[i]->name = (i == 3)? "ECN" : (i == 4)? "T1" : (i == 5)? "T2" : (i == 6)? "T3" : (i == 7)? "T4" : (i == 8)? "T5" : (i == 9)? "T6" : (i == 10)? "T7" : (i == 11)? "U1" : "IE"; + hss->FPtests[i] = new FingerTest(INT2ID(i), *o.reference_FPs->MatchPoints); + hss->FPtests[i]->setAVal("R", "N"); } else if (hss->FPtests[i]) { - /* Replace TTL with initial TTL. */ - for (it = hss->FPtests[i]->results->begin(); it != hss->FPtests[i]->results->end(); it++) { - if (strcmp(it->attribute, "T") == 0) { - /* Found TTL item. The value for this attribute is the - * received TTL. We replace it with the initial TTL. */ - ttl = strtol(it->value, NULL, 16); + FingerTest &test = *hss->FPtests[i]; + /* The value for this attribute is the + * received TTL. We replace it with the initial TTL. */ + const char *recvTTL = test.getAVal("T"); + if (recvTTL) { + int ttl = strtol(recvTTL, NULL, 16); - if (hss->distance_guess == -1) - hss->distance_guess = get_initial_ttl_guess(ttl) - ttl; + if (hss->distance_guess == -1) + hss->distance_guess = get_initial_ttl_guess(ttl) - ttl; - if (hss->distance != -1) { - /* We've gotten response for the UDP probe and thus have - the "true" hop count. Add the number of hops between - us and the target (hss->distance - 1) to the received - TTL to get the initial TTL. */ - it->value = hss->target->FPR->cp_hex(ttl + hss->distance - 1); - } else { - /* Guess the initial TTL value */ - it->attribute = "TG"; - it->value = hss->target->FPR->cp_hex(get_initial_ttl_guess(ttl)); - } - break; + if (hss->distance != -1) { + /* We've gotten response for the UDP probe and thus have + the "true" hop count. Add the number of hops between + us and the target (hss->distance - 1) to the received + TTL to get the initial TTL. */ + test.setAVal("T", hss->target->FPR->cp_hex(ttl + hss->distance - 1)); + } else { + /* Guess the initial TTL value */ + test.setAVal("TG", hss->target->FPR->cp_hex(get_initial_ttl_guess(ttl))); + /* Delete this unknown-distance-dependent value */ + test.setAVal("T", NULL); } } } @@ -2090,7 +2066,7 @@ void HostOsScan::makeFP(HostOsScanStats *hss) { for (i = 0; i < NUM_FPTESTS; i++) { if (hss->FPtests[i] == NULL) continue; - hss->FP->tests.push_back(*hss->FPtests[i]); + hss->FP->tests[i] = *hss->FPtests[i]; } } @@ -2314,11 +2290,8 @@ void HostOsScan::makeTSeqFP(HostOsScanStats *hss) { int good_tcp_ipid_num, good_tcp_closed_ipid_num, good_icmp_ipid_num; int tsnewval = 0; - std::vector *seq_AVs = new std::vector; - struct AVal AV; - - /* Need 8 AVals for SP, GCD, ISR, TI, CI, II, SS, TS. */ - seq_AVs->reserve(8); + hss->FP_TSeq = new FingerTest(FingerPrintDef::SEQ, *o.reference_FPs->MatchPoints); + FingerTest &test = *hss->FP_TSeq; /* Now we make sure there are no gaps in our response array ... */ for (i = 0, j = 0; i < NUM_SEQ_SAMPLES; i++) { @@ -2399,15 +2372,9 @@ void HostOsScan::makeTSeqFP(HostOsScanStats *hss) { } } - AV.attribute = "SP"; - AV.value = hss->target->FPR->cp_hex(hss->si.index); - seq_AVs->push_back(AV); - AV.attribute = "GCD"; - AV.value = hss->target->FPR->cp_hex(seq_gcd); - seq_AVs->push_back(AV); - AV.attribute = "ISR"; - AV.value = hss->target->FPR->cp_hex((unsigned int) seq_rate); - seq_AVs->push_back(AV); + test.setAVal("SP", hss->target->FPR->cp_hex(hss->si.index)); + test.setAVal("GCD", hss->target->FPR->cp_hex(seq_gcd)); + test.setAVal("ISR", hss->target->FPR->cp_hex((unsigned int) seq_rate)); } else if (hss->si.responses > 0) { if (o.debugging) log_write(LOG_PLAIN, "Insufficient responses from %s for TCP sequencing (%d), OS detection may be less accurate\n", hss->target->targetipstr(), hss->si.responses); @@ -2462,12 +2429,9 @@ void HostOsScan::makeTSeqFP(HostOsScanStats *hss) { } /* This fills in TI=Z or something like that. */ - if (make_aval_ipid_seq(&AV, "TI", tcp_ipid_seqclass, hss->ipid.tcp_ipids, hss) != NULL) - seq_AVs->push_back(AV); - if (make_aval_ipid_seq(&AV, "CI", tcp_closed_ipid_seqclass, hss->ipid.tcp_closed_ipids, hss) != NULL) - seq_AVs->push_back(AV); - if (make_aval_ipid_seq(&AV, "II", icmp_ipid_seqclass, hss->ipid.icmp_ipids, hss) != NULL) - seq_AVs->push_back(AV); + test.setAVal("TI", make_aval_ipid_seq(tcp_ipid_seqclass, hss->ipid.tcp_ipids, hss)); + test.setAVal("CI", make_aval_ipid_seq(tcp_closed_ipid_seqclass, hss->ipid.tcp_closed_ipids, hss)); + test.setAVal("II", make_aval_ipid_seq(icmp_ipid_seqclass, hss->ipid.icmp_ipids, hss)); /* SS: Shared IP ID sequence boolean */ if ((tcp_ipid_seqclass == IPID_SEQ_INCR || @@ -2478,14 +2442,12 @@ void HostOsScan::makeTSeqFP(HostOsScanStats *hss) { icmp_ipid_seqclass == IPID_SEQ_RPI)) { /* Both are incremental. Thus we have "SS" test. Check if they are in the same sequence. */ - AV.attribute = "SS"; u32 avg = (hss->ipid.tcp_ipids[good_tcp_ipid_num - 1] - hss->ipid.tcp_ipids[0]) / (good_tcp_ipid_num - 1); if (hss->ipid.icmp_ipids[0] < hss->ipid.tcp_ipids[good_tcp_ipid_num - 1] + 3 * avg) { - AV.value = "S"; + test.setAVal("SS", "S"); } else { - AV.value = "O"; + test.setAVal("SS", "O"); } - seq_AVs->push_back(AV); } /* Now we look at TCP Timestamp sequence prediction */ @@ -2541,16 +2503,12 @@ void HostOsScan::makeTSeqFP(HostOsScanStats *hss) { switch (hss->si.ts_seqclass) { case TS_SEQ_ZERO: - AV.attribute = "TS"; - AV.value = "0"; - seq_AVs->push_back(AV); + test.setAVal("TS", "0"); break; case TS_SEQ_2HZ: case TS_SEQ_100HZ: case TS_SEQ_1000HZ: case TS_SEQ_OTHER_NUM: - AV.attribute = "TS"; - /* Here we "cheat" a little to make the classes correspond more closely to common real-life frequencies (particularly 100) which aren't powers of two. */ @@ -2572,26 +2530,12 @@ void HostOsScan::makeTSeqFP(HostOsScanStats *hss) { tsnewval = (unsigned int)(0.5 + log(avg_ts_hz) / log(2.0)); } - AV.value = hss->target->FPR->cp_hex(tsnewval); - seq_AVs->push_back(AV); + test.setAVal("TS", hss->target->FPR->cp_hex(tsnewval)); break; case TS_SEQ_UNSUPPORTED: - AV.attribute = "TS"; - AV.value = "U"; - seq_AVs->push_back(AV); + test.setAVal("TS", "U"); break; } - - /* Now generate the SEQ line of the fingerprint if there are any test results - in seq_AVs. */ - if (!seq_AVs->empty()) { - hss->FP_TSeq = new FingerTest; - hss->FP_TSeq->name = "SEQ"; - hss->FP_TSeq->results = seq_AVs; - } - else { - delete seq_AVs; - } } @@ -2612,15 +2556,11 @@ void HostOsScan::makeTOpsFP(HostOsScanStats *hss) { return; } - std::vector *AVs = new std::vector; - AVs->reserve(n); + hss->FP_TOps = new FingerTest(FingerPrintDef::OPS, *o.reference_FPs->MatchPoints); + std::vector &results = *hss->FP_TOps->results; for (i = 0; i < n; i++) - AVs->push_back(*hss->TOps_AVs[i]); - - hss->FP_TOps = new FingerTest; - hss->FP_TOps->results = AVs; - hss->FP_TOps->name = "OPS"; + results[i] = hss->TOps_AVs[i]; } @@ -2641,15 +2581,11 @@ void HostOsScan::makeTWinFP(HostOsScanStats *hss) { return; } - std::vector *AVs = new std::vector; - AVs->reserve(n); + hss->FP_TWin = new FingerTest(FingerPrintDef::WIN, *o.reference_FPs->MatchPoints); + std::vector &results = *hss->FP_TWin->results; for (i = 0; i < n; i++) - AVs->push_back(*hss->TWin_AVs[i]); - - hss->FP_TWin = new FingerTest; - hss->FP_TWin->results = AVs; - hss->FP_TWin->name = "WIN"; + results[i] = hss->TWin_AVs[i]; } @@ -2727,37 +2663,15 @@ bool HostOsScan::processTOpsResp(HostOsScanStats *hss, const struct tcp_hdr *tcp if (hss->FP_TOps || hss->TOps_AVs[replyNo]) return false; - hss->TOps_AVs[replyNo] = (struct AVal *) safe_zalloc(sizeof(struct AVal)); int opsParseResult = get_tcpopt_string(tcp, this->tcpMss, ops_buf, sizeof(ops_buf)); if (opsParseResult <= 0) { if (opsParseResult < 0 && o.debugging) error("Option parse error for TOps response %d from %s.", replyNo, hss->target->targetipstr()); - hss->TOps_AVs[replyNo]->value = ""; + hss->TOps_AVs[replyNo] = ""; } else { - hss->TOps_AVs[replyNo]->value = hss->target->FPR->cp_dup(ops_buf, opsParseResult); - } - - switch (replyNo) { - case 0: - hss->TOps_AVs[replyNo]->attribute = "O1"; - break; - case 1: - hss->TOps_AVs[replyNo]->attribute = "O2"; - break; - case 2: - hss->TOps_AVs[replyNo]->attribute = "O3"; - break; - case 3: - hss->TOps_AVs[replyNo]->attribute = "O4"; - break; - case 4: - hss->TOps_AVs[replyNo]->attribute = "O5"; - break; - case 5: - hss->TOps_AVs[replyNo]->attribute = "O6"; - break; + hss->TOps_AVs[replyNo] = hss->target->FPR->cp_dup(ops_buf, opsParseResult); } hss->TOpsReplyNum++; @@ -2771,29 +2685,7 @@ bool HostOsScan::processTWinResp(HostOsScanStats *hss, const struct tcp_hdr *tcp if (hss->FP_TWin || hss->TWin_AVs[replyNo]) return false; - hss->TWin_AVs[replyNo] = (struct AVal *) safe_zalloc(sizeof(struct AVal)); - hss->TWin_AVs[replyNo]->value = hss->target->FPR->cp_hex(ntohs(tcp->th_win)); - - switch (replyNo) { - case 0: - hss->TWin_AVs[replyNo]->attribute = "W1"; - break; - case 1: - hss->TWin_AVs[replyNo]->attribute = "W2"; - break; - case 2: - hss->TWin_AVs[replyNo]->attribute = "W3"; - break; - case 3: - hss->TWin_AVs[replyNo]->attribute = "W4"; - break; - case 4: - hss->TWin_AVs[replyNo]->attribute = "W5"; - break; - case 5: - hss->TWin_AVs[replyNo]->attribute = "W6"; - break; - } + hss->TWin_AVs[replyNo] = hss->target->FPR->cp_hex(ntohs(tcp->th_win)); hss->TWinReplyNum++; return true; @@ -2801,73 +2693,58 @@ bool HostOsScan::processTWinResp(HostOsScanStats *hss, const struct tcp_hdr *tcp bool HostOsScan::processTEcnResp(HostOsScanStats *hss, const struct ip *ip) { - struct AVal AV; char ops_buf[256]; char quirks_buf[10]; char *p; - int numtests = 7; const struct tcp_hdr *tcp = ((struct tcp_hdr *) (((char *) ip) + 4 * ip->ip_hl)); if (hss->FP_TEcn) return false; /* Create the Avals */ - std::vector *AVs = new std::vector; - AVs->reserve(numtests); + hss->FP_TEcn = new FingerTest(FingerPrintDef::ECN, *o.reference_FPs->MatchPoints); + FingerTest &test = *hss->FP_TEcn; - AV.attribute = "R"; - AV.value = "Y"; - AVs->push_back(AV); + test.setAVal("R", "Y"); /* don't frag flag */ - AV.attribute = "DF"; if (ntohs(ip->ip_off) & IP_DF) - AV.value = "Y"; + test.setAVal("DF", "Y"); else - AV.value = "N"; - AVs->push_back(AV); + test.setAVal("DF", "N"); /* TTL */ - AV.attribute = "T"; - AV.value = hss->target->FPR->cp_hex(ip->ip_ttl); - AVs->push_back(AV); + test.setAVal("T", hss->target->FPR->cp_hex(ip->ip_ttl)); /* TCP Window size */ - AV.attribute = "W"; - AV.value = hss->target->FPR->cp_hex(ntohs(tcp->th_win)); - AVs->push_back(AV); + test.setAVal("W", hss->target->FPR->cp_hex(ntohs(tcp->th_win))); /* Now for the TCP options ... */ - AV.attribute = "O"; int opsParseResult = get_tcpopt_string(tcp, this->tcpMss, ops_buf, sizeof(ops_buf)); if (opsParseResult <= 0) { if (opsParseResult < 0 && o.debugging) error("Option parse error for ECN response from %s.", hss->target->targetipstr()); - AV.value = ""; + test.setAVal("O", ""); } else { - AV.value = hss->target->FPR->cp_dup(ops_buf, opsParseResult); + test.setAVal("O", hss->target->FPR->cp_dup(ops_buf, opsParseResult)); } - AVs->push_back(AV); /* Explicit Congestion Notification support test */ - AV.attribute = "CC"; if ((tcp->th_flags & TH_ECE) && (tcp->th_flags & TH_CWR)) /* echo back */ - AV.value = "S"; + test.setAVal("CC", "S"); else if (tcp->th_flags & TH_ECE) /* support */ - AV.value = "Y"; + test.setAVal("CC", "Y"); else if (!(tcp->th_flags & TH_CWR)) /* not support */ - AV.value = "N"; + test.setAVal("CC", "N"); else - AV.value = "O"; - AVs->push_back(AV); + test.setAVal("CC", "O"); /* TCP miscellaneous quirks test */ - AV.attribute = "Q"; p = quirks_buf; if (tcp->th_x2) { /* Reserved field of TCP is not zero */ @@ -2880,22 +2757,15 @@ bool HostOsScan::processTEcnResp(HostOsScanStats *hss, const struct ip *ip) { *p++ = 'U'; } *p = '\0'; - AV.value = hss->target->FPR->cp_dup(quirks_buf, p - quirks_buf); - AVs->push_back(AV); - - hss->FP_TEcn = new FingerTest; - hss->FP_TEcn->name = "ECN"; - hss->FP_TEcn->results = AVs; + test.setAVal("Q", hss->target->FPR->cp_dup(quirks_buf, p - quirks_buf)); return true; } bool HostOsScan::processT1_7Resp(HostOsScanStats *hss, const struct ip *ip, int replyNo) { - struct AVal AV; assert(replyNo >= 0 && replyNo < 7); - int numtests; const struct tcp_hdr *tcp = ((struct tcp_hdr *) (((char *) ip) + 4 * ip->ip_hl)); int i; @@ -2903,42 +2773,30 @@ bool HostOsScan::processT1_7Resp(HostOsScanStats *hss, const struct ip *ip, int char flags_buf[10]; char quirks_buf[10]; char *p; + FingerPrintDef::TestID testid = INT2ID(ID2INT(FingerPrintDef::T1) + replyNo); if (hss->FPtests[FP_T1_7_OFF + replyNo]) return false; - if (replyNo == 0) - numtests = 8; /* T1 doesn't has 'Win', 'Ops' tests. */ - else numtests = 10; - - /* Create the Avals */ - std::vector *AVs = new std::vector; - AVs->reserve(numtests); + hss->FPtests[FP_T1_7_OFF + replyNo] = new FingerTest(testid, *o.reference_FPs->MatchPoints); + FingerTest &test = *hss->FPtests[FP_T1_7_OFF + replyNo]; /* First we give the "response" flag to say we did actually receive a packet -- this way we won't match a template with R=N */ - AV.attribute = "R"; - AV.value = "Y"; - AVs->push_back(AV); + test.setAVal("R", "Y"); /* Next we check whether the Don't Fragment bit is set */ - AV.attribute = "DF"; if (ntohs(ip->ip_off) & IP_DF) - AV.value = "Y"; + test.setAVal("DF", "Y"); else - AV.value = "N"; - AVs->push_back(AV); + test.setAVal("DF", "N"); /* TTL */ - AV.attribute = "T"; - AV.value = hss->target->FPR->cp_hex(ip->ip_ttl); - AVs->push_back(AV); + test.setAVal("T", hss->target->FPR->cp_hex(ip->ip_ttl)); if (replyNo != 0) { /* Now we do the TCP Window size */ - AV.attribute = "W"; - AV.value = hss->target->FPR->cp_hex(ntohs(tcp->th_win)); - AVs->push_back(AV); + test.setAVal("W", hss->target->FPR->cp_hex(ntohs(tcp->th_win))); } /* Seq test values: @@ -2947,16 +2805,14 @@ bool HostOsScan::processT1_7Resp(HostOsScanStats *hss, const struct ip *ip, int A+ = ack + 1 O = other */ - AV.attribute = "S"; if (ntohl(tcp->th_seq) == 0) - AV.value = "Z"; + test.setAVal("S", "Z"); else if (ntohl(tcp->th_seq) == tcpAck) - AV.value = "A"; + test.setAVal("S", "A"); else if (ntohl(tcp->th_seq) == tcpAck + 1) - AV.value = "A+"; + test.setAVal("S", "A+"); else - AV.value = "O"; - AVs->push_back(AV); + test.setAVal("S", "O"); /* ACK test values: Z = zero @@ -2964,16 +2820,14 @@ bool HostOsScan::processT1_7Resp(HostOsScanStats *hss, const struct ip *ip, int S+ = syn + 1 O = other */ - AV.attribute = "A"; if (ntohl(tcp->th_ack) == 0) - AV.value = "Z"; + test.setAVal("A", "Z"); else if (ntohl(tcp->th_ack) == tcpSeqBase) - AV.value = "S"; + test.setAVal("A", "S"); else if (ntohl(tcp->th_ack) == tcpSeqBase + 1) - AV.value = "S+"; + test.setAVal("A", "S+"); else - AV.value = "O"; - AVs->push_back(AV); + test.setAVal("A", "O"); /* Flags. They must be in this order: E = ECN Echo @@ -2997,45 +2851,38 @@ bool HostOsScan::processT1_7Resp(HostOsScanStats *hss, const struct ip *ip, int { TH_FIN, 'F' }, }; assert(sizeof(flag_defs) / sizeof(flag_defs[0]) < sizeof(flags_buf)); - AV.attribute = "F"; p = flags_buf; for (i = 0; i < (int) (sizeof(flag_defs) / sizeof(flag_defs[0])); i++) { if (tcp->th_flags & flag_defs[i].flag) *p++ = flag_defs[i].c; } *p = '\0'; - AV.value = hss->target->FPR->cp_dup(flags_buf, p - flags_buf); - AVs->push_back(AV); + test.setAVal("F", hss->target->FPR->cp_dup(flags_buf, p - flags_buf)); if (replyNo != 0) { char ops_buf[256]; /* Now for the TCP options ... */ - AV.attribute = "O"; int opsParseResult = get_tcpopt_string(tcp, this->tcpMss, ops_buf, sizeof(ops_buf)); if (opsParseResult <= 0) { if (opsParseResult < 0 && o.debugging) error("Option parse error for T%d response from %s.", replyNo, hss->target->targetipstr()); - AV.value = ""; + test.setAVal("O", ""); } else { - AV.value = hss->target->FPR->cp_dup(ops_buf, opsParseResult); + test.setAVal("O", hss->target->FPR->cp_dup(ops_buf, opsParseResult)); } - AVs->push_back(AV); } /* Rst Data CRC32 */ - AV.attribute = "RD"; length = (int) ntohs(ip->ip_len) - 4 * ip->ip_hl -4 * tcp->th_off; if ((tcp->th_flags & TH_RST) && length>0) { - AV.value = hss->target->FPR->cp_hex(nbase_crc32(((u8 *)tcp) + 4 * tcp->th_off, length)); + test.setAVal("RD", hss->target->FPR->cp_hex(nbase_crc32(((u8 *)tcp) + 4 * tcp->th_off, length))); } else { - AV.value = "0"; + test.setAVal("RD", "0"); } - AVs->push_back(AV); /* TCP miscellaneous quirks test */ - AV.attribute = "Q"; p = quirks_buf; if (tcp->th_x2) { /* Reserved field of TCP is not zero */ @@ -3048,39 +2895,24 @@ bool HostOsScan::processT1_7Resp(HostOsScanStats *hss, const struct ip *ip, int *p++ = 'U'; } *p = '\0'; - AV.value = hss->target->FPR->cp_dup(quirks_buf, p - quirks_buf); - AVs->push_back(AV); - - hss->FPtests[FP_T1_7_OFF + replyNo] = new FingerTest; - hss->FPtests[FP_T1_7_OFF + replyNo]->results = AVs; - hss->FPtests[FP_T1_7_OFF + replyNo]->name = (replyNo == 0) ? "T1" : (replyNo == 1) ? "T2" : (replyNo == 2) ? "T3" : (replyNo == 3) ? "T4" : (replyNo == 4) ? "T5" : (replyNo == 5) ? "T6" : "T7"; + test.setAVal("Q", hss->target->FPR->cp_dup(quirks_buf, p - quirks_buf)); return true; } bool HostOsScan::processTUdpResp(HostOsScanStats *hss, const struct ip *ip) { - struct AVal AV; assert(hss); assert(ip); const struct icmp *icmp; const struct ip *ip2; - int numtests; unsigned short checksum; unsigned short *checksumptr; const struct udp_hdr *udp; const unsigned char *datastart, *dataend; -#if !defined(SOLARIS) && !defined(SUNOS) && !defined(IRIX) && !defined(HPUX) - numtests = 10; -#else - /* We don't do RID test under these operating systems, thus the - number of test is 1 less. */ - numtests = 9; -#endif - if (hss->FP_TUdp) return false; @@ -3097,94 +2929,75 @@ bool HostOsScan::processTUdpResp(HostOsScanStats *hss, const struct ip *ip) { return false; } - /* Create the Avals */ - std::vector *AVs = new std::vector; - AVs->reserve(numtests); + hss->FP_TUdp = new FingerTest(FingerPrintDef::U1, *o.reference_FPs->MatchPoints); + FingerTest &test = *hss->FP_TUdp; /* First of all, if we got this far the response was yes */ - AV.attribute = "R"; - AV.value = "Y"; - AVs->push_back(AV); + test.setAVal("R", "Y"); /* Also, we now know that the port we reached was closed */ if (hss->target->FPR->osscan_closedudpport == -1) hss->target->FPR->osscan_closedudpport = hss->upi.dport; /* Now let us do an easy one, Don't fragment */ - AV.attribute = "DF"; if (ntohs(ip->ip_off) & IP_DF) - AV.value = "Y"; + test.setAVal("DF", "Y"); else - AV.value = "N"; - AVs->push_back(AV); + test.setAVal("DF", "N"); /* TTL */ - AV.attribute = "T"; - AV.value = hss->target->FPR->cp_hex(ip->ip_ttl); - AVs->push_back(AV); + test.setAVal("T", hss->target->FPR->cp_hex(ip->ip_ttl)); /* Now we look at the IP datagram length that was returned, some machines send more of the original packet back than others */ - AV.attribute = "IPL"; - AV.value = hss->target->FPR->cp_hex(ntohs(ip->ip_len)); - AVs->push_back(AV); + test.setAVal("IPL", hss->target->FPR->cp_hex(ntohs(ip->ip_len))); /* unused filed not zero in Destination Unreachable Message */ - AV.attribute = "UN"; - AV.value = hss->target->FPR->cp_hex(ntohl(icmp->icmp_void)); - AVs->push_back(AV); + test.setAVal("UN", hss->target->FPR->cp_hex(ntohl(icmp->icmp_void))); /* OK, lets check the returned IP length, some systems @$@ this up */ - AV.attribute = "RIPL"; if (ntohs(ip2->ip_len) == 328) - AV.value = "G"; + test.setAVal("RIPL", "G"); else - AV.value = hss->target->FPR->cp_hex(ntohs(ip2->ip_len)); - AVs->push_back(AV); + test.setAVal("RIPL", hss->target->FPR->cp_hex(ntohs(ip2->ip_len))); /* This next test doesn't work on Solaris because the lamers overwrite our ip_id */ #if !defined(SOLARIS) && !defined(SUNOS) && !defined(IRIX) && !defined(HPUX) /* Now lets see how they treated the ID we sent ... */ - AV.attribute = "RID"; if (ntohs(ip2->ip_id) == hss->upi.ipid) - AV.value = "G"; /* The good "expected" value */ + test.setAVal("RID", "G"); /* The good "expected" value */ else - AV.value = hss->target->FPR->cp_hex(ntohs(ip2->ip_id)); - AVs->push_back(AV); + test.setAVal("RID", hss->target->FPR->cp_hex(ntohs(ip2->ip_id))); #endif /* Let us see if the IP checksum we got back computes */ - AV.attribute = "RIPCK"; /* Thanks to some machines not having struct ip member ip_sum we have to go with this BS */ checksumptr = (unsigned short *) ((char *) ip2 + 10); checksum = *checksumptr; if (checksum == 0) { - AV.value = "Z"; + test.setAVal("RIPCK", "Z"); } else { *checksumptr = 0; if (in_cksum((unsigned short *)ip2, 20) == checksum) { - AV.value = "G"; /* The "expected" good value */ + test.setAVal("RIPCK", "G"); /* The "expected" good value */ } else { - AV.value = "I"; /* They modified it */ + test.setAVal("RIPCK", "I"); /* They modified it */ } *checksumptr = checksum; } - AVs->push_back(AV); /* UDP checksum */ - AV.attribute = "RUCK"; if (udp->uh_sum == hss->upi.udpck) - AV.value = "G"; /* The "expected" good value */ + test.setAVal("RUCK", "G"); /* The "expected" good value */ else - AV.value = hss->target->FPR->cp_hex(ntohs(udp->uh_sum)); - AVs->push_back(AV); + test.setAVal("RUCK", hss->target->FPR->cp_hex(ntohs(udp->uh_sum))); /* Finally we ensure the data is OK */ datastart = ((unsigned char *)udp) + 8; @@ -3195,16 +3008,10 @@ bool HostOsScan::processTUdpResp(HostOsScanStats *hss, const struct ip *ip) { break; datastart++; } - AV.attribute = "RUD"; if (datastart < dataend) - AV.value = "I"; /* They modified it */ + test.setAVal("RUD", "I"); /* They modified it */ else - AV.value = "G"; - AVs->push_back(AV); - - hss->FP_TUdp = new FingerTest; - hss->FP_TUdp->name = "U1"; - hss->FP_TUdp->results = AVs; + test.setAVal("RUD", "G"); /* Count hop count */ if (hss->distance == -1) { @@ -3218,8 +3025,6 @@ bool HostOsScan::processTUdpResp(HostOsScanStats *hss, const struct ip *ip) { bool HostOsScan::processTIcmpResp(HostOsScanStats *hss, const struct ip *ip, int replyNo) { assert(replyNo == 0 || replyNo == 1); - struct AVal AV; - int numtests = 4; const struct ip *ip1, *ip2; const struct icmp *icmp1, *icmp2; unsigned short value1, value2; @@ -3252,13 +3057,10 @@ bool HostOsScan::processTIcmpResp(HostOsScanStats *hss, const struct ip *ip, int assert(icmp1->icmp_type == 0 && icmp2->icmp_type == 0); - /* Create the Avals */ - std::vector *AVs = new std::vector; - AVs->reserve(numtests); + hss->FP_TIcmp= new FingerTest(FingerPrintDef::IE, *o.reference_FPs->MatchPoints); + FingerTest &test = *hss->FP_TIcmp; - AV.attribute = "R"; - AV.value = "Y"; - AVs->push_back(AV); + test.setAVal("R", "Y"); /* DFI test values: * Y. Both set DF; @@ -3266,52 +3068,42 @@ bool HostOsScan::processTIcmpResp(HostOsScanStats *hss, const struct ip *ip, int * N. Both not set; * O. Other(both different with the sender, -_-b). */ - AV.attribute = "DFI"; value1 = (ntohs(ip1->ip_off) & IP_DF); value2 = (ntohs(ip2->ip_off) & IP_DF); if (value1 && value2) /* both set */ - AV.value = "Y"; + test.setAVal("DFI", "Y"); else if (value1 && !value2) /* echo back */ - AV.value = "S"; + test.setAVal("DFI", "S"); else if (!value1 && !value2) /* neither set */ - AV.value = "N"; + test.setAVal("DFI", "N"); else - AV.value = "O"; - AVs->push_back(AV); + test.setAVal("DFI", "O"); /* TTL */ - AV.attribute = "T"; - AV.value = hss->target->FPR->cp_hex(ip1->ip_ttl); - AVs->push_back(AV); + test.setAVal("T", hss->target->FPR->cp_hex(ip1->ip_ttl)); /* ICMP Code value. Test values: * [Value]. Both set Code to the same value [Value]; * S. Both use the Code that the sender uses; * O. Other. */ - AV.attribute = "CD"; value1 = icmp1->icmp_code; value2 = icmp2->icmp_code; if (value1 == value2) { if (value1 == 0) - AV.value = "Z"; + test.setAVal("CD", "Z"); else - AV.value = hss->target->FPR->cp_hex(value1); + test.setAVal("CD", hss->target->FPR->cp_hex(value1)); } else if (value1 == 9 && value2 == 0) /* both the same as in the corresponding probe */ - AV.value = "S"; + test.setAVal("CD", "S"); else - AV.value = "O"; - AVs->push_back(AV); - - hss->FP_TIcmp= new FingerTest; - hss->FP_TIcmp->name = "IE"; - hss->FP_TIcmp->results = AVs; + test.setAVal("CD", "O"); return true; } diff --git a/osscan2.h b/osscan2.h index b8fc5be8d..5e28bbfcc 100644 --- a/osscan2.h +++ b/osscan2.h @@ -71,8 +71,7 @@ #include #include #include "timing.h" -struct FingerPrint; -struct FingerTest; +#include "osscan.h" class FingerPrintResultsIPv4; class Target; @@ -81,8 +80,6 @@ class Target; * CONSTANT DEFINITIONS * ******************************************************************************/ -#define NUM_FPTESTS 13 - /* The number of tries we normally do. This may be increased if the target looks like a good candidate for fingerprint submission, or fewer if the user gave the --max-os-tries option */ @@ -173,11 +170,6 @@ typedef enum OFProbeType { * FUNCTION PROTOTYPES * ******************************************************************************/ -/* This is the primary OS detection function. If many Targets are - passed in (the threshold is based on timing level), they are - processed as smaller groups to improve accuracy */ -void os_scan2(std::vector &Targets); - int get_initial_ttl_guess(u8 ttl); int identify_sequence(int numSamples, u32 *ipid_diffs, int islocalhost, int allipideqz); @@ -303,22 +295,22 @@ class HostOsScanStats { * finally be passed to hs->target->FPR->FPs[x]. */ FingerPrint *FP; FingerTest *FPtests[NUM_FPTESTS]; - #define FP_TSeq FPtests[0] - #define FP_TOps FPtests[1] - #define FP_TWin FPtests[2] - #define FP_TEcn FPtests[3] - #define FP_T1_7_OFF 4 - #define FP_T1 FPtests[4] - #define FP_T2 FPtests[5] - #define FP_T3 FPtests[6] - #define FP_T4 FPtests[7] - #define FP_T5 FPtests[8] - #define FP_T6 FPtests[9] - #define FP_T7 FPtests[10] - #define FP_TUdp FPtests[11] - #define FP_TIcmp FPtests[12] - struct AVal *TOps_AVs[6]; /* 6 AVs of TOps */ - struct AVal *TWin_AVs[6]; /* 6 AVs of TWin */ + #define FP_TSeq FPtests[ID2INT(FingerPrintDef::SEQ)] + #define FP_TOps FPtests[ID2INT(FingerPrintDef::OPS)] + #define FP_TWin FPtests[ID2INT(FingerPrintDef::WIN)] + #define FP_TEcn FPtests[ID2INT(FingerPrintDef::ECN)] + #define FP_T1_7_OFF ID2INT(FingerPrintDef::T1) + #define FP_T1 FPtests[ID2INT(FingerPrintDef::T1)] + #define FP_T2 FPtests[ID2INT(FingerPrintDef::T2)] + #define FP_T3 FPtests[ID2INT(FingerPrintDef::T3)] + #define FP_T4 FPtests[ID2INT(FingerPrintDef::T4)] + #define FP_T5 FPtests[ID2INT(FingerPrintDef::T5)] + #define FP_T6 FPtests[ID2INT(FingerPrintDef::T6)] + #define FP_T7 FPtests[ID2INT(FingerPrintDef::T7)] + #define FP_TUdp FPtests[ID2INT(FingerPrintDef::U1)] + #define FP_TIcmp FPtests[ID2INT(FingerPrintDef::IE)] + const char *TOps_AVs[6]; /* 6 AVs of TOps */ + const char *TWin_AVs[6]; /* 6 AVs of TWin */ /* The following are variables to store temporary results * during the os fingerprinting process of this host. */