mirror of
https://github.com/frida/frida-python
synced 2026-06-08 14:16:17 +00:00
057d901c06
Lots of functions were using *args and **kwargs that were kind of useless with type hinting, so I explicitly named every possible argument and its type. It breaks backward compatibility (sort of) because you can pass nonsense arguments anymore.
307 lines
11 KiB
Python
Executable File
307 lines
11 KiB
Python
Executable File
import codecs
|
|
import hashlib
|
|
import os
|
|
import platform
|
|
import re
|
|
import shutil
|
|
import struct
|
|
import subprocess
|
|
import sys
|
|
import zipfile
|
|
from collections import namedtuple
|
|
from functools import partial
|
|
from html.parser import HTMLParser
|
|
from io import BytesIO
|
|
from urllib.parse import urljoin, urlparse, urlunparse
|
|
from urllib.request import urlopen
|
|
|
|
from setuptools import setup
|
|
from setuptools.command.build_ext import build_ext
|
|
from setuptools.extension import Extension
|
|
|
|
DEFAULT_INDEX_URL = "https://pypi.org/simple/"
|
|
|
|
python_version = sys.version_info[0:2]
|
|
|
|
package_dir = os.path.dirname(os.path.realpath(__file__))
|
|
pkg_info = os.path.join(package_dir, "PKG-INFO")
|
|
in_source_package = os.path.isfile(pkg_info)
|
|
if in_source_package:
|
|
with codecs.open(pkg_info, "r", "utf-8") as f:
|
|
version_line = [line.rstrip("\r") for line in f.read().split("\n") if line.startswith("Version: ")][0]
|
|
frida_version = version_line[9:]
|
|
long_description = None
|
|
else:
|
|
frida_version = os.environ.get("FRIDA_VERSION", None)
|
|
long_description = codecs.open(os.path.join(package_dir, "README.md"), "r", "utf-8").read()
|
|
frida_extension = os.environ.get("FRIDA_EXTENSION", None)
|
|
|
|
index_url_pip_configs = ("global.index-url", "global.extra-index-url")
|
|
|
|
Tag = namedtuple("Tag", ["tagname", "attrs"])
|
|
ParsedUrlInfo = namedtuple("ParsedUrlInfo", ["url", "filename", "major", "minor", "micro"])
|
|
|
|
|
|
class FridaPrebuiltExt(build_ext):
|
|
def build_extension(self, ext):
|
|
target = self.get_ext_fullpath(ext.name)
|
|
target_extension = os.path.splitext(target)[1]
|
|
target_dir = os.path.dirname(target)
|
|
try:
|
|
os.makedirs(target_dir)
|
|
except:
|
|
pass
|
|
|
|
if in_source_package:
|
|
system = platform.system()
|
|
arch = struct.calcsize("P") * 8
|
|
if system == "Windows":
|
|
os_version = "win-amd64" if arch == 64 else "win32"
|
|
elif system == "Darwin":
|
|
if platform.machine() == "x86_64":
|
|
os_version = "macosx-10.9-x86_64"
|
|
else:
|
|
os_version = "macosx-11.0-arm64"
|
|
elif system == "Linux":
|
|
os_name = (
|
|
"android"
|
|
if subprocess.check_output(["uname", "-o"]).decode("utf-8").rstrip() == "Android"
|
|
else "linux"
|
|
)
|
|
machine = platform.machine()
|
|
if machine == "" or "86" in machine:
|
|
arch_name = "x86_64" if arch == 64 else "i686"
|
|
elif os_name == "android" and machine.startswith("armv"):
|
|
arch_name = "armv7l"
|
|
else:
|
|
arch_name = machine
|
|
os_version = f"{os_name}-{arch_name}"
|
|
elif system == "FreeBSD":
|
|
os_version = "freebsd-" + platform.machine()
|
|
else:
|
|
raise NotImplementedError("unsupported OS")
|
|
|
|
egg_path = os.path.expanduser(
|
|
f"~{os.sep}frida-{frida_version}-py{python_version[0]}.{python_version[1]}-{os_version}.egg"
|
|
)
|
|
print("looking for prebuilt extension in home directory, i.e.", egg_path)
|
|
|
|
try:
|
|
with open(egg_path, "rb") as cache:
|
|
egg_data = cache.read()
|
|
except:
|
|
egg_data = None
|
|
|
|
if egg_data is None:
|
|
print("prebuilt extension not found in home directory, will try downloading it")
|
|
|
|
print("querying pypi for available prebuilds")
|
|
# index_url is a url compatible with PEP 503
|
|
index_url = get_index_url().strip()
|
|
index_url = normalize_url(index_url)
|
|
frida_url = urljoin(index_url, "frida/") # slash is necessary here
|
|
timeout = 20
|
|
errmsg = "unable to download it within {} seconds; " f"please download it manually to {egg_path}"
|
|
|
|
print("downloading package list from", frida_url)
|
|
try:
|
|
links_html = urlopen(frida_url, timeout=timeout).read().decode("utf-8")
|
|
except Exception:
|
|
print(errmsg.format(timeout))
|
|
raise
|
|
|
|
parser = PEP503PageParser("frida", frida_version, os_version)
|
|
parser.feed(links_html)
|
|
|
|
if len(parser.urls) == 0:
|
|
raise NotImplementedError(
|
|
"could not find prebuilt Frida extension; " "prebuilds only provided for Python 3.4+"
|
|
)
|
|
|
|
url = parser.urls[0]
|
|
egg_url = urljoin(frida_url, url.url)
|
|
|
|
try:
|
|
print("downloading prebuilt extension from", egg_url)
|
|
timeout = 120 # We'll assume the user has at least 200 kB/s transfer speed.
|
|
egg_data = urlopen(egg_url, timeout=timeout).read()
|
|
except Exception:
|
|
print(errmsg.format(timeout))
|
|
raise
|
|
else:
|
|
egg_url = None
|
|
|
|
egg_file = BytesIO(egg_data)
|
|
|
|
if egg_url is not None:
|
|
print("checking hash")
|
|
check_pep503_hash(egg_file, egg_url)
|
|
|
|
print("extracting prebuilt extension")
|
|
egg_zip = zipfile.ZipFile(egg_file)
|
|
extension_member = [info for info in egg_zip.infolist() if info.filename.endswith(target_extension)][0]
|
|
extension_data = egg_zip.read(extension_member)
|
|
if system == "Windows":
|
|
trailer = b"\x00" if python_version[1] >= 10 else b"\x00\x00"
|
|
extension_data = re.sub(
|
|
b"python[3-9][0-9][0-9]\\.dll\x00",
|
|
"python{}{}.dll".format(*python_version).encode("utf-8") + trailer,
|
|
extension_data,
|
|
)
|
|
with open(target, "wb") as f:
|
|
f.write(extension_data)
|
|
else:
|
|
shutil.copyfile(frida_extension, target)
|
|
|
|
|
|
def get_index_url():
|
|
"""get `index-url` from environment or pip
|
|
Use FRIDA_INDEX_URL environment variable to customize index-url compatible
|
|
with PEP 503.
|
|
"""
|
|
index_url = os.environ.get("FRIDA_INDEX_URL", None)
|
|
if index_url is not None:
|
|
return index_url
|
|
|
|
for config_name in index_url_pip_configs:
|
|
try:
|
|
index_url = get_index_url_from_pip(config_name)
|
|
except (subprocess.CalledProcessError, OSError):
|
|
pass
|
|
else:
|
|
return index_url
|
|
|
|
print(f"using default index URL: {DEFAULT_INDEX_URL}")
|
|
return DEFAULT_INDEX_URL
|
|
|
|
|
|
def get_index_url_from_pip(config_name):
|
|
assert config_name in index_url_pip_configs
|
|
|
|
return subprocess.check_output(
|
|
[sys.executable, "-m", "pip", "config", "get", config_name], stderr=subprocess.PIPE
|
|
).decode("utf-8")
|
|
|
|
|
|
def normalize_url(url):
|
|
parse_result = urlparse(url)
|
|
path = parse_result.path
|
|
if not path.endswith("/"):
|
|
path += "/"
|
|
return urlunparse(
|
|
(
|
|
parse_result.scheme,
|
|
parse_result.netloc,
|
|
path,
|
|
parse_result.params,
|
|
parse_result.query,
|
|
parse_result.fragment,
|
|
)
|
|
)
|
|
|
|
|
|
class PEP503PageParser(HTMLParser):
|
|
def __init__(self, name, version, os_version):
|
|
HTMLParser.__init__(self)
|
|
filename_pattern = (r"^{}\-{}\-py(?P<major>\d+)\.(?P<minor>\d+)(\.(?P<micro>\d+))?-{}.egg$").format(
|
|
*map(re.escape, [name, version, os_version])
|
|
)
|
|
self._filename_pattern = re.compile(filename_pattern)
|
|
|
|
def reset(self):
|
|
HTMLParser.reset(self)
|
|
self._path = []
|
|
self.urls = []
|
|
|
|
def handle_starttag(self, tag, attrs):
|
|
self._path.append(Tag(tag, dict(attrs)))
|
|
|
|
def handle_endtag(self, tag):
|
|
if tag == "a":
|
|
while True:
|
|
if self._path.pop().tagname == tag:
|
|
break
|
|
else:
|
|
if len(self._path) > 0 and self._path[-1].tagname == tag:
|
|
self._path.pop()
|
|
|
|
def handle_data(self, data):
|
|
if not (len(self._path) > 0 and self._path[-1].tagname == "a" and self._path[-1].attrs.get("href")):
|
|
return
|
|
|
|
match = self._filename_pattern.match(data)
|
|
if match is not None:
|
|
self.urls.append(
|
|
ParsedUrlInfo(
|
|
self._path[-1].attrs["href"],
|
|
data,
|
|
*map(lambda g: int(g) if g else None, map(match.group, ["major", "minor", "micro"])),
|
|
)
|
|
)
|
|
|
|
|
|
def check_pep503_hash(bytes_io, url):
|
|
parse_result = urlparse(url)
|
|
fragment = parse_result.fragment
|
|
if fragment == "":
|
|
return
|
|
|
|
hashname, hashvalue = fragment.split("=")
|
|
if hashname not in {"md5", "sha1", "sha224", "sha256", "sha348", "sha512"}:
|
|
raise ValueError(f"Unsupported hash algorithm: {hashname}, hashvalue={hashvalue}")
|
|
|
|
h = hashlib.new(hashname)
|
|
for block in iter(partial(bytes_io.read, 4096), b""): # iterate until EOF
|
|
h.update(block)
|
|
digest = h.hexdigest()
|
|
bytes_io.seek(0) # reset offset
|
|
|
|
if digest == hashvalue:
|
|
return
|
|
else:
|
|
raise ValueError(f"`{hashname}` hash checking failed! Expected: {hashvalue}, but got: {digest}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
setup(
|
|
name="frida",
|
|
version=frida_version,
|
|
description="Dynamic instrumentation toolkit for developers, reverse-engineers, and security researchers",
|
|
long_description=long_description,
|
|
long_description_content_type="text/markdown",
|
|
author="Frida Developers",
|
|
author_email="oleavr@frida.re",
|
|
url="https://frida.re",
|
|
install_requires=["setuptools"],
|
|
python_requires=">=3.7",
|
|
license="wxWindows Library Licence, Version 3.1",
|
|
keywords="frida debugger dynamic instrumentation inject javascript windows macos linux ios iphone ipad android qnx",
|
|
classifiers=[
|
|
"Development Status :: 5 - Production/Stable",
|
|
"Environment :: Console",
|
|
"Environment :: MacOS X",
|
|
"Environment :: Win32 (MS Windows)",
|
|
"Intended Audience :: Developers",
|
|
"Intended Audience :: Science/Research",
|
|
"License :: OSI Approved",
|
|
"Natural Language :: English",
|
|
"Operating System :: MacOS :: MacOS X",
|
|
"Operating System :: Microsoft :: Windows",
|
|
"Operating System :: POSIX :: Linux",
|
|
"Programming Language :: Python :: 3",
|
|
"Programming Language :: Python :: 3.7",
|
|
"Programming Language :: Python :: 3.8",
|
|
"Programming Language :: Python :: 3.9",
|
|
"Programming Language :: Python :: 3.10",
|
|
"Programming Language :: Python :: Implementation :: CPython",
|
|
"Programming Language :: JavaScript",
|
|
"Topic :: Software Development :: Debuggers",
|
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
],
|
|
packages=["frida"],
|
|
package_data={"frida": ["py.typed"]},
|
|
ext_modules=[Extension("_frida", [])],
|
|
cmdclass={"build_ext": FridaPrebuiltExt},
|
|
zip_safe=False,
|
|
)
|