/* * Copyright (C) 2025 Cisco Systems, Inc. and/or its affiliates. All rights reserved. * * Author: Valerie Snyder * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ /* * This example demonstrates using callbacks to record information about each * file found during a recursive scan. */ #include #include #include #ifndef _WIN32 #include #endif #include #include #include #include #ifdef _WIN32 typedef int ssize_t; #endif static cl_error_t get_all_calculated_hashes( cl_fmap_t *fmap, bool *have_md5_out, bool *have_sha1_out, bool *have_sha256_out, char **md5_hash_out, char **sha1_hash_out, char **sha256_hash_out); const char *command_list = "1 - Return CL_BREAK to abort scanning. Will still encounter POST_SCAN-callbacks on the way out.\n" "2 - Return CL_SUCCESS to keep scanning. Will ignore an alert in the ALERT-callback.\n" "3 - Return CL_VIRUS to create a new alert and keep scanning. Will agree with alert in the ALERT-callback.\n" "4 - Return CL_VERIFIED to trust this layer (discarding all alerts) and skip the rest of this layer.\n" "5 - Request md5 hash when it calculates any hash. Does not return from the callback!\n" "6 - Request sha1 hash when it calculates any hash. Does not return from the callback!\n" "7 - Request sha2-256 hash when it calculates any hash. Does not return from the callback!\n" "8 - Get md5 hash. Does not return from the callback!\n" "9 - Get sha1 hash. Does not return from the callback!\n" "10 - Get sha2-256 hash. Does not return from the callback!\n" "11 - Print all hashes that have already been calculated. Does not return from the callback!\n"; /** * @brief Select an action based on user input. * * @param layer The current scan layer. * @param is_alert_callback Indicates if this is an alert callback. * @param choice The user's choice. * @return cl_error_t The result of the action or else CL_EDUP to indicate the caller should run this again * with another choice. This is so that the user can select an action does not return. */ cl_error_t select_choice(cl_scan_layer_t *layer, int choice) { switch (choice) { case 1: { // Return CL_BREAK to abort scanning. Will still encounter POST_SCAN-callbacks on the way out. return CL_BREAK; } case 2: { // Return CL_SUCCESS to keep scanning. Will ignore an alert in the ALERT-callback. return CL_SUCCESS; } case 3: { // Return CL_VIRUS to create a new alert and keep scanning. Will agree with alert in the ALERT-callback. return CL_VIRUS; } case 4: { // Return CL_VERIFIED to trust this layer (discarding all alerts) and skip the rest of this layer. return CL_VERIFIED; } case 5: { // Request md5 hash when it calculates any hash. Does not return from the callback! cl_fmap_t *fmap = NULL; cl_error_t ret; /* * Indicate we want this hash calculated later. * We could just get the hash now using cl_fmap_get_hash(), * but this is just an example of how to request hashes * to be calculated later. */ if (CL_SUCCESS != (ret = cl_scan_layer_get_fmap(layer, &fmap))) { printf("❌ cl_scan_layer_get_fmap() failed: %s\n", cl_strerror(ret)); } else { if (CL_SUCCESS != (ret = cl_fmap_will_need_hash_later(fmap, "md5"))) { printf("❌ cl_fmap_will_need_hash_later(md5) failed: %s\n", cl_strerror(ret)); } } printf("\n✅ Requested md5 hash for next time.\n\n"); return CL_EDUP; // Indicate the caller should run this again with another choice to a return code. } case 6: { // Request sha1 hash when it calculates any hash. Does not return from the callback! cl_fmap_t *fmap = NULL; cl_error_t ret; /* * Indicate we want this hash calculated later. * We could just get the hash now using cl_fmap_get_hash(), * but this is just an example of how to request hashes * to be calculated later. */ if (CL_SUCCESS != (ret = cl_scan_layer_get_fmap(layer, &fmap))) { printf("❌ cl_scan_layer_get_fmap() failed: %s\n", cl_strerror(ret)); } else { if (CL_SUCCESS != (ret = cl_fmap_will_need_hash_later(fmap, "sha1"))) { printf("❌ cl_fmap_will_need_hash_later(sha1) failed: %s\n", cl_strerror(ret)); } } printf("\n✅ Requested sha1 hash for next time.\n\n"); return CL_EDUP; // Indicate the caller should run this again with another choice to a return code. } case 7: { // Request sha2-256 hash when it calculates any hash. Does not return from the callback! cl_fmap_t *fmap = NULL; cl_error_t ret; /* * Indicate we want this hash calculated later. * We could just get the hash now using cl_fmap_get_hash(), * but this is just an example of how to request hashes * to be calculated later. */ if (CL_SUCCESS != (ret = cl_scan_layer_get_fmap(layer, &fmap))) { printf("❌ cl_scan_layer_get_fmap() failed: %s\n", cl_strerror(ret)); } else { if (CL_SUCCESS != (ret = cl_fmap_will_need_hash_later(fmap, "sha256"))) { printf("❌ cl_fmap_will_need_hash_later(sha256) failed: %s\n", cl_strerror(ret)); } } printf("\n✅ Requested sha2-256 hash for next time.\n\n"); return CL_EDUP; // Indicate the caller should run this again with another choice to a return code. } case 8: { // Get md5 hash. Does not return from the callback! cl_fmap_t *fmap = NULL; cl_error_t ret; char *md5_hash = NULL; if (CL_SUCCESS != (ret = cl_scan_layer_get_fmap(layer, &fmap))) { printf("❌ cl_scan_layer_get_fmap() failed: %s\n", cl_strerror(ret)); return ret; } ret = cl_fmap_get_hash(fmap, "md5", &md5_hash); if (CL_SUCCESS != ret || !md5_hash) { printf("❌ Failed to get md5 hash: %s\n", cl_strerror(CL_ECVD)); return CL_ECVD; } printf("\n✅ MD5 Hash: %s\n\n", md5_hash); free(md5_hash); // Free the allocated hash string. return CL_EDUP; // Indicate the caller should run this again with another choice to a return code. } case 9: { // Get sha1 hash. Does not return from the callback! cl_fmap_t *fmap = NULL; cl_error_t ret; char *sha1_hash = NULL; if (CL_SUCCESS != (ret = cl_scan_layer_get_fmap(layer, &fmap))) { printf("❌ cl_scan_layer_get_fmap() failed: %s\n", cl_strerror(ret)); return ret; } ret = cl_fmap_get_hash(fmap, "sha1", &sha1_hash); if (CL_SUCCESS != ret || !sha1_hash) { printf("❌ Failed to get sha1 hash: %s\n", cl_strerror(CL_ECVD)); return CL_ECVD; } printf("\n✅ SHA1 Hash: %s\n\n", sha1_hash); free(sha1_hash); // Free the allocated hash string. return CL_EDUP; // Indicate the caller should run this again with another choice to a return code. } case 10: { // Get sha2-256 hash. Does not return from the callback! cl_fmap_t *fmap = NULL; cl_error_t ret; char *sha2_256_hash = NULL; if (CL_SUCCESS != (ret = cl_scan_layer_get_fmap(layer, &fmap))) { printf("❌ cl_scan_layer_get_fmap() failed: %s\n", cl_strerror(ret)); return ret; } ret = cl_fmap_get_hash(fmap, "sha2-256", &sha2_256_hash); if (CL_SUCCESS != ret || !sha2_256_hash) { printf("❌ Failed to get sha2-256 hash: %s\n", cl_strerror(CL_ECVD)); return CL_ECVD; } printf("\n✅ SHA2-256 Hash: %s\n\n", sha2_256_hash); free(sha2_256_hash); // Free the allocated hash string. return CL_EDUP; // Indicate the caller should run this again with another choice to a return code. } case 11: { // Print all hashes that have already been calculated. Does not return from the callback! cl_fmap_t *fmap = NULL; cl_error_t ret; if (CL_SUCCESS != (ret = cl_scan_layer_get_fmap(layer, &fmap))) { printf("❌ cl_scan_layer_get_fmap() failed: %s\n", cl_strerror(ret)); return ret; } get_all_calculated_hashes( fmap, NULL, // have_md5_out NULL, // have_sha1_out NULL, // have_sha256_out NULL, // md5_hash_out NULL, // sha1_hash_out NULL // sha256_hash_out ); return CL_EDUP; // Indicate the caller should run this again with another choice to a return code. } default: { printf("Invalid choice. Aborting scan.\n"); return CL_BREAK; } } } /** * @brief Prompt the user for input on what to do next. * * @return cl_error_t */ static cl_error_t prompt_user_for_what_to_do(cl_scan_layer_t *layer, bool is_alert_callback) { cl_error_t ret; printf("What do you want to do?\n" "%s", command_list); printf("👉 "); int choice = 0; // read a single character without the user having to press enter if (scanf("%d", &choice) != 1) { // clear stdin int c; while ((c = getchar()) != '\n' && c != EOF) { continue; } printf("Invalid input. Please enter a number between 1 and 11.\n"); return prompt_user_for_what_to_do(layer, is_alert_callback); } ret = select_choice(layer, choice); if (CL_EDUP == ret) { // Run this function again to get another choice. return prompt_user_for_what_to_do(layer, is_alert_callback); } return ret; } typedef struct { int *script_commands; size_t num_script_commands; size_t current_command_index; } script_context_t; void free_script_context(script_context_t *context) { if (context) { free(context->script_commands); free(context); } } /** * @brief Get the choice from the script which is a series of commands to run that we stored in the script_context. * * @param context The script context containing the commands to run. * @param layer The scan layer. * @param is_alert_callback Whether this is an alert callback. * @return cl_error_t */ static cl_error_t consult_script_for_what_to_do(script_context_t *context, cl_scan_layer_t *layer) { cl_error_t ret; if (context->current_command_index >= context->num_script_commands) { printf("No more commands in script. Aborting scan.\n"); return CL_BREAK; } int choice = context->script_commands[context->current_command_index++]; ret = select_choice(layer, choice); if (CL_EDUP == ret) { // Run this function again to get another choice. return consult_script_for_what_to_do(context, layer); } return ret; } /** * @brief Read script commands from a file. * * @param script_filepath The path to the script file. * @return script_context_t* A context containing the script commands, or NULL on failure. * The caller is responsible for freeing the context with free_script_context(). */ script_context_t *read_script_commands(const char *script_filepath) { int status = -1; script_context_t *context = NULL; char *script_contents = NULL; FILE *script_file = NULL; if (NULL == script_filepath) { printf("No script file provided.\n"); goto done; } // Load script commands from file script_file = fopen(script_filepath, "r"); if (!script_file) { printf("Can't open script file %s\n", script_filepath); goto done; } // Read the whole file into a string fseek(script_file, 0, SEEK_END); long script_size = ftell(script_file); if (script_size < 0) { printf("Error reading script file %s\n", script_filepath); goto done; } fseek(script_file, 0, SEEK_SET); script_contents = calloc(script_size + 1, sizeof(char)); if (!script_contents) { printf("Memory allocation failed for script contents\n"); goto done; } size_t bytes_read = fread(script_contents, 1, script_size, script_file); if (bytes_read != (size_t)script_size) { printf("Error reading script file %s\n", script_filepath); status = 2; goto done; } // Allocate context for script commands context = malloc(sizeof(script_context_t)); if (NULL == context) { printf("Memory allocation failed for script context\n"); goto done; } context->script_commands = NULL; context->num_script_commands = 0; context->current_command_index = 0; // split the script contents into commands char *command = strtok(script_contents, "\n"); while (command != NULL) { // Ignore empty lines and comments if (strlen(command) > 0 && command[0] != '#') { // Allocate more space for the commands int *new_commands = realloc(context->script_commands, (context->num_script_commands + 1) * sizeof(int)); if (new_commands == NULL) { printf("Memory allocation failed for script commands\n"); goto done; } context->script_commands = new_commands; // Get the command as an integer char *endptr; context->script_commands[context->num_script_commands] = strtol(command, &endptr, 10); if (*endptr != '\0') { printf("Invalid command in script: %s\n", command); goto done; } context->num_script_commands++; } command = strtok(NULL, "\n"); } status = 0; done: if (NULL != script_contents) { free(script_contents); } if (NULL != script_file) { fclose(script_file); } if (status != 0) { if (NULL != context) { free_script_context(context); context = NULL; } } return context; } /** * @brief Check if the data matches the given hash. * * @param data The data to check. * @param len The length of the data. * @param hash_type The type of hash (e.g., "md5", "sha1", "sha256"). * @param hash The hash to compare against. * @return true if the data matches the hash, false otherwise. */ static bool check_hash(const uint8_t *data, size_t len, const char *hash_type, const char *hash) { bool status = false; uint8_t computed_hash[SHA256_HASH_SIZE]; unsigned int computed_hash_len = 0; size_t i; char computed_hash_string[SHA256_HASH_SIZE * 2 + 1] = {0}; if (strcmp(hash_type, "md5") == 0) { // Compute MD5 hash of data (void)cl_hash_data(hash_type, data, len, computed_hash, &computed_hash_len); if (computed_hash_len != MD5_HASH_SIZE) { printf("Unexpected MD5 hash length: %u\n", computed_hash_len); goto done; } // Convert computed hash to hex string for (i = 0; i < MD5_HASH_SIZE; i++) { snprintf(&computed_hash_string[i * 2], 3, "%02x", computed_hash[i]); } } else if (strcmp(hash_type, "sha1") == 0) { // Compute SHA1 hash of data (void)cl_hash_data(hash_type, data, len, computed_hash, &computed_hash_len); if (computed_hash_len != SHA1_HASH_SIZE) { printf("Unexpected SHA1 hash length: %u\n", computed_hash_len); goto done; } // Convert computed hash to hex string for (i = 0; i < SHA1_HASH_SIZE; i++) { snprintf(&computed_hash_string[i * 2], 3, "%02x", computed_hash[i]); } } else if (strcmp(hash_type, "sha256") == 0) { // Compute SHA256 hash of data and compare with provided hash (void)cl_hash_data(hash_type, data, len, computed_hash, &computed_hash_len); if (computed_hash_len != SHA256_HASH_SIZE) { printf("Unexpected SHA256 hash length: %u\n", computed_hash_len); goto done; } // Convert computed hash to hex string for (i = 0; i < SHA256_HASH_SIZE; i++) { snprintf(&computed_hash_string[i * 2], 3, "%02x", computed_hash[i]); } } else { printf("Unsupported hash type: %s\n", hash_type); goto done; } // Compare with provided hash if (strcmp(computed_hash_string, hash) != 0) { printf("%s hash mismatch: computed %s, expected %s\n", hash_type, computed_hash_string, hash); goto done; } status = true; done: return status; } static cl_error_t get_all_calculated_hashes( cl_fmap_t *fmap, bool *have_md5_out, bool *have_sha1_out, bool *have_sha256_out, char **md5_hash_out, char **sha1_hash_out, char **sha256_hash_out) { cl_error_t status = CL_ERROR; bool have_md5 = false; bool have_sha1 = false; bool have_sha256 = false; char *md5_hash = NULL; char *sha1_hash = NULL; char *sha256_hash = NULL; /* * Get each hash type (if one exists) */ status = cl_fmap_have_hash(fmap, "md5", &have_md5); if (status != CL_SUCCESS) { printf("❌ cl_fmap_have_hash(md5) failed: %s\n", cl_strerror(status)); goto done; } if (have_md5) { if (have_md5_out) { *have_md5_out = true; } status = cl_fmap_get_hash(fmap, "md5", &md5_hash); if (status != CL_SUCCESS) { printf("❌ cl_fmap_get_hash(md5) failed: %s\n", cl_strerror(status)); goto done; } if (md5_hash_out) { *md5_hash_out = md5_hash; } } printf("MD5 Hash: %s\n", have_md5 ? md5_hash : ""); status = cl_fmap_have_hash(fmap, "sha1", &have_sha1); if (status != CL_SUCCESS) { printf("❌ cl_fmap_have_hash(sha1) failed: %s\n", cl_strerror(status)); goto done; } if (have_sha1) { if (have_sha1_out) { *have_sha1_out = true; } status = cl_fmap_get_hash(fmap, "sha1", &sha1_hash); if (status != CL_SUCCESS) { printf("❌ cl_fmap_get_hash(sha1) failed: %s\n", cl_strerror(status)); goto done; } if (sha1_hash_out) { *sha1_hash_out = sha1_hash; } } printf("SHA1 Hash: %s\n", have_sha1 ? sha1_hash : ""); status = cl_fmap_have_hash(fmap, "sha256", &have_sha256); if (status != CL_SUCCESS) { printf("❌ cl_fmap_have_hash(sha256) failed: %s\n", cl_strerror(status)); goto done; } if (have_sha256) { if (have_sha256_out) { *have_sha256_out = true; } status = cl_fmap_get_hash(fmap, "sha256", &sha256_hash); if (status != CL_SUCCESS) { printf("❌ cl_fmap_get_hash(sha256) failed: %s\n", cl_strerror(status)); goto done; } if (sha256_hash_out) { *sha256_hash_out = sha256_hash; } } printf("SHA256 Hash: %s\n", have_sha256 ? sha256_hash : ""); done: return CL_SUCCESS; } cl_error_t verify_data_using_hashes( const uint8_t *file_data, size_t file_size, bool have_md5, bool have_sha1, bool have_sha256, char *md5_hash, char *sha1_hash, char *sha256_hash) { cl_error_t status = CL_ERROR; /* * Verify the data using the hashes */ if (have_md5 && !check_hash(file_data, file_size, "md5", md5_hash)) { printf("❌ MD5 hash verification failed.\n"); goto done; } if (have_sha1 && !check_hash(file_data, file_size, "sha1", sha1_hash)) { printf("❌ SHA1 hash verification failed.\n"); goto done; } if (have_sha256 && !check_hash(file_data, file_size, "sha256", sha256_hash)) { printf("❌ SHA256 hash verification failed.\n"); goto done; } status = CL_SUCCESS; done: return status; } static cl_error_t print_layer_info(cl_scan_layer_t *layer) { cl_error_t status = CL_ERROR; cl_fmap_t *fmap = NULL; cl_scan_layer_t *parent = NULL; const char *file_type = NULL; uint32_t recursion_level = 0; uint64_t object_id = 0; const char *last_alert = NULL; uint32_t attributes = 0; const char *file_name = NULL; size_t file_size = 0; const char *file_path = NULL; size_t offset_from_path_fn = 0; size_t file_size_from_path_fn = 0; int fd_from_path_fn = -1; uint8_t *file_data_from_path = NULL; int fd = -1; size_t offset_from_fd_fn = 0; size_t file_size_from_fd_fn = 0; uint8_t *file_data_from_fd = NULL; const uint8_t *file_data = NULL; size_t file_size_from_data_fn = 0; bool have_md5 = false; bool have_sha1 = false; bool have_sha256 = false; char *md5_hash = NULL; char *sha1_hash = NULL; char *sha256_hash = NULL; while (NULL != layer) { /* * Collect, print, and verify attributes for each layer */ if (CL_SUCCESS != (status = cl_scan_layer_get_fmap(layer, &fmap))) { printf("❌ cl_scan_layer_get_fmap() failed: %s\n", cl_strerror(status)); goto done; } status = cl_scan_layer_get_recursion_level(layer, &recursion_level); if (status != CL_SUCCESS) { printf("❌ cl_scan_layer_get_recursion_level() failed: %s\n", cl_strerror(status)); goto done; } printf("Recursion Level: " STDu32 "\n", recursion_level); status = cl_scan_layer_get_object_id(layer, &object_id); if (status != CL_SUCCESS) { printf("❌ cl_scan_layer_get_object_id() failed: %s\n", cl_strerror(status)); goto done; } printf("Object ID: " STDu64 "\n", object_id); status = cl_fmap_get_name(fmap, &file_name); if (status != CL_SUCCESS) { printf("❌ cl_fmap_get_name() failed: %s\n", cl_strerror(status)); goto done; } printf("File Name: %s\n", file_name ? file_name : ""); status = cl_scan_layer_get_type(layer, &file_type); if (status != CL_SUCCESS) { printf("❌ cl_scan_layer_get_type() failed: %s\n", cl_strerror(status)); goto done; } printf("File Type: %s\n", file_type ? file_type : ""); status = cl_scan_layer_get_attributes(layer, &attributes); if (status != CL_SUCCESS) { printf("❌ cl_scan_layer_get_attributes() failed: %s\n", cl_strerror(status)); goto done; } if (attributes & LAYER_ATTRIBUTES_DECRYPTED) { printf("File Attributes: Decrypted\n"); } if (attributes & LAYER_ATTRIBUTES_NORMALIZED) { printf("File Attributes: Normalized\n"); } if (attributes & LAYER_ATTRIBUTES_EMBEDDED) { printf("File Attributes: Embedded\n"); } if (attributes & LAYER_ATTRIBUTES_NORMALIZED) { printf("File Attributes: Embedded\n"); } if (attributes & LAYER_ATTRIBUTES_RETYPED) { printf("File Attributes: Re-typed\n"); } if (attributes == LAYER_ATTRIBUTES_NONE) { printf("File Attributes: None\n"); } status = cl_scan_layer_get_last_alert(layer, &last_alert); if (status != CL_SUCCESS) { printf("❌ cl_scan_layer_get_last_alert() failed: %s\n", cl_strerror(status)); goto done; } if (last_alert) { printf("Last Alert: %s\n", last_alert); } status = get_all_calculated_hashes( fmap, &have_md5, &have_sha1, &have_sha256, &md5_hash, &sha1_hash, &sha256_hash); if (status != CL_SUCCESS) { printf("❌ Failed to get all calculated hashes: %s\n", cl_strerror(status)); goto done; } status = cl_fmap_get_size(fmap, &file_size); if (status != CL_SUCCESS) { printf("❌ cl_fmap_get_size() failed: %s\n", cl_strerror(status)); goto done; } printf("File Size: %zu bytes\n", file_size); /* * Check cl_fmap_get_data() */ status = cl_fmap_get_data(fmap, 0, file_size, &file_data, &file_size_from_data_fn); if (status != CL_SUCCESS) { printf("❌ cl_fmap_get_data() failed: %s\n", cl_strerror(status)); goto done; } /* Verify the alleged size */ if (file_size_from_data_fn != file_size) { printf("❌ Size mismatch: cl_fmap_get_data() => %zu != cl_fmap_get_size() => %zu\n", file_size_from_data_fn, file_size); goto done; } /* verify the data using the hashes */ status = verify_data_using_hashes( file_data, file_size, have_md5, have_sha1, have_sha256, md5_hash, sha1_hash, sha256_hash); if (CL_SUCCESS != status) { printf("❌ Hash verification failed for data read from file descriptor.\n"); goto done; } if (have_md5 || have_sha1 || have_sha256) { printf("✔️ Successfully verified data provided by cl_fmap_get_data()\n"); } /* * Check cl_fmap_get_path() */ status = cl_fmap_get_path(fmap, &file_path, &offset_from_path_fn, &file_size_from_path_fn); if (status != CL_SUCCESS && status != CL_EACCES) { printf("❌ cl_fmap_get_path() failed: %s\n", cl_strerror(status)); goto done; } if (NULL != file_path) { /* Verify the alleged size */ if (file_size_from_path_fn != file_size) { printf("❌ Size mismatch: cl_fmap_get_path() => %zu != cl_fmap_get_size() => %zu\n", file_size_from_path_fn, file_size); goto done; } file_data_from_path = (uint8_t *)malloc(file_size); if (NULL == file_data_from_path) { printf("❌ malloc() failed\n"); status = CL_EMEM; goto done; } // read the data from the file path fd_from_path_fn = open(file_path, O_RDONLY); if (fd_from_path_fn == -1) { printf("❌ open(%s) failed\n", file_path); status = CL_EOPEN; goto done; } // Seek to the offset if (lseek(fd_from_path_fn, offset_from_path_fn, SEEK_SET) == -1) { printf("❌ lseek(%s) failed\n", file_path); status = CL_ESEEK; goto done; } ssize_t bytes_read = read(fd_from_path_fn, file_data_from_path, file_size); if (bytes_read < 0) { printf("❌ read(%s) failed. Errno: %s (%d)\n", file_path, strerror(errno), errno); status = CL_EREAD; goto done; } if ((size_t)bytes_read != file_size) { printf("❌ read(%s) returned %zd bytes, expected %zu bytes\n", file_path, bytes_read, file_size); status = CL_EREAD; goto done; } /* verify the data using the hashes */ status = verify_data_using_hashes( file_data_from_path, file_size, have_md5, have_sha1, have_sha256, md5_hash, sha1_hash, sha256_hash); if (CL_SUCCESS != status) { printf("❌ Hash verification failed for data read from file descriptor.\n"); goto done; } free(file_data_from_path); file_data_from_path = NULL; close(fd_from_path_fn); fd_from_path_fn = -1; printf("File Path: %s\n", file_path); printf("Offset in File: %zu\n", offset_from_path_fn); if (have_md5 || have_sha1 || have_sha256) { printf("✔️ Successfully verified data read using cl_fmap_get_path()\n"); } } else { printf("👌No file path for this layer.\n"); } /* * Check cl_fmap_get_fd() */ status = cl_fmap_get_fd(fmap, &fd, &offset_from_fd_fn, &file_size_from_fd_fn); if (status != CL_SUCCESS && status != CL_EACCES) { printf("❌ cl_fmap_get_fd() failed: %s\n", cl_strerror(status)); goto done; } if (-1 != fd) { /* Verify the alleged size */ if (file_size_from_fd_fn != file_size) { printf("❌ Size mismatch: cl_fmap_get_fd() => %zu != cl_fmap_get_size() => %zu\n", file_size_from_fd_fn, file_size); goto done; } file_data_from_fd = (uint8_t *)malloc(file_size); if (NULL == file_data_from_fd) { printf("❌ malloc() failed\n"); status = CL_EMEM; goto done; } // Seek to the offset if (lseek(fd, offset_from_fd_fn, SEEK_SET) == -1) { printf("❌ lseek(fd: %d) failed\n", fd); status = CL_ESEEK; goto done; } ssize_t bytes_read = read(fd, file_data_from_fd, file_size); if (bytes_read < 0) { printf("❌ read(fd: %d) failed. Errno: %s (%d)\n", fd, strerror(errno), errno); status = CL_EREAD; goto done; } if ((size_t)bytes_read != file_size) { printf("❌ read(fd: %d) returned %zd bytes, expected %zu bytes\n", fd, bytes_read, file_size); status = CL_EREAD; goto done; } /* verify the data using the hashes */ status = verify_data_using_hashes( file_data_from_fd, file_size, have_md5, have_sha1, have_sha256, md5_hash, sha1_hash, sha256_hash); if (CL_SUCCESS != status) { printf("❌ Hash verification failed for data read from file descriptor.\n"); goto done; } free(file_data_from_fd); file_data_from_fd = NULL; printf("File Desc: %d\n", fd); printf("Offset in File: %zu\n", offset_from_fd_fn); if (have_md5 || have_sha1 || have_sha256) { printf("✔️ Successfully verified data read using cl_fmap_get_fd()\n"); } } else { printf("👌No file descriptor for this layer.\n"); } /* * Clean up for this layer */ if (NULL != md5_hash) { free((void *)md5_hash); md5_hash = NULL; have_md5 = false; } if (NULL != sha1_hash) { free((void *)sha1_hash); sha1_hash = NULL; have_sha1 = false; } if (NULL != sha256_hash) { free((void *)sha256_hash); sha256_hash = NULL; have_sha256 = false; } /* * Get the parent layer */ status = cl_scan_layer_get_parent_layer(layer, &parent); if (status != CL_SUCCESS) { printf("❌ cl_scan_layer_get_parent_layer() failed: %s\n", cl_strerror(status)); goto done; } layer = parent; printf("\n"); // print empty line between layers } // while layer != NULL status = CL_SUCCESS; done: if (NULL != md5_hash) { free((void *)md5_hash); md5_hash = NULL; } if (NULL != sha1_hash) { free((void *)sha1_hash); sha1_hash = NULL; } if (NULL != sha256_hash) { free((void *)sha256_hash); sha256_hash = NULL; } if (-1 != fd_from_path_fn) { close(fd_from_path_fn); fd_from_path_fn = -1; } if (NULL != file_data_from_path) { free(file_data_from_path); file_data_from_path = NULL; } if (NULL != file_data_from_fd) { free(file_data_from_fd); file_data_from_fd = NULL; } // We don't free the file_data read from cl_fmap_get_data() because // the documentation does not say to do so. // We don't close the fd from cl_fmap_get_fd() because // the documentation does not say to do so. return status; } const char *cl_error_t_to_string(cl_error_t clerror) { switch (clerror) { case CL_SUCCESS: return "CL_SUCCESS"; case CL_VIRUS: return "CL_VIRUS"; case CL_ENULLARG: return "CL_ENULLARG"; case CL_EARG: return "CL_EARG"; case CL_EMALFDB: return "CL_EMALFDB"; case CL_ECVD: return "CL_ECVD"; case CL_EVERIFY: return "CL_EVERIFY"; case CL_EUNPACK: return "CL_EUNPACK"; case CL_EPARSE: return "CL_EPARSE"; case CL_EOPEN: return "CL_EOPEN"; case CL_ECREAT: return "CL_ECREAT"; case CL_EUNLINK: return "CL_EUNLINK"; case CL_ESTAT: return "CL_ESTAT"; case CL_EREAD: return "CL_EREAD"; case CL_ESEEK: return "CL_ESEEK"; case CL_EWRITE: return "CL_EWRITE"; case CL_EDUP: return "CL_EDUP"; case CL_EACCES: return "CL_EACCES"; case CL_ETMPFILE: return "CL_ETMPFILE"; case CL_ETMPDIR: return "CL_ETMPDIR"; case CL_EMAP: return "CL_EMAP"; case CL_EMEM: return "CL_EMEM"; case CL_ETIMEOUT: return "CL_ETIMEOUT"; case CL_EMAXREC: return "CL_EMAXREC"; case CL_EMAXSIZE: return "CL_EMAXSIZE"; case CL_EMAXFILES: return "CL_EMAXFILES"; case CL_EFORMAT: return "CL_EFORMAT"; case CL_EBYTECODE: return "CL_EBYTECODE"; case CL_EBYTECODE_TESTFAIL: return "CL_EBYTECODE_TESTFAIL"; case CL_ELOCK: return "CL_ELOCK"; case CL_EBUSY: return "CL_EBUSY"; case CL_ESTATE: return "CL_ESTATE"; case CL_ERROR: return "CL_ERROR"; case CL_VERIFIED: return "CL_VERIFIED"; case CL_BREAK: return "CL_BREAK"; default: return "Unknown error code"; } } const char *cl_verdict_t_to_string(cl_verdict_t verdict) { switch (verdict) { case CL_VERDICT_NOTHING_FOUND: return "CL_VERDICT_NOTHING_FOUND"; case CL_VERDICT_TRUSTED: return "CL_VERDICT_TRUSTED"; case CL_VERDICT_STRONG_INDICATOR: return "CL_VERDICT_STRONG_INDICATOR"; case CL_VERDICT_POTENTIALLY_UNWANTED: return "CL_VERDICT_POTENTIALLY_UNWANTED"; default: return "Unknown verdict value"; } } cl_error_t pre_hash_callback(cl_scan_layer_t *layer, void *context) { cl_error_t status; script_context_t *script_context = (script_context_t *)context; printf("\n⭐In PRE_HASH callback⭐\n"); print_layer_info(layer); if (script_context) { status = consult_script_for_what_to_do(script_context, layer); } else { status = prompt_user_for_what_to_do(layer, false); } if (CL_EDUP != status) { // If the script returned CL_EDUP, we should not continue with the scan. printf("↩️Returning: %s\n", cl_error_t_to_string(status)); } return status; } cl_error_t pre_scan_callback(cl_scan_layer_t *layer, void *context) { cl_error_t status; script_context_t *script_context = (script_context_t *)context; printf("\n⭐In PRE_SCAN callback⭐\n"); print_layer_info(layer); if (script_context) { status = consult_script_for_what_to_do(script_context, layer); } else { status = prompt_user_for_what_to_do(layer, false); } if (CL_EDUP != status) { // If the script returned CL_EDUP, we should not continue with the scan. printf("↩️Returning: %s\n", cl_error_t_to_string(status)); } return status; } cl_error_t post_scan_callback(cl_scan_layer_t *layer, void *context) { cl_error_t status; script_context_t *script_context = (script_context_t *)context; printf("\n⭐In POST_SCAN callback⭐\n"); print_layer_info(layer); if (script_context) { status = consult_script_for_what_to_do(script_context, layer); } else { status = prompt_user_for_what_to_do(layer, false); } if (CL_EDUP != status) { // If the script returned CL_EDUP, we should not continue with the scan. printf("↩️Returning: %s\n", cl_error_t_to_string(status)); } return status; } cl_error_t alert_callback(cl_scan_layer_t *layer, void *context) { cl_error_t status; script_context_t *script_context = (script_context_t *)context; printf("\n⚠️In ALERT callback⚠️\n"); print_layer_info(layer); if (script_context) { status = consult_script_for_what_to_do(script_context, layer); } else { status = prompt_user_for_what_to_do(layer, true); } if (CL_EDUP != status) { // If the script returned CL_EDUP, we should not continue with the scan. printf("↩️Returning: %s\n", cl_error_t_to_string(status)); } return status; } cl_error_t file_type_callback(cl_scan_layer_t *layer, void *context) { cl_error_t status; script_context_t *script_context = (script_context_t *)context; printf("\n⭐In FILE_TYPE callback⭐\n"); print_layer_info(layer); if (script_context) { status = consult_script_for_what_to_do(script_context, layer); } else { status = prompt_user_for_what_to_do(layer, false); } if (CL_EDUP != status) { // If the script returned CL_EDUP, we should not continue with the scan. printf("↩️Returning: %s\n", cl_error_t_to_string(status)); } return status; } static void printBytes(uint64_t bytes) { if (bytes >= (1024 * 1024 * 1024)) { printf("%.02f GiB", bytes / (double)(1024 * 1024 * 1024)); } else if (bytes >= (1024 * 1024)) { printf("%.02f MiB", bytes / (double)(1024 * 1024)); } else if (bytes >= 1024) { printf("%.02f KiB", bytes / (double)(1024)); } else { printf("%" PRIu64 " B", bytes); } } int file_props_callback(const char *j_propstr, int rc, void *context) { (void)context; // Unused in this example printf("\n⭐In FILE_PROPS callback⭐\n"); if (j_propstr) { printf("%s\n", j_propstr); } printf("Metadata JSON Return Code: %s (%d)\n", cl_error_t_to_string((cl_error_t)rc), rc); // Pass through the return code so as not to alter the scan return code. // A real application might want to handle this differently. return rc; } /* * Exit codes: * 0: clean * 1: infected * 2: error */ int main(int argc, char **argv) { int status = 2; cl_error_t ret = CL_ERROR; int target_fd = -1; const char *filename = NULL; const char *db_filepath = NULL; const char *script_filepath = NULL; const char *hash_hint = NULL; const char *hash_alg = NULL; const char *file_type_hint = NULL; bool allmatch = true; bool gen_json = false; bool debug_mode = false; script_context_t *script_context = NULL; uint64_t size = 0; cl_verdict_t verdict = CL_VERDICT_NOTHING_FOUND; const char *alert_name; struct cl_engine *engine = NULL; struct cl_scan_options options; unsigned int signo = 0; char *hash_out = NULL; char *file_type_out = NULL; bool disable_cache = false; int i = 0; const char *help_string = "Usage: %s -d -f \n" "Example: %s -d /path/to/clamav.db -f /path/to/file.txt\n" "\n" "Options:\n" "--help (-h) : Help message.\n" "--database (-d) FILE : Path to the ClamAV database.\n" "--file (-f) FILE : Path to the file to scan.\n" "--hash-hint HASH : (optional) Hash of file to scan.\n" "--hash-alg ALGORITHM : (optional) Hash algorithm of hash-hint.\n" " Will also change the hash algorithm reported at end of scan.\n" "--file-type-hint CL_TYPE_* : (optional) File type hint for the file to scan.\n" "--script FILE : (optional) Path for non-interactive test script.\n" " Script must be a new-line delimited list of integers from 1-to-5\n" " Corresponding to the interactive scan options.\n" "--one-match (-1) : Disable allmatch (stops scans after one match).\n" "--gen-json : Generate scan metadata JSON.\n" "--disable-cache : Disable caching of clean scan results.\n" "\n" "Scripted scan options are:\n" "%s"; for (i = 1; i < argc; i++) { if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { printf(help_string, argv[0], argv[0], command_list); status = 0; goto done; } else if (strcmp(argv[i], "-d") == 0 || strcmp(argv[i], "--database") == 0) { db_filepath = argv[++i]; printf("Database file: %s\n", db_filepath); } else if (strcmp(argv[i], "-f") == 0 || strcmp(argv[i], "--file") == 0) { filename = argv[++i]; printf("File to scan: %s\n", filename); } else if (strcmp(argv[i], "--script") == 0) { script_filepath = argv[++i]; printf("Script file: %s\n", script_filepath); } else if (strcmp(argv[i], "--hash-hint") == 0) { hash_hint = argv[++i]; printf("Hash hint: %s\n", hash_hint); } else if (strcmp(argv[i], "--hash-alg") == 0) { hash_alg = argv[++i]; printf("Hash algorithm: %s\n", hash_alg); } else if (strcmp(argv[i], "--file-type-hint") == 0) { file_type_hint = argv[++i]; printf("File type hint: %s\n", file_type_hint); } else if (strcmp(argv[i], "--one-match") == 0 || strcmp(argv[i], "-1") == 0) { allmatch = false; printf("Disabling allmatch (stops scans after one match).\n"); } else if (strcmp(argv[i], "--gen-json") == 0) { gen_json = true; printf("Enabling scan metadata JSON feature.\n"); } else if (strcmp(argv[i], "--debug") == 0) { debug_mode = true; printf("Enabling debug mode.\n"); } else if (strcmp(argv[i], "--disable-cache") == 0) { printf("Disabling caching of clean scan results.\n"); disable_cache = true; } else { printf("Unknown option: %s\n", argv[i]); printf(help_string, argv[0], argv[0], command_list); status = 2; goto done; } } printf("\n"); if (NULL == db_filepath || NULL == filename) { printf("Usage: %s \n", argv[0]); status = 2; goto done; } if (NULL != script_filepath) { printf("Running in non-interactive mode using script: %s\n", script_filepath); script_context = read_script_commands(script_filepath); if (NULL == script_context) { printf("Failed to read script commands from %s\n", script_filepath); status = 2; goto done; } } if ((target_fd = open(filename, O_RDONLY)) == -1) { printf("Can't open file %s\n", filename); goto done; } if (CL_SUCCESS != (ret = cl_init(CL_INIT_DEFAULT))) { printf("Can't initialize libclamav: %s\n", cl_strerror(ret)); goto done; } if (debug_mode) { cl_debug(); } if (!(engine = cl_engine_new())) { printf("Can't create new engine\n"); goto done; } /* Example version macro usage to determine if new feature is available */ #if defined(LIBCLAMAV_VERSION_NUM) && (LIBCLAMAV_VERSION_NUM >= 0x090400) /* Example feature usage disabling the scan time limit (for this interactive program). */ cl_engine_set_num(engine, CL_ENGINE_MAX_SCANTIME, 0); #endif /* Example feature usage raising the max file-size and scan-size to 1024MB */ cl_engine_set_num(engine, CL_ENGINE_MAX_SCANSIZE, 1024 /*MB*/ * 1024 /*KB*/ * 1024 /*bytes*/); cl_engine_set_num(engine, CL_ENGINE_MAX_FILESIZE, 1024 /*MB*/ * 1024 /*KB*/ * 1024 /*bytes*/); if (disable_cache) { cl_engine_set_num(engine, CL_ENGINE_DISABLE_CACHE, 1); // Disable cache for clean results } /* * Load signatures. * At least 1 signature required to initialize stuff required for scanning. */ if (CL_SUCCESS != (ret = cl_load(db_filepath, engine, &signo, CL_DB_STDOPT))) { printf("Database initialization error: %s\n", cl_strerror(ret)); goto done; } /* Build engine */ if (CL_SUCCESS != (ret = cl_engine_compile(engine))) { printf("Database initialization error: %s\n", cl_strerror(ret)); goto done; } /* Enable all parsers plus heuristics, allmatch, and the gen-json metadata feature. */ memset(&options, 0, sizeof(struct cl_scan_options)); options.parse |= ~0; /* enable all parsers */ options.general |= CL_SCAN_GENERAL_HEURISTICS; /* enable heuristic alert options */ if (allmatch) { options.general |= CL_SCAN_GENERAL_ALLMATCHES; /* run in all-match mode, so it keeps looking for alerts after the first one */ } if (gen_json) { options.general |= CL_SCAN_GENERAL_COLLECT_METADATA; /* collect metadata may enable collecting additional filenames (like in zip) */ } /* * Set our callbacks. */ cl_engine_set_scan_callback(engine, &pre_hash_callback, CL_SCAN_CALLBACK_PRE_HASH); cl_engine_set_scan_callback(engine, &pre_scan_callback, CL_SCAN_CALLBACK_PRE_SCAN); cl_engine_set_scan_callback(engine, &post_scan_callback, CL_SCAN_CALLBACK_POST_SCAN); cl_engine_set_scan_callback(engine, &alert_callback, CL_SCAN_CALLBACK_ALERT); cl_engine_set_scan_callback(engine, &file_type_callback, CL_SCAN_CALLBACK_FILE_TYPE); if (gen_json) { cl_engine_set_clcb_file_props(engine, &file_props_callback); } printf("Testing scan layer callbacks on: %s (fd: %d)\n", filename, target_fd); /* * Run the scan. * Note that the callbacks will be called during this function. */ ret = cl_scandesc_ex( target_fd, filename, &verdict, &alert_name, &size, engine, &options, script_context, hash_hint, &hash_out, hash_alg, file_type_hint, &file_type_out); /* Calculate size of scanned data */ printf("\n"); printf("Data scanned: "); printBytes(size); printf("\n"); if (hash_out) { printf("Hash: %s\n", hash_out); } else { printf("No hash provided for this file.\n"); } if (file_type_out) { printf("File Type: %s\n", file_type_out); } else { printf("No file type provided for this file.\n"); } printf("Verdict: %s\n", cl_verdict_t_to_string(verdict)); if (alert_name) { printf("Alert Name: %s\n", alert_name); } printf("Return Code: %s (%d)\n", cl_error_t_to_string(ret), ret); status = ret == CL_VIRUS ? 1 : 0; done: if (-1 != target_fd) { close(target_fd); } if (NULL != engine) { cl_engine_free(engine); } if (NULL != hash_out) { free(hash_out); } if (NULL != file_type_out) { free(file_type_out); } if (NULL != script_context) { free_script_context(script_context); } return status; }