rizin/librz/sign/sigdb.c
2024-09-15 19:12:07 +08:00

304 lines
7.9 KiB
C

// SPDX-FileCopyrightText: 2021 RizinOrg <info@rizin.re>
// SPDX-FileCopyrightText: 2021 deroad <wargio@libero.it>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_flirt.h>
/**
* \brief Frees a RzSigDBEntry structure
*
* \param[in] entry The RzSigDBEntry to free
*/
RZ_API void rz_sign_sigdb_signature_free(RZ_NULLABLE RzSigDBEntry *entry) {
if (!entry) {
return;
}
// base_name points to file_path, so there is no need to call free
// short_path points to file_path, so there is no need to call free
free(entry->bin_name);
free(entry->arch_name);
free(entry->file_path);
free(entry);
}
static int sigdb_signature_cmp(const RzSigDBEntry *a, const RzSigDBEntry *b, void *user) {
return strcmp(a->short_path, b->short_path);
}
static bool sigdb_signature_resolve_details(RzSigDBEntry *entry, size_t path_len, bool with_details) {
char *bin_end = NULL;
char *arch_end = NULL;
char *bits_end = NULL;
char copy_path[1024] = { 0 };
RzFlirtNode *node = NULL;
RzFlirtInfo info = { 0 };
RzBuffer *buffer = NULL;
#if __WINDOWS__
rz_str_replace_char(entry->file_path, '/', '\\');
#endif
// expected path elf/x86/64/signature.sig/.pat
strncpy(copy_path, entry->file_path + path_len, sizeof(copy_path) - 1);
entry->base_name = rz_file_basename(entry->file_path);
entry->short_path = entry->file_path + path_len;
if (!(bin_end = strstr(copy_path, RZ_SYS_DIR))) {
RZ_LOG_WARN("sigdb: folder structure is invalid (missing bin name).\n");
return false;
} else if (!(arch_end = strstr(bin_end + strlen(RZ_SYS_DIR), RZ_SYS_DIR))) {
RZ_LOG_WARN("sigdb: folder structure is invalid (missing arch name).\n");
return false;
} else if (!(bits_end = strstr(arch_end + strlen(RZ_SYS_DIR), RZ_SYS_DIR))) {
RZ_LOG_WARN("sigdb: folder structure is invalid (missing arch bits).\n");
return false;
}
if (!with_details) {
goto skip_details;
}
buffer = rz_buf_new_file(entry->file_path, O_RDONLY, 0);
if (!buffer) {
RZ_LOG_WARN("sigdb: cannot open signature file '%s'.\n", entry->file_path);
return false;
}
if (rz_str_endswith(entry->base_name, ".sig")) {
bool success = rz_sign_flirt_parse_header_compressed_pattern_from_buffer(buffer, &info);
rz_buf_free(buffer);
if (!success) {
return false;
}
entry->details = RZ_STR_DUP(info.u.sig.name);
entry->n_modules = info.u.sig.n_modules;
} else {
node = rz_sign_flirt_parse_string_pattern_from_buffer(buffer, RZ_FLIRT_NODE_OPTIMIZE_NONE, &info);
rz_buf_free(buffer);
if (!node) {
return false;
}
entry->n_modules = info.u.pat.n_modules;
}
rz_sign_flirt_node_free(node);
rz_sign_flirt_info_fini(&info);
skip_details:
bin_end[0] = 0;
entry->bin_name = rz_str_dup(copy_path);
arch_end[0] = 0;
entry->arch_name = rz_str_dup(bin_end + strlen(RZ_SYS_DIR));
bits_end[0] = 0;
entry->arch_bits = rz_get_input_num_value(NULL, arch_end + strlen(RZ_SYS_DIR));
return true;
}
/**
* \brief Returns a database of signatures loaded from the signature database path
*
* \param sigdb_path The signature database path/location
* \param with_details When true, opens each signature within the db for extra details
* \return List of entries
*/
RZ_API RZ_OWN RzSigDb *rz_sign_sigdb_load_database(RZ_NONNULL const char *sigdb_path, bool with_details) {
rz_return_val_if_fail(RZ_STR_ISNOTEMPTY(sigdb_path), NULL);
char glob[1024];
if (!rz_file_is_directory(sigdb_path)) {
RZ_LOG_ERROR("sigdb path is unknown or invalid (path: %s)\n", sigdb_path);
return NULL;
}
size_t path_len = strlen(sigdb_path) + 1; // ignoring also the filesystem separator
RzSigDb *sigs = rz_sign_sigdb_new();
if (!sigs) {
RZ_LOG_ERROR("cannot allocate signature database\n");
return NULL;
}
rz_strf(glob, RZ_JOIN_2_PATHS("%s", "**"), sigdb_path);
RzList *files = rz_file_globsearch(glob, 10);
char *file = NULL;
RzListIter *iter = NULL;
RzSigDBEntry *sig = NULL;
rz_list_foreach (files, iter, file) {
if (!rz_str_endswith(file, ".pat") && !rz_str_endswith(file, ".sig")) {
continue;
}
sig = RZ_NEW0(RzSigDBEntry);
if (!sig) {
goto fail;
}
sig->file_path = rz_str_dup(file);
if (!sig->file_path || !sigdb_signature_resolve_details(sig, path_len, with_details)) {
rz_sign_sigdb_signature_free(sig);
goto fail;
}
rz_sign_sigdb_add_entry(sigs, sig);
}
rz_list_free(files);
return sigs;
fail:
rz_list_free(files);
rz_sign_sigdb_free(sigs);
return NULL;
}
/**
* \brief Add a new signature entry to a database
*
* \param db Database of signatures
* \param entry Single signature entry to add to the database
* \return true if the signature entry was correctly added to the database, false otherwise
*/
RZ_API bool rz_sign_sigdb_add_entry(RZ_NONNULL RzSigDb *db, RZ_NONNULL const RzSigDBEntry *entry) {
rz_return_val_if_fail(db && entry, false);
return ht_pu_insert(db->entries, entry, 1);
}
struct sigdb_move_data_t {
RzSigDb *src;
RzSigDb *dst;
};
static bool sigdb_move_entry(void *user, const void *k, const ut64 v) {
struct sigdb_move_data_t *move_data = (struct sigdb_move_data_t *)user;
rz_sign_sigdb_add_entry(move_data->dst, k);
ht_pu_delete(move_data->src->entries, k);
return true;
}
/**
* \brief Merge the signatures from \p db2 into \p db
*
* Data within \p db2 is moved into \p db, making it empty.
*
* \param db Database of signatures to extend
* \param db2 Database of signatures that need to be merged into \p db
* \return true if the databases were correctly merged, false otherwise
*/
RZ_API bool rz_sign_sigdb_merge(RZ_NONNULL RzSigDb *db, RZ_NONNULL RzSigDb *db2) {
rz_return_val_if_fail(db && db2, false);
struct sigdb_move_data_t opt = {
.src = db2,
.dst = db,
};
db2->entries->opt.finiKV = NULL;
ht_pu_foreach(db2->entries, sigdb_move_entry, &opt);
return true;
}
static int ut32cmp(int a, int b) {
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
return 0;
}
static int strcmp_null(const char *a, const char *b) {
if (!a && !b) {
return 0;
}
if (!a && b) {
return -1;
}
if (a && !b) {
return 1;
}
return strcmp(a, b);
}
static int sigdb_entry_cmp(const void *a, const void *b) {
#define field_cmp(fname, cmpfunction) \
do { \
int r = cmpfunction(sa->fname, sb->fname); \
if (r != 0) { \
return r; \
} \
} while (0)
const RzSigDBEntry *sa = (const RzSigDBEntry *)a;
const RzSigDBEntry *sb = (const RzSigDBEntry *)b;
field_cmp(bin_name, strcmp_null);
field_cmp(arch_name, strcmp_null);
field_cmp(arch_bits, ut32cmp);
field_cmp(base_name, strcmp_null);
field_cmp(short_path, strcmp_null);
field_cmp(file_path, strcmp_null);
field_cmp(details, strcmp_null);
field_cmp(n_modules, ut32cmp);
return 0;
#undef field_cmp
}
static ut32 sigdb_entry_hash(const void *k) {
const RzSigDBEntry *s = (const RzSigDBEntry *)k;
ut32 r = sdb_hash(s->bin_name);
r ^= sdb_hash(s->arch_name);
r ^= s->arch_bits;
r ^= sdb_hash(s->short_path);
return r;
}
static void ht_pu_sigdb_finikv(HtPUKv *kv, RZ_UNUSED void *user) {
if (!kv) {
return;
}
rz_sign_sigdb_signature_free(kv->key);
}
/**
* \brief Create a new empty \p RzSigDb instance
*/
RZ_API RZ_OWN RzSigDb *rz_sign_sigdb_new(void) {
RzSigDb *db = RZ_NEW0(RzSigDb);
if (!db) {
return NULL;
}
HtPUOptions opt = { 0 };
opt.cmp = sigdb_entry_cmp,
opt.hashfn = sigdb_entry_hash,
opt.finiKV = ht_pu_sigdb_finikv;
db->entries = ht_pu_new_opt(&opt);
if (!db->entries) {
free(db);
return NULL;
}
return db;
}
RZ_API void rz_sign_sigdb_free(RzSigDb *db) {
if (!db) {
return;
}
ht_pu_free(db->entries);
free(db);
}
static bool sigdb_to_list(void *user, const void *k, const ut64 v) {
RzList *l = (RzList *)user;
rz_list_append(l, (void *)k);
return true;
}
/**
* \brief Return the signature database as a list of entries
*/
RZ_API RZ_OWN RzList /*<RzSigDBEntry *>*/ *rz_sign_sigdb_list(RZ_NONNULL const RzSigDb *db) {
rz_return_val_if_fail(db, NULL);
RzList *res = rz_list_newf((RzListFree)rz_sign_sigdb_signature_free);
if (!res) {
return NULL;
}
ht_pu_foreach(db->entries, sigdb_to_list, res);
rz_list_sort(res, (RzListComparator)sigdb_signature_cmp, NULL);
return res;
}