Skip to content

Commit

Permalink
Mac: support not allocating a TTY and fix bug of not being able to us…
Browse files Browse the repository at this point in the history
…e scp or rsync with mac hosts.
  • Loading branch information
adamdoupe committed Sep 27, 2024
1 parent 9b11dc1 commit bdf5aef
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 52 deletions.
51 changes: 28 additions & 23 deletions dojo_plugin/utils/mac_docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def __init__(self, client):

def get(self, name):
# Run 'guest-control.py list-vms' and parse the output
exitcode, output = self.client._ssh_exec(f'{MAC_GUEST_CONTROL_FILE} list-vms')
exitcode, output = self.client._ssh_exec(f'{MAC_GUEST_CONTROL_FILE} list-vms', input=b"")
output = output.decode('latin-1')
vms = self.parse_list_vms(output)
for vm in vms:
Expand All @@ -100,7 +100,7 @@ def create(self, image, entrypoint=None, name=None, hostname=None, user=None,
command = f'{MAC_GUEST_CONTROL_FILE} create-vm {image}'
if name:
command += f' --id {name}'
exitcode, output = self.client._ssh_exec(command)
exitcode, output = self.client._ssh_exec(command, input=b"")
output = output.decode('latin-1')
# not sure if the following actually parses
if 'Started' in output:
Expand Down Expand Up @@ -142,14 +142,14 @@ def remove(self, force=True):
# first try to shutdown the VM
timeout_hit = False
try:
self.exec_run("/sbin/shutdown -h now", "0", timeout_seconds=10)
self.exec_run("/sbin/shutdown -h now", "0", timeout_seconds=10, input=b"")
except subprocess.TimeoutExpired:
# if that didn't work, kill it
timeout_hit = True

if force or timeout_hit:
command = f'{MAC_GUEST_CONTROL_FILE} kill-vm {self.id}'
exitcode, output = self.client._ssh_exec(command, exception_on_fail=False)
exitcode, output = self.client._ssh_exec(command, exception_on_fail=False, input=b"")

def wait(self, condition='removed'):
# Wait until the VM is removed
Expand All @@ -169,48 +169,53 @@ def start(self):
pass

# returns exit_code, output
def exec_run(self, cmd, user=None, input=None, capture_output=True, timeout_seconds=None, **kwargs):
def exec_run(self, cmd, user=None, input=None, capture_output=True, timeout_seconds=None, use_tty=True, **kwargs):
# SSH to the VM's IP address and run the command
if user == "0" or user == None:
# they want to run the command as root
cmd = f"exec sudo su - root -c {shlex.quote(cmd)}"
elif user == "1000":
# they want to run the command as hacker
cmd = f"exec sudo su - hacker -c {shlex.quote(cmd)}"
command = f"{MAC_GUEST_CONTROL_FILE} exec {self.id} {shlex.quote(cmd)}"
tty_arg = '--tty' if use_tty else ''
command = f"{MAC_GUEST_CONTROL_FILE} exec {tty_arg} {self.id} {shlex.quote(cmd)}"
exitcode, output = self.client._ssh_exec(command, only_stdout=False, exception_on_fail=False, input=input, capture_output=capture_output, timeout_seconds=timeout_seconds)
return exitcode, output

# execve shell
def execve_shell(self, cmd, user=None):
def execve_shell(self, cmd, user=None, use_tty=True):

if user == "0" or user == None:
# they want to run the command as root
cmd = f"exec sudo su - root -c {shlex.quote(cmd)}"
elif user == "1000":
# they want to run the command as hacker
cmd = f"exec sudo su - hacker -c {shlex.quote(cmd)}"
command = f"{MAC_GUEST_CONTROL_FILE} exec {self.id} {shlex.quote(cmd)}"
tty_arg = '--tty' if use_tty else ''
command = f"{MAC_GUEST_CONTROL_FILE} exec {tty_arg} {self.id} {shlex.quote(cmd)}"
to_exec = [
"ssh",
"-i", self.client.key_filename,
"-a", # prevent any SSH agent forward crazyness
"-o", "StrictHostKeychecking=no",
"-o", "UserKnownHostsFile=/dev/null",
"-o", "ControlMaster=no",
"-o", "LogLevel=ERROR",
]
if use_tty:
to_exec.append("-t")
to_exec.append(f"{self.client.username}@{self.client.hostname}")
to_exec.append(command)
os.execv(
"/usr/bin/ssh",
[
"ssh",
"-i", self.client.key_filename,
"-t", # force SSH to allocate a TTY
"-a", # prevent any SSH agent forward crazyness
"-o", "StrictHostKeychecking=no",
"-o", "UserKnownHostsFile=/dev/null",
"-o", "ControlMaster=no",
"-o", "LogLevel=ERROR",
f"{self.client.username}@{self.client.hostname}",
command
]
to_exec,
)


def send_flag(self, flag):
flag = flag.strip()
flag = flag.decode('latin-1')
self.exec_run(f"echo '{flag}' | sudo tee /flag")
self.exec_run(f"echo '{flag}' | sudo tee /flag", input=b"")

def attach_socket(self, params=None):
class MySendall:
Expand All @@ -228,7 +233,7 @@ def put_archive(self, path, data):
self.exec_run(f"cat - | tar -xvf - -C {shlex.quote(path)}", input=data.read())

def get_archive(self, path):
exitcode, output = self.exec_run(f"tar -cf - {shlex.quote(path)}")
exitcode, output = self.exec_run(f"tar -cf - {shlex.quote(path)}", input=b"")
if exitcode != 0:
raise docker.errors.NotFound(f'Getting archive {path=} failed {exitcode=} {output=}')
return output
Expand All @@ -246,7 +251,7 @@ def get(self, image_name):
image_name = image_name.split("mac:", maxsplit=1)[-1]
command = f"{MAC_GUEST_CONTROL_FILE} images {image_name}"
try:
exitcode, output = self.client._ssh_exec(command)
exitcode, output = self.client._ssh_exec(command, input=b"")
except Exception as e:
raise docker.errors.NotFound(f'Image {image_name} not found: {e}')

Expand Down
5 changes: 3 additions & 2 deletions sshd/enter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import json
import os
import pathlib
import shlex
import sys
import time

Expand Down Expand Up @@ -88,8 +89,8 @@ def print(*args, **kwargs):
if not os.fork():
ssh_entrypoint = "/run/dojo/bin/ssh-entrypoint"
if is_mac:
cmd = original_command if original_command else "zsh -i"
container.execve_shell(cmd, user="1000")
cmd = f"/bin/bash -c {shlex.quote(original_command)}" if original_command else "zsh -i"
container.execve_shell(cmd, user="1000", use_tty=tty)
else:
command = [ssh_entrypoint, "-c", original_command] if original_command else [ssh_entrypoint]
os.execve(
Expand Down
60 changes: 33 additions & 27 deletions sshd/mac_docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def __init__(self, client):

def get(self, name):
# Run 'guest-control.py list-vms' and parse the output
exitcode, output = self.client._ssh_exec(f'{MAC_GUEST_CONTROL_FILE} list-vms')
exitcode, output = self.client._ssh_exec(f'{MAC_GUEST_CONTROL_FILE} list-vms', input=b"")
output = output.decode('latin-1')
vms = self.parse_list_vms(output)
for vm in vms:
Expand All @@ -100,7 +100,7 @@ def create(self, image, entrypoint=None, name=None, hostname=None, user=None,
command = f'{MAC_GUEST_CONTROL_FILE} create-vm {image}'
if name:
command += f' --id {name}'
exitcode, output = self.client._ssh_exec(command)
exitcode, output = self.client._ssh_exec(command, input=b"")
output = output.decode('latin-1')
# not sure if the following actually parses
if 'Started' in output:
Expand Down Expand Up @@ -140,15 +140,16 @@ def remove(self, force=True):
# Kill the VM

# first try to shutdown the VM
timeout_hit = False
try:
self.exec_run("shutdown -h now", "0", timeout_seconds=10)
self.exec_run("/sbin/shutdown -h now", "0", timeout_seconds=10, input=b"")
except subprocess.TimeoutExpired:
# if that didn't work, kill it
if force:
command = f'{MAC_GUEST_CONTROL_FILE} kill-vm {self.id}'
exitcode, output = self.client._ssh_exec(command)
if b'Error' in output:
raise Exception(f'Error removing container: {output}')
timeout_hit = True

if force or timeout_hit:
command = f'{MAC_GUEST_CONTROL_FILE} kill-vm {self.id}'
exitcode, output = self.client._ssh_exec(command, exception_on_fail=False, input=b"")

def wait(self, condition='removed'):
# Wait until the VM is removed
Expand All @@ -168,48 +169,53 @@ def start(self):
pass

# returns exit_code, output
def exec_run(self, cmd, user=None, input=None, capture_output=True, timeout_seconds=None, **kwargs):
def exec_run(self, cmd, user=None, input=None, capture_output=True, timeout_seconds=None, use_tty=True, **kwargs):
# SSH to the VM's IP address and run the command
if user == "0" or user == None:
# they want to run the command as root
cmd = f"exec sudo su - root -c {shlex.quote(cmd)}"
elif user == "1000":
# they want to run the command as hacker
cmd = f"exec sudo su - hacker -c {shlex.quote(cmd)}"
command = f"{MAC_GUEST_CONTROL_FILE} exec {self.id} {shlex.quote(cmd)}"
tty_arg = '--tty' if use_tty else ''
command = f"{MAC_GUEST_CONTROL_FILE} exec {tty_arg} {self.id} {shlex.quote(cmd)}"
exitcode, output = self.client._ssh_exec(command, only_stdout=False, exception_on_fail=False, input=input, capture_output=capture_output, timeout_seconds=timeout_seconds)
return exitcode, output

# execve shell
def execve_shell(self, cmd, user=None):
def execve_shell(self, cmd, user=None, use_tty=True):

if user == "0" or user == None:
# they want to run the command as root
cmd = f"exec sudo su - root -c {shlex.quote(cmd)}"
elif user == "1000":
# they want to run the command as hacker
cmd = f"exec sudo su - hacker -c {shlex.quote(cmd)}"
command = f"{MAC_GUEST_CONTROL_FILE} exec {self.id} {shlex.quote(cmd)}"
tty_arg = '--tty' if use_tty else ''
command = f"{MAC_GUEST_CONTROL_FILE} exec {tty_arg} {self.id} {shlex.quote(cmd)}"
to_exec = [
"ssh",
"-i", self.client.key_filename,
"-a", # prevent any SSH agent forward crazyness
"-o", "StrictHostKeychecking=no",
"-o", "UserKnownHostsFile=/dev/null",
"-o", "ControlMaster=no",
"-o", "LogLevel=ERROR",
]
if use_tty:
to_exec.append("-t")
to_exec.append(f"{self.client.username}@{self.client.hostname}")
to_exec.append(command)
os.execv(
"/usr/bin/ssh",
[
"ssh",
"-i", self.client.key_filename,
"-t", # force SSH to allocate a TTY
"-a", # prevent any SSH agent forward crazyness
"-o", "StrictHostKeychecking=no",
"-o", "UserKnownHostsFile=/dev/null",
"-o", "ControlMaster=no",
"-o", "LogLevel=ERROR",
f"{self.client.username}@{self.client.hostname}",
command
]
to_exec,
)


def send_flag(self, flag):
flag = flag.strip()
flag = flag.decode('latin-1')
self.exec_run(f"echo '{flag}' | sudo tee /flag")
self.exec_run(f"echo '{flag}' | sudo tee /flag", input=b"")

def attach_socket(self, params=None):
class MySendall:
Expand All @@ -227,7 +233,7 @@ def put_archive(self, path, data):
self.exec_run(f"cat - | tar -xvf - -C {shlex.quote(path)}", input=data.read())

def get_archive(self, path):
exitcode, output = self.exec_run(f"tar -cf - {shlex.quote(path)}")
exitcode, output = self.exec_run(f"tar -cf - {shlex.quote(path)}", input=b"")
if exitcode != 0:
raise docker.errors.NotFound(f'Getting archive {path=} failed {exitcode=} {output=}')
return output
Expand All @@ -245,7 +251,7 @@ def get(self, image_name):
image_name = image_name.split("mac:", maxsplit=1)[-1]
command = f"{MAC_GUEST_CONTROL_FILE} images {image_name}"
try:
exitcode, output = self.client._ssh_exec(command)
exitcode, output = self.client._ssh_exec(command, input=b"")
except Exception as e:
raise docker.errors.NotFound(f'Image {image_name} not found: {e}')

Expand Down

0 comments on commit bdf5aef

Please sign in to comment.