Skip to content

Commit

Permalink
Merge pull request #178 from Derekt2/master
Browse files Browse the repository at this point in the history
Adding John the Ripper for Dictionary attacks & Option for Bruteforcing.
  • Loading branch information
phutelmyer authored Aug 20, 2021
2 parents e59a75b + 7e0c2c1 commit 1ed97a0
Show file tree
Hide file tree
Showing 6 changed files with 299 additions and 70 deletions.
14 changes: 14 additions & 0 deletions build/python/backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,20 @@ RUN apt-get -qq update && \
python3 setup.py build --dynamic-linking && \
python3 setup.py install


# Install JTR
RUN apt-get -qq update \
&& apt-get install -qq --no-install-recommends -y git python build-essential ca-certificates libssl-dev zlib1g-dev yasm libgmp-dev libpcap-dev libbz2-dev libgomp1
RUN git clone https://github.com/magnumripper/JohnTheRipper.git /jtr \
&& rm -rf /jtr/.git \
&& cd /jtr/src \
&& ./configure \
&& make -s clean \
&& make -sj4 \
&& make install \
&& cp -Tr /jtr/run/ /jtr && rm -rf /jtr/run \
&& chmod -R 777 /jtr

# Install Python packages
COPY ./build/python/backend/requirements.txt /strelka/requirements.txt
RUN pip3 install --no-cache-dir -r /strelka/requirements.txt && \
Expand Down
17 changes: 16 additions & 1 deletion configs/python/backend/backend.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,23 @@ scanners:
'ScanEncryptedDoc':
- positive:
flavors:
- 'application/encrypted'
- 'encrypted_word_document'
priority: 5
options:
max_length: 5
scanner_timeout: 150
log_pws: True
# brute_force: True
'ScanEncryptedZip':
- positive:
flavors:
- 'encrypted_zip'
priority: 5
options:
max_length: 5
scanner_timeout: 150
log_pws: True
# brute_force: True
'ScanEntropy':
- positive:
flavors:
Expand Down
33 changes: 33 additions & 0 deletions configs/python/backend/taste/taste.yara
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,39 @@ rule cpio_file {
$a at 0
}


rule encrypted_zip
{
meta:
author = "thudak@korelogic.com"
comment = "Solution 7 - encrypted zip file"

strings:
$local_file = { 50 4b 03 04 }
condition:
// look for the ZIP header
uint32(0) == 0x04034b50 and
// make sure we have a local file header
$local_file and
// go through each local file header and see if the encrypt bits are set
for any i in (1..#local_file): (uint16(@local_file[i]+6) & 0x1 == 0x1)
}

rule encrypted_word_document
{
meta:
author = "Derek Thomas"
strings:
$ = "EncryptionInfo" wide
$ = "Microsoft.Container.EncryptionTransform" wide
$ = "StrongEncryptionDataSpace" wide
$ = "StrongEncryptionTransform" wide
condition:
uint32be(0) == 0xd0cf11e0 and
any of them
}

rule iso_file {
meta:
type = "archive"
Expand Down
144 changes: 98 additions & 46 deletions src/python/strelka/scanners/scan_encrypted_doc.py
Original file line number Diff line number Diff line change
@@ -1,64 +1,116 @@
import io
import os
import msoffcrypto
import subprocess
import tempfile

from strelka import strelka

def crack_word(self, data, jtr_path, tmp_dir, password_file, max_length=10, scanner_timeout=150, brute=False):
try:
with tempfile.NamedTemporaryFile(dir=tmp_dir, mode='wb') as tmp_data:
tmp_data.write(data)
tmp_data.flush()

(office2john, stderr) = subprocess.Popen(
[jtr_path+'office2john.py', tmp_data.name],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL
).communicate()

with tempfile.NamedTemporaryFile(dir=tmp_dir) as tmp_data:
tmp_data.write(office2john)
tmp_data.flush()

class ScanEncryptedDoc(strelka.Scanner):
(stdout, stderr) = subprocess.Popen(
[jtr_path+'john', '--show', tmp_data.name],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL
).communicate()

if b"0 password hashes cracked" in stdout:

def init(self):
self.passwords = []
with tempfile.NamedTemporaryFile(dir=tmp_dir) as tmp_data:
tmp_data.write(office2john)
tmp_data.flush()

if os.path.isfile(password_file):
(stdout, stderr) = subprocess.Popen(
[jtr_path+'john', f'-w={password_file}', tmp_data.name],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL
).communicate(timeout=scanner_timeout)

if stdout.split(b'\n')[3]:
self.flags.append('cracked_by_wordlist')
return stdout.split(b'\n')[3].split()[0]

def scan(self, data, file, options, expire_at):
password_file = options.get('password_file', '/etc/strelka/passwords.dat')
if brute:
(stdout, stderr) = subprocess.Popen(
[jtr_path+'john', '--incremental=Alnum', f'--max-length={max_length}', f'--max-run-time={scanner_timeout}', tmp_data.name],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL
).communicate(timeout=scanner_timeout)
if stdout.split(b'\n')[3]:
self.flags.append('cracked_by_incremental')
return stdout.split(b'\n')[3].split()[0]
return ''
else:
return stdout.split(b':')[1].split()[0]

if not self.passwords:
if os.path.isfile(password_file):
with open(password_file, 'rb') as f:
for line in f:
self.passwords.append(line.strip())
except Exception as e:
self.flags.append(str(e))
return ''

with io.BytesIO(data) as doc_io:
class ScanEncryptedDoc(strelka.Scanner):
"""Extracts passwords from encrypted office word documents.
msoff_doc = msoffcrypto.OfficeFile(doc_io)
output_doc = io.BytesIO()
password = ''
extract_data = b''
Attributes:
passwords: List of passwords to use when bruteforcing encrypted files.
if msoff_doc.is_encrypted():
self.flags.append('password_protected')

for pw in self.passwords:
if not password:
try:
msoff_doc.load_key(password=pw.decode('utf-8'))
output_doc.seek(0)
msoff_doc.decrypt(output_doc)
output_doc.seek(0)
Options:
limit: Maximum number of files to extract.
Defaults to 1000.
password_file: Location of passwords file for zip archives.
Defaults to /etc/strelka/passwords.dat.
"""
def scan(self, data, file, options, expire_at):

jtr_path = options.get('jtr_path', '/jtr/')
tmp_directory = options.get('tmp_file_directory', '/tmp/')
password_file = options.get('password_file', '/etc/strelka/passwords.dat')
log_extracted_pws = options.get('log_pws', False)
scanner_timeout = options.get('scanner_timeout', 150)
brute = options.get('brute_force', False)
max_length = options.get('max_length', 5)

if output_doc.readable():
extract_data = output_doc.read()
password = pw.decode('utf-8')
break
with io.BytesIO(data) as doc_io:

except Exception:
pass
msoff_doc = msoffcrypto.OfficeFile(doc_io)
output_doc = io.BytesIO()
if extracted_pw := crack_word(self, data, jtr_path, tmp_directory, brute=brute, scanner_timeout=scanner_timeout, max_length=max_length, password_file=password_file):
if log_extracted_pws:
self.event['cracked_password'] = extracted_pw
try:
msoff_doc.load_key(password=extracted_pw.decode('utf-8'))
msoff_doc.decrypt(output_doc)
output_doc.seek(0)
extract_data = output_doc.read()
output_doc.seek(0)
extract_file = strelka.File(
source=self.name,
)

if password:
self.event['password'] = password
extract_file = strelka.File(
source=self.name,
)
for c in strelka.chunk_string(extract_data):
self.upload_to_coordinator(
extract_file.pointer,
c,
expire_at,
)

for c in strelka.chunk_string(extract_data):
self.upload_to_coordinator(
extract_file.pointer,
c,
expire_at,
)
self.files.append(extract_file)
except:
self.flags.append('Could not decrypt document with recovered password')

self.files.append(extract_file)
else:
self.flags.append('no_password_match_found')
else:
self.flags.append('Could not extract password')
138 changes: 138 additions & 0 deletions src/python/strelka/scanners/scan_encrypted_zip.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import subprocess
import tempfile
import io
import os
import zipfile
import zlib

from strelka import strelka

def crack_zip(self, data, jtr_path, tmp_dir, password_file, brute=False, max_length=10, scanner_timeout=150):
try:
with tempfile.NamedTemporaryFile(dir=tmp_dir, mode='wb') as tmp_data:
tmp_data.write(data)
tmp_data.flush()

(zip2john, stderr) = subprocess.Popen(
[jtr_path+'zip2john', tmp_data.name],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL
).communicate()

with tempfile.NamedTemporaryFile(dir=tmp_dir) as tmp_data:
tmp_data.write(zip2john)
tmp_data.flush()

(stdout, stderr) = subprocess.Popen(
[jtr_path+'john', '--show', tmp_data.name],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL
).communicate()

if b"0 password hashes cracked" in stdout:

with tempfile.NamedTemporaryFile(dir=tmp_dir) as tmp_data:
tmp_data.write(zip2john)
tmp_data.flush()

if os.path.isfile(password_file):
(stdout, stderr) = subprocess.Popen(
[jtr_path+'john', f'-w={password_file}', tmp_data.name],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL
).communicate(timeout=scanner_timeout)

if stdout.split(b'\n')[1]:
self.flags.append('cracked_by_wordlist')
return stdout.split(b'\n')[1].split()[0]
if brute:
(stdout, stderr) = subprocess.Popen(
[jtr_path+'john', '--incremental=Alnum', f'--max-length={max_length}', f'--max-run-time={scanner_timeout}', tmp_data.name],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL
).communicate(timeout=scanner_timeout)
if stdout.split(b'\n')[1]:
self.flags.append('cracked_by_incremental')
return stdout.split(b'\n')[1].split()[0]
return ''
else:
return stdout.split(b':')[1]

except Exception as e:
self.flags.append(str(e))
return ''


class ScanEncryptedZip(strelka.Scanner):
"""Extracts passwords from encrypted ZIP archives.
Attributes:
passwords: List of passwords to use when bruteforcing encrypted files.
Options:
limit: Maximum number of files to extract.
Defaults to 1000.
password_file: Location of passwords file for zip archives.
Defaults to /etc/strelka/passwords.dat.
"""

def scan(self, data, file, options, expire_at):

jtr_path = options.get('jtr_path', '/jtr/')
tmp_directory = options.get('tmp_file_directory', '/tmp/')
file_limit = options.get('limit', 1000)
password_file = options.get('password_file', '/etc/strelka/passwords.dat')
log_extracted_pws = options.get('log_pws', False)
scanner_timeout = options.get('scanner_timeout', 150)
brute = options.get('brute_force', False)
max_length = options.get('max_length', 5)

self.event['total'] = {'files': 0, 'extracted': 0}

with io.BytesIO(data) as zip_io:
try:
with zipfile.ZipFile(zip_io) as zip_obj:
name_list = zip_obj.namelist()
self.event['total']['files'] = len(name_list)

extracted_pw = crack_zip(self, data, jtr_path, tmp_directory, brute=brute, scanner_timeout=scanner_timeout, max_length=max_length, password_file=password_file)
if not extracted_pw:
self.flags.append('Could not extract password')
return
if log_extracted_pws:
self.event['cracked_password'] = extracted_pw
for i, name in enumerate(name_list):
if not name.endswith('/'):
if self.event['total']['extracted'] >= file_limit:
break

try:
extract_data = zip_obj.read(name, extracted_pw)

if extract_data:
extract_file = strelka.File(
name=name,
source=self.name,
)

for c in strelka.chunk_string(extract_data):
self.upload_to_coordinator(
extract_file.pointer,
c,
expire_at,
)

self.files.append(extract_file)
self.event['total']['extracted'] += 1

except NotImplementedError:
self.flags.append('unsupported_compression')
except RuntimeError:
self.flags.append('runtime_error')
except ValueError:
self.flags.append('value_error')
except zlib.error:
self.flags.append('zlib_error')

except zipfile.BadZipFile:
self.flags.append('bad_zip')
Loading

0 comments on commit 1ed97a0

Please sign in to comment.