Commit 095130c0 by Thomas Barabosch Committed by Enkelmann

cwe_checker_emulation plugin using BAP's Primus (#15)

* Initial commit of cwe_checker emulation feature using bap primus under the hood.

* Fixed some Core issues with Maps and Hashtbls

* Moved plugins to their own folders as expected by BAP.

* Added .merlin since everybody likes merlin

* Further improvements in the build process

* Commented cwe_checker_emulation plugin

* cwe_checker_emulation detects double frees with the help of Primus.

* Refactoring of cwe_checker_emulation, extracted incident reporting to
module Incident_reporter.

* Added test cases for cwe125, cwe416, and modified cwe415.

* Now reporting use-after-free correctly

* Adjusted README

* Adjusted CHANGES.md.

* Added spaces to content codacy.

* Adjusted build process for emulation plugin

* fixed emulation recipe

* Reports out-out-bounds read/writes, events are not reported multiple times now.

* Adds tests for cwe-415 and cwe-416. Arritifical examples for cwe-125 and cwe-787

* Travis aware emulation tests.

* Fixed acceptance tests.
parent de1cd7e3
......@@ -223,4 +223,4 @@ test/artificial_samples/dockcross*
.#*
.sconsign.dblite
\ No newline at end of file
.sconsign.dblite
PKG yojson unix bap-primus monads graphlib ppx_jane str
S cwe_checker_static/**
S cwe_checker_emulation/**
B cwe_checker_static/_build/**
B cwe_checker_emulation/_build/**
\ No newline at end of file
......@@ -8,6 +8,7 @@
- Improved cross compiling for acceptance test cases by using dockcross (PR #8)
- Added BAP recipe for standard cwe_checker run (PR #9)
- Improved check for CWE-476 (NULL Pointer Dereference) using data flow analysis (PR #11)
- Added cwe_checker_emulation plugin based on BAP's Primus to detect CWE-125, CWE-415, and CWE-416 (PR #15)
- Switched C build system from make to scons (PR #16)
- Added type inference pass (PR #14)
- Added unit tests to test suite (PR #14)
......
......@@ -3,6 +3,7 @@ all:
dune build --profile release
dune install
cd plugins/cwe_checker; make all; cd ../..
cd plugins/cwe_checker_emulation; make all; cd ../..
cd plugins/cwe_checker_type_inference; make all; cd ../..
cd plugins/cwe_checker_type_inference_print; make all; cd ../..
......@@ -15,11 +16,13 @@ clean:
bapbuild -clean
cd test/unit; make clean; cd ../..
cd plugins/cwe_checker; make clean; cd ../..
cd plugins/cwe_checker_emulation; make clean; cd ../..
cd plugins/cwe_checker_type_inference; make clean; cd ../..
cd plugins/cwe_checker_type_inference_print; make clean; cd ../..
uninstall:
dune uninstall
cd plugins/cwe_checker; make uninstall; cd ../..
cd plugins/cwe_checker_emulation; make uninstall; cd ../..
cd plugins/cwe_checker_type_inference; make uninstall; cd ../..
cd plugins/cwe_checker_type_inference_print; make uninstall; cd ../..
......@@ -3,25 +3,29 @@
[![Build Status](https://travis-ci.org/fkie-cad/cwe_checker.svg?branch=master)](https://travis-ci.org/fkie-cad/cwe_checker)
![Docker-Pulls](https://img.shields.io/docker/pulls/fkiecad/cwe_checker.svg)
## What is cwe_checker? ##
*cwe_checker* detects 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.
*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 represenetation (IR). cwe_checker implements its analyses on this IR. At time of writing, BAP 1.5 supports Intel x86/x64, ARM, MIPS, and PPC amongst others. Hence, this makes *cwe_checker* a valuable tool for firmware analysis.
*cwe_checker* implements a modular architecture that allows to add new analyses with ease. So far the following analyses are implemented:
- [CWE-190](https://cwe.mitre.org/data/definitions/190.html): Integer Overflow or Wraparound
- [CWE-215](https://cwe.mitre.org/data/definitions/215.html): Information Exposure Through Debug Information
- [CWE-243](https://cwe.mitre.org/data/definitions/243.html): Creation of chroot Jail Without Changing Working Directory
- [CWE-248](https://cwe.mitre.org/data/definitions/248.html): Uncaught Exception
- [CWE-332](https://cwe.mitre.org/data/definitions/332.html): Insufficient Entropy in PRNG
- [CWE-367](https://cwe.mitre.org/data/definitions/367.html): Time-of-check Time-of-use (TOCTOU) Race Condition
- [CWE-426](https://cwe.mitre.org/data/definitions/426.html): Untrusted Search Path
- [CWE-457](https://cwe.mitre.org/data/definitions/457.html): Use of Uninitialized Variable
- [CWE-467](https://cwe.mitre.org/data/definitions/467.html): Use of sizeof() on a Pointer Type
- [CWE-476](https://cwe.mitre.org/data/definitions/476.html): NULL Pointer Dereference
- [CWE-676](https://cwe.mitre.org/data/definitions/676.html): Use of Potentially Dangerous Function
- [CWE-782](https://cwe.mitre.org/data/definitions/782.html): Exposed IOCTL with Insufficient Access Control
*cwe_checker* implements a modular architecture that allows to add new analyses with ease. So far the following analyses are implemented across several BAP plugins:
- [CWE-125](https://cwe.mitre.org/data/definitions/125.html): Out-of-bounds read (via emulation)
- [CWE-190](https://cwe.mitre.org/data/definitions/190.html): Integer Overflow or Wraparound
- [CWE-215](https://cwe.mitre.org/data/definitions/215.html): Information Exposure Through Debug Information
- [CWE-243](https://cwe.mitre.org/data/definitions/243.html): Creation of chroot Jail Without Changing Working Directory
- [CWE-248](https://cwe.mitre.org/data/definitions/248.html): Uncaught Exception
- [CWE-332](https://cwe.mitre.org/data/definitions/332.html): Insufficient Entropy in PRNG
- [CWE-367](https://cwe.mitre.org/data/definitions/367.html): Time-of-check Time-of-use (TOCTOU) Race Condition
- [CWE-415](https://cwe.mitre.org/data/definitions/415.html): Double Free (via emulation)
- [CWE-416](https://cwe.mitre.org/data/definitions/416.html): Use After Free (UAF) (via emulation)
- [CWE-426](https://cwe.mitre.org/data/definitions/426.html): Untrusted Search Path
- [CWE-457](https://cwe.mitre.org/data/definitions/457.html): Use of Uninitialized Variable
- [CWE-467](https://cwe.mitre.org/data/definitions/467.html): Use of sizeof() on a Pointer Type
- [CWE-476](https://cwe.mitre.org/data/definitions/476.html): NULL Pointer Dereference
- [CWE-676](https://cwe.mitre.org/data/definitions/676.html): Use of Potentially Dangerous Function
- [CWE-782](https://cwe.mitre.org/data/definitions/782.html): Exposed IOCTL with Insufficient Access Control
- [CWE-787](https://cwe.mitre.org/data/definitions/787.html): Out-of-bounds Write
Please note that some of the above analyses only are partially implemented at the moment. Furthermore, false positives are to be expected due to shortcuts and the nature of static analysis.
Please note that some of the above analyses only are partially implemented at the moment. Furthermore, false positives are to be expected due to shortcuts and the nature of static analysis as well as over-approximation.
*cwe_checker* comes with a script called `cwe_checker_to_ida`, which parses the output of *cwe_checker* and generates a IDAPython script. This script annotates the found CWEs in IDA Pro, which helps during manual analysis of a binary. The following screenshot shows some results:
......
all:
bapbuild -pkgs yojson,unix,ppx_jane,bap-primus,monads,graphlib,str,cwe_checker_core cwe_checker_emulation.plugin
bapbundle install cwe_checker_emulation.plugin
clean:
bapbuild -clean
uninstall:
bapbundle remove cwe_checker_emulation.plugin
open Core_kernel
open Bap.Std
open Bap_primus.Std
open Bap_future.Std
open Graphlib.Std
open Monads.Std
open Format
open Ppx_jane
open Cwe_checker_core
include Self()
let pp_id = Monad.State.Multi.Id.pp
module Machine = struct
type 'a m = 'a
include Primus.Machine.Make(Monad.Ident)
end
open Machine.Syntax
module Main = Primus.Machine.Main(Machine)
module Interpreter = Primus.Interpreter.Make(Machine)
module Linker = Primus.Linker.Make(Machine)
module Env = Primus.Env.Make(Machine)
module Lisp = Primus.Lisp.Make(Machine)
module Eval = Primus.Interpreter.Make(Machine)
(** this array collects the observed primus events*)
let collected_events = ref ([||])
(** Converts a hexadecimal string representation of
an address to an integer. *)
let convert_location loc =
match (Str.split (Str.regexp ":") loc) with
| fst::snd::[] -> Int.of_string ("0x" ^ snd)
| _ -> failwith "Could not parse location"
(** Converts a list of hexadecimal strings to a
list of integers. *)
let convert_location_list loc_list =
let locs = ref [] in
Sexplib__Sexp_with_layout.List.iter loc_list ~f:(fun x -> locs := (convert_location @@ Sexp.to_string x)::(!locs));
!locs
(** Analyze events and report to the user. *)
let analyze_events _ =
let location_tbl = Hashtbl.create (module String) in
Array.iter ~f:(fun (p, ev) ->
begin
match ev with
| Sexp.Atom _ -> failwith "Sexp.Atom not expected in report_events."
| Sexp.List [Sexp.Atom location_id; Sexp.List location_list] -> Hashtbl.add_exn location_tbl location_id (convert_location_list location_list)
| Sexp.List incident -> Incident_reporter.report incident location_tbl
end) !collected_events
(** Just adds the observed Primus events to the collected_events array. *)
let collect_events p ev =
collected_events := Array.append !collected_events [|(p, ev)|]
(* Most functions beyond here have been taken and adjusted from BAP's Primus plugins*)
let string_of_name = function
| `symbol s -> s
| `tid t -> Tid.to_string t
| `addr x -> Addr.string_of_value x
(** Executes/forks another Primus machine. *)
let exec x =
Machine.current () >>= fun cid ->
info "Fork %a: starting from the %s entry point"
pp_id cid (string_of_name x);
Machine.catch (Linker.exec x)
(fun exn ->
info "execution from %s terminated with: %s "
(string_of_name x)
(Primus.Exn.to_string exn);
Machine.return ())
let rec run = function
| [] ->
info "all toplevel machines done, halting";
Eval.halt >>=
never_returns
| x :: xs ->
Machine.current () >>= fun pid ->
Machine.fork () >>= fun () ->
Machine.current () >>= fun cid ->
if pid = cid
then run xs
else
exec x >>= fun () ->
Eval.halt >>=
never_returns
(** 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
(** Register a monitor. *)
let monitor_provider name ps =
Primus.Observation.list_providers () |>
List.find ~f:(has_name name) |> function
| None -> invalid_argf "An unknown observation provider `%s'" name ()
| Some p -> p :: ps
let parse_monitors =
List.fold ~init:[] ~f:(fun ps name -> monitor_provider name ps)
(** Register monitors for 'incident' related events. *)
module Monitor(Machine : Primus.Machine.S) = struct
open Machine.Syntax
let init () =
parse_monitors ["incident"; "incident-location"] |>
List.iter ~f:(fun m ->
info "monitoring %s" (Primus.Observation.Provider.name m);
Stream.observe (Primus.Observation.Provider.data m) (collect_events m));
Machine.return ()
end
(** Main logic of program:
- we monitor all 'incident' related events
- for all subroutins we fork a Primus machine
- all monitored events are collected globally
- after the last Primus machine has terminated we report all observed incidents *)
let main {Config.get=(!)} proj =
Log_utils.set_log_level Log_utils.DEBUG;
Log_utils.set_output stdout;
Log_utils.color_on ();
Primus.Machine.add_component (module Monitor);
begin
let prog = (Project.program proj) in
let targets = Seq.to_list @@ Seq.map (Term.enum sub_t prog) ~f:(fun x -> `tid (Term.tid x)) in
Main.run ~envp:[||] ~args:[||] proj (run targets) |> function
| (Primus.Normal,proj)
| (Primus.Exn Primus.Interpreter.Halt,proj) ->
info "Ok, we've terminated normally";
| (Primus.Exn exn,proj) ->
info "program terminated by a signal: %s" (Primus.Exn.to_string exn);
end;
analyze_events ();
proj
(** At the moment this plugin depends due to Primus on the plugin
trivial-condition-form. *)
let deps = [
"trivial-condition-form"
]
let () =
Config.when_ready (fun conf -> Project.register_pass ~deps (main conf))
open Core_kernel
open Cwe_checker_core
let version = "0.1"
(** Keeps track of reported events so that events are not reported multiple times. *)
let reported_events = ref (String.Set.empty)
(** Builds a string of a path of addresses. *)
let build_location_path locations =
let rec internal locations path_str =
match locations with
| [] -> path_str
| hd::[] -> internal [] (path_str ^ (Printf.sprintf "0x%x" hd))
| hd::tl -> internal tl (path_str ^ (Printf.sprintf "0x%x -> " hd)) in
internal locations ""
(** Looks up a concrete address for an id in the location table loc_tbl. *)
let map_id_to_location id loc_tbl =
match Hashtbl.find loc_tbl id with
| Some loc -> loc
| _ -> failwith "Expected location in hashtbl but failed"
(** Translates a list of incident ids to a list of concrete addresses. *)
let get_incident_locations_from_ids ids location_tbl =
let incident_locations = ref [] in
Sexplib__Sexp_with_layout.List.iter ids ~f:(fun id -> incident_locations := (map_id_to_location (Sexp.to_string id) location_tbl)::(!incident_locations)); !incident_locations
let report_cwe_125 location_path =
Log_utils.warn "[CWE125] {%s} (Out-of-bounds Read) %s" version location_path;
Log_utils.warn "[CWE787] {%s} (Out-of-bounds Write) %s" version location_path
let report_cwe_415 location_path =
Log_utils.warn "[CWE415] {%s} (Double Free) %s" version location_path
let report_cwe_416 location_path =
Log_utils.warn "[CWE416] {%s} (Use After Free) %s" version location_path
let report_cwe_unknown location_path incident_str =
Log_utils.warn "[CWE UNKNOWN] {%s} (%s) %s" version incident_str location_path
(** Reports an incident. *)
let report incident location_tbl =
match incident with
| name::ids ->
begin
let incident_locations = get_incident_locations_from_ids ids location_tbl in
let filtered_locs = Int.Set.to_list (Int.Set.of_list (List.concat incident_locations)) in
let incident_str = Sexp.to_string name in
let location_path = build_location_path filtered_locs in
if Set.mem !reported_events location_path
then
()
else
begin
reported_events := Set.add !reported_events location_path;
match incident_str with
| "memcheck-out-of-bound" -> report_cwe_125 location_path
| "memcheck-double-release" -> report_cwe_415 location_path
| "memcheck-use-after-release" -> report_cwe_416 location_path
| _ -> report_cwe_unknown location_path incident_str
end
end
| __ -> failwith "Strange incident sexp encountered"
Runs cwe_checker's checks that utilize bap primus emulation.
\ No newline at end of file
;; taken from
;;https://github.com/BinaryAnalysisPlatform/bap-recipes/blob/master/primus-checks/limit-malloc.lisp
;; up to 4 Mb each chunk, up to 128 Mbytes total
(defmethod init ()
(set *malloc-max-chunk-size* (* 4 1024 1024))
(set *malloc-guard-edges* 0)
(set *malloc-max-arena-size* (* 32 *malloc-max-chunk-size*))
(set *malloc-arena-start* brk)
(set *malloc-zero-sentinel* 0))
(option primus-lisp-add $prefix)
(option primus-lisp-load
posix
memcheck-malloc
limit-malloc
taint-sources
sensitive-sinks)
(option primus-promiscuous-mode)
(option primus-greedy-scheduler)
(option primus-limit-max-length 4096)
(option pass cwe-checker-emulation)
open Core_kernel.Std
open Core_kernel
open Bap.Std
open Symbol_utils
......
open Core_kernel.Std
open Core_kernel
open Bap.Std
open Unix
......
open Core_kernel.Std
open Core_kernel
open Bap.Std
open Symbol_utils
......
open Core_kernel
open Bap.Std
open Core_kernel.Std
let name = "CWE248"
let version = "0.1"
......
open Core_kernel
open Bap.Std
open Core_kernel.Std
open Graph_utils
open Symbol_utils
......
open Core_kernel
open Bap.Std
open Core_kernel.Std
let name = "CWE367"
let version = "0.1"
......
open Core_kernel
open Bap.Std
open Core_kernel.Std
let name = "CWE426"
let version = "0.1"
......
open Core_kernel.Std
open Core_kernel
open Bap.Std
let name = "CWE457"
......
open Core_kernel.Std
open Core_kernel
open Bap.Std
open Symbol_utils
......
open Core_kernel
open Bap.Std
open Core_kernel.Std
let name = "CWE476"
let version = "0.2"
......
open Core_kernel
open Bap.Std
open Core_kernel.Std
let name = "CWE676"
let version = "0.1"
......
open Core_kernel
open Bap.Std
open Core_kernel.Std
let name = "CWE782"
let version = "0.1"
......
open Core_kernel.Std
open Core_kernel
open Bap.Std
let translate_tid_to_assembler_address_string tid tid_map =
......
open Core_kernel.Std
open Core_kernel
open Bap.Std
open Graphlib.Std
......
open Core_kernel.Std
open Core_kernel
open Yojson.Basic.Util
(** Extracts the symbols to check for from json document.
......
open Core_kernel.Std
open Core_kernel
open Bap.Std
type symbol =
......
import os
import subprocess
def build_bap_cmd(filename, target, arch):
if 'travis' in os.environ['USER']:
abs_path = os.path.abspath('test/artificial_samples/build/cwe_%s_%s.out' % (filename, arch))
cmd = 'docker run --rm -v %s:/tmp/input cwe-checker:latest bap /tmp/input --pass=cwe-checker --cwe-checker-partial=CWE%s --cwe-checker-config=/home/bap/cwe_checker/src/config.json' % (abs_path, target)
else:
cmd = 'bap test/artificial_samples/build/cwe_%s_%s.out --pass=cwe-checker --cwe-checker-partial=CWE%s --cwe-checker-config=src/config.json' % (filename, arch, target)
cmd = 'bap test/artificial_samples/build/cwe_%s_%s.out --pass=cwe-checker --cwe-checker-partial=CWE%s --cwe-checker-config=src/config.json' % (filename, arch, target)
return cmd.split()
def build_bap_emulation_cmd(filename, target, arch):
if 'travis' in os.environ['USER']:
abs_path = os.path.abspath('test/artificial_samples/build/cwe_%s_%s.out' % (filename, arch))
cmd = 'docker run --rm -v %s:/tmp/input cwe-checker:latest bap /tmp/input --recipe=recipes/emulation' % abs_path
else:
cmd = 'bap test/artificial_samples/build/cwe_%s_%s.out --recipe=recipes/emulation' % (filename, arch)
return cmd.split()
def execute_and_check_occurence(filename, target, arch, string):
occurence = 0
bap_cmd = build_bap_cmd(filename, target, arch)
......@@ -17,3 +28,13 @@ def execute_and_check_occurence(filename, target, arch, string):
if string in l:
occurence += 1
return occurence
def execute_emulation_and_check_occurence(filename, target, arch, string):
occurence = 0
bap_cmd = build_bap_emulation_cmd(filename, target, arch)
output = subprocess.check_output(bap_cmd)
for l in output.splitlines():
if string in l:
occurence += 1
return occurence
import unittest
import cwe_checker_testlib
class TestCwe415(unittest.TestCase):
def setUp(self):
self.target = '415'
self.string = b'Double Free'
def test_cwe415_01_arm(self):
expect_res = 5
res = cwe_checker_testlib.execute_emulation_and_check_occurence(self.target, self.target, 'arm', self.string)
assert res == expect_res
def test_cwe415_01_x86(self):
expect_res = 5
res = cwe_checker_testlib.execute_emulation_and_check_occurence(self.target, self.target, 'x86', self.string)
assert res == expect_res
def test_cwe415_01_x64(self):
expect_res = 9
res = cwe_checker_testlib.execute_emulation_and_check_occurence(self.target, self.target, 'x64', self.string)
assert res == expect_res
@unittest.skip("Depends on proper MIPS support in BAP")
def test_cwe415_01_mips(self):
expect_res = 1
res = cwe_checker_testlib.execute_emulation_and_check_occurence(self.target, self.target, 'mips', self.string)
assert res == expect_res
def test_cwe415_01_ppc(self):
expect_res = 3
res = cwe_checker_testlib.execute_emulation_and_check_occurence(self.target, self.target, 'ppc', self.string)
assert res == expect_res
import unittest
import cwe_checker_testlib
class TestCwe416(unittest.TestCase):
def setUp(self):
self.target = '416'
self.string = b'Use After Free'
def test_cwe416_01_arm(self):
expect_res = 2
res = cwe_checker_testlib.execute_emulation_and_check_occurence(self.target, self.target, 'arm', self.string)
assert res == expect_res
def test_cwe416_01_x86(self):
expect_res = 2
res = cwe_checker_testlib.execute_emulation_and_check_occurence(self.target, self.target, 'x86', self.string)
assert res == expect_res
def test_cwe416_01_x64(self):
expect_res = 4
res = cwe_checker_testlib.execute_emulation_and_check_occurence(self.target, self.target, 'x64', self.string)
assert res == expect_res
@unittest.skip("Depends on proper MIPS support in BAP")
def test_cwe416_01_mips(self):
expect_res = 1
res = cwe_checker_testlib.execute_emulation_and_check_occurence(self.target, self.target, 'mips', self.string)
assert res == expect_res
def test_cwe416_01_ppc(self):
expect_res = 1
res = cwe_checker_testlib.execute_emulation_and_check_occurence(self.target, self.target, 'ppc', self.string)
assert res == expect_res
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv) {
int id_sequence[3];
id_sequence[0] = 123;
id_sequence[1] = 234;
id_sequence[2] = 345;
id_sequence[0] = id_sequence[3];
}
......@@ -4,12 +4,23 @@
#define BUFSIZE1 512
void bla(){
char *buf1R1;
char *buf2R1;
buf1R1 = (char *) malloc(BUFSIZE1);
buf2R1 = (char *) malloc(BUFSIZE1);
free(buf1R1);
free(buf2R1);
free(buf1R1);
}
int main(int argc, char **argv) {
char *buf1R1;
char *buf2R1;
buf1R1 = (char *) malloc(BUFSIZE1);
buf2R1 = (char *) malloc(BUFSIZE1);
free(buf1R1);
free(buf2R1);
free(buf1R1);
char *buf1R1;
char *buf2R1;
buf1R1 = (char *) malloc(BUFSIZE1);
buf2R1 = (char *) malloc(BUFSIZE1);
free(buf1R1);
free(buf2R1);
free(buf1R1);
bla();
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFSIZE1 512
int main(int argc, char **argv) {
char *buf1R1;
char *buf2R1;
buf1R1 = (char *) malloc(BUFSIZE1);
buf2R1 = (char *) malloc(BUFSIZE1);
free(buf1R1);
free(buf2R1);
memset(buf1R1, 0x42, BUFSIZE1);
}
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv) {
int id_sequence[3];
id_sequence[0] = 123;
id_sequence[1] = 234;
id_sequence[2] = 345;
id_sequence[3] = 678;
}
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