1
0
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:
Caleb Stewart 2021-05-24 00:18:30 -04:00
parent 97c4d256ab
commit 830fe7b211
3 changed files with 87 additions and 17 deletions

View File

@ -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

View File

@ -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

View File

@ -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."""