Commit b150586b by Melvin Klimke Committed by Enkelmann

Cwe caller (#40)

Added cwe_checker executable allowing shorter command line calls
parent 09f8398b
......@@ -8,6 +8,7 @@ dev
- Added file output support via --out (PR #30)
- Surpress logging of info, error and warning to STDOUT via --no-logging (PR #32)
- Added check-path feature via --check-path that searches paths between interesting input functions and cwe hits (PR #31)
- Added convenience executable to enable shorter command line options (PR #40)
- Added a plugin for integration into Ghidra (PR #42)
0.2 (2019-06-25)
......
open Core_kernel
open Cwe_checker_core.Main
exception InvalidPathException of string
exception NoOutputFileException of string
exception NoModulesException of string
exception NoConfigException of string
exception NoBinaryPathException of string
let rec get_difference (set_a : 'a list) (set_b: 'a list) : 'a list =
match set_a with
| [] -> []
| element_of_a::remain_a ->
match (Stdlib.List.mem element_of_a set_b) with
| true -> get_difference remain_a set_b
| false -> List.append (get_difference remain_a set_b) [element_of_a]
let get_user_input ?(position=1) (() : unit) : string list =
Array.to_list (Array.sub Sys.argv ~pos:position ~len:(Array.length Sys.argv - position))
let rec find_prefix (input : string list) (prefix : string) : string option =
match input with
| [] -> None
| head::tail ->
match (String.is_prefix head ~prefix:prefix) with
| true -> Some head
| false -> find_prefix tail prefix
let rec replace_element (set : string list) (element : string) (replacement : string) : string list =
match set with
| [] -> []
| head::tail ->
match String.is_prefix ~prefix:element head with
| true -> replacement::tail
| false -> head::replace_element tail element replacement
let rec remove_element (flags : string list) (element: string): string list =
match flags with
| [] -> []
| head::tail ->
match String.is_prefix ~prefix:element head with
| true -> tail
| false -> head::remove_element tail element
let check_config (input : string list) : unit =
match find_prefix input "-config" with
| None -> Cwe_checker_core.Log_utils.info "Using standard configuration..."
| Some c ->
match Stdlib.List.nth_opt (String.split c ~on:'=') 1 with
| None | Some ""-> raise (NoConfigException "No config file provided. If -config flag set please provide a config file.")
| Some f -> if (Sys.file_exists f) then () else raise (InvalidPathException "Path to config file not valid")
let check_output_path (input : string list) : unit =
match find_prefix input "-out" with
| Some param -> begin
try
match Stdlib.List.nth (String.split param ~on:'=') 1 with
| "" -> raise (NoOutputFileException "No output file provided. If -out flag is set please provide an out file.")
| _ -> ()
with
| _ -> raise (NoOutputFileException "No output file provided. If -out flag is set please provide an out file.")
end
| None -> ()
let setup_flags (flags : string list) : string =
String.concat ~sep:" " (List.map ~f:(fun pre -> "--cwe-checker" ^ pre) flags)
let check_partial (input : string list) : unit =
match find_prefix input "-partial" with
| None -> ()
| Some p ->
match Stdlib.List.nth_opt (String.split p ~on:'=') 1 with
| None | Some "" -> raise (NoModulesException "No modules provided. If -partial flag is set please provide the corresponding modules.")
| Some modules -> check_valid_module_list (String.split_on_chars modules ~on:[','])
let validate_user_input (input : string list) : unit =
let valid_flags = List.map ~f:(fun x -> "-" ^ fst x) Cwe_checker_core.Main.cmdline_flags in
match get_difference input valid_flags with
| [] -> ()
| invalid -> failwith ("Invalid flags: " ^ String.concat ~sep:", " invalid)
let check_for_help (flags: string list) : bool =
if (Stdlib.List.mem "-h" flags)|| (Stdlib.List.mem "-help" flags) || (Stdlib.List.mem "--help" flags) then (
print_help_message (); true
) else false
let check_for_module_versions (flags: string list) : bool =
if Stdlib.List.mem "-module-versions" flags then
let () = Cwe_checker_core.Main.print_module_versions () in
true
else
false
let check_for_no_logging (flags: string list) : unit =
if Stdlib.List.mem "-no-logging" flags then
Cwe_checker_core.Log_utils.turn_off_logging ()
let process_flags (flags : string list) : string list =
match flags with
| [] -> []
| _ -> validate_user_input flags; flags
let rec check_for_binary_path (args : string list) : string =
match args with
| [] -> raise (NoBinaryPathException ("No binary path was provided. If you need help, please call the cwe_checker with the --help or -h flag"))
| head::tail ->(
try
match Sys.is_directory head with
| false -> head
| true -> raise (NoBinaryPathException ("No binary path was provided. If you need help, please call the cwe_checker with the --help or -h flag"))
with
| _ -> check_for_binary_path tail
)
let process_input () : string * string list =
match get_user_input () with
| [] -> raise (NoBinaryPathException ("No binary path was provided. If you need help, please call the cwe_checker with the --help or -h flag"))
| input -> (
if check_for_help input then exit 0;
if check_for_module_versions input then exit 0;
check_for_no_logging input;
let binary_path = check_for_binary_path input in
let split_flags = List.partition_tf input ~f:(fun x -> (String.is_prefix x ~prefix:"-config") || (String.is_prefix x ~prefix:"-out") || (String.is_prefix x ~prefix:"-partial")) in
let flags = remove_element (snd split_flags) binary_path in
let params = fst split_flags in
check_partial params; check_config params; check_output_path params;
(binary_path, params @ process_flags flags)
)
let main () : int =
match Array.length Sys.argv with
| 1 -> print_help_message (); 0
| _ ->
let args = process_input () in
match snd args with
| [] -> Sys.command ("bap " ^ fst args ^ " --pass=cwe-checker ")
| _ -> Sys.command ("bap " ^ fst args ^ " --pass=cwe-checker " ^ setup_flags (snd args))
let _ = exit (main ())
(executable
(name cwe_checker)
(public_name cwe_checker)
(libraries
cwe_checker_core
core_kernel)
)
......@@ -11,13 +11,17 @@
{i cwe_checker} is implemented as a plugin for the {{: https://github.com/BinaryAnalysisPlatform/bap} Binary Analysis Platform} (BAP).
To use it, just run BAP with {i cwe_checker} as a pass:
{[bap [BINARY_FILE] --pass=cwe_checker]}
{[bap [BINARY_FILE] --pass=cwe-checker]}
This runs all static analysis based checks.
You can find more documentation on these checks at their {{: ../cwe_checker_core/Cwe_checker_core/index.html} module documentation pages}.
The behaviour of these checks can be modified through an optional configuration file.
Just edit the [src/config.json] file and then add [--cwe-checker-config=src/config.json] as a command line option.
You can find more on the available command line options {{!section:CmdLineOptions} here}.
Alternatively, you can run the {i cwe_checker} through the command
{[cwe_checker [BINARY_FILE] ]}
Internally, this also calls BAP as above, but enables shorter {{!section:CmdLineOptions} command line options}.
The symbolic execution based checks can be run with the emulation recipe in the recipes folder.
{[bap PATH_TO_BINARY --recipe=recipes/emulation]}
Note that these checks are rather slow at the moment and should only be applied to small binaries.
......@@ -33,7 +37,8 @@ If you want to print the output to a file with [--cwe-checker-out], you also nee
{1:CmdLineOptions Command line options}
All command line options have to be prefixed with [--cwe-checker] (so that BAP knows to forward them to the {i cwe_checker} plugin).
If you run the {i cwe_checker} as a BAP plugin, all command line options have to be prefixed with [--cwe-checker] (so that BAP knows to forward them to the {i cwe_checker} plugin).
If you run the {i cwe_checker} directly, do not prefix the command line options.
The available command line options are:
- [-check-path] Find paths between input functions (configurable in the configuration file) and CWE hits.
Should be used together with the [-partial] command line option if you are only interested in paths to specific CWEs.
......
open Core_kernel
open Cwe_checker_core.Main
open Bap.Std
open Graphlib.Std
open Format
open Yojson.Basic.Util
open Cwe_checker_core
open Core_kernel
include Self()
type cwe_module = {
cwe_func : Bap.Std.program Bap.Std.term -> Bap.Std.project -> Bap.Std.word Bap.Std.Tid.Map.t -> string list list -> string list -> unit;
name : string;
version : string;
requires_pairs : bool;
has_parameters : bool;
}
let known_modules = [{cwe_func = Cwe_190.check_cwe; name = Cwe_190.name; version = Cwe_190.version; requires_pairs = false; has_parameters = false};
{cwe_func = Cwe_215.check_cwe; name = Cwe_215.name; version = Cwe_215.version; requires_pairs = false; has_parameters = false};
{cwe_func = Cwe_243.check_cwe; name = Cwe_243.name; version = Cwe_243.version; requires_pairs = true; has_parameters = false};
{cwe_func = Cwe_248.check_cwe; name = Cwe_248.name; version = Cwe_248.version; requires_pairs = false; has_parameters = false};
{cwe_func = Cwe_332.check_cwe; name = Cwe_332.name; version = Cwe_332.version; requires_pairs = true; has_parameters = false};
{cwe_func = Cwe_367.check_cwe; name = Cwe_367.name; version = Cwe_367.version; requires_pairs = true; has_parameters = false};
{cwe_func = Cwe_426.check_cwe; name = Cwe_426.name; version = Cwe_426.version; requires_pairs = false; has_parameters = false};
{cwe_func = Cwe_457.check_cwe; name = Cwe_457.name; version = Cwe_457.version; requires_pairs = false; has_parameters = false};
{cwe_func = Cwe_467.check_cwe; name = Cwe_467.name; version = Cwe_467.version; requires_pairs = false; has_parameters = false};
{cwe_func = Cwe_476.check_cwe; name = Cwe_476.name; version = Cwe_476.version; requires_pairs = false; has_parameters = true};
{cwe_func = Cwe_560.check_cwe; name = Cwe_560.name; version = Cwe_560.version; requires_pairs = false; has_parameters = false};
{cwe_func = Cwe_676.check_cwe; name = Cwe_676.name; version = Cwe_676.version; requires_pairs = false; has_parameters = false};
{cwe_func = Cwe_782.check_cwe; name = Cwe_782.name; version = Cwe_782.version; requires_pairs = false; has_parameters = false}]
let build_version_sexp () =
List.map known_modules ~f:(fun cwe -> Format.sprintf "\"%s\": \"%s\"" cwe.name cwe.version)
|> String.concat ~sep:", "
let print_module_versions () =
Log_utils.info (sprintf
"[cwe_checker] module_versions: {%s}"
(build_version_sexp ()))
let execute_cwe_module cwe json program project tid_address_map =
let parameters = match cwe.has_parameters with
| false -> []
| true -> Json_utils.get_parameter_list_from_json json cwe.name in
if cwe.requires_pairs = true then
begin
let symbol_pairs = Json_utils.get_symbol_lists_from_json json cwe.name in
cwe.cwe_func program project tid_address_map symbol_pairs parameters
end
else
begin
let symbols = Json_utils.get_symbols_from_json json cwe.name in
cwe.cwe_func program project tid_address_map [symbols] parameters
end
let partial_run project config modules =
let program = Project.program project in
let tid_address_map = Address_translation.generate_tid_map program in
let json = Yojson.Basic.from_file config in
Log_utils.info (sprintf "[cwe_checker] Just running the following analyses: %s." modules);
List.iter (String.split modules ~on: ',') ~f:(fun cwe ->
let cwe_mod = match List.find known_modules ~f:(fun x -> x.name = cwe) with
| Some(module_) -> module_
| None -> failwith "[CWE_CHECKER] Unknown CWE module" in
let program = Project.program project in
execute_cwe_module cwe_mod json program project tid_address_map
)
let full_run project config =
let program = Project.program project in
let tid_address_map = Address_translation.generate_tid_map program in
let json = Yojson.Basic.from_file config in
begin
List.iter known_modules ~f:(fun cwe -> execute_cwe_module cwe json program project tid_address_map)
end
let main config module_versions partial_update check_path json_output file_output no_logging project =
if no_logging then
begin
Log_utils.turn_off_logging ()
end;
if module_versions then
begin
print_module_versions ()
end
else
begin
let config =
if config = "" then
(* try the standard installation path for the config file instead *)
match Sys.getenv_opt "OPAM_SWITCH_PREFIX" with
| Some(prefix) -> prefix ^ "/etc/cwe_checker/config.json"
| None -> ""
else
config in
if config = "" then
Log_utils.error "[cwe_checker] No configuration file provided! Aborting..."
else if Sys.file_exists config <> true then
Log_utils.error "[cwe_checker] Configuration file not found. Aborting..."
else
begin
if partial_update = "" then
full_run project config
else
partial_run project config partial_update;
if check_path then
begin
let prog = Project.program project in
let tid_address_map = Address_translation.generate_tid_map prog in
let json = Yojson.Basic.from_file config in
let check_path_sources = Json_utils.get_symbols_from_json json "check_path" in
let check_path_sinks = Log_utils.get_cwe_warnings () in
Check_path.check_path prog tid_address_map check_path_sources check_path_sinks
end;
if json_output then
begin
match Project.get project filename with
| Some fname -> Log_utils.emit_json fname file_output
| None -> Log_utils.emit_json "" file_output
end
else
Log_utils.emit_native file_output
end
end
module Cmdline = struct
open Config
let config = param string "config" ~doc:"Path to configuration file."
let module_versions = flag "module-versions" ~doc:"Prints out the version numbers of all known modules."
let json_output = flag "json" ~doc:"Outputs the result as JSON."
let file_output = param string "out" ~doc:"Path to output file."
let no_logging = flag "no-logging" ~doc:"Outputs no logging (info, error, warning). This does not pollute STDOUT when output json to it."
let check_path = flag "check-path" ~doc:"Checks if there is a path from an input function to a CWE hit."
let partial_update = param string "partial" ~doc:"Comma separated list of modules to apply on binary, e.g. 'CWE332,CWE476,CWE782'"
let () = when_ready (fun ({get=(!!)}) -> Project.register_pass' ~deps:["callsites"] (main !!config !!module_versions !!partial_update !!check_path !!json_output !!file_output !!no_logging))
let () = manpage [
`S "DESCRIPTION";
`P "This plugin checks various CWEs such as Insufficient Entropy in PRNG (CWE-332) or Use of Potentially Dangerous Function (CWE-676)"
]
end
let generate_bap_flags flags =
List.map flags (fun (name, docstring) -> (name, Config.flag name ~doc:docstring))
let generate_bap_params params =
List.map params (fun (name, docstring) -> (name, Config.param Config.string name ~doc:docstring))
let () =
let cmdline_flags = generate_bap_flags cmdline_flags in
let cmdline_params = generate_bap_params cmdline_params in
let () = Config.when_ready (fun ({get=(!!)}) ->
let flags: Bool.t String.Map.t = List.fold cmdline_flags ~init:String.Map.empty ~f:(fun flag_map (name, bap_flag) ->
String.Map.set flag_map ~key:name ~data:(!!bap_flag)
) in
let params: String.t String.Map.t = List.fold cmdline_params ~init:String.Map.empty ~f:(fun param_map (name, bap_param) ->
String.Map.set param_map ~key:name ~data:(!!bap_param)
) in
Project.register_pass' ~deps:["callsites"] (main flags params)
) in
let () = Config.manpage [
`S "DESCRIPTION";
`P "This plugin checks various CWEs such as Insufficient Entropy in PRNG (CWE-332) or Use of Potentially Dangerous Function (CWE-676)"
] in
()
open Core_kernel
open Bap.Std
open Format
type cwe_module = {
cwe_func : Bap.Std.program Bap.Std.term -> Bap.Std.project -> Bap.Std.word Bap.Std.Tid.Map.t -> string list list -> string list -> unit;
name : string;
version : string;
requires_pairs : bool;
has_parameters : bool;
}
let known_modules = [{cwe_func = Cwe_190.check_cwe; name = Cwe_190.name; version = Cwe_190.version; requires_pairs = false; has_parameters = false};
{cwe_func = Cwe_215.check_cwe; name = Cwe_215.name; version = Cwe_215.version; requires_pairs = false; has_parameters = false};
{cwe_func = Cwe_243.check_cwe; name = Cwe_243.name; version = Cwe_243.version; requires_pairs = true; has_parameters = false};
{cwe_func = Cwe_248.check_cwe; name = Cwe_248.name; version = Cwe_248.version; requires_pairs = false; has_parameters = false};
{cwe_func = Cwe_332.check_cwe; name = Cwe_332.name; version = Cwe_332.version; requires_pairs = true; has_parameters = false};
{cwe_func = Cwe_367.check_cwe; name = Cwe_367.name; version = Cwe_367.version; requires_pairs = true; has_parameters = false};
{cwe_func = Cwe_426.check_cwe; name = Cwe_426.name; version = Cwe_426.version; requires_pairs = false; has_parameters = false};
{cwe_func = Cwe_457.check_cwe; name = Cwe_457.name; version = Cwe_457.version; requires_pairs = false; has_parameters = false};
{cwe_func = Cwe_467.check_cwe; name = Cwe_467.name; version = Cwe_467.version; requires_pairs = false; has_parameters = false};
{cwe_func = Cwe_476.check_cwe; name = Cwe_476.name; version = Cwe_476.version; requires_pairs = false; has_parameters = true};
{cwe_func = Cwe_560.check_cwe; name = Cwe_560.name; version = Cwe_560.version; requires_pairs = false; has_parameters = false};
{cwe_func = Cwe_676.check_cwe; name = Cwe_676.name; version = Cwe_676.version; requires_pairs = false; has_parameters = false};
{cwe_func = Cwe_782.check_cwe; name = Cwe_782.name; version = Cwe_782.version; requires_pairs = false; has_parameters = false}]
let cmdline_flags = [
("module-versions", "Prints out the version numbers of all known modules.");
("json", "Outputs the result as JSON.");
("no-logging", "Outputs no logging (info, error, warning). This does not pollute STDOUT when output json to it.");
("check-path", "Checks if there is a path from an input function to a CWE hit.");
]
let cmdline_params = [
("config", "Path to configuration file.");
("out", "Path to output file.");
("partial", "Comma separated list of modules to apply on binary, e.g. 'CWE332,CWE476,CWE782'");
]
let build_version_sexp () =
List.map known_modules ~f:(fun cwe -> Format.sprintf "\"%s\": \"%s\"" cwe.name cwe.version)
|> String.concat ~sep:", "
let print_module_versions () =
Log_utils.info (sprintf "[cwe_checker] module_versions: {%s}" (build_version_sexp ()))
let print_help_message ((): unit) : unit =
let flags = cmdline_flags in
let params = cmdline_params in
Printf.printf("Help:\n\nThe CWE checker is called using the following command structure:\n\n
cwe_checker path/to/binary -[FLAG] -[PARAM=VALUE] ...\n\nThe following flags and parameters are available:\n\nFLAGS:\n\n");
List.iter ~f:(fun x -> Printf.printf " -%s: %s\n" (fst x) (snd x)) flags;
Printf.printf("\nPARAMETERS:\n\n");
List.iter ~f:(fun x -> Printf.printf " -%s: %s\n" (fst x) (snd x)) params
let execute_cwe_module cwe json program project tid_address_map =
let parameters = match cwe.has_parameters with
| false -> []
| true -> Json_utils.get_parameter_list_from_json json cwe.name in
if cwe.requires_pairs = true then
let symbol_pairs = Json_utils.get_symbol_lists_from_json json cwe.name in
cwe.cwe_func program project tid_address_map symbol_pairs parameters
else
let symbols = Json_utils.get_symbols_from_json json cwe.name in
cwe.cwe_func program project tid_address_map [symbols] parameters
let check_valid_module_list (modules : string list) : unit =
let known_module_names = List.map ~f:(fun x -> x.name) known_modules in
match List.find modules ~f:(fun module_name -> not (Stdlib.List.mem module_name known_module_names) ) with
| Some module_name ->
failwith ("[cwe_checker] Unknown CWE module " ^ module_name ^ ". Known modules: " ^ String.concat (List.map ~f:(fun x -> x ^ " ") known_module_names));
| None -> ()
let partial_run project config modules =
let program = Project.program project in
let tid_address_map = Address_translation.generate_tid_map program in
let json = Yojson.Basic.from_file config in
let () = check_valid_module_list modules in
Log_utils.info (sprintf "[cwe_checker] Just running the following analyses: %s." (String.concat (List.map ~f:(fun x -> x ^ " ") modules)));
List.iter modules ~f:(fun cwe ->
let cwe_mod = match List.find known_modules ~f:(fun x -> x.name = cwe) with
| Some(module_) -> module_
| None -> failwith "[cwe_checker] Unknown CWE module" in
let program = Project.program project in
execute_cwe_module cwe_mod json program project tid_address_map
)
let full_run project config =
let program = Project.program project in
let tid_address_map = Address_translation.generate_tid_map program in
let json = Yojson.Basic.from_file config in
List.iter known_modules ~f:(fun cwe -> execute_cwe_module cwe json program project tid_address_map)
let build_output_path (path : string) : string =
try
match Sys.is_directory path with
| false -> path
| true ->
let path = match String.is_suffix path ~suffix:"/" with
| true -> path
| false -> path ^ "/" in
let path = path ^ "out-" ^ string_of_float (Unix.time ()) in
Log_utils.info (sprintf "Created: %s" path);
path
with
| _ -> path (* file does not exist. We generate a new file with this name. *)
let main flags params project =
let config = String.Map.find_exn params "config" in
let module_versions = String.Map.find_exn flags "module-versions" in
let partial_update = String.Map.find_exn params "partial" in
let check_path = String.Map.find_exn flags "check-path" in
let json_output = String.Map.find_exn flags "json" in
let file_output = String.Map.find_exn params "out" in
let no_logging = String.Map.find_exn flags "no-logging" in
if module_versions then
print_module_versions ()
else
begin
if no_logging then Log_utils.turn_off_logging ();
let config =
if config = "" then
(* try the standard installation path for the config file instead *)
match Sys.getenv_opt "OPAM_SWITCH_PREFIX" with
| Some(prefix) -> prefix ^ "/etc/cwe_checker/config.json"
| None -> ""
else
config in
if config = "" then
Log_utils.error "[cwe_checker] No configuration file provided! Aborting..."
else if Sys.file_exists config <> true then
Log_utils.error "[cwe_checker] Configuration file not found. Aborting..."
else
begin
if partial_update = "" then
full_run project config
else
partial_run project config (String.split partial_update ~on: ',');
if check_path then
begin
let prog = Project.program project in
let tid_address_map = Address_translation.generate_tid_map prog in
let json = Yojson.Basic.from_file config in
let check_path_sources = Json_utils.get_symbols_from_json json "check_path" in
let check_path_sinks = Log_utils.get_cwe_warnings () in
Check_path.check_path prog tid_address_map check_path_sources check_path_sinks
end;
let file_output =
if file_output <> "" then
build_output_path file_output
else
file_output in
if json_output then
begin
match Project.get project filename with
| Some fname -> Log_utils.emit_json fname file_output
| None -> Log_utils.emit_json "" file_output
end
else
Log_utils.emit_native file_output
end
end
(** This module defines the main driving function for the cwe_checker plugin in BAP.
*)
open Core_kernel
open Bap.Std
type cwe_module = {
cwe_func : Bap.Std.program Bap.Std.term -> Bap.Std.project -> Bap.Std.word Bap.Std.Tid.Map.t -> string list list -> string list -> unit;
name : string;
version : string;
requires_pairs : bool;
has_parameters : bool;
}
val known_modules: cwe_module List.t
val cmdline_flags: (String.t * String.t) List.t
val cmdline_params: (String.t * String.t) List.t
val print_module_versions: unit -> unit
val check_valid_module_list: string list -> unit
(** prints the help message *)
val print_help_message: unit -> unit
(** The main function drives the execution of the cwe_checker plugin in BAP.
The command line arguments are passed as maps from their name to to their values
(Bool.t for flags, String.t for other arguments) to this function.
*)
val main: Bool.t String.Map.t -> String.t String.Map.t -> Project.t -> unit
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment