Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Agent feature #465

Open
wants to merge 60 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
7ab6ce0
fixed agent build process
mutex-pup Aug 1, 2024
378ebfe
added osx and linux support
mutex-pup Aug 1, 2024
2a035aa
made define_permissions arg syntax less scuffed
mutex-pup Aug 6, 2024
c47d22a
adjusted manifest api
mutex-pup Aug 12, 2024
0cda5b4
added package renaming
mutex-pup Aug 12, 2024
a1260d8
added base for updated cli system
mutex-pup Aug 12, 2024
95b1afb
fixes + cleanup
mutex-pup Aug 13, 2024
d326bf2
added dynamic themes
mutex-pup Aug 13, 2024
44e7eb0
fixed namespaces in manifest
mutex-pup Aug 15, 2024
c423570
bold of you to assume I can spell "unsupported"
mutex-pup Aug 15, 2024
0fdbed4
weird off by one error causing random newlines in some shells
mutex-pup Aug 15, 2024
b6b368e
faster and clearner perm filtering
mutex-pup Aug 15, 2024
c718592
allow specification of output path
mutex-pup Aug 15, 2024
ea981e2
set base apk for use with build
mutex-pup Aug 15, 2024
c77a1f4
fuzzy string matching for autocorrect
mutex-pup Aug 19, 2024
2173ce6
added interactive build mode
mutex-pup Aug 21, 2024
5c29d57
fixed dynamic type issue
mutex-pup Aug 21, 2024
41b4c2a
updated library apk version
mutex-pup Aug 21, 2024
4b1ba3e
allow setting of default server port in interactive build
mutex-pup Aug 21, 2024
68fcf86
prompt suggestions for theme setting
mutex-pup Aug 21, 2024
1b1b62c
added virtual space after tab in prompt suggestion
mutex-pup Aug 21, 2024
310df10
fixed build issues on linux systems
mutex-pup Aug 22, 2024
88d7a0e
added .venv/ to gitignore
mutex-pup Aug 22, 2024
c53c891
added downloading of apk files from drozer-agent repo for use in buil…
mutex-pup Aug 22, 2024
12ac581
cleanup and bugfixes
mutex-pup Aug 22, 2024
bf2d760
fixed unwanted package removing
mutex-pup Aug 24, 2024
5ac36d9
fixed typo
mutex-pup Aug 24, 2024
20a5b78
moved define_permission string parsing out of main build function
mutex-pup Aug 24, 2024
07af4d8
fixed double space issue with user input
mutex-pup Aug 24, 2024
af2bb00
modified library file retrieval to better handle os dependent files
mutex-pup Aug 24, 2024
4dc1417
added doc strings
mutex-pup Aug 24, 2024
44a2ba9
changed set_apk cli syntax
mutex-pup Aug 24, 2024
5e543d4
added better error msgs
mutex-pup Aug 24, 2024
8ca89ec
allow creation of builder from inside tmp directory
mutex-pup Aug 26, 2024
2632457
depreciation notice for unpack
mutex-pup Aug 26, 2024
326dd9f
remove all permissions when saving new base apk
mutex-pup Aug 26, 2024
81640c9
output directory must now be specified with build in interactive mode…
mutex-pup Aug 26, 2024
92e6bb5
fixed adding security permissions
mutex-pup Aug 27, 2024
6fd4205
added error msgs for missing base apk
mutex-pup Aug 27, 2024
653ad05
allow use of '-' in commands
mutex-pup Aug 27, 2024
a854e12
fixed incorrect format string
mutex-pup Aug 27, 2024
6c07bd3
nothing to see here
mutex-pup Aug 29, 2024
1952fec
fixed manifest nodes inserting at wrong position
mutex-pup Aug 29, 2024
3806e2f
added agent presets + removed unused copy command
mutex-pup Aug 29, 2024
730644f
ensure minimum permissions are always added
mutex-pup Aug 29, 2024
686da39
made permission lists sets to avoid repeat permissions
mutex-pup Aug 29, 2024
d9bf287
fixed typos in agent builder
mutex-pup Aug 29, 2024
917d47b
added missing permissions to android.py permissions list
mutex-pup Aug 29, 2024
45f6bcb
removed accidentally pushed debug stuff in main.py
mutex-pup Aug 29, 2024
6099856
fixed trying to create set from possible None type
mutex-pup Aug 29, 2024
787be2b
forgot to move libc++.so into the linux library
mutex-pup Aug 29, 2024
99387d1
better None checking in agent build
mutex-pup Aug 29, 2024
d5d8948
fixed typo in permissions
mutex-pup Aug 29, 2024
0f6e37b
added execute flag to linux binaries
mutex-pup Aug 29, 2024
4a94e53
updated readme to remove agent being marked as broken
mutex-pup Aug 30, 2024
817e846
updated doc strings in agent
mutex-pup Aug 30, 2024
6337ed7
fixed regex match strings in stream.py
mutex-pup Aug 30, 2024
342730c
removed doc string in the middel of manager:do_interactive
mutex-pup Aug 30, 2024
94ef51c
bump version number for agent compatibility reasons
mutex-pup Aug 30, 2024
eba3ba8
more missing raw string specifiers for match strings containing '\'
mutex-pup Sep 7, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ obj
src/drozer/modules/**/*.class
src/drozer/modules/**/*.apk
src/drozer.egg-info
src/drozer/lib/standard-agent
mercury.log
deb_dist/*
*.tar.gz
.vs/
.vs/
.idea/
.venv/
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ drozer is open source software, maintained by WithSecure, and can be downloaded

This is an BETA release of a rewritten drozer version; this version is updated to support python3.

Currently, the following known issues are present:

- Building of custom agents functionality will crash the drozer client. This functionality is considered out of scope for the beta release of the revived drozer project.

## Docker Container

To help with making sure drozer can be run on all systems, a Docker container was created that has a working build of drozer.
Expand Down Expand Up @@ -92,6 +88,12 @@ drozer can be installed using Android Debug Bridge (adb).

Download the latest drozer Agent [here](https://github.com/WithSecureLabs/drozer-agent/releases/latest).

Or create a custom agent with
```
drozer agent set-apk --latest
drozer agent interactive
```
Then install with
```shell
adb install drozer-agent.apk
```
Expand Down
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ dependencies = ["protobuf>=4.25.2",
"twisted>=18.9.0",
"service-identity",
"distro",
"pyyaml"]
"pyyaml",
"readchar",
"ansi"]
requires-python = ">=3.8"
authors = [
{ name = "WithSecure", email = "[email protected]" },
Expand Down
6 changes: 3 additions & 3 deletions src/WithSecure/common/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def run(self, argv=None):
def do_commands(self, arguments):
"""shows a list of all console commands"""

print("usage: %s", self.__doc__.strip(), end="\n")
print("usage:", self.__doc__.strip(), end="\n")
print("available commands:")
print(self.__get_commands_help())

Expand All @@ -121,7 +121,7 @@ def __get_commands_help(self):
commands = {}

for command in self.__commands():
commands[command.replace("do_", "")] = getattr(self, command).__doc__.strip()
commands[command.replace("do_", "").replace("_", "-")] = getattr(self, command).__doc__.strip()

return console.format_dict(commands, left_margin=2)

Expand All @@ -131,7 +131,7 @@ def __invoke_command(self, arguments):
"""

try:
command = arguments.command
command = arguments.command.replace("-", "_")

if "do_" + command in dir(self):
getattr(self, "do_" + command)(arguments)
Expand Down
226 changes: 226 additions & 0 deletions src/WithSecure/common/cli_fancy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import os
from WithSecure.common import cli
import readchar
from ansi import *


class OT:
def __init__(self, string, children=None):
if children is None:
children = []
self.string = string
self.children = list(children)


class FancyBase(cli.Base):
def __init__(self, add_help=True):
cli.Base.__init__(self, add_help=add_help)

@staticmethod
def __ansi_print(*args, end=''):
print(*args, sep='', end=end)

@staticmethod
def __print_init(prompt, max_options):
print('\n' * max_options, end='')
FancyBase.__ansi_print(cursor.prev_line(max_options), prompt)

@staticmethod
def __print(input_str, options, max_options, selected_line, offset_x):
for i in range(max_options):
FancyBase.__ansi_print(cursor.next_line(1), cursor.erase_line(0))
if i < len(options):
if i + 1 == selected_line:
FancyBase.__ansi_print(color.fg.blue, '-', cursor.goto_x(offset_x + 1), options[i],
color.fx.reset)
else:
FancyBase.__ansi_print(cursor.goto_x(offset_x + 1), options[i])

FancyBase.__ansi_print(cursor.prev_line(max_options), cursor.goto_x(offset_x + 1), cursor.erase_line(0),
input_str)

# printing built string last ensures that cursor is left at correct pos

@staticmethod
def __print__cleanup(input_str, max_options, offset_x):
for i in range(max_options):
FancyBase.__ansi_print(cursor.next_line(1), cursor.erase_line(2))

FancyBase.__ansi_print(cursor.prev_line(max_options), cursor.goto_x(offset_x + 1), cursor.erase_line(0),
input_str, end='\n')

# Levenshtein string distance calculator
# modified to assign 0 cost to appending or prepending to string a
# seems to underestimate cost oops
# TODO: On second thoughts this might not be the best approach, lev may work with longer a strings but may need to use a diffrent algo for short strings

@staticmethod
def lev(a, b):
len_a, len_b = len(a), len(b)
if len_a == 0:
return len_b
if len_b == 0:
return len_a

a = a.lower()
b = b.lower()

dist_m = [[0] * (len_b + 1) for _ in range(len_a + 1)]

for itr_a in range(1, len_a + 1):
dist_m[itr_a][0] = itr_a
for itr_b in range(1, len_b + 1):
dist_m[0][itr_b] = 0 if itr_b <= len_b else itr_b # No cost for insertions at the start of s1

for itr_a in range(1, len_a + 1):
for itr_b in range(1, len_b + 1):
if a[itr_a - 1] == b[itr_b - 1]:
dist_m[itr_a][itr_b] = dist_m[itr_a - 1][itr_b - 1]
else:
ins_cost = (0 if itr_a == len_a or itr_a == 1 else 1) # Free if at start or end of s1
dist_m[itr_a][itr_b] = min(
dist_m[itr_a - 1][itr_b] + 1, # Deletion
dist_m[itr_a][itr_b - 1] + ins_cost, # Insertion,
dist_m[itr_a - 1][itr_b - 1] + 1 # Substitution
)

if len_a <= len_b:
return min(dist_m[len_a][len_a:len_b + 1])
else:
return dist_m[len_a][len_b]

"""
finds all options in the options tree that begins with the given string
"""

@staticmethod
def __matches(options, string): # not super efficient but was easy to write
def __impl(_segments, _option, _built, _valid):
if len(_segments) == 1: # base case, last segment
if _segments[0] == _option.string:
for _child in _option.children: # show only children if segment matches exactly
_valid.append((' '.join(_built + [_option.string, _child.string]), 0))
elif _segments[0] == "":
valid.append((' '.join(_built + [_option.string]), 0))
else:
_valid.append((' '.join(_built + [_option.string]),
FancyBase.lev(_segments[0], _option.string)))
return

if _option.string != _segments[0]: # option body does must match non-last segment
return

if len(_option.children) == 0:
return

_built.append(_option.string)
for _child in _option.children:
__impl(_segments[1:], _child, _built, _valid)
_built.pop()

segments = string.split(' ')
valid = []
for option in options:
__impl(segments, option, [], valid)
valid.sort(key=lambda x: x[1])
return list(map(lambda x: x[0],
filter(lambda x: x[1] < 4, # max lev distance before the option is not suggested
valid)))

@staticmethod
def __strict_match(options, string):
def __impl(_segments, _option):
if _option.string != _segments[0]:
return False

if len(_option.children) == 0 and len(_segments) == 1:
return True

if len(_option.children) == 0 or len(_segments) == 1:
return False

return any(map(lambda x: __impl(_segments[1:], x), _option.children))

segments = string.split(' ')
for option in options:
if __impl(segments, option):
return True
return False

@staticmethod
def choose_fill(options, strict=False, head=None, prompt="> ", max_options=5):
if os.name == 'nt': # fix ansi if we are running on Windows
# TODO: detect if windows version does not support ansi flag and fall back to base choose
from ctypes import windll
k = windll.kernel32
k.SetConsoleMode(k.GetStdHandle(-11), 7)

if head is not None:
print(head)

while True:
choice = FancyBase.__choose_fill_impl(options, prompt, max_options)

if not strict or FancyBase.__strict_match(options, choice):
return choice

print("input was not recognised")

@staticmethod
def __choose_fill_impl(options, prompt, max_options):
cursor_x = len(prompt)

FancyBase.__print_init(prompt, max_options)
matching_options = list(map(lambda x: x.string, options))
FancyBase.__print("", matching_options, max_options, 0, cursor_x)

stringbuilder = ""
selected_line = 0
max_line = min(max_options, len(matching_options))
virtual_space = False

while True:
print('', end='', flush=True)
char = readchar.readkey()

if char == readchar.key.ENTER:
if selected_line == 0:
FancyBase.__print__cleanup(stringbuilder, max_options, cursor_x)
return stringbuilder
else:
FancyBase.__print__cleanup(matching_options[selected_line - 1], max_options, cursor_x)
return matching_options[selected_line - 1]

if char == readchar.key.BACKSPACE and len(stringbuilder) > 0:
virtual_space = False
stringbuilder = stringbuilder[:-1]
elif char.isprintable():
if virtual_space:
virtual_space = False
if char != " ":
stringbuilder += " "
stringbuilder += char
elif char == readchar.key.UP and selected_line > 0:
selected_line -= 1
elif char == readchar.key.DOWN and selected_line < max_line:
selected_line += 1
elif char == readchar.key.TAB and max_line > 0:
virtual_space = True
stringbuilder = matching_options[max(0, selected_line - 1)]
selected_line = 0
elif char == readchar.key.CTRL_W:
space_index = stringbuilder.rfind(' ')
selected_line = 0
if space_index == -1:
virtual_space = False
stringbuilder = ""
else:
stringbuilder = stringbuilder[:space_index]

matching_options = FancyBase.__matches(options, stringbuilder if not virtual_space else stringbuilder + " ")

max_line = min(max_options, len(matching_options))
if selected_line > max_line:
selected_line = max_line

FancyBase.__print(stringbuilder, matching_options, max_options, selected_line, cursor_x)
4 changes: 2 additions & 2 deletions src/WithSecure/common/command_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@


class Wrapper(object):

def _execute(self, argv):
@classmethod
def _execute(cls, argv):
if platform.system() != "Windows":
return os.spawnve(os.P_WAIT, argv[0], argv, os.environ)
else:
Expand Down
2 changes: 1 addition & 1 deletion src/WithSecure/common/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# src: http://stackoverflow.com/questions/566746/how-to-get-console-window-width-in-python

def format_dict(values, left_margin=0):
width = {'gutter': 2, 'left_margin': left_margin, 'total': get_size()[0] - left_margin}
width = {'gutter': 2, 'left_margin': left_margin, 'total': get_size()[0] - left_margin - 1}
width['key'] = min([max([len(k) for k in values.keys()] + [0]), width['total'] / 3])
width['value'] = width['total'] - (width['gutter'] + width['key'])

Expand Down
2 changes: 1 addition & 1 deletion src/WithSecure/common/path_completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def complete(path, include_files=True):
folder, search_path = get_folder_and_search_path(path, os.path.sep)
folders = os.listdir(folder)

return [s.replace(" ", "\ ") for s in get_suggestions(folder, search_path, folders, os.path.sep, include_files)]
return [s.replace(" ", r"\ ") for s in get_suggestions(folder, search_path, folders, os.path.sep, include_files)]


def get_folder_and_search_path(path, sep):
Expand Down
4 changes: 2 additions & 2 deletions src/WithSecure/common/stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def replace_color(m):

return "%s%s%s" % (Colors[m.group(1)], m.group(2), Colors['end'])

text = re.sub("\[color\s*([a-z]+)\](.*?)\[\/color\]", replace_color, text)
text = re.sub(r"\[color\s*([a-z]+)\](.*?)\[\/color\]", replace_color, text)

return text

Expand All @@ -122,6 +122,6 @@ def remove_color(m):

return "%s" % (m.group(2))

text = re.sub("\[color\s*([a-z]+)\](.*?)\[\/color\]", remove_color, text)
text = re.sub(r"\[color\s*([a-z]+)\](.*?)\[\/color\]", remove_color, text)

return text
2 changes: 1 addition & 1 deletion src/drozer/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "3.1.0"
__version__ = "3.1.1"
Loading