1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-15 20:29:03 +00:00

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.
This commit is contained in:
dmiller
2022-11-11 18:44:19 +00:00
parent 1fb680b93f
commit 56f59de131
4 changed files with 504 additions and 656 deletions

525
osscan.cc
View File

@@ -78,6 +78,88 @@
extern NmapOps o;
template<u8 _MaxStrLen> void ShortStr<_MaxStrLen>::setStr(const char *in) {
const char *end = in;
while (end - in < _MaxStrLen && *++end);
setStr(in, end);
trunc = trunc || *end;
}
template<u8 _MaxStrLen> 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<FingerPrint *>::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<struct AVal>;
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<FPstr, TestID>::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<FPstr, u8>::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<FingerTest>::iterator t = this->tests.begin();
t != this->tests.end(); t++) {
t->erase();
for (int i=0; i < NUM_FPTESTS; i++) {
tests[i].erase();
}
}
@@ -186,48 +284,34 @@ 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<struct AVal>::const_iterator current_ref, prev_ref;
std::vector<struct AVal>::const_iterator current_fp, prev_fp;
std::vector<struct AVal>::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<Attr> &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<const char *> &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;
}
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);
const std::vector<const char *> &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->value, current_ref->value)) {
if (expr_match(current_fp, current_ref)) {
subtests_succeeded += pointsThisTest;
} else {
if (shortcut) {
@@ -236,19 +320,9 @@ static int AVal_match(const FingerTest *reference, const FingerTest *fprint, con
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++;
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,57 +339,24 @@ 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<FingerTest>::const_iterator current_ref, prev_ref;
std::vector<FingerTest>::const_iterator current_fp, prev_fp;
std::vector<FingerTest>::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 &current_ref = referenceFP->tests[i];
const FingerTest &current_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,
AVal_match(current_ref, current_fp, 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++;
}
}
assert(num_subtests_succeeded <= num_subtests);
return (num_subtests) ? (num_subtests_succeeded / (double) num_subtests) : 0;
}
@@ -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<struct AVal>::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<const char *> &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)
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;
*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)
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)
goto error;
// 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 = '\0';
@@ -566,101 +592,94 @@ error:
return -1;
}
static std::vector<struct AVal> *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<struct AVal> *AVs = new std::vector<struct AVal>;
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<const char *> &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<FPstr, u8>::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 = 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<struct AVal>::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;
/* 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;
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;
}
/* 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);
else if (b->results == NULL) {
return false;
}
const std::vector<const char *> &av_a = *a->results;
size_t numtests = av_a.size();
const std::vector<const char *> &av_b = *b->results;
assert(av_b.size() == numtests);
return ia < ib;
}
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;
}
};
/* 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<const FingerTest *> tests;
std::list<const FingerTest *>::iterator iter;
std::vector<FingerTest>::iterator ft;
std::set<const FingerTest *, FingerTestCmp> tests;
std::set<const FingerTest *, FingerTestCmp>::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<const FingerTest *>::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<FingerTest>::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;
}
/* 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);

115
osscan.h
View File

@@ -67,6 +67,7 @@
#include <nbase.h>
#include <vector>
#include <map>
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<u8 _MaxStrLen>
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<FP_MAX_NAME_LEN> 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<FPstr, u8> AttrIdx;
std::vector<Attr> Attrs;
FingerTestDef() : name(), numAttrs(0), hasR(false) {}
FingerTestDef(const FPstr &n, const char *a[]);
};
#define ID2INT(_i) static_cast<int>(_i)
#define INT2ID(_i) static_cast<FingerPrintDef::TestID>(_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<FPstr, TestID> TestIdx;
std::vector<FingerTestDef> TestDefs;
};
struct OS_Classification {
@@ -124,30 +185,46 @@ struct FingerMatch {
}
};
struct FingerPrintDB;
struct FingerTest {
const char *name;
std::vector<struct AVal> *results;
FingerTest(bool allocResults=false);
FingerPrintDef::TestID id;
const FingerTestDef *def;
std::vector<const char *> *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<const char *>(def->numAttrs, NULL);
}
FingerTest(FingerPrintDef::TestID testid, FingerPrintDef &Defs)
: id(testid), results(NULL) {
def = &Defs.getTestDef(id);
results = new std::vector<const char *>(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<FingerTest> 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<FingerPrint *> 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

View File

@@ -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<struct AVal>::iterator it;
int ttl;
if (!hss->FP_TSeq)
makeTSeqFP(hss);
@@ -2051,19 +2031,16 @@ 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
FingerTest &test = *hss->FPtests[i];
/* The value for this attribute is the
* received TTL. We replace it with the initial TTL. */
ttl = strtol(it->value, NULL, 16);
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;
@@ -2073,13 +2050,12 @@ void HostOsScan::makeFP(HostOsScanStats *hss) {
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);
test.setAVal("T", 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;
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<struct AVal> *seq_AVs = new std::vector<struct AVal>;
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<struct AVal> *AVs = new std::vector<struct AVal>;
AVs->reserve(n);
hss->FP_TOps = new FingerTest(FingerPrintDef::OPS, *o.reference_FPs->MatchPoints);
std::vector<const char *> &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<struct AVal> *AVs = new std::vector<struct AVal>;
AVs->reserve(n);
hss->FP_TWin = new FingerTest(FingerPrintDef::WIN, *o.reference_FPs->MatchPoints);
std::vector<const char *> &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<struct AVal> *AVs = new std::vector<struct AVal>;
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<struct AVal> *AVs = new std::vector<struct AVal>;
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<struct AVal> *AVs = new std::vector<struct AVal>;
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<struct AVal> *AVs = new std::vector<struct AVal>;
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;
}

View File

@@ -71,8 +71,7 @@
#include <vector>
#include <list>
#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<Target *> &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. */