diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml
new file mode 100644
index 0000000..5fe3fea
--- /dev/null
+++ b/.github/workflows/python-publish.yml
@@ -0,0 +1,70 @@
+# This workflow will upload a Python Package to PyPI when a release is created
+# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
+
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+name: Upload Python Package
+
+on:
+ release:
+ types: [published]
+
+permissions:
+ contents: read
+
+jobs:
+ release-build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-python@v5
+ with:
+ python-version: "3.x"
+
+ - name: Build release distributions
+ run: |
+ # NOTE: put your own distribution build steps here.
+ python -m pip install poetry
+ python -m poetry build
+
+ - name: Upload distributions
+ uses: actions/upload-artifact@v4
+ with:
+ name: release-dists
+ path: dist/
+
+ pypi-publish:
+ runs-on: ubuntu-latest
+ needs:
+ - release-build
+ permissions:
+ # IMPORTANT: this permission is mandatory for trusted publishing
+ id-token: write
+
+ # Dedicated environments with protections for publishing are strongly recommended.
+ # For more information, see: https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#deployment-protection-rules
+ environment:
+ name: pypi
+ # OPTIONAL: uncomment and update to include your PyPI project URL in the deployment status:
+ # url: https://pypi.org/p/YOURPROJECT
+ #
+ # ALTERNATIVE: if your GitHub Release name is the PyPI project version string
+ # ALTERNATIVE: exactly, uncomment the following line instead:
+ # url: https://pypi.org/project/YOURPROJECT/${{ github.event.release.name }}
+
+ steps:
+ - name: Retrieve release distributions
+ uses: actions/download-artifact@v4
+ with:
+ name: release-dists
+ path: dist/
+
+ - name: Publish release distributions to PyPI
+ uses: pypa/gh-action-pypi-publish@release/v1
+ with:
+ packages-dir: dist/
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0a19790
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,174 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# UV
+# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+#uv.lock
+
+# poetry
+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+# in version control.
+# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
+.pdm.toml
+.pdm-python
+.pdm-build/
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
+
+# Ruff stuff:
+.ruff_cache/
+
+# PyPI configuration file
+.pypirc
diff --git a/README.md b/README.md
index b97fdf1..e6503d4 100644
--- a/README.md
+++ b/README.md
@@ -1,26 +1,44 @@
-# fridump3
+# fridump3.1
+
Fridump is an open source memory dumping tool, primarily aimed to penetration testers and developers. Fridump is using the Frida framework to dump accessible memory addresses from any platform supported. It can be used from a Windows, Linux or Mac OS X system to dump the memory of an iOS, Android or Windows application.
-This project is based on the following project: [https://github.com/Nightbringer21/fridump](https://github.com/Nightbringer21/fridump) and the pending PR concerning the python3 support (especially from [georgepetz](https://github.com/georgepetz) . Additionally I added the network support in addition to the USB support.
+## Requirements
+
+- **Python** 3.7+
+- **Frida** 17+
+
+## Installation
+
+Simply run one of the following commands:
+> :warning: pipx is recommended for system or user wide installations
+```
+pipx install fridump3
+pip install fridump3
+```
-FYI: I will destroy this repo is the Fridump author will integrate the pending PR concerning Python3 support.
+## Usage
-Usage
---
```
-usage: fridump [-h] [-o dir] [-u] [-H HOST] [-v] [-r] [-s] [--max-size bytes] process
+usage: fridump3 [-h] [-o dir] [-u] [-H HOST] [-r] [-s] [--max-size bytes]
+ [--log-level {none,debug,info,warning,error,critical}]
+ [--log-filename LOG_FILENAME]
+ process
positional arguments:
- process the process that you will be injecting to
+ process the process name, not package name that you will be injecting to
-optional arguments:
+options:
-h, --help show this help message and exit
- -o dir, --out dir provide full output directory path. (def: 'dump')
+ -o, --out dir provide full output directory path. (def: "dump")
-u, --usb device connected over usb
- -H HOST, --host HOST device connected over IP
- -v, --verbose verbose
+ -H, --host HOST device connected over IP
-r, --read-only dump read-only parts of memory. More data, more errors
-s, --strings run strings on all dump files. Saved in output dir.
--max-size bytes maximum size of dump file in bytes (def: 20971520)
+ --log-level, -l {none,debug,info,warning,error,critical}
+ level of debug you wish to display.
+ --log-filename LOG_FILENAME
+ output file used to store logs
```
diff --git a/dumper.py b/dumper.py
deleted file mode 100644
index ced3637..0000000
--- a/dumper.py
+++ /dev/null
@@ -1,39 +0,0 @@
-import os
-import logging
-
-# Reading bytes from session and saving it to a file
-
-
-def dump_to_file(agent, base, size, error, directory):
- try:
- filename = str(base) + '_dump.data'
- dump = agent.read_memory(base, size)
- f = open(os.path.join(directory, filename), 'wb')
- f.write(dump)
- f.close()
- return error
- except Exception as e:
- logging.debug(str(e))
- print("Oops, memory access violation!")
- return error
-
-# Read bytes that are bigger than the max_size value, split them into chunks and save them to a file
-
-def splitter(agent,base,size,max_size,error,directory):
- times = size//max_size
- diff = size % max_size
- if diff == 0:
- logging.debug("Number of chunks:"+str(times+1))
- else:
- logging.debug("Number of chunks:"+str(times))
- global cur_base
- cur_base = int(base,0)
-
- for time in range(times):
- # logging.debug("Save bytes: "+str(cur_base)+" till "+str(hex(cur_base+max_size)))
- dump_to_file(agent, cur_base, max_size, error, directory)
- cur_base = cur_base + max_size
-
- if diff != 0:
- # logging.debug("Save bytes: "+str(hex(cur_base))+" till "+str(hex(cur_base+diff)))
- dump_to_file(agent, cur_base, diff, error, directory)
diff --git a/fridump3.py b/fridump3.py
deleted file mode 100644
index f113090..0000000
--- a/fridump3.py
+++ /dev/null
@@ -1,165 +0,0 @@
-import textwrap
-import frida
-import os
-import sys
-import frida.core
-import dumper
-import utils
-import argparse
-import logging
-
-logo = """
- ______ _ _
- | ___| (_) | |
- | |_ _ __ _ __| |_ _ _ __ ___ _ __
- | _| '__| |/ _` | | | | '_ ` _ \| '_ \\
- | | | | | | (_| | |_| | | | | | | |_) |
- \_| |_| |_|\__,_|\__,_|_| |_| |_| .__/
- | |
- |_|
- """
-
-
-# Main Menu
-def MENU():
- parser = argparse.ArgumentParser(
- prog='fridump',
- formatter_class=argparse.RawDescriptionHelpFormatter,
- description=textwrap.dedent(""))
-
- parser.add_argument(
- 'process', help='the process that you will be injecting to')
- parser.add_argument('-o', '--out', type=str, help='provide full output directory path. (def: \'dump\')',
- metavar="dir")
- parser.add_argument('-u', '--usb', action='store_true',
- help='device connected over usb')
- parser.add_argument('-H', '--host', type=str,
- help='device connected over IP')
- parser.add_argument('-v', '--verbose', action='store_true', help='verbose')
- parser.add_argument('-r', '--read-only', action='store_true',
- help="dump read-only parts of memory. More data, more errors")
- parser.add_argument('-s', '--strings', action='store_true',
- help='run strings on all dump files. Saved in output dir.')
- parser.add_argument('--max-size', type=int, help='maximum size of dump file in bytes (def: 20971520)',
- metavar="bytes")
- args = parser.parse_args()
- return args
-
-
-print(logo)
-
-arguments = MENU()
-
-# Define Configurations
-APP_NAME = utils.normalize_app_name(appName=arguments.process)
-DIRECTORY = ""
-USB = arguments.usb
-NETWORK=False
-DEBUG_LEVEL = logging.INFO
-STRINGS = arguments.strings
-MAX_SIZE = 20971520
-PERMS = 'rw-'
-
-if arguments.host is not None:
- NETWORK=True
- IP=arguments.host
-
-if arguments.read_only:
- PERMS = 'r--'
-
-if arguments.verbose:
- DEBUG_LEVEL = logging.DEBUG
-logging.basicConfig(format='%(levelname)s:%(message)s', level=DEBUG_LEVEL)
-
-
-# Start a new Session
-session = None
-try:
- if USB:
- session = frida.get_usb_device().attach(APP_NAME)
- elif NETWORK:
- session = frida.get_device_manager().add_remote_device(IP).attach(APP_NAME)
- else:
- session = frida.attach(APP_NAME)
-except Exception as e:
- print(str(e))
- sys.exit()
-
-
-# Selecting Output directory
-if arguments.out is not None:
- DIRECTORY = arguments.out
- if os.path.isdir(DIRECTORY):
- print("Output directory is set to: " + DIRECTORY)
- else:
- print("The selected output directory does not exist!")
- sys.exit(1)
-
-else:
- print("Current Directory: " + str(os.getcwd()))
- DIRECTORY = os.path.join(os.getcwd(), "dump")
- print("Output directory is set to: " + DIRECTORY)
- if not os.path.exists(DIRECTORY):
- print("Creating directory...")
- os.makedirs(DIRECTORY)
-
-mem_access_viol = ""
-
-print("Starting Memory dump...")
-
-def on_message(message, data):
- print("[on_message] message:", message, "data:", data)
-
-
-script = session.create_script("""'use strict';
-
-rpc.exports = {
- enumerateRanges: function (prot) {
- return Process.enumerateRangesSync(prot);
- },
- readMemory: function (address, size) {
- return Memory.readByteArray(ptr(address), size);
- }
-};
-""")
-script.on("message", on_message)
-script.load()
-
-agent = script.exports_sync
-ranges = agent.enumerate_ranges(PERMS)
-
-if arguments.max_size is not None:
- MAX_SIZE = arguments.max_size
-
-i = 0
-l = len(ranges)
-
-# Performing the memory dump
-for range in ranges:
- logging.debug("Base Address: " + str(range["base"]))
- logging.debug("")
- logging.debug("Size: " + str(range["size"]))
- if range["size"] > MAX_SIZE:
- logging.debug("Too big, splitting the dump into chunks")
- mem_access_viol = dumper.splitter(
- agent, range["base"], range["size"], MAX_SIZE, mem_access_viol, DIRECTORY)
- continue
- mem_access_viol = dumper.dump_to_file(
- agent, range["base"], range["size"], mem_access_viol, DIRECTORY)
- i += 1
- utils.printProgress(i, l, prefix='Progress:', suffix='Complete', bar=50)
-
-# Run Strings if selected
-
-if STRINGS:
- files = os.listdir(DIRECTORY)
- i = 0
- l = len(files)
- print("Running strings on all files:")
- for f1 in files:
- utils.strings(f1, DIRECTORY)
- i += 1
- utils.printProgress(i, l, prefix='Progress:',
- suffix='Complete', bar=50)
-print("Finished!")
-#raw_input('Press Enter to exit...')
diff --git a/fridump3/__main__.py b/fridump3/__main__.py
new file mode 100644
index 0000000..4dfcc87
--- /dev/null
+++ b/fridump3/__main__.py
@@ -0,0 +1,12 @@
+#!/usr/bin/env python3
+
+# Local imports
+from fridump3 import console
+
+
+def main() -> None:
+ console.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/fridump3/console.py b/fridump3/console.py
new file mode 100644
index 0000000..687a32e
--- /dev/null
+++ b/fridump3/console.py
@@ -0,0 +1,235 @@
+import argparse
+import sys
+import textwrap
+from pathlib import Path
+
+import frida
+import frida.core
+from loguru import logger
+
+from fridump3.src import dumper, utils
+
+logo = r"""
+ ______ _ _
+ | ___| (_) | |
+ | |_ _ __ _ __| |_ _ _ __ ___ _ __
+ | _| '__| |/ _` | | | | '_ ` _ \| '_ \\
+ | | | | | | (_| | |_| | | | | | | |_) |
+ \_| |_| |_|\__,_|\__,_|_| |_| |_| .__/
+ | |
+ |_|
+ """
+
+
+def set_log_level(level, output_file):
+ """Sets the log level and log file.
+
+ Args:
+ level -- the log level to display (default "info")
+ output_file -- the file used to output logs, including commands
+ """
+ logger.remove() # Remove any default handlers
+ if level.upper() != "NONE":
+ LOG_FORMAT = "{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {message}"
+ logger.add(sys.stderr, format=LOG_FORMAT, level=level.upper())
+ if output_file:
+ logger.debug(f"Initializing logfile")
+ try:
+ logger.add(
+ output_file,
+ rotation="10 MB",
+ retention="30 days",
+ level=level.upper(),
+ format=LOG_FORMAT,
+ )
+ except PermissionError:
+ logger.critical(
+ f"Permission denied to write to {output_file}, continuing without logs"
+ )
+ logger.debug(f"Logger initialized to {level.upper()}")
+
+
+def MENU():
+ parser = argparse.ArgumentParser(
+ prog="fridump3",
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ description=textwrap.dedent(""),
+ )
+
+ parser.add_argument("process", help="name of the process to dump memory from")
+ parser.add_argument(
+ "-o",
+ "--out",
+ type=str,
+ help='provide full output directory path. (def: "dump").',
+ metavar="DUMP_DIRECTORY",
+ )
+ parser.add_argument(
+ "-u", "--usb", action="store_true", help="device connected over usb."
+ )
+ parser.add_argument("-H", "--host", type=str, help="device connected over IP.")
+ parser.add_argument(
+ "-r",
+ "--read-only",
+ action="store_true",
+ help="dump read-only parts of memory. More data, more errors.",
+ )
+ parser.add_argument(
+ "-s",
+ "--strings",
+ action="store_true",
+ help="run strings on all dump files. Saved in output dir.",
+ )
+ parser.add_argument(
+ "--max-size",
+ type=int,
+ help="maximum size of dump file in bytes (def: 20971520)",
+ metavar="bytes",
+ )
+ parser.add_argument(
+ "--log-level",
+ "-l",
+ choices=["none", "debug", "info", "warning", "error", "critical"],
+ default="info",
+ help="level of debug you wish to display.",
+ )
+ parser.add_argument(
+ "--log-filename",
+ default=None,
+ help="output file used to store logs.",
+ )
+ args = parser.parse_args()
+ return args
+
+@logger.catch
+def run() -> None:
+ print(logo)
+
+ arguments = MENU()
+
+ # Define configurations
+ APP_NAME = utils.normalize_app_name(appName=arguments.process)
+ DIRECTORY = ""
+ USB = arguments.usb
+ IP = None
+ STRINGS = arguments.strings
+ MAX_SIZE = 20971520
+ PERMS = "rw-"
+
+ if arguments.host is not None:
+ IP = arguments.host
+
+ if arguments.read_only:
+ PERMS = "r--"
+
+ set_log_level(arguments.log_level, arguments.log_filename)
+
+ # Start a new session
+ session = None
+ try:
+ if USB:
+ session = frida.get_usb_device().attach(APP_NAME)
+ elif IP:
+ session = frida.get_device_manager().add_remote_device(IP).attach(APP_NAME)
+ else:
+ session = frida.attach(APP_NAME)
+ except Exception as e:
+ logger.error(str(e))
+ sys.exit(1)
+
+ # Selecting output directory
+ if arguments.out is not None:
+ DIRECTORY = Path(arguments.out)
+ if DIRECTORY.is_dir():
+ logger.info(f"Output directory is set to: {DIRECTORY}")
+ else:
+ current_dir = Path.cwd()
+ logger.info(f"Current directory: {current_dir}")
+ DIRECTORY = current_dir / "dump"
+ logger.info(f"Output directory is set to: {DIRECTORY}")
+
+ if not DIRECTORY.exists():
+ logger.info(f"Creating directory {DIRECTORY}")
+ try:
+ DIRECTORY.mkdir(parents=True, exist_ok=True)
+ except PermissionError:
+ logger.error(f"Permission denied to create {DIRECTORY}")
+ sys.exit(1)
+
+ mem_access_viol = ""
+
+ logger.info("Starting memory dump")
+
+ def on_message(message, data):
+ print("[on_message] message:", message, "data:", data)
+
+ script = session.create_script(
+ """'use strict';
+
+ rpc.exports = {
+ enumerateRanges: async function (prot) {
+ const ranges = await Process.enumerateRanges(prot);
+ return ranges;
+ },
+ readMemory: function (address, size) {
+ return ptr(address).readByteArray(size);
+ }
+ };
+ """
+ )
+ script.on("message", on_message)
+ script.load()
+
+ agent = script.exports_sync
+ ranges = agent.enumerate_ranges(PERMS)
+
+ if arguments.max_size is not None:
+ MAX_SIZE = arguments.max_size
+
+ i = 0
+ l = len(ranges)
+
+ try:
+ # Performing the memory dump
+ for range in ranges:
+ logger.debug(f"Base address: {str(range["base"])}")
+ logger.debug(f"Size: {str(range["size"])}")
+ if range["size"] > MAX_SIZE:
+ logger.debug("Size is too big, splitting the dump into chunks")
+ mem_access_viol = dumper.splitter(
+ agent,
+ range["base"],
+ range["size"],
+ MAX_SIZE,
+ mem_access_viol,
+ DIRECTORY,
+ )
+ continue
+ mem_access_viol = dumper.dump_to_file(
+ agent, range["base"], range["size"], mem_access_viol, DIRECTORY
+ )
+ i += 1
+ utils.printProgress(i, l, prefix="Progress:", suffix="Complete", bar=50)
+ print("")
+
+ # Run Strings if selected
+ if STRINGS:
+ files = list(
+ DIRECTORY.iterdir()
+ ) # Get all items in the directory as Path objects
+ total_files = len(files)
+
+ logger.info("Running strings on all files")
+
+ for i, file in enumerate(files, start=1):
+ utils.strings(file.name, DIRECTORY) # Pass only the filename if needed
+ utils.printProgress(
+ i, total_files, prefix="Progress:", suffix="Complete", bar=50
+ )
+ print("")
+ except KeyboardInterrupt:
+ # Helps removing the current progress bar before printing the error message
+ print(" " * 80 + "\r", end="\n", flush=True)
+ logger.success(f"Exiting gracefully.")
+ sys.exit(0)
+ logger.success("Finished!")
diff --git a/fridump3/src/dumper.py b/fridump3/src/dumper.py
new file mode 100644
index 0000000..ad693d4
--- /dev/null
+++ b/fridump3/src/dumper.py
@@ -0,0 +1,45 @@
+import sys
+from pathlib import Path
+
+from loguru import logger
+
+
+def dump_to_file(agent, base, size, error, directory):
+ """Reading bytes from session and saving it to a file"""
+ try:
+ filename = str(base) + "_dump.data"
+ dump = agent.read_memory(base, size)
+ file_path = Path(directory) / filename
+ with file_path.open("wb") as f:
+ f.write(dump)
+ return error
+ except PermissionError:
+ logger.error(f"Permission denied to write to {directory}")
+ sys.exit(1)
+ except Exception as e:
+ # Helps removing the current progress bar before printing the error message
+ print(" " * 80 + "\r", end="", flush=True)
+ logger.debug(str(e))
+ logger.warning("Oops, memory access violation!")
+ return error
+
+
+def splitter(agent, base, size, max_size, error, directory):
+ """Read bytes that are bigger than the max_size value, split them into chunks and save them to a file"""
+ times = size // max_size
+ diff = size % max_size
+ if diff == 0:
+ logger.debug("Number of chunks:" + str(times + 1))
+ else:
+ logger.debug("Number of chunks:" + str(times))
+ global cur_base
+ cur_base = int(base, 0)
+
+ for time in range(times):
+ logger.debug(f"Save bytes: {str(cur_base)} till {str(hex(cur_base+max_size))}")
+ dump_to_file(agent, cur_base, max_size, error, directory)
+ cur_base = cur_base + max_size
+
+ if diff != 0:
+ logger.debug(f"Save bytes: {str(cur_base)} till {str(hex(cur_base+diff))}")
+ dump_to_file(agent, cur_base, diff, error, directory)
diff --git a/fridump3/src/utils.py b/fridump3/src/utils.py
new file mode 100644
index 0000000..f694603
--- /dev/null
+++ b/fridump3/src/utils.py
@@ -0,0 +1,34 @@
+import re
+from pathlib import Path
+
+from loguru import logger
+
+
+def printProgress(times, total, prefix="", suffix="", decimals=2, bar=100):
+ """Print a progress bar"""
+ filled = int(round(bar * times / float(total)))
+ percents = round(100.00 * (times / float(total)), decimals)
+ bar = "#" * filled + "-" * (bar - filled)
+ print(f"{prefix} [{bar}] {percents}% {suffix} ", end="\r", flush=True)
+
+
+def strings(filename, directory, min=4):
+ """A very basic implementations of Strings"""
+ strings_file = Path(directory) / "strings.txt"
+ path = Path(directory) / filename
+ with open(path, encoding="Latin-1") as infile:
+ str_list = re.findall(r"[A-Za-z0-9/\-:;.,_$%'!()[\]<> \#]+", infile.read())
+ with open(strings_file, "a") as st:
+ for string in str_list:
+ if len(string) > min:
+ logger.debug(string)
+ st.write(f"{string}\n")
+
+
+def normalize_app_name(appName=str):
+ """Normalize the name of application works better on frida"""
+ try:
+ appName = int(appName)
+ except ValueError:
+ pass
+ return appName
diff --git a/poetry.lock b/poetry.lock
new file mode 100644
index 0000000..bd5467c
--- /dev/null
+++ b/poetry.lock
@@ -0,0 +1,93 @@
+# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand.
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+description = "Cross-platform colored terminal text."
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+groups = ["main"]
+markers = "sys_platform == \"win32\""
+files = [
+ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
+]
+
+[[package]]
+name = "frida"
+version = "16.6.6"
+description = "Dynamic instrumentation toolkit for developers, reverse-engineers, and security researchers"
+optional = false
+python-versions = ">=3.7"
+groups = ["main"]
+files = [
+ {file = "frida-16.6.6-cp37-abi3-macosx_10_13_x86_64.whl", hash = "sha256:b00a2b5f877cbb4955e72413c1f620dc75498a93d4d9a01f663053f2cfb94992"},
+ {file = "frida-16.6.6-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:dcf59869117f4f6eaa6514555fa0a6499b5efa4e095a5785979b057841037f04"},
+ {file = "frida-16.6.6-cp37-abi3-manylinux1_i686.whl", hash = "sha256:9dc05717c8079cad31268a9714cc6966931e7400487cfa0c19e6c077dea129ac"},
+ {file = "frida-16.6.6-cp37-abi3-manylinux1_x86_64.whl", hash = "sha256:288d99fb774bd04b229977bf112878674e851dbb6e14b528aeb04ec085b10a4f"},
+ {file = "frida-16.6.6-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:d74bd8de1aebd0acae49c89fad6ace2dddf5852df236b805dc83d5c524745e59"},
+ {file = "frida-16.6.6-cp37-abi3-manylinux2014_armv7l.whl", hash = "sha256:853e1e786b24b726ea0e91e6855ca879271bf3dd4265085c019c19d716f489fd"},
+ {file = "frida-16.6.6-cp37-abi3-manylinux_2_17_aarch64.whl", hash = "sha256:ab9ca0e41e89cc881dbaa33c724066c9839c1b92a06b9f92fa9ab00dc31d62e0"},
+ {file = "frida-16.6.6-cp37-abi3-manylinux_2_17_armv7l.whl", hash = "sha256:47aaacb1246a0f8587a1f4083761189aea39c51ce545cf998076814172d31770"},
+ {file = "frida-16.6.6-cp37-abi3-manylinux_2_5_i686.whl", hash = "sha256:9ba5c342fc7a46599a4835e2fb3693342ec732963a0518b2d884d951bb5ea5f4"},
+ {file = "frida-16.6.6-cp37-abi3-manylinux_2_5_x86_64.whl", hash = "sha256:f27b230dd243ab3708f4051bcd9567cfe02893a706c5c3fe1573ccc2ef5a2969"},
+ {file = "frida-16.6.6-cp37-abi3-win32.whl", hash = "sha256:44b4044625afa60f0799fedecfe0a25dab610556a469151dc02569555d0436a9"},
+ {file = "frida-16.6.6-cp37-abi3-win_amd64.whl", hash = "sha256:12d0117ea035bfcb5e4d685e88af02ce815169945702192ea9c21c0ebba42706"},
+ {file = "frida-16.6.6.tar.gz", hash = "sha256:384572cd21185e6b152628216e83fcddbb869572ad55609582580494ce8b6c2a"},
+]
+
+[package.dependencies]
+typing_extensions = {version = "*", markers = "python_version < \"3.11\""}
+
+[[package]]
+name = "loguru"
+version = "0.7.3"
+description = "Python logging made (stupidly) simple"
+optional = false
+python-versions = "<4.0,>=3.5"
+groups = ["main"]
+files = [
+ {file = "loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c"},
+ {file = "loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6"},
+]
+
+[package.dependencies]
+colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""}
+win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
+
+[package.extras]
+dev = ["Sphinx (==8.1.3)", "build (==1.2.2)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.5.0)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.13.0)", "mypy (==v1.4.1)", "myst-parser (==4.0.0)", "pre-commit (==4.0.1)", "pytest (==6.1.2)", "pytest (==8.3.2)", "pytest-cov (==2.12.1)", "pytest-cov (==5.0.0)", "pytest-cov (==6.0.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.1.0)", "sphinx-rtd-theme (==3.0.2)", "tox (==3.27.1)", "tox (==4.23.2)", "twine (==6.0.1)"]
+
+[[package]]
+name = "typing-extensions"
+version = "4.7.1"
+description = "Backported and Experimental Type Hints for Python 3.7+"
+optional = false
+python-versions = ">=3.7"
+groups = ["main"]
+markers = "python_version < \"3.11\""
+files = [
+ {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"},
+ {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"},
+]
+
+[[package]]
+name = "win32-setctime"
+version = "1.2.0"
+description = "A small Python utility to set file creation time on Windows"
+optional = false
+python-versions = ">=3.5"
+groups = ["main"]
+markers = "sys_platform == \"win32\""
+files = [
+ {file = "win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390"},
+ {file = "win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0"},
+]
+
+[package.extras]
+dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"]
+
+[metadata]
+lock-version = "2.1"
+python-versions = ">=3.7,<4.0"
+content-hash = "ad043c15520028ff97145b4da3fd60cfe2b377ffdb5f81e04c46b38758de6d97"
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..3ed8f7b
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,21 @@
+[project]
+name = "fridump3"
+version = "1.0.0"
+description = "A universal memory dumper using Frida for Python 3."
+license = {text = "GPL-3.0-or-later"}
+readme = "README.md"
+requires-python = ">=3.7,<4.0"
+dependencies = [
+ "frida",
+ "loguru"
+]
+
+[project.urls]
+repository = "https://github.com/rootbsd/fridump3"
+
+[project.scripts]
+fridump3 = 'fridump3.console:run'
+
+[build-system]
+requires = ["poetry-core>=2.0.0,<3.0.0"]
+build-backend = "poetry.core.masonry.api"
diff --git a/utils.py b/utils.py
deleted file mode 100644
index 13784fa..0000000
--- a/utils.py
+++ /dev/null
@@ -1,39 +0,0 @@
-import sys
-import string
-import logging
-import os
-import re
-
-# Progress bar function
-
-
-def printProgress(times, total, prefix='', suffix='', decimals=2, bar=100):
- filled = int(round(bar * times / float(total)))
- percents = round(100.00 * (times / float(total)), decimals)
- bar = '#' * filled + '-' * (bar - filled)
- sys.stdout.write('%s [%s] %s%s %s\r' %
- (prefix, bar, percents, '%', suffix)),
- sys.stdout.flush()
- if times == total:
- print("\n")
-
-
-# A very basic implementations of Strings
-def strings(filename, directory, min=4):
- strings_file = os.path.join(directory, "strings.txt")
- path = os.path.join(directory, filename)
- with open(path, encoding='Latin-1') as infile:
- str_list = re.findall("[A-Za-z0-9/\-:;.,_$%'!()[\]<> \#]+", infile.read())
- with open(strings_file, "a") as st:
- for string in str_list:
- if len(string) > min:
- logging.debug(string)
- st.write(string + "\n")
-
-# Normalize thw namo of application works better on frida
-def normalize_app_name(appName=str):
- try:
- appName = int(appName)
- except ValueError:
- pass
- return appName