TC$mC$ on erinomainen lC6ytC6. "Security Type: 19" (VeNCrypt / TLS) selittC$C$
tC$ysin, miksi aVNC jC$C$ pyC6rimC$C$n. Oletuksena moni moderni VNC-palvelin
(kuten TigerVNC tai uudempi x11vnc) pyrkii neuvottelemaan salatun TLS-putken
turvallisuuden takaamiseksi.
Kun yhteys pC$ivitetC$C$n TLS:C$C$n (Security Type 19 = VeNCrypt), kaikki
myC6hempi liikenne on salattua, eikC$ meidC$n proxy-koodimme voi enC$C$ lukea
paketista PointerEvent (viestityyppi 5) tai kC$ttelyn loppuosaa, koska se on
puhdasta TLS-kryptotekstiC$. Proxyn parsinta menee sekaisin, ja VNC-asiakas
jC$C$ odottamaan, ettC$ TLS-kC$ttely viedC$C$n loppuun.
Ratkaisu: EstetC$C$n TLS:n kC$yttC6 (Downgrade-hyC6kkC$ys)
Koska VNC-proxy on luotetussa lokaalissa verkossasi (tai suoraan samalla
koneella, 127.0.0.1), me emme tarvitse salattua yhteyttC$ palvelimen ja proxyn
vC$liin. Haluamme pakottaa palvelimen ja asiakkaan kC$yttC$mC$C$n perinteistC$
VNC-autentikointia (Tyyppi 2) tai salaamatonta yhteyttC$ (Tyyppi 1).
Koska olemme Man-in-the-Middle (MitM), voimme muokata palvelimen
lC$hettC$mC$C$ "Tuetut Security Typet" -listaa ennen kuin asiakas (aVNC)
nC$kee sen. Palvelin saattaa sanoa: "Tuen tyyppejC$ 19, 18, 2". Me muutamme
viestin muotoon: "Tuen vain tyyppiC$ 2 (tai 1)".
TC$mC$ pakottaa aVNC:n valitsemaan selvC$kielisen protokollan.
Korjattu handle_handshake -metodi
TC$ssC$ on koodi, joka muokkaa lennosta palvelimen lC$hettC$mC$C$ kC$ttelyC$
ja poistaa sieltC$ VeNCryptin (19), pakottaen yhteyden selvC$kieliseksi (joko
salasana 2, tai ei salasanaa 1).
Muokkaa vncproxy.py tiedostostasi handle_handshake -funktio tC$smC$lleen
tC$hC$n muotoon:
async def handle_handshake(self, c_reader, c_writer, s_reader, s_writer):
# Vaihe 1: Protocol Version (12 tavua)
s_version = await s_reader.readexactly(12)
c_writer.write(s_version)
await c_writer.drain()
c_version = await c_reader.readexactly(12)
s_writer.write(c_version)
await s_writer.drain()
# Vaihe 2: Security Types
# Palvelin kertoo montako tyyppiC$ se tukee
num_sec_types_bytes = await s_reader.readexactly(1)
num_sec_types = num_sec_types_bytes[0]
if num_sec_types == 0:
# Jos 0, palvelin lC$hettC$C$ heti perC$C$n virheviestin syystC$
c_writer.write(num_sec_types_bytes)
await c_writer.drain()
reason_len = struct.unpack('>I', await s_reader.readexactly(4))[0]
reason = await s_reader.readexactly(reason_len)
c_writer.write(struct.pack('>I', reason_len) + reason)
await c_writer.drain()
raise Exception(f"Palvelin sulki yhteyden: {reason.decode('utf-8',
'ignore')}")
# Luetaan palvelimen tukemat tyypit
server_sec_types = await s_reader.readexactly(num_sec_types)
print(f"[*] Palvelimen alkuperC$iset Security Typet: {list(server_sec_t
ypes)}")
# DOWNGRADE: Suodatetaan pois tyypit > 2 (esim 19 VeNCrypt, 18 TLS)
# Sallitaan vain 1 (None) ja 2 (VNC Auth)
allowed_types = [t for t in server_sec_types if t in (1, 2)]
if not allowed_types:
raise Exception("Palvelin tukee vain salattuja yhteyksiC$ (esim.
vain VeNCrypt). Proxyn kC$yttC6 vaatii selvC$kielisen VNC-salasanan tai
No-Auth tuen.")
# LC$hetetC$C$n asiakkaalle (aVNC) meidC$n manipuloitu lista
print(f"[*] Pakotetaan asiakkaalle Security Typet: {allowed_types}")
c_writer.write(struct.pack('B', len(allowed_types)))
c_writer.write(bytes(allowed_types))
await c_writer.drain()
# Vaihe 3: Client valitsee Security Typen meidC$n manipuloidulta
listalta
selected_sec_type_bytes = await c_reader.readexactly(1)
sec_type = selected_sec_type_bytes[0]
# LC$hetetC$C$n asiakkaan valinta palvelimelle.
# HUOM: Koska poistimme salausvaihtoehdot, palvelin hyvC$ksyy tyypin 1
tai 2,
# KOSKA se alunperinkin ilmoitti tukevansa niitC$.
s_writer.write(selected_sec_type_bytes)
await s_writer.drain()
print(f"[*] Asiakas valitsi Security Typen: {sec_type}")
# Vaihe 4: Autentikaatio
if sec_type == 2: # VNC Authentication
challenge = await s_reader.readexactly(16)
c_writer.write(challenge)
await c_writer.drain()
response = await c_reader.readexactly(16)
s_writer.write(response)
await s_writer.drain()
sec_result = await s_reader.readexactly(4)
c_writer.write(sec_result)
await c_writer.drain()
result_code = struct.unpack('>I', sec_result)[0]
if result_code != 0:
raise Exception("Salasana vC$C$rin")
elif sec_type == 1: # None
# RFB 3.8 vaatii SecurityResultin myC6s None-tyypille
sec_result = await s_reader.readexactly(4)
c_writer.write(sec_result)
await c_writer.drain()
# Vaihe 5: ClientInit (1 = Exclusive, 0 = Shared)
client_init = await c_reader.readexactly(1)
s_writer.write(client_init)
await s_writer.drain()
# Vaihe 6: ServerInit
server_init_header = await s_reader.readexactly(24)
c_writer.write(server_init_header)
name_length = struct.unpack('>I', server_init_header[20:24])[0]
name = await s_reader.readexactly(name_length)
c_writer.write(name)
await c_writer.drain()
print(f"[*] KC$ttely ohitettu onnistuneesti. TyC6pC6ydC$n nimi:
{name.decode('utf-8', 'ignore')}")
MitC$ nyt pitC$isi tapahtua?
Kun aVNC yhdistC$C$, se nC$kee proxyn manipuloiman vastauksen: "Tuen vain VNC
Authia (2)". aVNC kysyy sinulta VNC-salasanaa (jos sellaista kC$ytetC$C$n),
lC$hettC$C$ salasanavasteen lC$pi proxyn, ja loppuyhteys pysyy perinteisessC$,
selvC$kielisessC$ RFB-muodossa.
Koska liikenne on selvC$kielistC$, silmukka nC$kee taas hiiren klikkaukset ja
koordinaatit.
Kokeile ajaa proxy uudelleen tC$llC$ muutoksella ja yhdistC$ aVNC:llC$.