@@ -190,17 +190,35 @@ def url_re(open_end=False):
190190HTTPS_SCHEME_DEFANG_RE = re .compile ('hxxps' , re .IGNORECASE )
191191
192192# Get some valid obfuscated ip addresses.
193- IPV4_RE = re .compile (r"""
194- (?:^|
195- (?![^\d\.])
196- )
197- (?:
193+ def ipv4_len (ip_len = 3 ):
194+ if ip_len == 3 :
195+ IPV4_RE = re .compile (r"""
196+ (?:^|
197+ (?![^\d\.])
198+ )
199+ (?:
200+ (?:[1-9]?\d|1\d\d|2[0-4]\d|25[0-5])
201+ [\[\(\\]*?\.[\]\)]*?
202+ ){3}
203+ (?:[1-9]?\d|1\d\d|2[0-4]\d|25[0-5])
204+ (?:(?=[^\d\.])|$)
205+ """ , re .VERBOSE )
206+
207+ elif ip_len == 4 :
208+ IPV4_RE = re .compile (r"""
209+ (?:^|
210+ (?![^\d\.])
211+ )
212+ (?:
213+ (?:[1-9]?\d|1\d\d|2[0-4]\d|25[0-5])
214+ [\[\(\\]*?\.[\]\)]*?
215+ ){4}
216+ ([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])
198217 (?:[1-9]?\d|1\d\d|2[0-4]\d|25[0-5])
199- [\[\(\\]*?\.[\]\)]*?
200- ){3}
201- (?:[1-9]?\d|1\d\d|2[0-4]\d|25[0-5])
202- (?:(?=[^\d\.])|$)
203- """ , re .VERBOSE )
218+ (?:(?=[^\d\.])|$)
219+ """ , re .VERBOSE )
220+
221+ return IPV4_RE
204222
205223# Experimental IPv6 regex, will not catch everything but should be sufficent for now.
206224IPV6_RE = re .compile (r"""
@@ -210,6 +228,26 @@ def url_re(open_end=False):
210228# Capture email addresses including common defangs.
211229EMAIL_RE = re .compile (r"""
212230 (
231+ [a-z0-9_.+-]+
232+ [\(\[{\x20]*
233+ (?:
234+ (?:
235+ (?:
236+ \x20*
237+ """ + SEPARATOR_DEFANGS + r"""
238+ \x20*
239+ )*
240+ \.
241+ (?:
242+ \x20*
243+ """ + SEPARATOR_DEFANGS + r"""
244+ \x20*
245+ )*
246+ |
247+ \W+dot\W+
248+ )
249+ [a-z0-9-]+?
250+ )*
213251 [a-z0-9_.+-]+
214252 [\(\[{\x20]*
215253 (?:@|\Wat\W)
@@ -283,6 +321,7 @@ def extract_iocs(data, refang=False, strip=False):
283321 :param bool strip: Strip possible garbage from the end of URLs
284322 :rtype: :py:func:`itertools.chain`
285323 """
324+
286325 return itertools .chain (
287326 extract_urls (data , refang = refang , strip = strip ),
288327 extract_ips (data , refang = refang ),
@@ -292,21 +331,22 @@ def extract_iocs(data, refang=False, strip=False):
292331 )
293332
294333
295- def extract_urls (data , refang = False , strip = False , delimiter = None , open_punc = False ):
334+ def extract_urls (data , refang = False , strip = False , delimiter = None , open_punc = False , no_scheme = False ):
296335 """Extract URLs.
297336
298337 :param data: Input text
299338 :param bool refang: Refang output?
300339 :param bool strip: Strip possible garbage from the end of URLs
301340 :rtype: :py:func:`itertools.chain`
302341 """
342+
303343 return itertools .chain (
304- extract_unencoded_urls (data , refang = refang , strip = strip , open_punc = open_punc ),
344+ extract_unencoded_urls (data , refang = refang , strip = strip , open_punc = open_punc , no_scheme = no_scheme ),
305345 extract_encoded_urls (data , refang = refang , strip = strip , delimiter = delimiter ),
306346 )
307347
308348
309- def extract_unencoded_urls (data , refang = False , strip = False , open_punc = False ):
349+ def extract_unencoded_urls (data , refang = False , strip = False , open_punc = False , no_scheme = False ):
310350 """Extract only unencoded URLs.
311351
312352 :param data: Input text
@@ -323,7 +363,7 @@ def extract_unencoded_urls(data, refang=False, strip=False, open_punc=False):
323363
324364 for url in unencoded_urls :
325365 if refang :
326- url = refang_url (url .group (1 ))
366+ url = refang_url (url .group (1 ), no_scheme = no_scheme )
327367 else :
328368 url = url .group (1 )
329369
@@ -411,12 +451,29 @@ def extract_ipv4s(data, refang=False):
411451 :param bool refang: Refang output?
412452 :rtype: Iterator[:class:`str`]
413453 """
414- for ip_address in IPV4_RE .finditer (data ):
454+
455+ def ipv4_str (data ):
456+ protocol_str = re .compile (r"https|http|ftp" )
457+
458+ for pro in protocol_str .finditer (data ):
459+ if refang :
460+ return refang_ipv4 (pro .group (0 ))
461+ else :
462+ return pro .group (0 )
463+
464+ for ip_address in ipv4_len ().finditer (data ):
465+ # Iterates over any ip address with 4 numbers after the final (3rd) octet
466+ for ip_address in ipv4_len (4 ).finditer (data ):
467+ pass
468+
415469 if refang :
416470 yield refang_ipv4 (ip_address .group (0 ))
417471 else :
418472 yield ip_address .group (0 )
419473
474+ if ipv4_str (data ) != None :
475+ yield ipv4_str (data )
476+
420477
421478def extract_ipv6s (data ):
422479 """Extract IPv6 addresses.
@@ -426,8 +483,11 @@ def extract_ipv6s(data):
426483 :param data: Input text
427484 :rtype: Iterator[:class:`str`]
428485 """
486+
429487 for ip_address in IPV6_RE .finditer (data ):
430- yield ip_address .group (0 )
488+ # Sets a minimal standard for IPv6 (0:0:0:0:0:0:0:0)
489+ if len (data ) >= 15 :
490+ yield ip_address .group (0 )
431491
432492
433493def extract_emails (data , refang = False ):
@@ -437,7 +497,9 @@ def extract_emails(data, refang=False):
437497 :param bool refang: Refang output?
438498 :rtype: Iterator[:class:`str`]
439499 """
500+
440501 for email in EMAIL_RE .finditer (data ):
502+
441503 if refang :
442504 email = refang_email (email .group (1 ))
443505 else :
@@ -455,6 +517,7 @@ def extract_hashes(data):
455517 :param data: Input text
456518 :rtype: :py:func:`itertools.chain`
457519 """
520+
458521 return itertools .chain (
459522 extract_md5_hashes (data ),
460523 extract_sha1_hashes (data ),
@@ -469,6 +532,7 @@ def extract_md5_hashes(data):
469532 :param data: Input text
470533 :rtype: Iterator[:class:`str`]
471534 """
535+
472536 for md5 in MD5_RE .finditer (data ):
473537 yield md5 .group (1 )
474538
@@ -479,6 +543,7 @@ def extract_sha1_hashes(data):
479543 :param data: Input text
480544 :rtype: Iterator[:class:`str`]
481545 """
546+
482547 for sha1 in SHA1_RE .finditer (data ):
483548 yield sha1 .group (1 )
484549
@@ -489,6 +554,7 @@ def extract_sha256_hashes(data):
489554 :param data: Input text
490555 :rtype: Iterator[:class:`str`]
491556 """
557+
492558 for sha256 in SHA256_RE .finditer (data ):
493559 yield sha256 .group (1 )
494560
@@ -499,6 +565,7 @@ def extract_sha512_hashes(data):
499565 :param data: Input text
500566 :rtype: Iterator[:class:`str`]
501567 """
568+
502569 for sha512 in SHA512_RE .finditer (data ):
503570 yield sha512 .group (1 )
504571
@@ -509,6 +576,7 @@ def extract_yara_rules(data):
509576 :param data: Input text
510577 :rtype: Iterator[:class:`str`]
511578 """
579+
512580 for yara_rule in YARA_PARSE_RE .finditer (data ):
513581 yield yara_rule .group (1 ).strip ()
514582
@@ -546,6 +614,7 @@ def extract_custom_iocs(data, regex_list):
546614 :param regex_list: List of strings to treat as regex and match against data.
547615 :rtype: Iterator[:class:`str`]
548616 """
617+
549618 # Compile all the regex strings first, so we can error out quickly.
550619 regex_objects = []
551620 for regex_string in regex_list :
@@ -563,6 +632,7 @@ def _is_ipv6_url(url):
563632 :param url: String URL
564633 :rtype: bool
565634 """
635+
566636 # Fix urlparse exception.
567637 parsed = urlparse (url )
568638
@@ -586,6 +656,7 @@ def _refang_common(ioc):
586656 :param ioc: String IP/Email Address or URL netloc.
587657 :rtype: str
588658 """
659+
589660 return ioc .replace ('[dot]' , '.' ).\
590661 replace ('(dot)' , '.' ).\
591662 replace ('[.]' , '.' ).\
@@ -601,6 +672,7 @@ def refang_email(email):
601672 :param email: String email address.
602673 :rtype: str
603674 """
675+
604676 # Check for ' at ' and ' dot ' first.
605677 email = re .sub ('\W[aA][tT]\W' , '@' , email .lower ())
606678 email = re .sub ('\W*[dD][oO][tT]\W*' , '.' , email )
@@ -609,16 +681,15 @@ def refang_email(email):
609681 return _refang_common (email ).replace ('[' , '' ).\
610682 replace (']' , '' ).\
611683 replace ('{' , '' ).\
612- replace ('}' , '' ).\
613- replace ('{' , '' )
614-
684+ replace ('}' , '' )
615685
616- def refang_url (url ):
686+ def refang_url (url , no_scheme = False ):
617687 """Refang a URL.
618688
619689 :param url: String URL
620690 :rtype: str
621691 """
692+
622693 # First fix urlparse errors.
623694 # Fix ipv6 parsing exception.
624695 if '[.' in url and '[.]' not in url :
@@ -647,7 +718,8 @@ def refang_url(url):
647718 url = url .replace ('\\ \\ ' , '//' , 1 )
648719 else :
649720 # Support no-protocol.
650- url = 'http://' + url
721+ # url = 'http://' + url
722+ pass
651723
652724 # Refang (/), since it's not entirely in the netloc.
653725 url = url .replace ('(/)' , '/' )
@@ -676,7 +748,10 @@ def refang_url(url):
676748 elif HTTPS_SCHEME_DEFANG_RE .fullmatch (parsed .scheme ):
677749 scheme = 'https'
678750 else :
679- scheme = 'http'
751+ if no_scheme :
752+ scheme = ''
753+ else :
754+ scheme = 'http'
680755
681756 parsed = parsed ._replace (scheme = scheme )
682757 replacee = '{}:///' .format (scheme )
@@ -708,6 +783,7 @@ def refang_ipv4(ip_address):
708783 :param ip_address: String IPv4 address.
709784 :rtype: str
710785 """
786+
711787 return _refang_common (ip_address ).replace ('[' , '' ).\
712788 replace (']' , '' ).\
713789 replace ('\\ ' , '' )
@@ -719,6 +795,7 @@ def defang(ioc):
719795 :param ioc: String URL, domain, or IPv4 address.
720796 :rtype: str
721797 """
798+
722799 # If it's a url, defang just the scheme and netloc.
723800 try :
724801 parsed = urlparse (ioc )
@@ -745,32 +822,34 @@ def main():
745822 description = """Advanced Indicator of Compromise (IOC) extractor.
746823 If no arguments are specified, the default behavior is
747824 to extract all IOCs.""" )
748- parser .add_argument ('--input' , type = lambda x : io .open (x , 'r' , encoding = 'utf-8' , errors = 'ignore' ),
825+ parser .add_argument ('-i' , '- -input' , type = lambda x : io .open (x , 'r' , encoding = 'utf-8' , errors = 'ignore' ),
749826 default = io .open (0 , 'r' , encoding = 'utf-8' , errors = 'ignore' ), help = "default: stdin" )
750- parser .add_argument ('--output' , type = lambda x : io .open (x , 'w' , encoding = 'utf-8' , errors = 'ignore' ),
827+ parser .add_argument ('-o' , '- -output' , type = lambda x : io .open (x , 'w' , encoding = 'utf-8' , errors = 'ignore' ),
751828 default = io .open (1 , 'w' , encoding = 'utf-8' , errors = 'ignore' ), help = "default: stdout" )
752- parser .add_argument ('--extract-emails' , action = 'store_true' )
753- parser .add_argument ('--extract-ips' , action = 'store_true' )
754- parser .add_argument ('--extract-ipv4s' , action = 'store_true' )
755- parser .add_argument ('--extract-ipv6s' , action = 'store_true' )
756- parser .add_argument ('--extract-urls' , action = 'store_true' )
757- parser .add_argument ('--extract-yara-rules' , action = 'store_true' )
758- parser .add_argument ('--extract-hashes' , action = 'store_true' )
759- parser .add_argument ('--custom-regex' , type = lambda x : io .open (x , 'r' , encoding = 'utf-8' , errors = 'ignore' ),
829+ parser .add_argument ('-ee' , '- -extract-emails' , action = 'store_true' )
830+ parser .add_argument ('-ip' , '- -extract-ips' , action = 'store_true' )
831+ parser .add_argument ('-ip4' , '- -extract-ipv4s' , action = 'store_true' )
832+ parser .add_argument ('-ip6' , '- -extract-ipv6s' , action = 'store_true' )
833+ parser .add_argument ('-u' , '- -extract-urls' , action = 'store_true' )
834+ parser .add_argument ('-y' , '- -extract-yara-rules' , action = 'store_true' )
835+ parser .add_argument ('-ha' , '- -extract-hashes' , action = 'store_true' )
836+ parser .add_argument ('-cr' , '- -custom-regex' , type = lambda x : io .open (x , 'r' , encoding = 'utf-8' , errors = 'ignore' ),
760837 metavar = 'REGEX_FILE' ,
761838 help = "file with custom regex strings, one per line, with one capture group each" )
762- parser .add_argument ('--refang' , action = 'store_true' , help = "default: no" )
763- parser .add_argument ('--strip-urls' , action = 'store_true' ,
839+ parser .add_argument ('-r' , '- -refang' , action = 'store_true' , help = "default: no" )
840+ parser .add_argument ('-su' , '- -strip-urls' , action = 'store_true' ,
764841 help = "remove possible garbage from the end of urls. default: no" )
765- parser .add_argument ('--wide' , action = 'store_true' ,
842+ parser .add_argument ('-w' , '- -wide' , action = 'store_true' ,
766843 help = "preprocess input to allow wide-encoded character matches. default: no" )
767- parser .add_argument ('--json' , action = 'store_true' )
768- parser .add_argument ('--open' , action = 'store_true' , help = "Removes the end puncuation regex when extracting URLs" )
844+ parser .add_argument ('-j' , '--json' , action = 'store_true' )
845+ parser .add_argument ('-op' , '--open' , action = 'store_true' , help = "Removes the end puncuation regex when extracting URLs" )
846+ parser .add_argument ('-rm' , '--rm_scheme' , action = 'store_true' , help = "Removes the protocol from the url (i.e. http, https, etc.)" )
769847
770848 args = parser .parse_args ()
771849
772- # Read input.
850+ # Read user unput
773851 data = args .input .read ()
852+
774853 if args .wide :
775854 data = data .replace ('\x00 ' , '' )
776855
@@ -785,6 +864,7 @@ def main():
785864 args .extract_emails or
786865 args .custom_regex
787866 )
867+
788868 memo = {}
789869
790870 if args .extract_emails or extract_all :
@@ -795,8 +875,10 @@ def main():
795875 memo ["ipv6s" ] = list (extract_ipv6s (data ))
796876 if args .extract_urls or extract_all :
797877 memo ["urls" ] = list (extract_urls (data , refang = args .refang , strip = args .strip_urls ))
798- if args .extract_urls and args .open :
799- memo ["urls" ] = list (extract_urls (data , refang = args .refang , strip = args .strip_urls , open_punc = True ))
878+ if args .open :
879+ memo ["open_punc" ] = list (extract_urls (data , refang = args .refang , strip = args .strip_urls , open_punc = args .open ))
880+ if args .rm_scheme :
881+ memo ["no_protocol" ] = list (extract_urls (data , refang = args .refang , strip = args .strip_urls , open_punc = args .open , no_scheme = args .rm_scheme ))
800882 if args .extract_yara_rules or extract_all :
801883 memo ["yara_rules" ] = list (extract_yara_rules (data ))
802884 if args .extract_hashes or extract_all :
0 commit comments