Skip to content

Instantly share code, notes, and snippets.

@hillar
Created November 12, 2014 19:15
Show Gist options
  • Save hillar/4f284b1bbf35e49314ab to your computer and use it in GitHub Desktop.
Save hillar/4f284b1bbf35e49314ab to your computer and use it in GitHub Desktop.
check hash with bro
##! modified TeamCymruMalwareHashRegistry
@load base/frameworks/files
@load base/frameworks/notice
@load frameworks/files/hash-all-files
module TeamCymruMalwareHashRegistryPlusVirusTotalPublicAPI;
export {
redef enum Notice::Type += {
Match
};
## File types to attempt matching against the Malware Hash Registry.
const match_file_types = /application\/x-dosexec/ |
/application\/vnd.ms-cab-compressed/ |
/application\/pdf/ |
/application\/x-shockwave-flash/ |
/application\/x-java-applet/ |
/application\/jar/ |
/video\/mp4/ &redef;
## The Match notice has a sub message with a URL where you can get more
## information about the file. The %s will be replaced with the SHA-1
## hash of the file.
const match_sub_url = "https://www.virustotal.com/en/search/?query=%s" &redef;
## The malware hash registry runs each malware sample through several
## A/V engines. Team Cymru returns a percentage to indicate how
## many A/V engines flagged the sample as malicious. This threshold
## allows you to require a minimum detection rate.
const notice_threshold = 10 &redef;
const curl: string = "/usr/bin/curl" &redef;
const url: string = "https://www.virustotal.com/vtapi/v2/file/report";
const user_agent = "Bro VirusTotal Checker (thanks for being awesome)" &redef;
# set your api key in local.bro (or somewhere)
const vt_apikey = "" &redef;
type VTMessage: record
{
positives: int;
total: int;
detect_rate: int;
};
}
# helper functions, see https://gist.github.com/mavam/4141216
## Extract integer (or quoted string) value from a key:value (or key:"value").
function extract_value(str: string) : string
{
local s = split1(str, /:/)[2];
s = sub(s, /^\"/, ""); #"
return sub(s, /\"$/, ""); #"
}
## brute force JSON data.
function parse_vt_response(data: string) : VTMessage
{
local msg: VTMessage;
local array = split(data, /,/); #
for ( i in array )
{
local val = array[i];
if ( strstr(val, "positives\":") > 0 )
msg$positives = to_int(extract_value(val));
else if ( strstr(val, "total\":") > 0 )
msg$total = to_int(extract_value(val));
}
#msg$detect_rate = to_int(to_int(msg$positives) / to_int(msg$total));
return msg;
}
# keep list of checked & matched
global checked_hashes: set[string] &synchronized;
global matched_hashes: set[string] &synchronized;
# check VT PUBLIC API only if Team Cymru does not have it (yet)
function do_vt_public_api_lookup(hash: string, fi: Notice::FileInfo)
{
# see https://github.com/sooshie/bro-scripts/blob/master/2.2-scripts/vt_check.bro
local bodyfile = fmt("%s.txt", hash);
local _cmd = fmt("%s -s -k -A \"%s\" -d resource=%s -d apikey=%s \"%s\"", curl, user_agent, hash, vt_apikey, url);
local command: Exec::Command = Exec::Command($cmd=_cmd);
when ( local result = Exec::run(command) )
{
local response = fmt("%s", result$stdout);
if ( |response| > 0 )
{
local msg = parse_vt_response(response);
local _sub = fmt("total: %s positives: %s",msg$total,msg$positives);
local n: Notice::Info = Notice::Info($note=Match, $msg=_sub, $sub=_cmd);
Notice::populate_file_info2(fi, n);
NOTICE(n);
add(matched_hashes[hash]);
}
}
}
function do_mhr_lookup(hash: string, fi: Notice::FileInfo)
{
local hash_domain = fmt("%s.malware.hash.cymru.com", hash);
when ( local MHR_result = lookup_hostname_txt(hash_domain) )
{
# Data is returned as "<dateFirstDetected> <detectionRate>"
local MHR_answer = split1(MHR_result, / /);
if ( |MHR_answer| == 2 )
{
local mhr_detect_rate = to_count(MHR_answer[2]);
if ( mhr_detect_rate >= notice_threshold )
{
add(matched_hashes[hash]);
local mhr_first_detected = double_to_time(to_double(MHR_answer[1]));
local readable_first_detected = strftime("%Y-%m-%d %H:%M:%S", mhr_first_detected);
local message = fmt("Malware Hash Registry Detection rate: %d%% Last seen: %s", mhr_detect_rate, readable_first_detected);
local virustotal_url = fmt(match_sub_url, hash);
# We dont have the full fa_file record here in order to
# avoid the "when" statement cloning it (expensive!).
local n: Notice::Info = Notice::Info($note=Match, $msg=message, $sub=virustotal_url);
Notice::populate_file_info2(fi, n);
NOTICE(n);
}
}
else
{
# check VT only if Team Cymru does not have it (yet)
if ( vt_apikey != "" )
{
do_vt_public_api_lookup(hash, fi);
}
}
}
}
event file_hash(f: fa_file, kind: string, hash: string)
{
if ( kind == "sha1" && f?$mime_type && match_file_types in f$mime_type )
if ( ! ( hash in checked_hashes ) )
{
add(checked_hashes[hash]);
#TODO remove older matches
do_mhr_lookup(hash, Notice::create_file_info(f));
}
else
{
if ( hash in matched_hashes )
{
local n: Notice::Info = Notice::Info($note=Match, $msg="Match already in queue", $sub=fmt(match_sub_url, hash));
Notice::populate_file_info2(Notice::create_file_info(f), n);
NOTICE(n);
}
}
}
@bhite1
Copy link

bhite1 commented Aug 21, 2015

Did you run into any problems using Run::exec within the do_vt_public_api_lookup function? I'm working on something similar and my when statement never evaluates to True, therefore it never runs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment