/* p0f - p0f.fp file parser ------------------------ Every project has this one really ugly C file. This is ours. Copyright (C) 2012 by Michal Zalewski Distributed under the terms and conditions of GNU LGPL. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include "types.h" #include "config.h" #include "debug.h" #include "alloc-inl.h" #include "fp_tcp.h" #include "fp_mtu.h" #include "fp_http.h" #include "readfp.h" static u32 sig_cnt; /* Total number of p0f.fp sigs */ static u8 state = CF_NEED_SECT, /* Parser state (CF_NEED_*) */ mod_type, /* Current module (CF_MOD_*) */ mod_to_srv, /* Traffic direction */ generic; /* Generic signature? */ static s32 sig_class; /* Signature class ID (-1 = userland) */ static u32 sig_name; /* Signature name */ static u8* sig_flavor; /* Signature flavor */ static u32* cur_sys; /* Current 'sys' values */ static u32 cur_sys_cnt; /* Number of 'sys' entries */ u8 **fp_os_classes, /* Map of OS classes */ **fp_os_names; /* Map of OS names */ static u32 class_cnt, /* Sizes for maps */ name_cnt, label_id, /* Current label ID */ line_no; /* Current line number */ /* Parse 'classes' parameter by populating fp_os_classes. */ static void config_parse_classes(u8* val) { while (*val) { u8* nxt; while (isblank(*val) || *val == ',') val++; nxt = val; while (isalnum(*nxt)) nxt++; if (nxt == val || (*nxt && *nxt != ',')) FATAL("Malformed class entry in line %u.", line_no); fp_os_classes = DFL_ck_realloc(fp_os_classes, (class_cnt + 1) * sizeof(u8*)); fp_os_classes[class_cnt++] = DFL_ck_memdup_str(val, nxt - val); val = nxt; } } /* Look up or create OS or application id. */ u32 lookup_name_id(u8* name, u8 len) { u32 i; for (i = 0; i < name_cnt; i++) if (!strncasecmp((char*)name, (char*)fp_os_names[i], len) && !fp_os_names[i][len]) break; if (i == name_cnt) { sig_name = name_cnt; fp_os_names = DFL_ck_realloc(fp_os_names, (name_cnt + 1) * sizeof(u8*)); fp_os_names[name_cnt++] = DFL_ck_memdup_str(name, len); } return i; } /* Parse 'label' parameter by looking up ID and recording name / flavor. */ static void config_parse_label(u8* val) { u8* nxt; u32 i; /* Simplified handling for [mtu] signatures. */ if (mod_type == CF_MOD_MTU) { if (!*val) FATAL("Empty MTU label in line %u.\n", line_no); sig_flavor = DFL_ck_strdup(val); return; } if (*val == 'g') generic = 1; else if (*val == 's') generic = 0; else FATAL("Malformed class entry in line %u.", line_no); if (val[1] != ':') FATAL("Malformed class entry in line %u.", line_no); val += 2; nxt = val; while (isalnum(*nxt) || *nxt == '!') nxt++; if (nxt == val || *nxt != ':') FATAL("Malformed class entry in line %u.", line_no); if (*val == '!' && val[1] == ':') { sig_class = -1; } else { *nxt = 0; for (i = 0; i < class_cnt; i++) if (!strcasecmp((char*)val, (char*)fp_os_classes[i])) break; if (i == class_cnt) FATAL("Unknown class '%s' in line %u.", val, line_no); sig_class = i; } nxt++; val = nxt; while (isalnum(*nxt) || (*nxt && strchr(NAME_CHARS, *nxt))) nxt++; if (nxt == val || *nxt != ':') FATAL("Malformed name in line %u.", line_no); sig_name = lookup_name_id(val, nxt - val); if (nxt[1]) sig_flavor = DFL_ck_strdup(nxt + 1); else sig_flavor = NULL; label_id++; } /* Parse 'sys' parameter into cur_sys[]. */ static void config_parse_sys(u8* val) { if (cur_sys) { cur_sys = NULL; cur_sys_cnt = 0; } while (*val) { u8* nxt; u8 is_cl = 0, orig; u32 i; while (isblank(*val) || *val == ',') val++; if (*val == '@') { is_cl = 1; val++; } nxt = val; while (isalnum(*nxt) || (*nxt && strchr(NAME_CHARS, *nxt))) nxt++; if (nxt == val || (*nxt && *nxt != ',')) FATAL("Malformed sys entry in line %u.", line_no); orig = *nxt; *nxt = 0; if (is_cl) { for (i = 0; i < class_cnt; i++) if (!strcasecmp((char*)val, (char*)fp_os_classes[i])) break; if (i == class_cnt) FATAL("Unknown class '%s' in line %u.", val, line_no); i |= SYS_CLASS_FLAG; } else { for (i = 0; i < name_cnt; i++) if (!strcasecmp((char*)val, (char*)fp_os_names[i])) break; if (i == name_cnt) { fp_os_names = DFL_ck_realloc(fp_os_names, (name_cnt + 1) * sizeof(u8*)); fp_os_names[name_cnt++] = DFL_ck_memdup_str(val, nxt - val); } } cur_sys = DFL_ck_realloc(cur_sys, (cur_sys_cnt + 1) * 4); cur_sys[cur_sys_cnt++] = i; *nxt = orig; val = nxt; } } /* Read p0f.fp line, dispatching it to fingerprinting modules as necessary. */ static void config_parse_line(u8* line) { u8 *val,*eon; /* Special handling for [module:direction]... */ if (*line == '[') { u8* dir; line++; /* Simplified case for [mtu]. */ if (!strcmp((char*)line, "mtu]")) { mod_type = CF_MOD_MTU; state = CF_NEED_LABEL; return; } dir = (u8*)strchr((char*)line, ':'); if (!dir) FATAL("Malformed section identifier in line %u.", line_no); *dir = 0; dir++; if (!strcmp((char*)line, "tcp")) { mod_type = CF_MOD_TCP; } else if (!strcmp((char*)line, "http")) { mod_type = CF_MOD_HTTP; } else { FATAL("Unrecognized fingerprinting module '%s' in line %u.", line, line_no); } if (!strcmp((char*)dir, "request]")) { mod_to_srv = 1; } else if (!strcmp((char*)dir, "response]")) { mod_to_srv = 0; } else { FATAL("Unrecognized traffic direction in line %u.", line_no); } state = CF_NEED_LABEL; return; } /* Everything else follows the 'name = value' approach. */ val = line; while (isalpha(*val) || *val == '_') val++; eon = val; while (isblank(*val)) val++; if (line == val || *val != '=') FATAL("Unexpected statement in line %u.", line_no); while (isblank(*++val)); *eon = 0; if (!strcmp((char*)line, "classes")) { if (state != CF_NEED_SECT) FATAL("misplaced 'classes' in line %u.", line_no); config_parse_classes(val); } else if (!strcmp((char*)line, "ua_os")) { if (state != CF_NEED_LABEL || mod_to_srv != 1 || mod_type != CF_MOD_HTTP) FATAL("misplaced 'us_os' in line %u.", line_no); http_parse_ua(val, line_no); } else if (!strcmp((char*)line, "label")) { /* We will drop sig_sys / sig_flavor on the floor if no signatures actually created, but it's not worth tracking that. */ if (state != CF_NEED_LABEL && state != CF_NEED_SIG) FATAL("misplaced 'label' in line %u.", line_no); config_parse_label(val); if (mod_type != CF_MOD_MTU && sig_class < 0) state = CF_NEED_SYS; else state = CF_NEED_SIG; } else if (!strcmp((char*)line, "sys")) { if (state != CF_NEED_SYS) FATAL("Misplaced 'sys' in line %u.", line_no); config_parse_sys(val); state = CF_NEED_SIG; } else if (!strcmp((char*)line, "sig")) { if (state != CF_NEED_SIG) FATAL("Misplaced 'sig' in line %u.", line_no); switch (mod_type) { case CF_MOD_TCP: tcp_register_sig(mod_to_srv, generic, sig_class, sig_name, sig_flavor, label_id, cur_sys, cur_sys_cnt, val, line_no); break; case CF_MOD_MTU: mtu_register_sig(sig_flavor, val, line_no); break; case CF_MOD_HTTP: http_register_sig(mod_to_srv, generic, sig_class, sig_name, sig_flavor, label_id, cur_sys, cur_sys_cnt, val, line_no); break; } sig_cnt++; } else { FATAL("Unrecognized field '%s' in line %u.", line, line_no); } } /* Top-level file parsing. */ void read_config(u8* fname) { s32 f; struct stat st; u8 *data, *cur; f = open((char*)fname, O_RDONLY); if (f < 0) PFATAL("Cannot open '%s' for reading.", fname); if (fstat(f, &st)) PFATAL("fstat() on '%s' failed.", fname); if (!st.st_size) { close(f); goto end_fp_read; } cur = data = ck_alloc(st.st_size + 1); if (read(f, data, st.st_size) != st.st_size) FATAL("Short read from '%s'.", fname); data[st.st_size] = 0; close(f); /* If you put NUL in your p0f.fp... Well, sucks to be you. */ while (1) { u8 *eol; line_no++; while (isblank(*cur)) cur++; eol = cur; while (*eol && *eol != '\n') eol++; if (*cur != ';' && cur != eol) { u8* line = ck_memdup_str(cur, eol - cur); config_parse_line(line); ck_free(line); } if (!*eol) break; cur = eol + 1; } ck_free(data); end_fp_read: if (!sig_cnt) SAYF("[!] No signatures found in '%s'.\n", fname); else SAYF("[+] Loaded %u signature%s from '%s'.\n", sig_cnt, sig_cnt == 1 ? "" : "s", fname); }