mirror of
https://github.com/calebstewart/pwncat.git
synced 2024-11-24 01:25:37 +01:00
Initial working windows setup
I have opened the Windows C2 repository, and added the ability for pwncat to automatically download the C2 DLLs. If you don't have internet or would rather grab them yourself, you can place them in ~/.local/share/pwncat (or point the `windows_c2_dir` config at the directory where you do place them). If `stageone.dll` and `stagetwo.dll` exist in that directory, pwncat will not attempt to download them from github.
This commit is contained in:
parent
97c4d256ab
commit
830fe7b211
@ -88,6 +88,10 @@ class Config:
|
|||||||
"cross": {"value": None, "type": str},
|
"cross": {"value": None, "type": str},
|
||||||
"psmodules": {"value": ".", "type": local_dir_type},
|
"psmodules": {"value": ".", "type": local_dir_type},
|
||||||
"verbose": {"value": False, "type": bool_type},
|
"verbose": {"value": False, "type": bool_type},
|
||||||
|
"windows_c2_dir": {
|
||||||
|
"value": "~/.local/share/pwncat",
|
||||||
|
"type": local_dir_type,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
# Locals are set per-used-module
|
# Locals are set per-used-module
|
||||||
|
@ -100,7 +100,7 @@ class Module(BaseModule):
|
|||||||
with open(output, "w") as filp:
|
with open(output, "w") as filp:
|
||||||
template.stream(context).dump(filp)
|
template.stream(context).dump(filp)
|
||||||
else:
|
else:
|
||||||
markdown = Markdown(template.render(context))
|
markdown = Markdown(template.render(context), hyperlinks=False)
|
||||||
console.print(markdown)
|
console.print(markdown)
|
||||||
except jinja2.TemplateError as exc:
|
except jinja2.TemplateError as exc:
|
||||||
raise ModuleFailed(str(exc)) from exc
|
raise ModuleFailed(str(exc)) from exc
|
||||||
|
@ -8,30 +8,26 @@ import time
|
|||||||
import base64
|
import base64
|
||||||
import shutil
|
import shutil
|
||||||
import pathlib
|
import pathlib
|
||||||
|
import tarfile
|
||||||
import termios
|
import termios
|
||||||
import readline
|
import readline
|
||||||
import textwrap
|
import textwrap
|
||||||
import subprocess
|
import subprocess
|
||||||
from io import (
|
from io import (BytesIO, StringIO, RawIOBase, TextIOWrapper, BufferedIOBase,
|
||||||
BytesIO,
|
UnsupportedOperation)
|
||||||
StringIO,
|
|
||||||
RawIOBase,
|
|
||||||
TextIOWrapper,
|
|
||||||
BufferedIOBase,
|
|
||||||
UnsupportedOperation,
|
|
||||||
)
|
|
||||||
from typing import List, Union, BinaryIO
|
from typing import List, Union, BinaryIO
|
||||||
from subprocess import TimeoutExpired, CalledProcessError
|
from subprocess import TimeoutExpired, CalledProcessError
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
import pkg_resources
|
|
||||||
|
|
||||||
import pwncat
|
import pwncat
|
||||||
|
import requests
|
||||||
import pwncat.util
|
import pwncat.util
|
||||||
|
import pkg_resources
|
||||||
import pwncat.subprocess
|
import pwncat.subprocess
|
||||||
from pwncat.platform import Path, Platform, PlatformError
|
from pwncat.platform import Path, Platform, PlatformError
|
||||||
|
|
||||||
INTERACTIVE_END_MARKER = b"\nINTERACTIVE_COMPLETE\r\n"
|
INTERACTIVE_END_MARKER = b"\nINTERACTIVE_COMPLETE\r\n"
|
||||||
|
PWNCAT_WINDOWS_C2_RELEASE_URL = "https://github.com/calebstewart/pwncat-windows-c2/releases/download/v0.0.1/pwncat-windows-v0.0.1.tar.gz"
|
||||||
|
|
||||||
|
|
||||||
class PowershellError(Exception):
|
class PowershellError(Exception):
|
||||||
@ -122,6 +118,7 @@ class WindowsFile(RawIOBase):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
self.platform.run_method("File", "read")
|
self.platform.run_method("File", "read")
|
||||||
|
self.platform.channel.sendline(str(self.handle).encode("utf-8"))
|
||||||
self.platform.channel.sendline(str(len(b)).encode("utf-8"))
|
self.platform.channel.sendline(str(len(b)).encode("utf-8"))
|
||||||
count = int(self.platform.channel.recvuntil(b"\n").strip())
|
count = int(self.platform.channel.recvuntil(b"\n").strip())
|
||||||
|
|
||||||
@ -390,8 +387,24 @@ class Windows(Platform):
|
|||||||
# Tracks paths to modules which have been sideloaded into powershell
|
# Tracks paths to modules which have been sideloaded into powershell
|
||||||
self.psmodules = []
|
self.psmodules = []
|
||||||
|
|
||||||
|
# Ensure we have the C2 libraries downloaded
|
||||||
|
self._ensure_libs()
|
||||||
|
|
||||||
self._bootstrap_stage_two()
|
self._bootstrap_stage_two()
|
||||||
|
|
||||||
|
# This is a dirty hack
|
||||||
|
old_close = self.channel.close
|
||||||
|
|
||||||
|
def new_close():
|
||||||
|
self.run_method("StageTwo", "exit")
|
||||||
|
old_close()
|
||||||
|
|
||||||
|
self.channel.close = new_close
|
||||||
|
|
||||||
|
self.refresh_uid()
|
||||||
|
|
||||||
|
self.setup_prompt()
|
||||||
|
|
||||||
# Load requested libraries
|
# Load requested libraries
|
||||||
# for library, methods in self.LIBRARY_IMPORTS.items():
|
# for library, methods in self.LIBRARY_IMPORTS.items():
|
||||||
# self._load_library(library, methods)
|
# self._load_library(library, methods)
|
||||||
@ -401,6 +414,41 @@ class Windows(Platform):
|
|||||||
|
|
||||||
self.channel.send(f"{typ}\n{method}\n".encode("utf-8"))
|
self.channel.send(f"{typ}\n{method}\n".encode("utf-8"))
|
||||||
|
|
||||||
|
def setup_prompt(self):
|
||||||
|
""" Set a prompt method for powershell to ensure our prompt looks pretty :) """
|
||||||
|
|
||||||
|
self.powershell(
|
||||||
|
"""
|
||||||
|
function prompt {
|
||||||
|
$ESC = [char]27
|
||||||
|
Write-Host "$ESC[31m(remote)$ESC[33m $env:UserName@$env:ComputerName$ESC[0m:$ESC[36m$($executionContext.SessionState.Path.CurrentLocation)$ESC[0m$" -NoNewLine
|
||||||
|
return " "
|
||||||
|
}"""
|
||||||
|
)
|
||||||
|
|
||||||
|
def _ensure_libs(self):
|
||||||
|
"""This method checks that stageone.dll and stagetwo.dll exist within
|
||||||
|
the directory specified by the windows_c2_dir configuration. If they do
|
||||||
|
not, a release copy is downloaded from GitHub."""
|
||||||
|
|
||||||
|
location = pathlib.Path(self.session.config["windows_c2_dir"]).expanduser()
|
||||||
|
location.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
if (
|
||||||
|
not (location / "stageone.dll").exists()
|
||||||
|
or not (location / "stagetwo.dll").exists()
|
||||||
|
):
|
||||||
|
self.session.manager.log("Downloading Windows C2 binaries from GitHub...")
|
||||||
|
with requests.get(PWNCAT_WINDOWS_C2_RELEASE_URL, stream=True) as request:
|
||||||
|
data = request.raw.read()
|
||||||
|
with tarfile.open(mode="r:gz", fileobj=BytesIO(data)) as tar:
|
||||||
|
with tar.extractfile("stageone.dll") as stageone:
|
||||||
|
with (location / "stageone.dll").open("wb") as output:
|
||||||
|
shutil.copyfileobj(stageone, output)
|
||||||
|
with tar.extractfile("stagetwo.dll") as stagetwo:
|
||||||
|
with (location / "stagetwo.dll").open("wb") as output:
|
||||||
|
shutil.copyfileobj(stagetwo, output)
|
||||||
|
|
||||||
def _bootstrap_stage_two(self):
|
def _bootstrap_stage_two(self):
|
||||||
"""This takes the stage one C2 (powershell) and boostraps it for stage
|
"""This takes the stage one C2 (powershell) and boostraps it for stage
|
||||||
two. Stage two is C# code dynamically compiled and executed. We first
|
two. Stage two is C# code dynamically compiled and executed. We first
|
||||||
@ -432,11 +480,17 @@ class Windows(Platform):
|
|||||||
chunk_sz = 1900
|
chunk_sz = 1900
|
||||||
|
|
||||||
loader_encoded_name = pwncat.util.random_string()
|
loader_encoded_name = pwncat.util.random_string()
|
||||||
|
stageone = (
|
||||||
|
pathlib.Path(self.session.config["windows_c2_dir"]).expanduser()
|
||||||
|
/ "stageone.dll"
|
||||||
|
)
|
||||||
|
stagetwo = (
|
||||||
|
pathlib.Path(self.session.config["windows_c2_dir"]).expanduser()
|
||||||
|
/ "stagetwo.dll"
|
||||||
|
)
|
||||||
|
|
||||||
# Read the loader
|
# Read the loader
|
||||||
with open(
|
with stageone.open("rb") as filp:
|
||||||
pkg_resources.resource_filename("pwncat", "data/loader.dll"), "rb"
|
|
||||||
) as filp:
|
|
||||||
loader_dll = base64.b64encode(filp.read())
|
loader_dll = base64.b64encode(filp.read())
|
||||||
|
|
||||||
# Extract first chunk
|
# Extract first chunk
|
||||||
@ -519,9 +573,7 @@ class Windows(Platform):
|
|||||||
self.channel.recvuntil(b"\n")
|
self.channel.recvuntil(b"\n")
|
||||||
|
|
||||||
# Load, Compress and Encode stage two
|
# Load, Compress and Encode stage two
|
||||||
with open(
|
with stagetwo.open("rb") as filp:
|
||||||
pkg_resources.resource_filename("pwncat", "data/stagetwo.dll"), "rb"
|
|
||||||
) as filp:
|
|
||||||
stagetwo_dll = filp.read()
|
stagetwo_dll = filp.read()
|
||||||
compressed = BytesIO()
|
compressed = BytesIO()
|
||||||
with gzip.GzipFile(fileobj=compressed, mode="wb") as gz:
|
with gzip.GzipFile(fileobj=compressed, mode="wb") as gz:
|
||||||
@ -774,6 +826,20 @@ class Windows(Platform):
|
|||||||
except CalledProcessError:
|
except CalledProcessError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def refresh_uid(self):
|
||||||
|
""" Retrieve the current user ID """
|
||||||
|
|
||||||
|
self.powershell(
|
||||||
|
"Add-Type -AssemblyName System.DirectoryServices.AccountManagement"
|
||||||
|
)
|
||||||
|
self.user_info = self.powershell(
|
||||||
|
"([System.DirectoryServices.AccountManagement.UserPrincipal]::Current).SID.Value"
|
||||||
|
)
|
||||||
|
|
||||||
|
def getuid(self):
|
||||||
|
|
||||||
|
return self.user_info
|
||||||
|
|
||||||
def new_item(self, **kwargs):
|
def new_item(self, **kwargs):
|
||||||
"""Run the `New-Item` commandlet with specified arguments and
|
"""Run the `New-Item` commandlet with specified arguments and
|
||||||
raise the appropriate local exception if requried."""
|
raise the appropriate local exception if requried."""
|
||||||
|
Loading…
Reference in New Issue
Block a user