diff --git a/docs/conf.py b/docs/conf.py index cbe8dd8..5fd43f4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,7 +11,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. import os import sys -from subprocess import Popen, PIPE +from subprocess import PIPE, Popen DOCS = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, os.path.abspath(os.path.join(DOCS, "..", "src"))) diff --git a/pyproject.toml b/pyproject.toml index 762a9eb..3ce1199 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,8 @@ dependencies = [ extra-dependencies = [ "rich~=12.5", "black~=22.6", - "isort~=5.10", + "isort~=5.10", + "ward~=0.66.1b0" ] [tool.hatch.envs.dev.scripts] @@ -59,4 +60,4 @@ include = "^/src" extend-exclude = "^/src/silver/pb" [tool.isort] -profile = "black" \ No newline at end of file +profile = "black" diff --git a/scripts/protobufgen.py b/scripts/protobufgen.py index d47428f..a88d8c0 100644 --- a/scripts/protobufgen.py +++ b/scripts/protobufgen.py @@ -1,10 +1,10 @@ import os -import pkg_resources from pathlib import Path + +import pkg_resources from grpc_tools import protoc from rich.console import Console - console = Console(log_time=False, log_path=False) ROOT_DIR = Path(__file__).parents[1] os.chdir(ROOT_DIR) diff --git a/src/sliver/client.py b/src/sliver/client.py index c7b24d9..129fd06 100644 --- a/src/sliver/client.py +++ b/src/sliver/client.py @@ -16,7 +16,6 @@ import logging -from telnetlib import SE from typing import AsyncGenerator, Dict, Iterable, List, Optional, Union import grpc @@ -235,8 +234,8 @@ class SliverClient(BaseClient): :type name: str :param timeout: gRPC timeout, defaults to 60 seconds :type timeout: int, optional - :return: Updated protobuf session object - :rtype: client_pb2.Session + :return: None + :rtype: None """ rename_req = client_pb2.RenameReq(SessionID=session_id, Name=name) await self._stub.Rename(rename_req, timeout=timeout) @@ -266,8 +265,23 @@ class SliverClient(BaseClient): ) return list(beacons.Beacons) - async def rm_beacon(self, beacon_id: str, timeout=TIMEOUT) -> None: - """Remove a beacon + async def rename_beacon(self, beacon_id: str, name: str, timeout=TIMEOUT) -> None: + """Rename a beacon + + :param beacon_id: Beacon ID to update + :type beacon_id: str + :param name: Rename beacon to this value + :type name: str + :param timeout: gRPC timeout, defaults to 60 seconds + :type timeout: int, optional + :return: None + :rtype: None + """ + rename_req = client_pb2.RenameReq(BeaconID=beacon_id, Name=name) + await self._stub.Rename(rename_req, timeout=timeout) + + async def kill_beacon(self, beacon_id: str, timeout=TIMEOUT) -> None: + """Kill a beacon :param beacon_id: Numeric beacon ID to remove :type beacon_id: str @@ -283,7 +297,7 @@ class SliverClient(BaseClient): """Get a list of tasks for a beacon :param beacon_id: Beacon ID to get tasks for - :type beacon_id: sts + :type beacon_id: str :param timeout: gRPC timeout, defaults to 60 seconds :type timeout: int, optional :return: List of protobuf Task objects @@ -322,6 +336,34 @@ class SliverClient(BaseClient): ) return list(jobs.Active) + async def job_by_id(self, job_id: int, timeout=TIMEOUT) -> Optional[client_pb2.Job]: + """Get job by id + + :param job_id: Beacon ID to get tasks for + :type job_id: str + :param timeout: gRPC timeout, defaults to 60 seconds + :type timeout: int, optional + :return: List of protobuf Job objects + :rtype: List[client_pb2.Job] + """ + for job in await self.jobs(timeout=timeout): + if job.ID == job_id: + return job + + async def job_by_port( + self, job_port: int, timeout=TIMEOUT + ) -> Optional[client_pb2.Job]: + """Get job by port + + :param timeout: gRPC timeout, defaults to 60 seconds + :type timeout: int, optional + :return: List of protobuf Job objects + :rtype: List[client_pb2.Job] + """ + for job in await self.jobs(timeout=timeout): + if job.Port == job_port: + return job + async def kill_job(self, job_id: int, timeout=TIMEOUT) -> client_pb2.KillJob: """Kill a job @@ -477,7 +519,7 @@ class SliverClient(BaseClient): async def start_https_listener( self, host: str = "0.0.0.0", - port: int = 80, + port: int = 443, website: str = "", domain: str = "", cert: bytes = b"", @@ -627,7 +669,7 @@ class SliverClient(BaseClient): ) return await self._stub.StartHTTPStagerListener(stage_req, timeout=timeout) - async def generate( + async def generate_implant( self, config: client_pb2.ImplantConfig, timeout: int = 360 ) -> client_pb2.Generate: """Generate a new implant using a given configuration @@ -642,7 +684,7 @@ class SliverClient(BaseClient): req = client_pb2.GenerateReq(Config=config) return await self._stub.Generate(req, timeout=timeout) - async def regenerate( + async def regenerate_implant( self, implant_name: str, timeout=TIMEOUT ) -> client_pb2.Generate: """Regenerate an implant binary given the implants "name" @@ -708,7 +750,7 @@ class SliverClient(BaseClient): common_pb2.Empty(), timeout=timeout ) - async def generate_unique_ip(self, timeout=TIMEOUT) -> client_pb2.UniqueWGIP: + async def generate_wg_ip(self, timeout=TIMEOUT) -> client_pb2.UniqueWGIP: """Generate a unique IP address for use with WireGuard :param timeout: gRPC timeout, defaults to 60 seconds @@ -757,7 +799,7 @@ class SliverClient(BaseClient): """ return await self._stub.SaveImplantProfile(profile, timeout=timeout) - async def msf_stage( + async def generate_msf_stager( self, arch: str, format: str, @@ -801,7 +843,7 @@ class SliverClient(BaseClient): return await self._stub.MsfStage(stagerReq, timeout=timeout) async def shellcode( - self, data: bytes, function_name: str, arguments: str, timeout=TIMEOUT + self, data: bytes, function_name: str, arguments: str = "", timeout=TIMEOUT ) -> client_pb2.ShellcodeRDI: """Generate Donut shellcode @@ -855,7 +897,7 @@ class SliverClient(BaseClient): :type timeout: int, optional """ website = client_pb2.Website(Name=name) - await self._stub.Websites(website, timeout=timeout) + await self._stub.WebsiteRemove(website, timeout=timeout) async def add_website_content( self, @@ -881,12 +923,10 @@ class SliverClient(BaseClient): :rtype: client_pb2.Website """ web = client_pb2.WebContent( - Path=web_path, ContentType=content_type, Content=content + Path=web_path, ContentType=content_type, Content=content, Size=len(content) ) - web.Size = len(content) - web_add = client_pb2.WebsiteAddContent(Name=name) - web_add.Contents[web_path] = web + web_add = client_pb2.WebsiteAddContent(Name=name, Contents={web_path: web}) return await self._stub.WebsiteAddContent(web_add, timeout=timeout) async def update_website_content( @@ -913,12 +953,10 @@ class SliverClient(BaseClient): :rtype: client_pb2.Website """ web = client_pb2.WebContent( - Path=web_path, ContentType=content_type, Content=content + Path=web_path, ContentType=content_type, Content=content, Size=len(content) ) - web.Size = len(content) - web_update = client_pb2.WebsiteAddContent(Name=name) - web_update.Contents[web_path] = web + web_update = client_pb2.WebsiteAddContent(Name=name, Contents={web_path: web}) return await self._stub.WebsiteUpdateContent(web_update, timeout=timeout) async def remove_website_content( diff --git a/src/sliver/interactive.py b/src/sliver/interactive.py index 1882aef..633d84c 100644 --- a/src/sliver/interactive.py +++ b/src/sliver/interactive.py @@ -16,6 +16,8 @@ from typing import List, Literal, Optional +from sliver.pb.commonpb import common_pb2 + from ._protocols import InteractiveObject from .protobuf import client_pb2, sliver_pb2 @@ -155,23 +157,25 @@ class BaseInteractiveCommands: return await self._stub.Mkdir(self._request(make), timeout=self.timeout) async def download( - self: InteractiveObject, remote_path: str + self: InteractiveObject, remote_path: str, recurse: bool = False ) -> sliver_pb2.Download: - """Download a file from the remote file system + """Download a file or directory from the remote file system :param remote_path: File to download :type remote_path: str + :param recurse: Download all files in a directory + :type recurse: bool :return: Protobuf Download object :rtype: sliver_pb2.Download """ - download = sliver_pb2.DownloadReq(Path=remote_path) + download = sliver_pb2.DownloadReq(Path=remote_path, Recurse=recurse) return await self._stub.Download(self._request(download), timeout=self.timeout) async def upload( self: InteractiveObject, remote_path: str, data: bytes, - encoder: Literal["", "gzip"] = "", + is_ioc: bool = False, ) -> sliver_pb2.Upload: """Write data to specified path on remote file system @@ -179,12 +183,12 @@ class BaseInteractiveCommands: :type remote_path: str :param data: Data to write :type data: bytes - :param encoder: Data encoder ('', 'gzip'), defaults to '' - :type encoder: str, optional + :param is_ioc: Data is an indicator of compromise, defaults to False + :type is_ioc: bool, optional :return: Protobuf Upload object :rtype: sliver_pb2.Upload """ - upload = sliver_pb2.UploadReq(Path=remote_path, Data=data, Encoder=encoder) + upload = sliver_pb2.UploadReq(Path=remote_path, Data=data, IsIOC=is_ioc) return await self._stub.Upload(self._request(upload), timeout=self.timeout) async def process_dump(self: InteractiveObject, pid: int) -> sliver_pb2.ProcessDump: @@ -532,7 +536,7 @@ class BaseInteractiveCommands: return await self._stub.GetEnv(self._request(env), timeout=self.timeout) async def set_env( - self: InteractiveObject, name: str, value: str + self: InteractiveObject, key: str, value: str ) -> sliver_pb2.SetEnv: """Set an environment variable @@ -543,10 +547,20 @@ class BaseInteractiveCommands: :return: Protobuf SetEnv object :rtype: sliver_pb2.SetEnv """ - env = sliver_pb2.SetEnvReq() - env.Variable.Key = name - env.Variable.Value = value - return await self._stub.SetEnv(self._request(env), timeout=self.timeout) + env_var = common_pb2.EnvVar(Key=key, Value=value) + env_req = sliver_pb2.SetEnvReq(Variable=env_var) + return await self._stub.SetEnv(self._request(env_req), timeout=self.timeout) + + async def unset_env(self: InteractiveObject, key: str) -> sliver_pb2.UnsetEnv: + """Unset an environment variable + + :param value: Value of the environment variable + :type value: str + :return: Protobuf SetEnv object + :rtype: sliver_pb2.SetEnv + """ + env = sliver_pb2.UnsetEnvReq(Name=key) + return await self._stub.UnsetEnv(self._request(env), timeout=self.timeout) async def registry_read( self: InteractiveObject, hive: str, reg_path: str, key: str, hostname: str diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/data/test_write.exe b/tests/data/test_write.exe new file mode 100644 index 0000000..3237545 Binary files /dev/null and b/tests/data/test_write.exe differ diff --git a/tests/data/test_write.v b/tests/data/test_write.v new file mode 100644 index 0000000..526d148 --- /dev/null +++ b/tests/data/test_write.v @@ -0,0 +1,13 @@ +import os + +[callconv: stdcall] +[export: Main] +fn output() { + mut output := os.open_file("test_write.txt", "w") or {return} + output.write_string("Hello, DLL!") or {return} + output.close() +} + +fn main () { + output() +} diff --git a/tests/data/website.html b/tests/data/website.html new file mode 100644 index 0000000..02943f4 --- /dev/null +++ b/tests/data/website.html @@ -0,0 +1,8 @@ + + +SliverPy Test + + +

SliverPy Test

+ + \ No newline at end of file diff --git a/tests/data/website_update.html b/tests/data/website_update.html new file mode 100644 index 0000000..fa25cdb --- /dev/null +++ b/tests/data/website_update.html @@ -0,0 +1,8 @@ + + +SliverPy Test + + +

SliverPy Test (UPDATED!)

+ + \ No newline at end of file diff --git a/tests/test_client.py b/tests/test_client.py new file mode 100644 index 0000000..0970e23 --- /dev/null +++ b/tests/test_client.py @@ -0,0 +1,306 @@ +import os +from pathlib import Path + +from ward import fixture, test, skip + +from sliver import SliverClient, SliverClientConfig +from sliver.pb.clientpb.client_pb2 import ( + ImplantC2, + ImplantConfig, + ImplantProfile, + OutputFormat, + StageProtocol, +) + + +@fixture(scope="global") +async def sliver_client() -> SliverClient: + CONFIG_PATH = Path("~/.sliver-client/configs/sliverpy.cfg").expanduser() + config = SliverClientConfig.parse_config_file(CONFIG_PATH) + client = SliverClient(config) + await client.connect() + return client + + +@fixture(scope="global") +async def implant_config() -> ImplantConfig: + return ImplantConfig( + IsBeacon=False, + Name="sliver-pytest-" + os.urandom(8).hex(), + GOARCH="amd64", + GOOS="linux", + Format=OutputFormat.EXECUTABLE, + ObfuscateSymbols=False, + C2=[ImplantC2(Priority=0, URL="http://localhost:80")], + ) + + +@fixture(scope="global") +def sliverpy_random_name() -> str: + return "sliver-pytest-" + os.urandom(8).hex() + + +@fixture(scope="global") +def data_dir() -> Path: + return Path(__file__).parent / "data" + + +@test("**Client can get version**") +async def _(client: SliverClient = sliver_client): + assert await client.version() + + +@test("Client can list operators") +async def _(client: SliverClient = sliver_client): + assert await client.operators() + + +@test("Client can list beacons") +async def _(client: SliverClient = sliver_client): + assert await client.beacons() + + +@test("Client can list beacons by ID") +async def _(client: SliverClient = sliver_client): + beacons = await client.beacons() + assert await client.beacon_by_id(beacons[0].ID) + + +@test("Client can rename a beacon") +async def _(client: SliverClient = sliver_client): + beacons = await client.beacons() + beacon_name = beacons[0].Name + beacon_id = beacons[0].ID + await client.rename_beacon(beacon_id, "sliver-pytest") + + beacon = await client.beacon_by_id(beacon_id) + assert beacon.Name == "sliver-pytest" + + await client.rename_beacon(beacon.ID, beacon_name) + + +@test("Client can list sessions") +async def _(client: SliverClient = sliver_client): + assert await client.sessions() + + +@test("Client can list sessions by ID") +async def _(client: SliverClient = sliver_client): + sessions = await client.sessions() + assert await client.session_by_id(sessions[0].ID) + + +@test("Client can rename a session") +async def _(client: SliverClient = sliver_client): + sessions = await client.sessions() + session_name = sessions[0].Name + session_id = sessions[0].ID + await client.rename_session(session_id, "sliver-pytest") + + session = await client.session_by_id(session_id) + assert session.Name == "sliver-pytest" + + await client.rename_session(session.ID, session_name) + + +@test("Client can list implant builds") +async def _(client: SliverClient = sliver_client): + assert await client.implant_builds() + + +@test("Client can generate a new implant") +async def _( + client: SliverClient = sliver_client, config: ImplantConfig = implant_config +): + + assert await client.generate_implant(config) + + +@test("Client can regenerate an implant") +async def _( + client: SliverClient = sliver_client, config: ImplantConfig = implant_config +): + assert await client.regenerate_implant(config.Name) + + +@test("Client can save implant profiles") +async def _( + client: SliverClient = sliver_client, + config: ImplantConfig = implant_config, + name: str = sliverpy_random_name, +): + implant_profile = ImplantProfile(Name=name, Config=config) + assert await client.save_implant_profile(implant_profile) + + +@test("Client can list implant profiles") +async def _(client: SliverClient = sliver_client, name: str = sliverpy_random_name): + assert name in [profile.Name for profile in await client.implant_profiles()] + + +@test("Client can delete implant profiles") +async def _(client: SliverClient = sliver_client, name: str = sliverpy_random_name): + await client.delete_implant_profile(name) + assert name not in [profile.Name for profile in await client.implant_profiles()] + + +@test("Client can delete implant builds") +async def _( + client: SliverClient = sliver_client, + config: ImplantConfig = implant_config, +): + await client.delete_implant_build(config.Name) + assert config.Name not in [build for build in await client.implant_builds()] + + +@test("Client can list jobs") +async def _(client: SliverClient = sliver_client): + assert await client.jobs() + + +@test("Client can get job by ID") +async def _(client: SliverClient = sliver_client): + jobs = await client.jobs() + assert await client.job_by_id(jobs[0].ID) + + +@test("Client can get job by port") +async def _(client: SliverClient = sliver_client): + assert await client.job_by_port(80) + + +@test("Client can kill jobs") +async def _(client: SliverClient = sliver_client): + jobs = await client.jobs() + for job in jobs: + if job.Port != 80: + await client.kill_job(job.ID) + assert len(await client.jobs()) == 1 + + +@test("Client can start HTTP listener on port 8080") +async def _(client: SliverClient = sliver_client): + assert await client.start_http_listener() + + +@test("Client can start HTTPS listener on port 8443") +async def _(client: SliverClient = sliver_client): + assert await client.start_https_listener() + + +@test("Client can start DNS listener on port 53") +async def _(client: SliverClient = sliver_client): + assert await client.start_dns_listener(domains=["sliverpy.local"]) + + +@test("Client can start MTLS listener on port 8888") +async def _(client: SliverClient = sliver_client): + assert await client.start_mtls_listener() + + +@test("Client can start TCP stager listener on port 9000") +async def _(client: SliverClient = sliver_client): + assert await client.start_tcp_stager_listener("0.0.0.0", 9000, b"sliver-pytest") + + +@test("Client can start HTTP stager listener on port 9001") +async def _(client: SliverClient = sliver_client): + assert await client.start_http_stager_listener("0.0.0.0", 9001, b"sliver-pytest") + + +@skip("Cert generation not implemented") +@test("Client can start HTTPS stager listener on port 9002") +async def _(client: SliverClient = sliver_client): + assert await client.start_http_stager_listener("0.0.0.0", 9002, b"sliver-pytest") + + +@test("Client can generate a WireGuard IP") +async def _(client: SliverClient = sliver_client): + assert await client.generate_wg_ip() + + +@skip("Something is wrong with killing WG listeners on the server") +@test("Client can start WG listener on ports 5353/8889/1338") +async def _(client: SliverClient = sliver_client): + ip = await client.generate_wg_ip() + print(ip.IP) + assert await client.start_wg_listener(ip.IP, 5353, 8889, 1338) + + +@test("Client can generate a WireGuard client config") +async def _(client: SliverClient = sliver_client): + assert await client.generate_wg_client_config() + + +@test("Client can kill jobs (again) except WireGuard") +async def _(client: SliverClient = sliver_client): + jobs = await client.jobs() + for job in jobs: + if job.Port != 80: + await client.kill_job(job.ID) + assert len(await client.jobs()) <= 2 + + +@test("Client can generate an MSF stager") +async def _(client: SliverClient = sliver_client): + assert await client.generate_msf_stager( + arch="amd64", + format="raw", + host="127.0.0.1", + port=9000, + os="windows", + protocol=StageProtocol.TCP, + badchars=[], + ) + + +@test("Client can generate Donut shellcode") +async def _(client: SliverClient = sliver_client, data_dir: Path = data_dir): + dll_data = Path(data_dir / "test_write.exe").read_bytes() + assert await client.shellcode(dll_data, "Main") + + +@test("Client can interact with a session") +async def _(client: SliverClient = sliver_client): + sessions = await client.sessions() + session = sessions[0] + assert await client.interact_session(session.ID) + + +@test("Client can interact with a beacon") +async def _(client: SliverClient = sliver_client): + beacons = await client.beacons() + beacon = beacons[0] + assert await client.interact_beacon(beacon.ID) + + +@test("Client can add website content") +async def _(client: SliverClient = sliver_client, data_dir: Path = data_dir): + html_content = Path(data_dir / "website.html").read_bytes() + assert await client.add_website_content( + "sliverpy-test", "sliverpy", "test/html", html_content + ) + + +@test("Client can update website content") +async def _(client: SliverClient = sliver_client, data_dir: Path = data_dir): + html_content = Path(data_dir / "website_update.html").read_bytes() + assert await client.add_website_content( + "sliverpy-test", "sliverpy", "test/html", html_content + ) + + +@test("Client can list websites") +async def _(client: SliverClient = sliver_client): + assert "sliverpy-test" in [website.Name for website in await client.websites()] + + +@test("Client can remove website content") +async def _(client: SliverClient = sliver_client, data_dir: Path = data_dir): + assert await client.remove_website_content("sliverpy-test", ["sliverpy"]) + + +@test("Client can remove website") +async def _(client: SliverClient = sliver_client, data_dir: Path = data_dir): + await client.remove_website("sliverpy-test") + assert "sliverpy-test" not in [website.Name for website in await client.websites()] diff --git a/tests/test_interactive.py b/tests/test_interactive.py new file mode 100644 index 0000000..ae64a51 --- /dev/null +++ b/tests/test_interactive.py @@ -0,0 +1,112 @@ +from pathlib import Path +from ward import test, fixture +from sliver import SliverClient +from sliver.session import InteractiveSession + +from .test_client import data_dir, sliver_client, sliverpy_random_name + + +@fixture(scope="module") +async def session_zero(client: SliverClient = sliver_client) -> InteractiveSession: + sessions = await client.sessions() + return await client.interact_session(sessions[0].ID) # type: ignore + + +@test("InteractiveObject can send ping to server", tags=["interactive"]) +async def _(session: InteractiveSession = session_zero): + assert await session.ping() + + +@test("InteractiveObject can list processes", tags=["interactive"]) +async def _(session: InteractiveSession = session_zero): + assert await session.ps() + + +@test("InteractiveObject can get network interfaces", tags=["interactive"]) +async def _(session: InteractiveSession = session_zero): + assert await session.ifconfig() + + +@test("InteractiveObject can get network connections", tags=["interactive"]) +async def _(session: InteractiveSession = session_zero): + assert await session.netstat(True, True, True, True, True) + + +@test("InteractiveObject can get working directory", tags=["interactive"]) +async def _(session: InteractiveSession = session_zero): + assert await session.pwd() + + +@test("InteractiveObject can list directory", tags=["interactive"]) +async def _(session: InteractiveSession = session_zero): + assert await session.ls() + + +@test("InteractiveObject can change directory", tags=["interactive"]) +async def _(session: InteractiveSession = session_zero): + assert await session.cd(".") + + +@test("InteractiveObject can make a directory", tags=["interactive"]) +async def _( + session: InteractiveSession = session_zero, target_dir: str = sliverpy_random_name +): + assert await session.mkdir(target_dir) + + +@test("InteractiveObject can upload a file", tags=["interactive"]) +async def _( + session: InteractiveSession = session_zero, + target_dir: str = sliverpy_random_name, +): + assert await session.upload(target_dir + "/sliverpy.txt", b"sliverpy") + + +@test("InteractiveObject can download files", tags=["interactive"]) +async def _( + session: InteractiveSession = session_zero, + target_dir: str = sliverpy_random_name, +): + assert await session.download(target_dir, True) + + +@test("InteractiveObject can remove a directory", tags=["interactive"]) +async def _( + session: InteractiveSession = session_zero, path: str = sliverpy_random_name +): + assert await session.rm(path, recursive=True, force=True) + + +@test("InteractiveObject can set an environment variable", tags=["interactive"]) +async def _( + session: InteractiveSession = session_zero, value: str = sliverpy_random_name +): + assert await session.set_env("SLIVERPY_TEST", value) + + +@test("InteractiveObject can get an environment variable", tags=["interactive"]) +async def _( + session: InteractiveSession = session_zero, value: str = sliverpy_random_name +): + assert await session.get_env(value) + + +@test("InteractiveObject can unset an environment variable", tags=["interactive"]) +async def _( + session: InteractiveSession = session_zero, value: str = sliverpy_random_name +): + assert await session.unset_env(value) + + +@test("InteractiveObject can take a screenshot", tags=["interactive"]) +async def _( + session: InteractiveSession = session_zero, value: str = sliverpy_random_name +): + assert await session.screenshot() + + +@test("InteractiveObject can take a memory dump", tags=["interactive"]) +async def _( + session: InteractiveSession = session_zero, value: str = sliverpy_random_name +): + assert await session.process_dump(session.pid)