Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100755 455 lines (390 sloc) 23.644 kB
164d280 @bworrell added support for cybox output. changed Tk imports to work with pytho…
bworrell authored
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
178e4f1 @stephenbrannon Initial upload
authored
4 #This script helps extract indicators of compromise (IOCs) from a text file.
5 #A user can add or remove tagged indicators then export the remaining tags.
6 #Usage: "python IOCextractor.py" or "python IOCextractor.py document.txt"
7 #2012 Stephen Brannon, Verizon RISK Team
8
9 import re
10 import sys
8742e90 @williamgibb Added support for OpenIOC 1.1 output.
williamgibb authored
11
12 from Tkinter import *
13 from tkFileDialog import askopenfilename, asksaveasfilename, askdirectory
485dbec @bworrell Replaced regex version checking code with LooseVersion
bworrell authored
14 from distutils.version import LooseVersion
8742e90 @williamgibb Added support for OpenIOC 1.1 output.
williamgibb authored
15
c426dfb @bworrell Updated IOCextractor to use python-cybox v2.0.0. Removed cybox folder…
bworrell authored
16 try:
972894d @bworrell Added check for python-cybox v2.0 or greater. Added work-around for C…
bworrell authored
17 import cybox
c426dfb @bworrell Updated IOCextractor to use python-cybox v2.0.0. Removed cybox folder…
bworrell authored
18 from cybox import helper as cybox_helper
972894d @bworrell Added check for python-cybox v2.0 or greater. Added work-around for C…
bworrell authored
19 from cybox.core import Observables, Observable
20 from cybox.objects.uri_object import URI
c426dfb @bworrell Updated IOCextractor to use python-cybox v2.0.0. Removed cybox folder…
bworrell authored
21 import cybox.utils
972894d @bworrell Added check for python-cybox v2.0 or greater. Added work-around for C…
bworrell authored
22
ed1a2f4 @bworrell Updated IOCextractor to export CybOX v2.0.1 by requiring python-cybox…
bworrell authored
23 if hasattr(cybox, "__version__"):
485dbec @bworrell Replaced regex version checking code with LooseVersion
bworrell authored
24 if (LooseVersion(cybox.__version__) >= LooseVersion('2.0.1.0')):
ed1a2f4 @bworrell Updated IOCextractor to export CybOX v2.0.1 by requiring python-cybox…
bworrell authored
25 python_cybox_available = True
26 else:
485dbec @bworrell Replaced regex version checking code with LooseVersion
bworrell authored
27 raise ImportError("python-cybox must be v2.0.1.0 or greater. Found v%s" % cybox.__version__)
972894d @bworrell Added check for python-cybox v2.0 or greater. Added work-around for C…
bworrell authored
28 else:
ed1a2f4 @bworrell Updated IOCextractor to export CybOX v2.0.1 by requiring python-cybox…
bworrell authored
29 raise ImportError("python-cybox must be v2.0.1.0 or greater")
972894d @bworrell Added check for python-cybox v2.0 or greater. Added work-around for C…
bworrell authored
30
31 except Exception as e:
32 print "ERROR: Could not load python-cybox. Will not be able to export IOCs in CybOX 2.0 format.\nException:[%s]" % (str(e))
c426dfb @bworrell Updated IOCextractor to use python-cybox v2.0.0. Removed cybox folder…
bworrell authored
33 python_cybox_available = False
34
1af146f @williamgibb Added exception handling if ioc_writer library is not present
williamgibb authored
35 try:
36 from ioc_writer import ioc_api, ioc_common
37 ioc_writer_available = True
38 except ImportError, e:
39 print 'ERROR: Could not load ioc_writer. Will not be able to export IOCs in OpenIOC 1.1 format.'
40 ioc_writer_available = False
178e4f1 @stephenbrannon Initial upload
authored
41
42 tags = ['md5', 'ipv4', 'url', 'domain', 'email']
b2a8a16 @stephenbrannon Collection of Bug Fixes and Updates
authored
43
44 reMD5 = r"([A-F]|[0-9]){32}"
b318067 Bug fixes and updates
Stephen Brannon authored
45 reIPv4 = r"\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|\[\.\])){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"
55f0741 Updated regular expressions and exports to better handle [.] and [@] …
Stephen Brannon authored
46 reURL = r"[A-Z0-9\-\.\[\]]+(\.|\[\.\])(XN--CLCHC0EA0B2G2A9GCD|XN--HGBK6AJ7F53BBA|XN--HLCJ6AYA9ESC7A|XN--11B5BS3A9AJ6G|XN--MGBERP4A5D4AR|XN--XKC2DL3A5EE0H|XN--80AKHBYKNJ4F|XN--XKC2AL3HYE2A|XN--LGBBAT1AD8J|XN--MGBC0A9AZCG|XN--9T4B11YI5A|XN--MGBAAM7A8H|XN--MGBAYH7GPA|XN--MGBBH1A71E|XN--FPCRJ9C3D|XN--FZC2C9E2C|XN--YFRO4I67O|XN--YGBI2AMMX|XN--3E0B707E|XN--JXALPDLP|XN--KGBECHTV|XN--OGBPF8FL|XN--0ZWM56D|XN--45BRJ9C|XN--80AO21A|XN--DEBA0AD|XN--G6W251D|XN--GECRJ9C|XN--H2BRJ9C|XN--J6W193G|XN--KPRW13D|XN--KPRY57D|XN--PGBS0DH|XN--S9BRJ9C|XN--90A3AC|XN--FIQS8S|XN--FIQZ9S|XN--O3CW4H|XN--WGBH1C|XN--WGBL6A|XN--ZCKZAH|XN--P1AI|MUSEUM|TRAVEL|AERO|ARPA|ASIA|COOP|INFO|JOBS|MOBI|NAME|BIZ|CAT|COM|EDU|GOV|INT|MIL|NET|ORG|PRO|TEL|XXX|AC|AD|AE|AF|AG|AI|AL|AM|AN|AO|AQ|AR|AS|AT|AU|AW|AX|AZ|BA|BB|BD|BE|BF|BG|BH|BI|BJ|BM|BN|BO|BR|BS|BT|BV|BW|BY|BZ|CA|CC|CD|CF|CG|CH|CI|CK|CL|CM|CN|CO|CR|CU|CV|CW|CX|CY|CZ|DE|DJ|DK|DM|DO|DZ|EC|EE|EG|ER|ES|ET|EU|FI|FJ|FK|FM|FO|FR|GA|GB|GD|GE|GF|GG|GH|GI|GL|GM|GN|GP|GQ|GR|GS|GT|GU|GW|GY|HK|HM|HN|HR|HT|HU|ID|IE|IL|IM|IN|IO|IQ|IR|IS|IT|JE|JM|JO|JP|KE|KG|KH|KI|KM|KN|KP|KR|KW|KY|KZ|LA|LB|LC|LI|LK|LR|LS|LT|LU|LV|LY|MA|MC|MD|ME|MG|MH|MK|ML|MM|MN|MO|MP|MQ|MR|MS|MT|MU|MV|MW|MX|MY|MZ|NA|NC|NE|NF|NG|NI|NL|NO|NP|NR|NU|NZ|OM|PA|PE|PF|PG|PH|PK|PL|PM|PN|PR|PS|PT|PW|PY|QA|RE|RO|RS|RU|RW|SA|SB|SC|SD|SE|SG|SH|SI|SJ|SK|SL|SM|SN|SO|SR|ST|SU|SV|SX|SY|SZ|TC|TD|TF|TG|TH|TJ|TK|TL|TM|TN|TO|TP|TR|TT|TV|TW|TZ|UA|UG|UK|US|UY|UZ|VA|VC|VE|VG|VI|VN|VU|WF|WS|YE|YT|ZA|ZM|ZW)(/\S+)"
47 reDomain = r"[A-Z0-9\-\.\[\]]+(\.|\[\.\])(XN--CLCHC0EA0B2G2A9GCD|XN--HGBK6AJ7F53BBA|XN--HLCJ6AYA9ESC7A|XN--11B5BS3A9AJ6G|XN--MGBERP4A5D4AR|XN--XKC2DL3A5EE0H|XN--80AKHBYKNJ4F|XN--XKC2AL3HYE2A|XN--LGBBAT1AD8J|XN--MGBC0A9AZCG|XN--9T4B11YI5A|XN--MGBAAM7A8H|XN--MGBAYH7GPA|XN--MGBBH1A71E|XN--FPCRJ9C3D|XN--FZC2C9E2C|XN--YFRO4I67O|XN--YGBI2AMMX|XN--3E0B707E|XN--JXALPDLP|XN--KGBECHTV|XN--OGBPF8FL|XN--0ZWM56D|XN--45BRJ9C|XN--80AO21A|XN--DEBA0AD|XN--G6W251D|XN--GECRJ9C|XN--H2BRJ9C|XN--J6W193G|XN--KPRW13D|XN--KPRY57D|XN--PGBS0DH|XN--S9BRJ9C|XN--90A3AC|XN--FIQS8S|XN--FIQZ9S|XN--O3CW4H|XN--WGBH1C|XN--WGBL6A|XN--ZCKZAH|XN--P1AI|MUSEUM|TRAVEL|AERO|ARPA|ASIA|COOP|INFO|JOBS|MOBI|NAME|BIZ|CAT|COM|EDU|GOV|INT|MIL|NET|ORG|PRO|TEL|XXX|AC|AD|AE|AF|AG|AI|AL|AM|AN|AO|AQ|AR|AS|AT|AU|AW|AX|AZ|BA|BB|BD|BE|BF|BG|BH|BI|BJ|BM|BN|BO|BR|BS|BT|BV|BW|BY|BZ|CA|CC|CD|CF|CG|CH|CI|CK|CL|CM|CN|CO|CR|CU|CV|CW|CX|CY|CZ|DE|DJ|DK|DM|DO|DZ|EC|EE|EG|ER|ES|ET|EU|FI|FJ|FK|FM|FO|FR|GA|GB|GD|GE|GF|GG|GH|GI|GL|GM|GN|GP|GQ|GR|GS|GT|GU|GW|GY|HK|HM|HN|HR|HT|HU|ID|IE|IL|IM|IN|IO|IQ|IR|IS|IT|JE|JM|JO|JP|KE|KG|KH|KI|KM|KN|KP|KR|KW|KY|KZ|LA|LB|LC|LI|LK|LR|LS|LT|LU|LV|LY|MA|MC|MD|ME|MG|MH|MK|ML|MM|MN|MO|MP|MQ|MR|MS|MT|MU|MV|MW|MX|MY|MZ|NA|NC|NE|NF|NG|NI|NL|NO|NP|NR|NU|NZ|OM|PA|PE|PF|PG|PH|PK|PL|PM|PN|PR|PS|PT|PW|PY|QA|RE|RO|RS|RU|RW|SA|SB|SC|SD|SE|SG|SH|SI|SJ|SK|SL|SM|SN|SO|SR|ST|SU|SV|SX|SY|SZ|TC|TD|TF|TG|TH|TJ|TK|TL|TM|TN|TO|TP|TR|TT|TV|TW|TZ|UA|UG|UK|US|UY|UZ|VA|VC|VE|VG|VI|VN|VU|WF|WS|YE|YT|ZA|ZM|ZW)\b"
48 reEmail = r"\b[A-Za-z0-9._%+-]+(@|\[@\])[A-Za-z0-9.-]+(\.|\[\.\])(XN--CLCHC0EA0B2G2A9GCD|XN--HGBK6AJ7F53BBA|XN--HLCJ6AYA9ESC7A|XN--11B5BS3A9AJ6G|XN--MGBERP4A5D4AR|XN--XKC2DL3A5EE0H|XN--80AKHBYKNJ4F|XN--XKC2AL3HYE2A|XN--LGBBAT1AD8J|XN--MGBC0A9AZCG|XN--9T4B11YI5A|XN--MGBAAM7A8H|XN--MGBAYH7GPA|XN--MGBBH1A71E|XN--FPCRJ9C3D|XN--FZC2C9E2C|XN--YFRO4I67O|XN--YGBI2AMMX|XN--3E0B707E|XN--JXALPDLP|XN--KGBECHTV|XN--OGBPF8FL|XN--0ZWM56D|XN--45BRJ9C|XN--80AO21A|XN--DEBA0AD|XN--G6W251D|XN--GECRJ9C|XN--H2BRJ9C|XN--J6W193G|XN--KPRW13D|XN--KPRY57D|XN--PGBS0DH|XN--S9BRJ9C|XN--90A3AC|XN--FIQS8S|XN--FIQZ9S|XN--O3CW4H|XN--WGBH1C|XN--WGBL6A|XN--ZCKZAH|XN--P1AI|MUSEUM|TRAVEL|AERO|ARPA|ASIA|COOP|INFO|JOBS|MOBI|NAME|BIZ|CAT|COM|EDU|GOV|INT|MIL|NET|ORG|PRO|TEL|XXX|AC|AD|AE|AF|AG|AI|AL|AM|AN|AO|AQ|AR|AS|AT|AU|AW|AX|AZ|BA|BB|BD|BE|BF|BG|BH|BI|BJ|BM|BN|BO|BR|BS|BT|BV|BW|BY|BZ|CA|CC|CD|CF|CG|CH|CI|CK|CL|CM|CN|CO|CR|CU|CV|CW|CX|CY|CZ|DE|DJ|DK|DM|DO|DZ|EC|EE|EG|ER|ES|ET|EU|FI|FJ|FK|FM|FO|FR|GA|GB|GD|GE|GF|GG|GH|GI|GL|GM|GN|GP|GQ|GR|GS|GT|GU|GW|GY|HK|HM|HN|HR|HT|HU|ID|IE|IL|IM|IN|IO|IQ|IR|IS|IT|JE|JM|JO|JP|KE|KG|KH|KI|KM|KN|KP|KR|KW|KY|KZ|LA|LB|LC|LI|LK|LR|LS|LT|LU|LV|LY|MA|MC|MD|ME|MG|MH|MK|ML|MM|MN|MO|MP|MQ|MR|MS|MT|MU|MV|MW|MX|MY|MZ|NA|NC|NE|NF|NG|NI|NL|NO|NP|NR|NU|NZ|OM|PA|PE|PF|PG|PH|PK|PL|PM|PN|PR|PS|PT|PW|PY|QA|RE|RO|RS|RU|RW|SA|SB|SC|SD|SE|SG|SH|SI|SJ|SK|SL|SM|SN|SO|SR|ST|SU|SV|SX|SY|SZ|TC|TD|TF|TG|TH|TJ|TK|TL|TM|TN|TO|TP|TR|TT|TV|TW|TZ|UA|UG|UK|US|UY|UZ|VA|VC|VE|VG|VI|VN|VU|WF|WS|YE|YT|ZA|ZM|ZW)\b"
b2a8a16 @stephenbrannon Collection of Bug Fixes and Updates
authored
49
50 def dotToNum(ip): return int(''.join(["%02x"%int(i) for i in ip.split('.')]),16)
178e4f1 @stephenbrannon Initial upload
authored
51
52 def tag_initial():
53 lines = text.get(1.0, 'end').split('\n')
54
55 #md5
56 text.tag_configure('md5', background='#FE6AA8')
57 linenumber = 1
58 for line in lines:
b2a8a16 @stephenbrannon Collection of Bug Fixes and Updates
authored
59 for m in re.finditer(reMD5, line, re.IGNORECASE):
178e4f1 @stephenbrannon Initial upload
authored
60 text.tag_add('md5',str(linenumber) + '.' + str(m.start()), str(linenumber) + '.' + str(m.end()))
61 linenumber += 1
62
63 #ipv4
64 text.tag_configure('ipv4', background='#6CB23E')
65 linenumber = 1
66 for line in lines:
7cca4ba IPv4 REGEX is more accurate. Also fewer private ip addresses will be…
U-US1\V527234 authored
67 for m in re.finditer(reIPv4, line, re.IGNORECASE):
178e4f1 @stephenbrannon Initial upload
authored
68 result = text.get(str(linenumber) + '.' + str(m.start()), str(linenumber) + '.' + str(m.end()))
b2a8a16 @stephenbrannon Collection of Bug Fixes and Updates
authored
69 result = result.replace('[','').replace(']','') #remove brackets
70 #reject private, link-local, and loopback IPs
71 if result.find('10.') != 0 and \
72 result.find('192.168') != 0 and \
73 result.find('127') != 0:
74 if (dotToNum(result) < dotToNum('172.16.0.0') or \
75 dotToNum(result) > dotToNum('172.31.255.255')) and \
76 (dotToNum(result) < dotToNum('169.254.1.0') or \
77 dotToNum(result) > dotToNum('169.254.254.255')):
78 text.tag_add('ipv4',str(linenumber) + '.' + str(m.start()), str(linenumber) + '.' + str(m.end()))
178e4f1 @stephenbrannon Initial upload
authored
79 linenumber += 1
80
81 #url
82 text.tag_configure('url', background='#378A20')
83 linenumber = 1
84 for line in lines:
b2a8a16 @stephenbrannon Collection of Bug Fixes and Updates
authored
85 for m in re.finditer(reURL, line, re.IGNORECASE):
178e4f1 @stephenbrannon Initial upload
authored
86 result = text.get(str(linenumber) + '.' + str(m.start()), str(linenumber) + '.' + str(m.end()))
87 end = m.end()
88 #drop trailing punctuation
19a735a @bworrell fixed a unicode issue in the string literal on line 62
bworrell authored
89 while (u'.,\u201d"\'\u2019').find(result[len(result)-1:len(result)]) != -1:
178e4f1 @stephenbrannon Initial upload
authored
90 result = result[:len(result)-1]
91 end -= 1
92 text.tag_add('url',str(linenumber) + '.' + str(m.start()), str(linenumber) + '.' + str(end))
93 linenumber += 1
94
95 #domain
96 text.tag_configure('domain', background='#5DC5D1')
97 linenumber = 1
98 for line in lines:
b2a8a16 @stephenbrannon Collection of Bug Fixes and Updates
authored
99 for m in re.finditer(reDomain, line, re.IGNORECASE):
178e4f1 @stephenbrannon Initial upload
authored
100 #reject if preceding character is @ or following character is /
101 if not text.get(str(linenumber) + '.' + str(m.start()-1), str(linenumber) + '.' + str(m.start())) == '@':
102 if not text.get(str(linenumber) + '.' + str(m.end()), str(linenumber) + '.' + str(m.end()+1)) == '/':
103 text.tag_add('domain',str(linenumber) + '.' + str(m.start()), str(linenumber) + '.' + str(m.end()))
104 linenumber += 1
105
106 #email
107 text.tag_configure('email', background='#0224F2')
108 linenumber = 1
109 for line in lines:
b2a8a16 @stephenbrannon Collection of Bug Fixes and Updates
authored
110 for m in re.finditer(reEmail, line, re.IGNORECASE):
111 #reject verizon emails
178e4f1 @stephenbrannon Initial upload
authored
112 result = text.get(str(linenumber) + '.' + str(m.start()), str(linenumber) + '.' + str(m.end()))
b2a8a16 @stephenbrannon Collection of Bug Fixes and Updates
authored
113 if result.find('@verizon.com') == -1 and \
114 result.find('@verizonbusiness.com') == -1 and \
115 result.find('@one.verizon.com') == -1 and \
116 result.find('@jp.verizonbusiness.com') == -1:
178e4f1 @stephenbrannon Initial upload
authored
117 text.tag_add('email',str(linenumber) + '.' + str(m.start()), str(linenumber) + '.' + str(m.end()))
118 linenumber += 1
119
b2a8a16 @stephenbrannon Collection of Bug Fixes and Updates
authored
120 def askopen(filename = ''):
121 if filename == '':
122 filename = askopenfilename(title="Select plain-text file", filetypes=[("txt file",".txt"),("All files",".*")])
178e4f1 @stephenbrannon Initial upload
authored
123 if filename != '':
b2a8a16 @stephenbrannon Collection of Bug Fixes and Updates
authored
124 with open(filename, 'rb') as f: #read as binary
178e4f1 @stephenbrannon Initial upload
authored
125 doc = f.read()
b2a8a16 @stephenbrannon Collection of Bug Fixes and Updates
authored
126 doc = doc.decode('utf_8', 'ignore') #drop any non-ascii bytes
b318067 Bug fixes and updates
Stephen Brannon authored
127
b2a8a16 @stephenbrannon Collection of Bug Fixes and Updates
authored
128 #if a carriage return is orphaned, replace it with a new line
129 doc = list(doc)
130 i = 0
131 while (i < len(doc) - 1):
132 if ord(doc[i]) == 13: #it's a carriage return
133 if ord(doc[i + 1]) != 10: #it's not followed by a new line
134 doc[i] = chr(10) #replace it with a new line
135 i += 1
b318067 Bug fixes and updates
Stephen Brannon authored
136 if ord(doc[len(doc)-1]) == 13: #end
b2a8a16 @stephenbrannon Collection of Bug Fixes and Updates
authored
137 doc[len(doc)-1] = chr(10)
b318067 Bug fixes and updates
Stephen Brannon authored
138
139 #drop carriage returns
140 i = 0
141 while (i < len(doc) - 1):
142 if ord(doc[i]) == 13: #it's a carriage return
143 doc.pop(i)
144 else:
145 i += 1
146
b2a8a16 @stephenbrannon Collection of Bug Fixes and Updates
authored
147 doc = ''.join(doc)
148
149 text.delete('1.0',END)
178e4f1 @stephenbrannon Initial upload
authored
150 text.insert('1.0', doc)
151 tag_initial()
b318067 Bug fixes and updates
Stephen Brannon authored
152 root.title(filename + ' - IOCextractor')
178e4f1 @stephenbrannon Initial upload
authored
153
154 def clear_tag(holder = 0):
155 if len(text.tag_ranges("sel")) != 0: #selection is not empty
156 #untag all occurrences of selected string
b318067 Bug fixes and updates
Stephen Brannon authored
157 key = text.get(text.tag_ranges("sel")[0], text.tag_ranges("sel")[1])
178e4f1 @stephenbrannon Initial upload
authored
158 lines = text.get(1.0, 'end').split('\n')
159 linenumber = 1
b318067 Bug fixes and updates
Stephen Brannon authored
160
178e4f1 @stephenbrannon Initial upload
authored
161 for line in lines:
b2a8a16 @stephenbrannon Collection of Bug Fixes and Updates
authored
162 for m in re.finditer(re.escape(key), line, re.IGNORECASE):
178e4f1 @stephenbrannon Initial upload
authored
163 for t in tags:
164 text.tag_remove(t, str(linenumber) + '.' + str(m.start()), str(linenumber) + '.' + str(m.end()))
165 linenumber += 1
166
167 for t in tags: #necessary backup in case the regex fails to match
168 text.tag_remove(t, text.tag_ranges("sel")[0], text.tag_ranges("sel")[1])
b318067 Bug fixes and updates
Stephen Brannon authored
169
178e4f1 @stephenbrannon Initial upload
authored
170 def tag_new(tag):
171 if len(text.tag_ranges("sel")) != 0: #selection is not empty
b318067 Bug fixes and updates
Stephen Brannon authored
172 #remove any newline characters from the selection
173 if '\n' in text.get(text.tag_ranges("sel")[0], text.tag_ranges("sel")[1]):
174 line_start = (str(text.tag_ranges("sel")[0]).split('.'))[1]
175 newline_index = text.get(text.tag_ranges("sel")[0], text.tag_ranges("sel")[1]).index('\n')
176 del_line = str(text.tag_ranges("sel")[0]).split('.')[0]
177 del_position = str(int(line_start) + int(newline_index))
178 text.delete(del_line + '.' + del_position)
179
178e4f1 @stephenbrannon Initial upload
authored
180 #tag all occurences of selected string
181 key = text.get(text.tag_ranges("sel")[0], text.tag_ranges("sel")[1])
182 lines = text.get(1.0, 'end').split('\n')
183 linenumber = 1
184 for line in lines:
b2a8a16 @stephenbrannon Collection of Bug Fixes and Updates
authored
185 for m in re.finditer(re.escape(key), line, re.IGNORECASE):
178e4f1 @stephenbrannon Initial upload
authored
186 for t in tags:
187 text.tag_add(tag, str(linenumber) + '.' + str(m.start()), str(linenumber) + '.' + str(m.end()))
188 linenumber += 1
189
190 def export_console():
b2a8a16 @stephenbrannon Collection of Bug Fixes and Updates
authored
191 #need better way to iterate through highlights and remove brackets
178e4f1 @stephenbrannon Initial upload
authored
192 for t in tags:
193 indicators = []
194 print(t + ':')
195 myhighlights = text.tag_ranges(t)
196 mystart = 0
197 for h in myhighlights:
198 if mystart == 0:
199 mystart = h
200 else:
201 mystop = h
b318067 Bug fixes and updates
Stephen Brannon authored
202 if t == 'md5': #make all hashes uppercase
55f0741 Updated regular expressions and exports to better handle [.] and [@] …
Stephen Brannon authored
203 if not text.get(mystart,mystop).upper() in indicators:
204 indicators.append(text.get(mystart,mystop).upper())
b318067 Bug fixes and updates
Stephen Brannon authored
205 else:
55f0741 Updated regular expressions and exports to better handle [.] and [@] …
Stephen Brannon authored
206 if not text.get(mystart,mystop).replace('[.]','.').replace('[@]','@') in indicators:
207 indicators.append(text.get(mystart,mystop).replace('[.]','.').replace('[@]','@'))
178e4f1 @stephenbrannon Initial upload
authored
208 mystart = 0
209 for i in indicators:
210 print(i)
211 print()
212
213 def export_csv():
214 filename = asksaveasfilename(title="Save As", filetypes=[("csv file",".csv"),("All files",".*")])
215 if filename != '':
216 output = 'IOC,Type\n'
b2a8a16 @stephenbrannon Collection of Bug Fixes and Updates
authored
217 #need better way to iterate through highlights and remove brackets
178e4f1 @stephenbrannon Initial upload
authored
218 for t in tags:
219 indicators = []
220 myhighlights = text.tag_ranges(t)
221 mystart = 0
222 for h in myhighlights:
223 if mystart == 0:
224 mystart = h
225 else:
226 mystop = h
b318067 Bug fixes and updates
Stephen Brannon authored
227 if t == 'md5': #make all hashes uppercase
55f0741 Updated regular expressions and exports to better handle [.] and [@] …
Stephen Brannon authored
228 if not text.get(mystart,mystop).upper() in indicators:
229 indicators.append(text.get(mystart,mystop).upper())
b318067 Bug fixes and updates
Stephen Brannon authored
230 else:
55f0741 Updated regular expressions and exports to better handle [.] and [@] …
Stephen Brannon authored
231 if not text.get(mystart,mystop).replace('[.]','.').replace('[@]','@') in indicators:
232 indicators.append(text.get(mystart,mystop).replace('[.]','.').replace('[@]','@'))
178e4f1 @stephenbrannon Initial upload
authored
233 mystart = 0
234 for i in indicators:
235 if i.find(',') == -1: #no commas, print normally
236 output += str(i) + ',' + t + '\n'
237 else: #internal comma, surround in double quotes
238 output += '"' + str(i) + '",' + t + '\n'
239 if len(filename) - filename.find('.csv') != 4:
240 filename += '.csv' #add .csv extension if missing
241 with open(filename, 'w') as f:
c426dfb @bworrell Updated IOCextractor to use python-cybox v2.0.0. Removed cybox folder…
bworrell authored
242 f.write(output)
178e4f1 @stephenbrannon Initial upload
authored
243
164d280 @bworrell added support for cybox output. changed Tk imports to work with pytho…
bworrell authored
244
245 def export_cybox():
c426dfb @bworrell Updated IOCextractor to use python-cybox v2.0.0. Removed cybox folder…
bworrell authored
246 """
972894d @bworrell Added check for python-cybox v2.0 or greater. Added work-around for C…
bworrell authored
247 Export the tagged items in CybOX format.
248 This prompts the user to determine which file they want the CybOX saved
c426dfb @bworrell Updated IOCextractor to use python-cybox v2.0.0. Removed cybox folder…
bworrell authored
249 out too.
250 """
164d280 @bworrell added support for cybox output. changed Tk imports to work with pytho…
bworrell authored
251 filename = asksaveasfilename(title="Save As", filetypes=[("xml file",".xml"),("All files",".*")])
252 observables_doc = None
253
254 if filename:
255 observables = []
256 for t in tags:
257 indicators = []
258 myhighlights = text.tag_ranges(t)
259 mystart = 0
260 for h in myhighlights:
261 if mystart == 0:
262 mystart = h
263 else:
264 mystop = h
55f0741 Updated regular expressions and exports to better handle [.] and [@] …
Stephen Brannon authored
265 value = text.get(mystart,mystop).replace('[.]','.').replace('[@]','@')
164d280 @bworrell added support for cybox output. changed Tk imports to work with pytho…
bworrell authored
266
267 if t == 'md5':
268 value = value.upper()
269 if value not in indicators:
c426dfb @bworrell Updated IOCextractor to use python-cybox v2.0.0. Removed cybox folder…
bworrell authored
270 observable = cybox_helper.create_file_hash_observable('', value)
164d280 @bworrell added support for cybox output. changed Tk imports to work with pytho…
bworrell authored
271 observables.append(observable)
272 indicators.append(value)
273
274 elif t == 'ipv4':
275 if not value in indicators:
c426dfb @bworrell Updated IOCextractor to use python-cybox v2.0.0. Removed cybox folder…
bworrell authored
276 observable = cybox_helper.create_ipv4_observable(value)
164d280 @bworrell added support for cybox output. changed Tk imports to work with pytho…
bworrell authored
277 observables.append(observable)
278 indicators.append(value)
279
280 elif t == 'domain':
281 if not value in indicators:
ed1a2f4 @bworrell Updated IOCextractor to export CybOX v2.0.1 by requiring python-cybox…
bworrell authored
282 observable = cybox_helper.create_domain_name_observable(value)
283 observables.append(observable)
164d280 @bworrell added support for cybox output. changed Tk imports to work with pytho…
bworrell authored
284 indicators.append(value)
285
286 elif t == 'url':
287 if not value in indicators:
c426dfb @bworrell Updated IOCextractor to use python-cybox v2.0.0. Removed cybox folder…
bworrell authored
288 observable = cybox_helper.create_url_observable(value)
164d280 @bworrell added support for cybox output. changed Tk imports to work with pytho…
bworrell authored
289 observables.append(observable)
290 indicators.append(value)
291
292 elif t == 'email':
293 if not value in indicators:
c426dfb @bworrell Updated IOCextractor to use python-cybox v2.0.0. Removed cybox folder…
bworrell authored
294 observable = cybox_helper.create_email_address_observable(value)
164d280 @bworrell added support for cybox output. changed Tk imports to work with pytho…
bworrell authored
295 observables.append(observable)
296 indicators.append(value)
297
298 mystart = 0
299 # end if
300 # end for
301 # end for
302
303 if len(observables) > 0:
c426dfb @bworrell Updated IOCextractor to use python-cybox v2.0.0. Removed cybox folder…
bworrell authored
304 NS = cybox.utils.Namespace("http://example.com/", "example")
305 cybox.utils.set_id_namespace(NS)
306 observables_doc = Observables(observables=observables)
164d280 @bworrell added support for cybox output. changed Tk imports to work with pytho…
bworrell authored
307
972894d @bworrell Added check for python-cybox v2.0 or greater. Added work-around for C…
bworrell authored
308 if not filename.endswith('.xml'):
164d280 @bworrell added support for cybox output. changed Tk imports to work with pytho…
bworrell authored
309 filename = "%s.xml" % filename #add .xml extension if missing
310 # end if
311
c426dfb @bworrell Updated IOCextractor to use python-cybox v2.0.0. Removed cybox folder…
bworrell authored
312 with open(filename, "wb") as f:
ed1a2f4 @bworrell Updated IOCextractor to export CybOX v2.0.1 by requiring python-cybox…
bworrell authored
313 cybox_xml = observables_doc.to_xml()
c426dfb @bworrell Updated IOCextractor to use python-cybox v2.0.0. Removed cybox folder…
bworrell authored
314 f.write(cybox_xml)
315
164d280 @bworrell added support for cybox output. changed Tk imports to work with pytho…
bworrell authored
316 # end if
317
8742e90 @williamgibb Added support for OpenIOC 1.1 output.
williamgibb authored
318 def export_openioc():
319 '''
320 Export the tagged items in OpenIOC 1.1 format.
321 This prompts the user to determine which directory they want the IOC saved
322 out too.
323
324 Email tags default to 'Email/From' address, implying that the email address
325 found is the source address of an email. This may not be accurate in all
326 cases.
327 '''
328 def make_network_uri(uri, condition='contains', negate=False, preserve_case = False):
329 document = 'Network'
330 search = 'Network/URI'
331 content_type = 'string'
332 content = uri
333 IndicatorItem_node = ioc_api.make_IndicatorItem_node(condition, document, search, content_type, content, negate=negate, preserve_case=preserve_case, context_type = None)
334 return IndicatorItem_node
335
336 def make_email_from(from_address, condition='contains', negate=False, preserve_case = False):
337 document = 'Email'
338 search = 'Email/From'
339 content_type = 'string'
340 content = from_address
341 IndicatorItem_node = ioc_api.make_IndicatorItem_node(condition, document, search, content_type, content, negate=negate, preserve_case=preserve_case, context_type = None)
342 return IndicatorItem_node
343
344 output_directory = askdirectory(title = "Save IOC To")
345
346 if output_directory:
347 indicator_nodes = []
348 for tag in tags:
349 temp_indicators = []
350 myhighlights = text.tag_ranges(tag)
351 mystart = 0
352 for h in myhighlights:
353 if mystart == 0:
354 mystart = h
355 else:
356 mystop = h
357 # Deobfuscate ip addresses, domain names and email addresses
358 value = text.get(mystart,mystop).replace('[.]','.').replace('[@]','@')
359 if tag == 'md5':
360 value = value.upper()
361 if value not in temp_indicators:
362 indicator_node = ioc_common.make_fileitem_md5sum(value)
363 indicator_nodes.append(indicator_node)
364 temp_indicators.append(value)
365 elif tag == 'ipv4':
366 if value not in temp_indicators:
367 indicator_node = ioc_common.make_portitem_remoteip(value)
368 indicator_nodes.append(indicator_node)
369 temp_indicators.append(value)
370 elif tag == 'domain':
371 if value not in temp_indicators:
372 indicator_node = ioc_common.make_dnsentryitem_recordname(value)
373 indicator_nodes.append(indicator_node)
374 temp_indicators.append(value)
375 elif tag == 'url':
376 if value not in temp_indicators:
377 indicator_node = make_network_uri(value)
378 indicator_nodes.append(indicator_node)
379 temp_indicators.append(value)
380 elif tag == 'email':
381 if value not in temp_indicators:
382 indicator_node = make_email_from(value)
383 indicator_nodes.append(indicator_node)
384 temp_indicators.append(value)
385 else:
386 print 'Unknown tag encountered [%s]' % str(tag)
387 mystart = 0
388
389 if len(indicator_nodes) > 0:
390 ioc_obj = ioc_api.IOC(name = "IOC Extractor", description = "IOC generated with IOCExtractor")
391 for indicator in indicator_nodes:
392 ioc_obj.top_level_indicator.append(indicator)
393 ioc_obj.write_ioc_to_file(output_directory)
394 return True
164d280 @bworrell added support for cybox output. changed Tk imports to work with pytho…
bworrell authored
395
178e4f1 @stephenbrannon Initial upload
authored
396 root = Tk()
b318067 Bug fixes and updates
Stephen Brannon authored
397 root.title('IOCextractor')
178e4f1 @stephenbrannon Initial upload
authored
398
399 topframe = Frame(root)
400 topframe.pack()
401 bottomframe = Frame(root)
402 bottomframe.pack(side=BOTTOM)
403
404 openb = Button(topframe, text = "Open File", command = askopen)
b2a8a16 @stephenbrannon Collection of Bug Fixes and Updates
authored
405 openb.pack(side=LEFT)
178e4f1 @stephenbrannon Initial upload
authored
406
407 clear = Button(topframe, text = "Clear", command = clear_tag)
408 clear.pack({"side": "left"})
409
410 md5 = Button(topframe, text = "MD5", command = lambda: tag_new('md5'), bg = "#FE6AA8")
411 md5.pack({"side": "left"})
412
413 ipv4 = Button(topframe, text = "IPV4", command = lambda: tag_new('ipv4'), bg = "#6CB23E")
414 ipv4.pack({"side": "left"})
415
416 url = Button(topframe, text = "URL", command = lambda: tag_new('url'), bg = "#378A20")
417 url.pack({"side": "left"})
418
419 domain = Button(topframe, text = "Domain", command = lambda: tag_new('domain'), bg = "#5DC5D1")
420 domain.pack({"side": "left"})
421
422 email = Button(topframe, text = "Email", command = lambda: tag_new('email'), bg = "#0224F2")
423 email.pack({"side": "left"})
424
425 export_console = Button(topframe, text = "Export Console", command = export_console)
426 export_console.pack({"side": "left"})
427
428 export_csv = Button(topframe, text = "Export CSV", command = export_csv)
429 export_csv.pack({"side": "left"})
430
c426dfb @bworrell Updated IOCextractor to use python-cybox v2.0.0. Removed cybox folder…
bworrell authored
431 if python_cybox_available:
432 export_cybox = Button(topframe, text = "Export CybOX", command = export_cybox)
433 export_cybox.pack({"side": "left"})
164d280 @bworrell added support for cybox output. changed Tk imports to work with pytho…
bworrell authored
434
1af146f @williamgibb Added exception handling if ioc_writer library is not present
williamgibb authored
435 if ioc_writer_available:
436 export_openioc = Button(topframe, text = "Export OpenIOC 1.1", command = export_openioc)
437 export_openioc.pack({"side" : "left"})
164d280 @bworrell added support for cybox output. changed Tk imports to work with pytho…
bworrell authored
438
178e4f1 @stephenbrannon Initial upload
authored
439 #build main text area
440 text = Text(bottomframe, width=120, height=50)
441 text.pack({"side": "left"})
442 scrollbar = Scrollbar(bottomframe)
443 scrollbar.pack({"side": "left", "fill" : "y"})
444 scrollbar.config(command = text.yview)
445 text.config(yscrollcommand = scrollbar.set)
446
b318067 Bug fixes and updates
Stephen Brannon authored
447 text.bind('<Button-3>', clear_tag) #right-click selection to untag (Windows, Linux)
448 text.bind('<Command-Button-1>', clear_tag) #command-click selection to untag (Mac)
178e4f1 @stephenbrannon Initial upload
authored
449
450 #insert doc if received as commandline argument
451 if len(sys.argv) == 2:
b2a8a16 @stephenbrannon Collection of Bug Fixes and Updates
authored
452 askopen(sys.argv[1])
178e4f1 @stephenbrannon Initial upload
authored
453
454 root.mainloop()
Something went wrong with that request. Please try again.