Skip to content

Commit

Permalink
βœ… 🐍 πŸ”… ℹ️ πŸ“š
Browse files Browse the repository at this point in the history
πŸ”… version 1.2.0
🐍 added hachoir to deps
🐍 added pyexiftool to deps
ℹ️ loop_list and loop_dict now only uses string callbacks
ℹ️ minor changes
πŸ“š updated docs
βœ… from_hex method added
βœ… get_mime method added
βœ… get_metadata method added
βœ… embedded_files method added
βœ… get_exif method added
  • Loading branch information
securisecctf committed Nov 30, 2019
1 parent afb43fb commit 92005ce
Show file tree
Hide file tree
Showing 10 changed files with 223 additions and 12 deletions.
3 changes: 3 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ phpserialize = "==1.3"
pycipher = "==0.5.2"
jsonpickle = "==1.2"
pillow = "==6.2.1"
python-magic-bin = "==0.4.14"
hachoir = "==3.1.0"
pyexiftool = "==0.1.1"

[requires]
python_version = "3.7"
38 changes: 34 additions & 4 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion chepy/__version__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
__version__ = "1.1.0" # pragma: no cover
__version__ = "1.2.0" # pragma: no cover
__author__ = "Hapsida @securisec" # pragma: no cover
13 changes: 9 additions & 4 deletions chepy/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@

from .modules.exceptions import PrintException

logging.getLogger().setLevel(logging.INFO)


class ChepyDecorators(object):
"""A class to house all the decorators for Chepy
Expand Down Expand Up @@ -78,6 +76,13 @@ def __init__(self, *data):
#: Holds all the methods that are called/chanined and their args
self._stack = list()

#: Log level
self.log_level = logging.INFO
#: Log format message
self.log_format = "%(levelname)-2s - %(message)s"
logging.getLogger().setLevel(self.log_level)
logging.basicConfig(format=self.log_format)

@property
def state(self):
return self.states[self._current_index]
Expand Down Expand Up @@ -823,7 +828,7 @@ def loop_list(self, callback: str, args: dict = {}):
['5cbe6ca2a66b380aec1449d4ebb0d40ac5e1b92e', '30d75bf34740e8781cd4ec7b122e3efd8448e270']
"""
assert isinstance(self.state, list), "State is not a list"
assert isinstance(callback, str), 'Callback must be a'
assert isinstance(callback, str), "Callback must be a"
hold = []
current_state = self.state
# find the last index that this method was run
Expand Down Expand Up @@ -878,7 +883,7 @@ def loop_dict(self, keys: list, callback: str, args: dict = {}):
"""
assert isinstance(keys, list), "Keys needs to be a list of keys"
assert isinstance(args, dict), "Args needs to be a dict"
assert isinstance(callback, str), 'Callback must be a string'
assert isinstance(callback, str), "Callback must be a string"
hold = {}
current_state = self.state
# find the last index that this method was run
Expand Down
17 changes: 15 additions & 2 deletions chepy/modules/dataformat.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@


class DataFormat(ChepyCore):

@ChepyDecorators.call_stack
def list_to_str(self, join_by=" "):
"""Join an array by `join_by`
Expand Down Expand Up @@ -50,7 +49,7 @@ def str_list_to_list(self):
return self

@ChepyDecorators.call_stack
def join_list(self, by: str=""):
def join_list(self, by: str = ""):
"""Join a list with specified character
Args:
Expand Down Expand Up @@ -326,6 +325,20 @@ def to_hex(self):
self.state = binascii.hexlify(self._convert_to_bytes())
return self

@ChepyDecorators.call_stack
def from_hex(self):
"""Convert a non delimited hex string to string
Returns:
Chepy: The Chepy object.
Examples:
>>> Chepy("414141").from_hex().out()
b"AAA"
"""
self.state = binascii.unhexlify(self._convert_to_bytes())
return self

@ChepyDecorators.call_stack
def hex_to_int(self):
"""Converts hex into its intiger represantation
Expand Down
118 changes: 117 additions & 1 deletion chepy/modules/forensics.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,121 @@
import io
import os
import logging
import pathlib
import tempfile
import pprint

import exiftool
from hachoir.core.log import Logger
from hachoir.parser import createParser
from hachoir.metadata import extractMetadata
from hachoir.subfile.search import SearchSubfile
from hachoir.stream import FileInputStream
from hachoir.metadata.metadata_item import QUALITY_BEST
import hachoir.core.config as hachoir_config

from ..core import ChepyCore, ChepyDecorators


class Forensics(ChepyCore):
pass
hachoir_config.quiet = True

def _temp_file(self) -> str:
"""Get a random temporary file. os.urandom is used here
because of permission issues using tempfile on Windows.
The state is then saved in this temp file.
Returns:
str: cross platform temporary file
"""
temp_file = str(pathlib.Path(tempfile.gettempdir()) / os.urandom(24).hex())
with open(temp_file, "wb") as f:
f.write(self._convert_to_bytes())
return temp_file

@ChepyDecorators.call_stack
def get_mime(self, set_state: bool = False):
"""Detect the file type based on magic.
Args:
set_state (bool, optional): Save the output to state. Defaults to False.
Returns:
Chepy: The Chepy object.
"""
filename = self._temp_file()
parser = createParser(filename)
if not parser:
mimetype = "text/plain"
logging.info(mimetype)
else:
mimetype = str(parser.mime_type)
logging.info(mimetype)
if set_state:
self.state = mimetype
pathlib.Path(filename).unlink()
return self

@ChepyDecorators.call_stack
def get_metadata(self, set_state: bool = False):
"""Get metadata from a file
Args:
set_state (bool, optional): Save the output to state. Defaults to False.
Returns:
Chepy: The Chepy object.
"""
filename = self._temp_file()
filename, realname = filename, filename
parser = createParser(filename, realname)
if not parser: # pragma: no cover
logging.warning("Unable to parse file")

metadata = extractMetadata(parser, quality=QUALITY_BEST)

if metadata is not None:
meta = metadata.exportDictionary()["Metadata"]
if set_state:
self.state = meta
else: # pragma: no cover
logging.info(pprint.pformat(meta))
pathlib.Path(filename).unlink()
return self

@ChepyDecorators.call_stack
def embedded_files(self, extract_path: str = None):
"""Search for embedded files and extract them
This method does not change the state.
Args:
extract_path (str, optional): Path to extract files to. Defaults to None.
Returns:
Chepy: The Chepy object.
"""
filename = self._temp_file()
inp = FileInputStream(filename)
subfile = SearchSubfile(inp)
if extract_path is not None: # pragma: no cover
subfile.setOutput(extract_path)
subfile.loadParsers()
subfile.main()
pathlib.Path(filename).unlink()
return self

@ChepyDecorators.call_stack
def get_exif(self): # pragma: no cover
"""Extract EXIF data from a file
Returns:
Chepy: The Chepy object.
"""
filename = self._temp_file()
with exiftool.ExifTool() as et:
exif = et.get_metadata(filename)
if exif:
self.state = exif
pathlib.Path(filename).unlink()
return self
5 changes: 5 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ Either of these methods can be used to load a file info chepy. These methods loa
c = Chepy("/path/to/some/file.txt").read_file()
```

```eval_rst
.. warning::
Chepy will read the entire file to memory, so working with very large files will really slow things down.
```

#### [load_dir](./chepy.html#chepy.Chepy.load_dir)
The `load_dir` method is used to load the entire contents of a directory into Chepy. This method optionally takes a pattern argument to specify which files to load. A state is created for each file that matches the pattern in the directory. To load recursively, the pattern `**/*` can be used.
```python
Expand Down
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ decorator
docstring-parser
emoji
fire==0.2.1
hachoir==3.1.0
hashid
hexdump
jsonpath-rw
Expand All @@ -19,6 +20,7 @@ pydash
pyjwt
pyOpenSSL
pyperclip
PyExifTool
PyYAML
regex
requests
Expand Down
4 changes: 4 additions & 0 deletions tests/test_dataformat.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ def test_to_hex():
assert Chepy("AAA").to_hex().out().decode() == "414141"


def test_from_hex():
assert Chepy("414141").from_hex().out().decode() == "AAA"


def test_hex_to_int():
assert Chepy("0x123").hex_to_int().output == 291
assert Chepy("123").hex_to_int().output == 291
Expand Down
33 changes: 33 additions & 0 deletions tests/test_forensics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from chepy import Chepy


def test_detect_file_type():
Chepy("tests/files/hello").load_file().get_mime(
set_state=True
).o == "application/x-executable"
assert True
Chepy("tests/files/encoding").load_file().get_mime(
set_state=True
).o == "text/plain"
assert True


def test_get_metadata():
assert Chepy("logo.png").load_file().get_metadata(set_state=True).o == {
"Bits/pixel": "32",
"Compression": "deflate",
"Compression rate": "117.2x",
"Creation date": "2019-11-19 02:46:07",
"Endianness": "Big endian",
"Image DPI height": "3780 DPI",
"Image DPI width": "3780 DPI",
"Image height": "1080 pixels",
"Image width": "1080 pixels",
"MIME type": "image/png",
"Pixel format": "RGBA",
}


def test_embedded():
Chepy("logo.png").load_file().embedded_files()
assert True

0 comments on commit 92005ce

Please sign in to comment.