Skip to content
This repository has been archived by the owner on Jul 27, 2024. It is now read-only.

Commit

Permalink
Add command line options for the keystore
Browse files Browse the repository at this point in the history
  • Loading branch information
ClaudiuGeorgiu committed May 21, 2020
1 parent 74bef7c commit 7860fc1
Show file tree
Hide file tree
Showing 11 changed files with 236 additions and 20 deletions.
5 changes: 2 additions & 3 deletions .idea/Obfuscapk.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions .idea/webResources.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 16 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,10 @@ Let's start by looking at the help message:

```Shell
$ obfuscapk --help
obfuscapk [-h] -o OBFUSCATOR [-w DIR] [-d OUT_APK] [-i] [-p] [-k VT_API_KEY] <APK_FILE>
obfuscapk [-h] -o OBFUSCATOR [-w DIR] [-d OUT_APK] [-i] [-p] [-k VT_API_KEY]
[--keystore-file KEYSTORE_FILE] [--keystore-password KEYSTORE_PASSWORD]
[--key-alias KEY_ALIAS] [--key-password KEY_PASSWORD]
<APK_FILE>
```

There are two mandatory parameters: `<APK_FILE>`, the path (relative or absolute) to
Expand Down Expand Up @@ -283,6 +286,15 @@ key(s) to be used when communicating with Virus Total. Can be set multiple times
cycle through the API keys during the requests (e.g.,
`-k VALID_VT_KEY_1 -k VALID_VT_KEY_2`).

* `--keystore-file KEYSTORE_FILE`, `--keystore-password KEYSTORE_PASSWORD`,
`--key-alias KEY_ALIAS` and `--key-password KEY_PASSWORD` can be used to specify a
custom keystore (needed for the apk signing). If `--keystore-file` is used,
`--keystore-password` and `--key-alias` must be specified too, while `--key-password`
is needed only if the chosen key has a different password from the keystore password.
By default (when `--keystore-file` is not specified), a
[keystore bundled with Obfuscapk](https://github.com/ClaudiuGeorgiu/Obfuscapk/blob/master/src/obfuscapk/resources/obfuscation_keystore.jks)
is used for the signing operations.

Let's consider now a simple working example to see how Obfuscapk works:

```Shell
Expand Down Expand Up @@ -313,8 +325,9 @@ available and ready to be used
apk file is saved in the working directory created before

- `NewSignature` obfuscator signs the newly created apk file with a custom
certificate contained in
[this keystore](https://github.com/ClaudiuGeorgiu/Obfuscapk/blob/master/src/obfuscapk/resources/obfuscation_keystore.jks)
certificate contained in a
[keystore bundled with Obfuscapk](https://github.com/ClaudiuGeorgiu/Obfuscapk/blob/master/src/obfuscapk/resources/obfuscation_keystore.jks)
(though a different keystore can be specified with the `--keystore-file` parameter)

- `NewAlignment` obfuscator uses `zipalign` tool to align the resulting apk file

Expand Down
44 changes: 44 additions & 0 deletions src/obfuscapk/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,34 @@ def get_cmd_args(args: list = None):
"Can be specified multiple times to use a different API key for each request "
"(cycling through the API keys)",
)
parser.add_argument(
"--keystore-file",
type=str,
metavar="KEYSTORE_FILE",
help="The path to a custom keystore file to be used for signing the obfuscated "
".apk file. By default a keystore bundled with this tool will be used",
)
parser.add_argument(
"--keystore-password",
type=str,
metavar="KEYSTORE_PASSWORD",
help="The password of the custom keystore used for signing the obfuscated .apk "
"file (needed only when specifying a custom keystore file)",
)
parser.add_argument(
"--key-alias",
type=str,
metavar="KEY_ALIAS",
help="The key alias for signing the obfuscated .apk file (needed only when "
"specifying a custom keystore file)",
)
parser.add_argument(
"--key-password",
type=str,
metavar="KEY_PASSWORD",
help="The key password for signing the obfuscated .apk file (needed only when "
"specifying a custom keystore file)",
)
return parser.parse_args(args)


Expand Down Expand Up @@ -118,6 +146,18 @@ def main():
key.strip(" '\"") for key in arguments.virus_total_key
]

if arguments.keystore_file:
arguments.keystore_file = arguments.keystore_file.strip(" '\"")

if arguments.keystore_password:
arguments.keystore_password = arguments.keystore_password.strip(" '\"")

if arguments.key_alias:
arguments.key_alias = arguments.key_alias.strip(" '\"")

if arguments.key_password:
arguments.key_password = arguments.key_password.strip(" '\"")

perform_obfuscation(
arguments.apk_file,
arguments.obfuscator,
Expand All @@ -126,6 +166,10 @@ def main():
arguments.interactive,
arguments.ignore_libs,
arguments.virus_total_key,
arguments.keystore_file,
arguments.keystore_password,
arguments.key_alias,
arguments.key_password,
)


Expand Down
29 changes: 23 additions & 6 deletions src/obfuscapk/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ def perform_obfuscation(
interactive: bool = False,
ignore_libs: bool = False,
virus_total_api_key: List[str] = None,
keystore_file: str = None,
keystore_password: str = None,
key_alias: str = None,
key_password: str = None,
):
"""
Apply the obfuscation techniques to an input application and generate an obfuscated
Expand All @@ -69,6 +73,17 @@ def perform_obfuscation(
obfuscation operations.
:param virus_total_api_key: A list containing Virus Total API keys, needed only
when using Virus Total obfuscator.
:param keystore_file: The path to a custom keystore file to be used for signing the
resulting obfuscated application. If not provided, a default
keystore bundled with this tool will be used instead.
:param keystore_password: The password of the custom keystore used for signing the
resulting obfuscated application (needed only when
specifying a custom keystore file).
:param key_alias: The key alias for signing the resulting obfuscated application
(needed only when specifying a custom keystore file).
:param key_password: The key password for signing the resulting obfuscated
application (needed only when specifying a custom keystore
file).
"""

check_external_tool_dependencies()
Expand All @@ -83,9 +98,13 @@ def perform_obfuscation(
input_apk_path,
working_dir_path,
obfuscated_apk_path,
interactive=interactive,
ignore_libs=ignore_libs,
virus_total_api_key=virus_total_api_key,
interactive,
ignore_libs,
virus_total_api_key,
keystore_file,
keystore_password,
key_alias,
key_password,
)

manager = ObfuscatorManager()
Expand Down Expand Up @@ -124,7 +143,5 @@ def perform_obfuscation(
)
(obfuscator_name_to_function[obfuscator_name])(obfuscation)
except Exception as e:
logger.critical(
"Error during obfuscation: {0}".format(e), exc_info=True
)
logger.critical("Error during obfuscation: {0}".format(e), exc_info=True)
raise
40 changes: 35 additions & 5 deletions src/obfuscapk/obfuscation.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ def __init__(
ignore_libs: bool = False,
interactive: bool = False,
virus_total_api_key: List[str] = None,
keystore_file: str = None,
keystore_password: str = None,
key_alias: str = None,
key_password: str = None,
):
self.logger = logging.getLogger(__name__)

Expand All @@ -34,6 +38,10 @@ def __init__(
self.ignore_libs: bool = ignore_libs
self.interactive: bool = interactive
self.virus_total_api_key: List[str] = virus_total_api_key
self.keystore_file: str = keystore_file
self.keystore_password: str = keystore_password
self.key_alias: str = key_alias
self.key_password: str = key_password

# Random string (32 chars long) generation with ASCII letters and digits
self.encryption_secret = "".join(
Expand Down Expand Up @@ -503,14 +511,36 @@ def sign_obfuscated_apk(self) -> None:
# The obfuscated apk will be signed with jarsigner.
jarsigner: Jarsigner = Jarsigner()

# If a custom keystore file is not provided, use the default one bundled with
# the tool. Otherwise check that the keystore password and a key alias are
# provided along with the custom keystore.
if not self.keystore_file:
self.keystore_file = os.path.join(
os.path.dirname(__file__), "resources", "obfuscation_keystore.jks"
)
self.keystore_password = "obfuscation_password"
self.key_alias = "obfuscation_key"
else:
if not os.path.isfile(self.keystore_file):
self.logger.error(
'Unable to find keystore file "{0}"'.format(self.keystore_file)
)
raise FileNotFoundError(
'Unable to find keystore file "{0}"'.format(self.keystore_file)
)
if not self.keystore_password or not self.key_alias:
raise ValueError(
"When using a custom keystore file, keystore password and key "
"alias must be provided too"
)

try:
jarsigner.resign(
self.obfuscated_apk_path,
os.path.join(
os.path.dirname(__file__), "resources", "obfuscation_keystore.jks"
),
"obfuscation_password",
"obfuscation_key",
self.keystore_file,
self.keystore_password,
self.key_alias,
self.key_password,
)
except Exception as e:
self.logger.error("Error during apk signing: {0}".format(e))
Expand Down
1 change: 1 addition & 0 deletions src/obfuscapk/resources/libs_to_ignore.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ android/content/
android/opengl/
android/support/
android/widget/
androidx/
anet/channel/
anetwork/channel/
anywheresoftware/
Expand Down
10 changes: 9 additions & 1 deletion src/obfuscapk/tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ def sign(
keystore_file_path: str,
keystore_password: str,
key_alias: str,
key_password: str = None,
) -> str:

# Check if the apk file to sign is a valid file.
Expand All @@ -220,6 +221,10 @@ def sign(
key_alias,
]

if key_password:
sign_cmd.insert(-2, "-keypass")
sign_cmd.insert(-2, key_password)

try:
self.logger.info('Running sign command "{0}"'.format(" ".join(sign_cmd)))
output = subprocess.check_output(sign_cmd, stderr=subprocess.STDOUT).strip()
Expand All @@ -241,6 +246,7 @@ def resign(
keystore_file_path: str,
keystore_password: str,
key_alias: str,
key_password: str = None,
) -> str:

# If present, delete the old signature of the apk and then sign it with the
Expand Down Expand Up @@ -282,7 +288,9 @@ def resign(
)
raise

return self.sign(apk_path, keystore_file_path, keystore_password, key_alias)
return self.sign(
apk_path, keystore_file_path, keystore_password, key_alias, key_password
)


class Zipalign(object):
Expand Down
35 changes: 35 additions & 0 deletions src/test/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,41 @@ def test_valid_basic_command_with_quotes(

assert os.path.isfile(obfuscated_apk_path)

def test_valid_basic_command_with_custom_keystore(
self,
tmp_working_directory_path: str,
tmp_demo_apk_v10_original_path: str,
monkeypatch,
):
obfuscated_apk_path = os.path.join(tmp_working_directory_path, "obfuscated.apk")

# Mock the command line parser.
arguments = cli.get_cmd_args(
"-w {working_dir} -d {destination} "
"-o Rebuild -o NewSignature -o NewAlignment "
"--keystore-file {keystore_file} --keystore-password {keystore_password} "
"--key-alias {key_alias} --key-password {key_password} {apk_file}".format(
working_dir=tmp_working_directory_path,
destination=obfuscated_apk_path,
apk_file=tmp_demo_apk_v10_original_path,
keystore_file=os.path.join(
os.path.dirname(__file__),
os.path.pardir,
"obfuscapk",
"resources",
"obfuscation_keystore.jks",
),
keystore_password="obfuscation_password",
key_alias="obfuscation_key",
key_password="obfuscation_password",
).split()
)
monkeypatch.setattr(cli, "get_cmd_args", lambda: arguments)

cli.main()

assert os.path.isfile(obfuscated_apk_path)

def test_missing_required_parameters(self, monkeypatch):
# Mock the command line parser.
original = cli.get_cmd_args
Expand Down
Loading

0 comments on commit 7860fc1

Please sign in to comment.