diff --git a/CTConsole.py b/CTConsole.py index 26e17fd..ff00e4b 100644 --- a/CTConsole.py +++ b/CTConsole.py @@ -190,6 +190,10 @@ def do_open(self, line): ans = raw_input() if ans.lower() == "y" or ans == "": bOpen = True + CTCore.web_server = server() + CTCore.web_server.start() + time.sleep(0.1) # Fixes graphic issues + CTCore.web_server_turned_on = True else: bOpen = True @@ -452,7 +456,8 @@ def do_server(self,line): s_cmd = l[0] if s_cmd.lower() == "on": if CTCore.web_server_turned_on: - print " Web Server already on: http://" + CTCore.HOST + ":" + CTCore.PORT + print " Web Server already on: http://" + CTCore.HOST + ":" +\ + str(CTCore.PORT) else: CTCore.web_server = server() CTCore.web_server.start() @@ -720,12 +725,15 @@ def do_strings(self, line): if (l[0] == ""): self.help_strings() else: + string_minlen = 5 + if len(l) == 2: + string_minlen = int(l[1]) id, size = get_id_size(line) response, size = CTCore.get_response_and_size(id, "all") name = CTCore.get_name(id) print "Strings found in object {} ({}) [{} bytes]:".format(id, name, size) - strings = CTCore.get_strings(response) + strings = CTCore.get_strings(response, string_minlen) print (newLine.join(str for str in strings)) except Exception,e: print str(e) @@ -782,6 +790,7 @@ def help_plugin(self): print " plugin find_scripts" print " plugin 1" print " p find_scripts" + print " p check_yara all" def do_output(self, line): diff --git a/CTCore.py b/CTCore.py index c40a528..1c2ad86 100644 --- a/CTCore.py +++ b/CTCore.py @@ -35,7 +35,7 @@ plugins_folder = "plugins/" pcap_file = "" VERSION = "0.3" -BUILD = "13" +BUILD = "14" ABOUT = "CapTipper v" + VERSION + " b" + BUILD + " - Malicious HTTP traffic explorer tool" + newLine + \ "Copyright 2015 Omri Herscovici " + newLine @@ -388,8 +388,8 @@ def print_objects(self): def update_captipper(): currentVersion = "v{} b{}".format(VERSION,BUILD) - rawURL = "https://raw.githubusercontent.com/omriher/CapTipper/master/" - archiveURL = "https://github.com/omriher/CapTipper/archive/" + rawURL = "https://raw.githubusercontent.com/kisec/CapTipper/master/" + archiveURL = "https://github.com/kisec/CapTipper/archive/" CoreFile = "CTCore.py" CTArchive = "master.zip" @@ -406,10 +406,12 @@ def update_captipper(): if repoVer: newVersion = "v{} b{}".format(repoVer[0][0],repoVer[0][1]) else: + alert_message("Error getting repository version", msg_type.ERROR) sys.exit('[-] Error getting repository version') if newVersion == currentVersion: - sys.exit("[+] You have the newest version!") + alert_message("You have the newest version!", msg_type.INFO) + return else: print "[+] Updating CapTipper to {}".format(newVersion) bPackSize = False @@ -498,9 +500,9 @@ def send_to_vt(md5, key_vt): return (-1, 'Error during VirusTotal response parsing') return (0, json_dict) -def get_strings(content): - strings = re.findall("[\x1f-\x7e]{5,}", content) - strings += [str(ws.decode("utf-16le")) for ws in re.findall("(?:[\x1f-\x7e][\x00]){5,}", content)] +def get_strings(content, minlen=5): + strings = re.findall("[\x1f-\x7e]{"+str(minlen)+",}", content) + strings += [str(ws.decode("utf-16le")) for ws in re.findall("(?:[\x1f-\x7e][\x00]){"+str(minlen)+",}", content)] return strings def get_request_size(id, size, full_request=False): diff --git a/CTServer.py b/CTServer.py index 4b8fb3b..3ce14bc 100644 --- a/CTServer.py +++ b/CTServer.py @@ -117,7 +117,7 @@ def log(self, uri): def handle(self): try: - self.data = self.request.recv(1024).strip() + self.data = self.request.recv(4096).strip() request = HTTPRequest(self.data) if self.data != "": diff --git a/README.md b/README.md index 56cb6cd..a4ece26 100644 --- a/README.md +++ b/README.md @@ -394,6 +394,53 @@ CT> vt 13 AVG Exploit_c.ABKR 15.0.0.4235 20141211 Baidu-International Trojan.Win32.CVE-2013-0074.bBZ 3.5.1.41473 20141211 ``` +YARA Pattern Detection plugin(check\_yara) +```sh +CT> plugin -l +Loaded Plugins (4): + 0 : find_scripts - Finds external scripts included in the object body + 1 : check_yara - Checks Yara Patterns : Usage>> plugin check_yara [all | conversation ID] + 2 : check_host - Checks if a given id's host is alive + 3 : print_body - Prints the body of a conversation and ungzip if needed + +``` + +Sample YARA Pattern(./plugins/rules/redirection_302.yar) +``` +rule rediction_302 +{ + meta: + description = "302 Redirection detection rule" + author = "madwind@kisec.com" + + strings: + $a = "302 Found" + $b = "

The document has moved" + + condition: + all of them +} +``` + +302 Redirection Detection + +```sh +CT> plugin check_yara all +[!] Check Yara Patterns in objects +[+] Detection: [2] seedadmin17.html :[rediction_302] + +CT> body 2 200 +Displaying body of object 2 (seedadmin17.html) [200 bytes]: + + + +302 Found + +

Found

+

The document has moved 0: + command = args[0] + if command == "all": + alert_message("Check Yara Patterns in objects", msg_type.INFO) + for id in range(len(self.objects)): + # Check if id number is a valid conversation + if self.is_valid_id(id): + name = self.get_name_by_id(id) + # Get response body as text even in case it was Gzipped + response_body = self.get_plaintext_body_by_id(id) + matches = self.rules.match(data=response_body) + if matches: + alert_message("Detection: [{}] {} :[{}]".format( + id, name, (", ".join([str(rule) for rule in matches]))), + msg_type.GOOD) + else: + alert_message("Invalid conversation ID {}".format(str(id)), msg_type.ERROR) + elif command.isdigit(): + # Gets the conversation ID + convid = int(command) + # Check if id number is a valid conversation + if self.is_valid_id(convid): + name = self.get_name_by_id(convid) + alert_message("Check Yara Patterns in object {} ({})...".format( + str(convid), name), + msg_type.INFO) + # Get response body as text even in case it was Gzipped + response_body = self.get_plaintext_body_by_id(convid) + matches = self.rules.match(data=response_body) + if matches: + alert_message("Detection: [{}] {} :{}".format( + convid, name, (", ".join([str(rule) for rule in matches]))), + msg_type.GOOD) + else: + alert_message("Invalid conversation ID {}".format(str(convid)), msg_type.ERROR) + else: + alert_message("Invalid command {} : {}".format( + command, self.help_string), msg_type.ERROR) + else: + alert_message("No arguments given : {}".format(self.help_string), msg_type.ERROR) + + def yara_rule_load(self): + """Yara rule load""" + plugin_path = os.path.dirname(os.path.realpath(__file__)) + p_files = glob.glob(plugin_path + "/rules/*.yar") + total_yara_rule = "" + for p in p_files: + yara_rule = "" + with open(p, "r") as rule_file: + yara_rule = rule_file.read() + try: + yara.compile(source=yara_rule) + except yara.SyntaxError as error: + alert_message("{} : SyntaxError : {}".format(p, error), msg_type.ERROR) + return False + total_yara_rule += yara_rule+"\n" + try: + self.rules = yara.compile(source=total_yara_rule) + except yara.SyntaxError as error: + alert_message("{} : SyntaxError : {}".format(p, error), msg_type.ERROR) + return False + if self.rules is None: + alert_message("Empty Yara rules", msg_type.ERROR) + return False + return True diff --git a/plugins/print_body.py b/plugins/print_body.py index bfbddf7..2314489 100644 --- a/plugins/print_body.py +++ b/plugins/print_body.py @@ -14,4 +14,5 @@ def run(self, args): else: data = self.get_body_by_id(id) else: - print "invalid id" \ No newline at end of file + print "invalid id" + return data diff --git a/plugins/rules/redirection_302.yar b/plugins/rules/redirection_302.yar new file mode 100644 index 0000000..30bdc4a --- /dev/null +++ b/plugins/rules/redirection_302.yar @@ -0,0 +1,13 @@ +rule rediction_302 +{ + meta: + description = "302 Redirection detection rule" + author = "madwind@kisec.com" + + strings: + $a = "302 Found" + $b = "

The document has moved" + + condition: + all of them +} \ No newline at end of file