├── LICENSE ├── README.md ├── evilurl.py └── test.txt /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Undead Sec 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | EvilURL v3.0 2 | == 3 | Generate unicode domains for IDN Homograph Attack and detect them. 4 | 5 | The EvilURL is released under a BSD-style license. See 6 | [LICENSE](LICENSE) for more details. 7 | 8 | ### CLONE 9 | ``` 10 | git clone https://github.com/UndeadSec/EvilURL.git 11 | ``` 12 | ### INSTALL 13 | ``` 14 | pip install python-nmap python-whois 15 | ``` 16 | ### RUNNING 17 | ``` 18 | cd EvilURL 19 | ``` 20 | ``` 21 | python3 evilurl.py 22 | ``` 23 | ### VIDEO 24 | https://www.youtube.com/watch?v=COyFfSlexTw 25 | 26 | ### DISCLAIMER 27 | 28 | TO BE USED FOR EDUCATIONAL PURPOSES ONLY 29 | 30 | The use of the EvilURL is COMPLETE RESPONSIBILITY of the END-USER. Developer assume NO liability and are NOT responsible for any misuse or damage caused by this program. 31 | 32 | "DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 33 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 34 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 35 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 36 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 37 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 38 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 39 | Taken from [LICENSE](LICENSE). 40 | 41 | ### CHANGELOG 42 | * Improved permutations 43 | * Full script updated to CLI 44 | * Check domains from lists 45 | * Check avaliable domains 46 | * Check domains connection 47 | * Logging 48 | -------------------------------------------------------------------------------- /evilurl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #------------------------------------------------------ 3 | # BY: UNDEADSEC from BRAZIL :) 4 | # YouTube: https://www.youtube.com/c/UndeadSec 5 | # Github: https://github.com/UndeadSec/EvilURL 6 | #------------------------------------------------------ 7 | from platform import python_version 8 | from sys import exit, argv 9 | from argparse import ArgumentParser 10 | from nmap import PortScanner 11 | from whois import whois 12 | 13 | version = python_version().startswith('2', 0, len(python_version())) 14 | if version: 15 | print('Are you using python version {}\n' 16 | 'Please, use version 3.X of python'.format(python_version())) 17 | exit(1) 18 | 19 | from os import system 20 | 21 | RED, WHITE, GREEN, END, YELLOW = '\033[91m', '\33[97m', '\033[1;32m', '\033[0m', '\33[93m' 22 | 23 | unicodes = [{'\u0430':'Cyrillic Small Letter A'}, 24 | {'\u03F2':'Greek Lunate Sigma Symbol'}, 25 | {'\u0435':'Cyrillic Small Letter Ie'}, 26 | {'\u043E':'Cyrillic Small Letter O'}, 27 | {'\u0440':'Cyrillic Small Letter Er'}, 28 | {'\u0455':'Cyrillic Small Letter Dze'}, 29 | {'\u0501':'Cyrillic Small Letter Komi De'}, 30 | {'\u051B':'Cyrillic Small Letter Qa'}, 31 | {'\u051D':'Cyrillic Small Letter We'}] 32 | 33 | def message(output=False): 34 | system('clear') 35 | printParser('''{0} 36 | {0}88888888888 88 88{1} 88 88 88888888ba 88 37 | {0}88 "" 88{1} 88 88 88 "8b 88 38 | {0}88 88{1} 88 88 88 ,8P 88 39 | {0}88aaaaa 8b d8 88 88{1} 88 88 88aaaaaa8P' 88 40 | {0}88""""" `8b d8' 88 88{1} 88 88 88""""88' 88 v3.0 41 | {0}88 `8b d8' 88 88{1} 88 88 88 `8b 88 42 | {0}88 `8b,d8' 88 88{1} Y8a. .a8P 88 `8b 88 43 | {0}88888888888 "8" 88 88{1} `"Y8888Y"' 88 `8b 88888888 {1} 44 | 45 | [ by UNDEAD{0}SEC{1} - Alisson Moretto @UndeadSec ]\n'''.format(RED, END), output) 46 | 47 | def cleanTxt(txt): 48 | for i in (RED, WHITE, GREEN, END, YELLOW): 49 | txt = txt.replace(i, '') 50 | return txt 51 | 52 | def cleanFile(output): 53 | arq = open(output, 'w') 54 | arq.write('') 55 | arq.close() 56 | 57 | def checkAval(domain): 58 | try: 59 | return whois(domain).registrar 60 | except: 61 | return None 62 | 63 | def printParser(txt, output=False): 64 | print(txt) 65 | if output: 66 | arq = open(output, 'a') 67 | arq.write(cleanTxt(txt)+'\n') 68 | arq.close() 69 | 70 | def printOriginal(url, checkConnection, output): 71 | printParser('{0}[{1}~{0}]{1} Original: {2}'.format(GREEN, END, url), output) 72 | if checkConnection: printParser(check_url(url), output) 73 | 74 | def makeEvil(char, unicd, uninum, newurl, oldurl, output): 75 | printParser('\n{0}[{1}*{0}]{1} Domain name: %s\n{0}[{1}*{0}]{1} Char replaced: %s\n{0}[{1}*{0}]{1} Using Unicode: %s\n{0}[{1}*{0}]{1} Unicode number: %s\n{0}[{1}*{0}]{1} Evil URL: {3}%s{1}'.format(GREEN, END, YELLOW, RED) % (oldurl, char, unicd, uninum, newurl), output) 76 | 77 | import itertools 78 | 79 | def gen(url, tld, checkConnection=False, output=False, aval=False): 80 | url = url.lower() 81 | 82 | evils = [{'a':'\u0430'},{'c': '\u03F2'}, {'e': '\u0435'}, {'o': '\u043E'}, {'p': '\u0440'}, {'s': '\u0455'}, {'d': '\u0501'}, {'q': '\u051B'}, {'w': '\u051D'}] 83 | e_matchs = [] 84 | 85 | for em in evils: 86 | if list(em)[0].upper() in url.upper(): 87 | e_matchs.append(list(em)[0]) 88 | 89 | cst = '' 90 | for ch in e_matchs: 91 | cst += list(ch)[0] 92 | 93 | words = [] 94 | for i in range(1, 9): 95 | for j in itertools.combinations(cst, i): 96 | temp = ''.join(j) 97 | words.append(temp) 98 | 99 | for word in words: 100 | newurl = url 101 | unicd = [] 102 | name = [] 103 | chars = [] 104 | for w in word: 105 | for em in evils: 106 | if list(em)[0] == w: 107 | chr = em[list(em)[0]] 108 | unicd.append(chr) 109 | chars.append(w) 110 | for u in unicodes: 111 | if list(u)[0] == chr: 112 | name.append(u[chr]) 113 | newurl = newurl.replace(w, chr) 114 | makeEvil(chars, unicd, name, newurl+tld, url, output) 115 | if checkConnection: printParser(check_url(newurl+tld), output) 116 | if aval: 117 | if checkAval(newurl+tld) is None: 118 | printParser('{0}[{1}*{0}]{1} Available domain'.format(GREEN, END), output) 119 | else: 120 | printParser('{0}[{1}!{0}]{1} Unavailable domain'.format(YELLOW, END), output) 121 | 122 | # -------------- BEGIN CHECKURL MODULE----------------- # 123 | def check_url(url): 124 | 125 | ''' 126 | Check connection 127 | :param url: suspicious url 128 | :return: status of connection 129 | ''' 130 | 131 | nmScan = PortScanner() 132 | result = nmScan.scan(url, arguments='-sn') 133 | 134 | if int(result['nmap']['scanstats']['uphosts']) > 0: 135 | msg = '{0}[{1}*{0}]{1} Connection test: UP'.format(GREEN, END) 136 | else: 137 | msg = '{0}[{1}!{0}]{1} Connection test: DOWN'.format(YELLOW, END) 138 | 139 | return msg 140 | 141 | def check_EVIL(url): 142 | 143 | ''' 144 | Check evil chars in URL 145 | :param url: suspicious URL 146 | :return: result of check and the evil chars 147 | ''' 148 | 149 | bad_chars = ['\u0430', '\u03F2', '\u0435', '\u043E', '\u0440', '\u0455', '\u0501', '\u051B', '\u051D'] 150 | result = [bad_chars[i] for i in range(len(bad_chars)) if bad_chars[i] in url] 151 | 152 | if result: 153 | msg = '{0}[{1}*{0}]{1} Evil URL detected: {2}{3}{1}'.format(GREEN,END,RED,url) 154 | msg += '\n{0}[{1}*{0}]{1} Unicode characters used: {2}'.format(GREEN,END,result) 155 | else: 156 | msg = '{0}[{1}!{0}]{1} Evil URL NOT detected: {2}'.format(YELLOW, END, url) 157 | 158 | return msg 159 | 160 | def urls_list(file, checkConnection, output): 161 | ''' 162 | Read the file to verify Evil URL 163 | :param file: file with a list of Evil URLs 164 | :return: file reading 165 | ''' 166 | 167 | with open(file) as arq: 168 | urls = [f.strip() for f in arq] 169 | for i in range(len(urls)): 170 | printParser(check_EVIL(urls[i]), output) 171 | if checkConnection: 172 | printParser(check_url(urls[i]), output) 173 | printParser('', output) 174 | 175 | # -------------- END CHECKURL MODULE ----------------- # 176 | 177 | def parseHandler(): 178 | parser = ArgumentParser(usage="evilurl.py [options]", description="Generate unicode evil domains for IDN Homograph Attack and detect them.") 179 | parser.add_argument("-g", dest="generate", action="store_true", help="Generate unicode evil domains") 180 | parser.add_argument("-d", dest="domain", help="Domain name with termination (example.com)") 181 | parser.add_argument("-c", dest="checkConnection", action="store_true", help="Check generated/input domain connections") 182 | parser.add_argument("-o", dest="output", help="Save generated evil domains to a file") 183 | parser.add_argument("-f", dest="filepath", help="Import domains from a file and check them") 184 | parser.add_argument("-a", dest="aval", action="store_true", help="Check if domain is available") 185 | 186 | if len(argv) == 1: 187 | parser.print_help() 188 | exit(1) 189 | 190 | args = parser.parse_args() 191 | domain = '' if args.domain is None else args.domain 192 | generate = args.generate 193 | checkConnection = args.checkConnection 194 | filepath = args.filepath 195 | output = args.output 196 | aval = args.aval 197 | tld = '' 198 | for x in domain.split('.')[1:]: 199 | tld += '.' + x 200 | if output: 201 | cleanFile(output) 202 | message(output) 203 | if generate and not domain or generate and domain and filepath or domain and filepath: 204 | parser.print_help() 205 | elif generate and len(domain) > 0: 206 | printOriginal(domain, checkConnection, output) 207 | gen(domain.split('.')[0], tld, checkConnection, output, aval) 208 | elif len(domain) > 0: 209 | printParser(check_EVIL(domain), output) 210 | elif filepath: 211 | urls_list(filepath, checkConnection, output) 212 | if checkConnection and not filepath and not generate: 213 | printParser(check_url(domain), output) 214 | if output: 215 | print('\n{1}[{2}*{1}]{2} Logs stored to {0}'.format(output, GREEN, END)) 216 | 217 | if __name__ == '__main__': 218 | try: 219 | message() 220 | parseHandler() 221 | except KeyboardInterrupt: 222 | exit() 223 | -------------------------------------------------------------------------------- /test.txt: -------------------------------------------------------------------------------- 1 | example.com 2 | github.com 3 | exаmрle.com 4 | --------------------------------------------------------------------------------