Skip to content

Commit

Permalink
Upgrade to Ocaml 4.8 and BAP 2.2 (#122)
Browse files Browse the repository at this point in the history
  • Loading branch information
Enkelmann authored Jan 7, 2021
1 parent e74d0fd commit 22f15e2
Show file tree
Hide file tree
Showing 30 changed files with 163 additions and 160 deletions.
6 changes: 5 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
dev
0.4 (2021-01)
====

- Added a lot more test cases to acceptance tests (PR #46)
Expand All @@ -11,6 +11,10 @@ dev
- Several code improvements to for the CWE 415 and 416 checks (PRs #76, #77. #78, #84)
- Report more accurate incident locations for CWE 476 (PR #80)
- Enable Ghidra as an alternative Backend to BAP (still experimental) (PRs #86, #87)
- Added acceptance tests for the Ghidra backend (PRs #91, #99)
- Bugfixes for the Ghidra backend (PRs #98, #101, #104, #106, #110, #114, #120)
- Ported the CWE checks to Rust for the Ghidra backend (PRs #88, #95, #100, #102, #111, #117, #119, #121)
- Added support for Ghidra 9.2 (PR #116)

0.3 (2019-12)
====
Expand Down
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
## What is cwe_checker? ##
*cwe_checker* is a suite of tools to detect common bug classes such as use of dangerous functions and simple integer overflows. These bug classes are formally known as [Common Weakness Enumerations](https://cwe.mitre.org/) (CWEs). Its main goal is to aid analysts to quickly find vulnerable code paths.

Its main focus are ELF binaries that are commonly found on Linux and Unix operating systems. *cwe_checker* is built on top of [BAP](https://github.com/BinaryAnalysisPlatform/bap) (Binary Analysis Platform). By using BAP, we are not restricted to one low level instruction set architectures like Intel x86. BAP lifts several of them to one common intermediate representation (IR). cwe_checker implements its analyses on this IR. At time of writing, BAP 2.1 supports Intel x86/x64, ARM, MIPS, and PPC amongst others. Hence, this makes *cwe_checker* a valuable tool for firmware analysis.
Its main focus are ELF binaries that are commonly found on Linux and Unix operating systems. *cwe_checker* is built on top of [BAP](https://github.com/BinaryAnalysisPlatform/bap) (Binary Analysis Platform). By using BAP, we are not restricted to one low level instruction set architectures like Intel x86. BAP lifts several of them to one common intermediate representation (IR). cwe_checker implements its analyses on this IR. At time of writing, BAP 2.2 supports Intel x86/x64, ARM, MIPS, and PPC amongst others. Hence, this makes *cwe_checker* a valuable tool for firmware analysis.

The following arguments should convince you to give *cwe_checker* a try:
- it is very easy to set up, just build the Docker container!
Expand All @@ -33,13 +33,11 @@ If you want to build the docker image yourself, just run `docker build -t cwe_ch

### Local installation with BAP as backend ###

Another way is to get cwe_checker from the Ocaml package manager Opam. You can install cwe_checker via the package [cwe_checker](https://opam.ocaml.org/packages/cwe_checker/) (`opam install cwe_checker`). This gives you the latest stable release version of the *cwe_checker*.

If you plan to develop *cwe_checker*, it is recommended to build it using the provided `Makefile`. In this case you must ensure that all dependencies are fulfilled:
- Ocaml 4.07.1
- Ocaml 4.08.0
- Opam 2.0.2
- dune >= 2.0
- BAP (and its dependencies). Development on the master branch depends on the master branch of BAP which can be added with `opam repo add bap-testing git+https://github.com/BinaryAnalysisPlatform/opam-repository#testing` to the sources of the Opam package manager. The stable release of the *cwe_checker* depends on BAP 1.6.
- BAP 2.2.0 (and its dependencies).
- yojson >= 1.6.0
- ppx_deriving_yojson >= 3.5.1
- alcotest >= 0.8.3 (for tests)
Expand Down
2 changes: 1 addition & 1 deletion caller/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cwe_checker"
version = "0.4.0-dev"
version = "0.4.0"
authors = ["Enkelmann <nils-edvin.enkelmann@fkie.fraunhofer.de>"]
edition = "2018"

Expand Down
12 changes: 6 additions & 6 deletions cwe_checker.opam
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
opam-version: "2.0"
name: "cwe_checker"
version: "0.3"
version: "0.4"
synopsis: "BAP plugin collection to detect common bug classes"
description: """
cwe_checker is a suite of tools to detect common bug classes such as use of dangerous functions and simple integer overflows. These bug classes are formally known as Common Weakness Enumerations (CWEs).
Expand All @@ -12,13 +12,13 @@ homepage: "https://github.com/fkie-cad/cwe_checker"
bug-reports: "https://github.com/fkie-cad/cwe_checker/issues"
dev-repo: "git+https://github.com/fkie-cad/cwe_checker"
depends: [
"ocaml" {>= "4.07.1"}
"dune" {>= "1.6"}
"ocaml" {>= "4.08.0"}
"dune" {>= "2.0"}
"yojson" {>= "1.6.0"}
"bap" {>= "2.0"}
"bap" {>= "2.2.0"}
"alcotest" {>= "0.8.3"}
"core_kernel" {>= "v0.11" & < "v0.12"}
"ppx_jane" {>= "v0.11" & < "v0.12"}
"core_kernel" {>= "v0.14"}
"ppx_jane" {>= "v0.14"}
"ppx_deriving_yojson" {>= "3.5.1"}
"odoc" {>= "1.4"}
]
Expand Down
2 changes: 1 addition & 1 deletion cwe_checker_rs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cwe_checker_rs"
version = "0.4.0-dev"
version = "0.4.0"
authors = ["Nils-Edvin Enkelmann <nils-edvin.enkelmann@fkie.fraunhofer.de>"]
edition = "2018"

Expand Down
4 changes: 2 additions & 2 deletions plugins/cwe_checker_emulation/cwe_checker_emulation.ml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ let rec run = function
Machine.current () >>= fun pid ->
Machine.fork () >>= fun () ->
Machine.current () >>= fun cid ->
if pid = cid
if Poly.(=) pid cid
then run xs
else
exec x >>= fun () ->
Expand All @@ -95,7 +95,7 @@ let rec run = function
(** Checks if a certain Primus.Observation.Provider is equal
to a string like 'incident'. *)
let has_name name p =
Primus.Observation.Provider.name p = name
Poly.(=) (Primus.Observation.Provider.name p) name

(** Register a monitor. *)
let monitor_provider name ps =
Expand Down
12 changes: 6 additions & 6 deletions src/analysis/check_path.ml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ type proof =

(** Taken from https://stackoverflow.com/questions/8373460/substring-check-in-ocaml *)
let contains_substring search target =
String.substr_index ~pattern:search target <> None
Option.is_some (String.substr_index ~pattern:search target)

let format_path get_source get_destination path tid_map =
let e_count = List.length (Seq.to_list (Path.edges path)) in
Expand Down Expand Up @@ -78,7 +78,7 @@ let block_has_callsite blk t =
match Jmp.kind j with
| Goto _ | Ret _ | Int (_,_) -> false
| Call destination -> begin match Call.target destination with
| Direct tid -> tid = t
| Direct tid -> Tid.(=) tid t
| _ -> false
end)

Expand All @@ -90,11 +90,11 @@ let collect_callsites program t =

let sub_has_tid sub tid =
Term.enum blk_t sub
|> Seq.exists ~f:(fun blk -> Term.tid blk = tid || Blk.elts blk
|> Seq.exists ~f:(fun blk -> Tid.(=) (Term.tid blk) tid || Blk.elts blk
|> Seq.exists ~f:(fun e -> match e with
| `Def d -> Term.tid d = tid
| `Jmp j -> Term.tid j = tid
| `Phi p -> Term.tid p = tid ))
| `Def d -> Tid.(=) (Term.tid d) tid
| `Jmp j -> Tid.(=) (Term.tid j) tid
| `Phi p -> Tid.(=) (Term.tid p) tid ))

let find_sub_tid_of_term_tid program tid =
match tid with
Expand Down
8 changes: 4 additions & 4 deletions src/analysis/mem_region.ml
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ let rec mark_error mem_region ~pos ~size =
else if pos + size <= hd.pos then
(error_elem ~pos ~size) :: mem_region
else
let start_pos = min pos hd.pos in
let end_pos_plus_one = max (pos + size) (hd.pos + hd.size) in
let start_pos = Word.min pos hd.pos in
let end_pos_plus_one = Word.max (pos + size) (hd.pos + hd.size) in
mark_error tl ~pos:start_pos ~size:(end_pos_plus_one - start_pos)


Expand All @@ -133,8 +133,8 @@ let rec merge mem_region1 mem_region2 ~data_merge =
end
| _ -> { hd1 with data = Error(()) } :: merge tl1 tl2 ~data_merge
else
let start_pos = min hd1.pos hd2.pos in
let end_pos_plus_one = max (hd1.pos + hd1.size) (hd2.pos + hd2.size) in
let start_pos = Word.min hd1.pos hd2.pos in
let end_pos_plus_one = Word.max (hd1.pos + hd1.size) (hd2.pos + hd2.size) in
let mem_region = merge tl1 tl2 ~data_merge in
mark_error mem_region ~pos:start_pos ~size:(end_pos_plus_one - start_pos)

Expand Down
10 changes: 5 additions & 5 deletions src/analysis/type_inference.ml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ let version = "0.2"
(* generic merge of two ('a, unit) Result.t Option.t *)
let merge_result_option val1 val2 =
match (val1, val2) with
| (Some(Ok(x)), Some(Ok(y))) when x = y -> Some(Ok(x))
| (Some(Ok(x)), Some(Ok(y))) when Poly.(=) x y -> Some(Ok(x))
| (Some(x), None)
| (None, Some(x)) -> Some(x)
| (None, None) -> None
Expand Down Expand Up @@ -258,7 +258,7 @@ let get_stack_elem state exp ~sub_tid ~project =
| Some(offset) -> begin
match Mem_region.get state.TypeInfo.stack offset with
| Some(Ok(elem, elem_size)) ->
if Bitvector.to_int elem_size = Ok(Size.in_bytes size) then
if Poly.(=) (Bitvector.to_int elem_size) (Ok(Size.in_bytes size)) then
Some(Ok(elem))
else
Some(Error())
Expand Down Expand Up @@ -399,7 +399,7 @@ let add_mem_address_registers state exp ~sub_tid ~project =
| Bil.BinOp(Bil.AND, exp2, Bil.Var(addr))
| Bil.BinOp(Bil.OR, Bil.Var(addr), exp2)
| Bil.BinOp(Bil.OR, exp2, Bil.Var(addr)) ->
if type_of_exp exp2 state ~sub_tid ~project = Some(Ok(Register.Data)) then
if Poly.(=) (type_of_exp exp2 state ~sub_tid ~project) (Some(Ok(Register.Data))) then
begin match Map.find state.TypeInfo.reg addr with
| Some(Ok(Pointer(_))) -> state
| _ -> { state with TypeInfo.reg = Map.set state.TypeInfo.reg ~key:addr ~data:(Ok(Register.Pointer(Tid.Map.empty))) }
Expand Down Expand Up @@ -491,7 +491,7 @@ let update_state_jmp state jmp ~sub_tid ~project =
| Some(_left, right) -> right
| None -> Tid.name tid in
if String.Set.mem (Symbol_utils.parse_dyn_syms project) func_name then
begin if List.exists (malloc_like_function_list ()) ~f:(fun elem -> elem = func_name) then
begin if List.exists (malloc_like_function_list ()) ~f:(fun elem -> String.(=) elem func_name) then
update_state_malloc_call state tid jmp ~project
else
let empty_state = TypeInfo.empty () in (* TODO: to preserve stack information we need to be sure that the callee does not write on the stack. Can we already check that? *)
Expand Down Expand Up @@ -542,7 +542,7 @@ let intraprocedural_fixpoint func ~project =
let fn_start_state = TypeInfo.function_start_state sub_tid project in
let fn_start_block = Option.value_exn (Term.first blk_t func) in
let fn_start_state = update_block_analysis fn_start_block fn_start_state ~sub_tid ~project in
let fn_start_node = Seq.find_exn (Graphs.Ir.nodes cfg) ~f:(fun node -> (Term.tid fn_start_block) = (Term.tid (Graphs.Ir.Node.label node))) in
let fn_start_node = Seq.find_exn (Graphs.Ir.nodes cfg) ~f:(fun node -> Tid.(=) (Term.tid fn_start_block) (Term.tid (Graphs.Ir.Node.label node))) in
let empty = Map.empty (module Graphs.Ir.Node) in
let with_start_node = Map.set empty ~key:fn_start_node ~data:fn_start_state in
let init = Graphlib.Std.Solution.create with_start_node only_sp in
Expand Down
6 changes: 3 additions & 3 deletions src/checkers/cwe_215.ml
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ let check_cwe _ project _ _ _ =
| Some fname -> begin
let cmd = Format.sprintf "objdump --dwarf=decodedline %s | grep CU" fname in
try
let in_chan = Unix.open_process_in cmd in
let in_chan = Caml_unix.open_process_in cmd in
In_channel.input_lines in_chan |> List.iter ~f:(fun l ->
let description = sprintf "(Information Exposure Through Debug Information) %s" l in
let cwe_warning = cwe_warning_factory name version description ~symbols:[l] in
collect_cwe_warning cwe_warning)


with
Unix.Unix_error (e,fm,argm) ->
Log_utils.error (sprintf "[%s] {%s} %s %s %s" name version (Unix.error_message e) fm argm)
Caml_unix.Unix_error (e,fm,argm) ->
Log_utils.error (sprintf "[%s] {%s} %s %s %s" name version (Caml_unix.error_message e) fm argm)
end
| _ -> failwith "[CWE215] symbol_names not as expected"
6 changes: 3 additions & 3 deletions src/checkers/cwe_243.ml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ let rec check dests (symbols : symbol list) =
| [] -> true
| first_symbol :: symbol_rest -> begin
match first_symbol.address with
| Some address -> if address = hd then check tl symbol_rest else check tl symbols
| Some address -> if Tid.(=) address hd then check tl symbol_rest else check tl symbols
| _ -> false
end
end
Expand Down Expand Up @@ -72,8 +72,8 @@ If all of them fail then we supose that the program handles chroot on
let check_subfunction prog tid_map sub pathes =
if sub_calls_symbol prog sub "chroot" then
begin
let path_checks = List.map pathes ~f:(fun path -> check_path prog tid_map sub path) in
if not (List.exists path_checks ~f:(fun x -> x = true)) then
let path_checks: Bool.t List.t = List.map pathes ~f:(fun path -> check_path prog tid_map sub path) in
if not (List.exists path_checks ~f:(fun x -> x)) then
let address = (Address_translation.translate_tid_to_assembler_address_string (Term.tid sub) tid_map) in
let tid = Address_translation.tid_to_string @@ Term.tid sub in
let symbol = Term.name sub in
Expand Down
10 changes: 5 additions & 5 deletions src/checkers/cwe_248.ml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ let extract_direct_call_symbol block =
(* check whether block contains a direct call to a symbol with name symbol_name *)
let contains_symbol block symbol_name =
match extract_direct_call_symbol block with
| Some(symb) -> symb = symbol_name
| Some(symb) -> String.(=) symb symbol_name
| None -> false

(* Checks whether a subfunction contains a catch block. *)
Expand All @@ -31,11 +31,11 @@ let contains_symbol block symbol_name =

(* Find all calls to subfunctions that are reachable from this subfunction. The calls are returned
as a list, except for calls to "@__cxa_throw", which are logged as possibly uncaught exceptions. *)
let find_calls_and_throws subfunction ~tid_map =
let find_calls_and_throws (subfunction: Sub.t) ~tid_map : Tid.t List.t =
let blocks = Term.enum blk_t subfunction in
Seq.fold blocks ~init:[] ~f:(fun call_list block ->
if contains_symbol block "@__cxa_throw" then
let () = print_uncatched_exception (Term.tid block) ~tid_map:tid_map in
let () = print_uncatched_exception (Term.tid block) ~tid_map:tid_map in
call_list
else
match Symbol_utils.extract_direct_call_tid_from_block block with
Expand All @@ -52,7 +52,7 @@ let rec find_uncaught_exceptions subfunction already_checked_functions program ~
else
let subfunction_calls = find_calls_and_throws subfunction ~tid_map:tid_map in
List.fold subfunction_calls ~init:already_checked_functions ~f:(fun already_checked subfunc ->
match List.exists ~f:(fun a -> a = subfunc) already_checked with
match List.exists ~f:(fun a -> Tid.(=) a subfunc) already_checked with
| true -> already_checked
| false -> find_uncaught_exceptions ~tid_map:tid_map (Core_kernel.Option.value_exn (Term.find sub_t program subfunc)) (subfunc :: already_checked) program)

Expand All @@ -61,5 +61,5 @@ let rec find_uncaught_exceptions subfunction already_checked_functions program ~
way. We should check whether this produces a lot of false negatives. *)
let check_cwe program _project tid_map _symbol_pairs _ =
let entry_points = Symbol_utils.get_program_entry_points program in
let _ = List.fold entry_points ~init:[] ~f:(fun already_checked_functions sub -> find_uncaught_exceptions ~tid_map:tid_map sub already_checked_functions program) in
let _: Tid.t List.t = List.fold entry_points ~init:[] ~f:(fun already_checked_functions sub -> find_uncaught_exceptions ~tid_map:tid_map sub already_checked_functions program) in
()
4 changes: 2 additions & 2 deletions src/checkers/cwe_367.ml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ let get_calls_to_symbol symbol_name callsites program =
Seq.filter callsites ~f:(fun callsite -> match Jmp.kind callsite with
| Goto _ | Ret _ | Int (_,_) -> false
| Call destination -> match Call.target destination with
| Direct addr -> addr = symbol
| Direct addr -> Tid.(=) addr symbol
| _ -> false)
end
| None -> Seq.empty
Expand All @@ -21,7 +21,7 @@ let get_blk_tid_of_tid sub tid =
let blk = Seq.find (Term.enum blk_t sub) ~f:(
fun b ->
match Term.last jmp_t b with
| Some last_term -> tid = (Term.tid last_term)
| Some last_term -> Tid.(=) tid (Term.tid last_term)
| None -> false) in
match blk with
| Some b -> Term.tid b
Expand Down
6 changes: 3 additions & 3 deletions src/checkers/cwe_457.ml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ let get_fp_of_arch arch =
| _ -> failwith "Unknown architecture."

let vars_contain_fp vars fp_pointer =
let regs = Set.filter vars ~f:(fun var -> Var.to_string var = fp_pointer) in
let regs = Set.filter vars ~f:(fun var -> String.(=) (Var.to_string var) fp_pointer) in
Set.length regs > 0

let is_interesting_load_store def fp_pointer =
Expand All @@ -61,7 +61,7 @@ let is_interesting_load_store def fp_pointer =
contains_mem && contains_fp

(*TODO: implement real filtering*)
let filter_mem_address i min_fp_offset = Set.filter i ~f:(fun elem -> (Word.of_int ~width:32 min_fp_offset) < elem)
let filter_mem_address i min_fp_offset = Set.filter i ~f:(fun elem -> Word.(<) (Word.of_int ~width:32 min_fp_offset) elem)

let log_cwe_warning sub i d tid_map =
let word = Word.to_string i in
Expand Down Expand Up @@ -95,7 +95,7 @@ let check_subfunction _prog proj tid_map sub =
else
begin
let filter_mem_addresses = filter_mem_address ints min_fp_offset in
Set.iter filter_mem_addresses ~f:(fun i -> if not (Array.exists !stores ~f:(fun elem -> elem = i)) then
Set.iter filter_mem_addresses ~f:(fun i -> if not (Array.exists !stores ~f:(fun elem -> Word.(=) elem i)) then
log_cwe_warning sub i d tid_map)
end
end)
Expand Down
Loading

0 comments on commit 22f15e2

Please sign in to comment.