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

Commit

Permalink
Replacing Jarsigner with Apksigner (#83)
Browse files Browse the repository at this point in the history
* replacement of jarsigner with apksigner

* add verbose option to apksigner and add test case to apksigner

* fix error import JarSigner

* fix error import JarSigner

* fix inserting error keypass

* fix name testool

* fix name testool

* fix name usage apksigner

* remove logger error

* fix readme
  • Loading branch information
Dado1513 authored Apr 12, 2021
1 parent 4b4b369 commit 6d99a9b
Show file tree
Hide file tree
Showing 9 changed files with 133 additions and 127 deletions.
31 changes: 16 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ information.

Make sure to have a recent version of
[`apktool`](https://ibotpeaches.github.io/Apktool/),
[`jarsigner`](https://docs.oracle.com/javase/8/docs/technotes/tools/windows/jarsigner.html)
[`apksigner`](https://developer.android.com/studio/command-line/apksigner)
and [`zipalign`](https://developer.android.com/studio/command-line/zipalign) installed
and available from the command line:

Expand All @@ -161,9 +161,10 @@ Apktool v2.5.0 - a tool for reengineering Android apk files
...
```
```Shell
$ jarsigner
Usage: jarsigner [options] jar-file alias
jarsigner -verify [options] jar-file [alias...]
$ apksigner
Usage: apksigner <command> [options]
apksigner --version
apksigner --help
...
```
```Shell
Expand All @@ -173,10 +174,10 @@ Copyright (C) 2009 The Android Open Source Project
...
```

To install and use `apktool` you need a recent version of Java, which should also have
`jarsigner` bundled. `zipalign` is included in the Android SDK. The location of the
To install and use `apktool` you need a recent version of Java.
`zipalign` and `apksigner` are included in the Android SDK. The location of the
executables can also be specified through the following environment variables:
`APKTOOL_PATH`, `JARSIGNER_PATH` and `ZIPALIGN_PATH` (e.g., in Ubuntu, run
`APKTOOL_PATH`, `APKSIGNER_PATH` and `ZIPALIGN_PATH` (e.g., in Ubuntu, run
`export APKTOOL_PATH=/custom/location/apktool` before running Obfuscapk in the same
terminal).

Expand Down Expand Up @@ -257,7 +258,7 @@ obfuscapk [-h] -o OBFUSCATOR [-w DIR] [-d OUT_APK] [-i] [-p] [-k VT_API_KEY]
There are two mandatory parameters: `<APK_FILE>`, the path (relative or absolute) to
the apk file to obfuscate and the list with the names of the obfuscation techniques to
apply (specified with a `-o` option that can be used multiple times, e.g.,
`-o Rebuild -o NewSignature -o NewAlignment`). The other optional arguments are as
`-o Rebuild -o NewAlignment -o NewSignature`). The other optional arguments are as
follows:

* `-w DIR` is used to set the working directory where to save the intermediate files
Expand Down Expand Up @@ -307,7 +308,7 @@ Let's consider now a simple working example to see how Obfuscapk works:

```Shell
$ # original.apk is a valid Android apk file.
$ obfuscapk -o RandomManifest -o Rebuild -o NewSignature -o NewAlignment original.apk
$ obfuscapk -o RandomManifest -o Rebuild -o NewAlignment -o NewSignature original.apk
```

When running the above command, this is what happens behind the scenes:
Expand All @@ -332,18 +333,18 @@ available and ready to be used
manifest) using `apktool`, and since no output file was specified, the resulting
apk file is saved in the working directory created before

- `NewSignature` obfuscator signs the newly created apk file with a custom
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
- `NewSignature` obfuscator signs the newly created apk file with a custom
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)

* when all the obfuscators have been executed without errors, the resulting obfuscated
apk file can be found in `obfuscation_working_dir/original_obfuscated.apk`, signed,
aligned and ready to be installed into a device/emulator

As seen in the previous example, `Rebuild`, `NewSignature` and `NewAlignment`
As seen in the previous example, `Rebuild`, `NewAlignment` and `NewSignature`
obfuscators are always needed to complete an obfuscation operation, to build the final
obfuscated apk. They are not actual obfuscation techniques, but they are needed in the
build process and so they are included in the list of obfuscators to keep the overall
Expand Down
2 changes: 1 addition & 1 deletion docs/TROUBLESHOOTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ from Docker Hub.
If you are not using the Docker image, make sure to install and setup properly the
additional tools needed for Obfuscapk to work:
[`apktool`](https://ibotpeaches.github.io/Apktool/),
[`jarsigner`](https://docs.oracle.com/javase/8/docs/technotes/tools/windows/jarsigner.html)
[`apksigner`](https://developer.android.com/studio/command-line/apksigner)
and [`zipalign`](https://developer.android.com/studio/command-line/zipalign). Please
ensure to be using a recent release of
[`apktool`](https://ibotpeaches.github.io/Apktool/) (some systems, like Kali Linux,
Expand Down
2 changes: 1 addition & 1 deletion src/obfuscapk/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def main():
-o AssetEncryption -o MethodOverload -o ConstStringEncryption \
-o ResStringEncryption -o ArithmeticBranch -o FieldRename -o Nop -o Goto \
-o ClassRename -o Reflection -o AdvancedReflection -o Reorder -o RandomManifest \
-o Rebuild -o NewSignature -o NewAlignment \
-o Rebuild -o NewAlignment -o NewSignature \
-o VirusTotal -k virus_total_key \
/path/to/original.apk
"""
Expand Down
6 changes: 3 additions & 3 deletions src/obfuscapk/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from obfuscapk import util
from obfuscapk.obfuscation import Obfuscation
from obfuscapk.obfuscator_manager import ObfuscatorManager
from obfuscapk.tool import Apktool, Jarsigner, Zipalign
from obfuscapk.tool import Apktool, Zipalign, ApkSigner

if "LOG_LEVEL" in os.environ:
log_level = os.environ["LOG_LEVEL"]
Expand All @@ -32,13 +32,13 @@ def check_external_tool_dependencies():
"""
Make sure all the external needed tools are available and ready to be used.
"""
# APKTOOL_PATH, JARSIGNER_PATH and ZIPALIGN_PATH environment variables can be
# APKTOOL_PATH, APKSIGNER_PATH and ZIPALIGN_PATH environment variables can be
# used to specify the location of the external tools (make sure they have the
# execute permission). If there is a problem with any of the executables below,
# an exception will be thrown by the corresponding constructor.
logger.debug("Checking external tool dependencies")
Apktool()
Jarsigner()
ApkSigner()
Zipalign()


Expand Down
8 changes: 4 additions & 4 deletions src/obfuscapk/obfuscation.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from typing import List, Union

from obfuscapk import util
from obfuscapk.tool import Apktool, Jarsigner, Zipalign
from obfuscapk.tool import Apktool, Zipalign, ApkSigner


class Obfuscation(object):
Expand Down Expand Up @@ -510,8 +510,8 @@ def sign_obfuscated_apk(self) -> None:

# This method must be called AFTER the obfuscated apk has been built.

# The obfuscated apk will be signed with jarsigner.
jarsigner: Jarsigner = Jarsigner()
# The obfuscated apk will be signed with apksigner.
apksigner: ApkSigner = ApkSigner()

# 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
Expand All @@ -537,7 +537,7 @@ def sign_obfuscated_apk(self) -> None:
)

try:
jarsigner.resign(
apksigner.resign(
self.obfuscated_apk_path,
self.keystore_file,
self.keystore_password,
Expand Down
173 changes: 89 additions & 84 deletions src/obfuscapk/tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,27 +188,98 @@ def build(self, source_dir_path: str, output_apk_path: str = None) -> str:
raise


class Jarsigner(object):
class Zipalign(object):
def __init__(self):
self.logger = logging.getLogger(
"{0}.{1}".format(__name__, self.__class__.__name__)
)

if "JARSIGNER_PATH" in os.environ:
self.jarsigner_path: str = os.environ["JARSIGNER_PATH"]
if "ZIPALIGN_PATH" in os.environ:
self.zipalign_path: str = os.environ["ZIPALIGN_PATH"]
else:
self.jarsigner_path: str = "jarsigner"
self.zipalign_path: str = "zipalign"

full_jarsigner_path = shutil.which(self.jarsigner_path)
full_zipalign_path = shutil.which(self.zipalign_path)

# Make sure to use the full path of the executable (needed for cross-platform
# compatibility).
if full_jarsigner_path is None:
if full_zipalign_path is None:
raise RuntimeError(
'Something is wrong with executable "{0}"'.format(self.jarsigner_path)
'Something is wrong with executable "{0}"'.format(self.zipalign_path)
)
else:
self.jarsigner_path = full_jarsigner_path
self.zipalign_path = full_zipalign_path

def align(self, apk_path: str) -> str:

# Check if the apk file to align is a valid file.
if not os.path.isfile(apk_path):
self.logger.error('Unable to find file "{0}"'.format(apk_path))
raise FileNotFoundError('Unable to find file "{0}"'.format(apk_path))

# Since zipalign cannot be run inplace, a temp file will be created.
apk_copy_path = "{0}.copy.apk".format(
os.path.join(
os.path.dirname(apk_path),
os.path.splitext(os.path.basename(apk_path))[0],
)
)

try:
apk_copy_path = shutil.copy2(apk_path, apk_copy_path)

align_cmd = [
self.zipalign_path,
"-p",
"-v",
"-f",
"4",
apk_copy_path,
apk_path,
]

self.logger.info('Running align command "{0}"'.format(" ".join(align_cmd)))
output = subprocess.check_output(
align_cmd, stderr=subprocess.STDOUT
).strip()
return output.decode(errors="replace")
except subprocess.CalledProcessError as e:
self.logger.error(
"Error during align command: {0}".format(
e.output.decode(errors="replace") if e.output else e
)
)
raise
except Exception as e:
self.logger.error("Error during aligning: {0}".format(e))
raise
finally:
# Remove the temp file used for zipalign.
if os.path.isfile(apk_copy_path):
os.remove(apk_copy_path)


class ApkSigner(object):
def __init__(self):
self.logger = logging.getLogger(
"{0}.{1}".format(__name__, self.__class__.__name__)
)

if "APKSIGNER_PATH" in os.environ:
self.apksigner_path: str = os.environ["APKSIGNER_PATH"]
else:
self.apksigner_path: str = "apksigner"

full_apksigner_path = shutil.which(self.apksigner_path)

# Make sure to use the full path of the executable (needed for cross-platform
# compatibility).
if full_apksigner_path is None:
raise RuntimeError(
'Something is wrong with executable "{0}"'.format(self.apksigner_path)
)
else:
self.apksigner_path = full_apksigner_path

def sign(
self,
Expand All @@ -225,24 +296,21 @@ def sign(
raise FileNotFoundError('Unable to find file "{0}"'.format(apk_path))

sign_cmd: List[str] = [
self.jarsigner_path,
"-tsa",
"http://timestamp.comodoca.com/rfc3161",
"-sigalg",
"SHA1withRSA",
"-digestalg",
"SHA1",
"-keystore",
self.apksigner_path,
"sign",
"-v",
"--ks",
keystore_file_path,
"-storepass",
keystore_password,
apk_path,
"--ks-key-alias",
key_alias,
"--ks-pass",
f"pass:{keystore_password}",
apk_path,
]

if key_password:
sign_cmd.insert(-2, "-keypass")
sign_cmd.insert(-2, key_password)
sign_cmd.insert(-1, "--key-pass")
sign_cmd.insert(-1, f"pass:{key_password}")

try:
self.logger.info('Running sign command "{0}"'.format(" ".join(sign_cmd)))
Expand Down Expand Up @@ -310,66 +378,3 @@ def resign(
return self.sign(
apk_path, keystore_file_path, keystore_password, key_alias, key_password
)


class Zipalign(object):
def __init__(self):
self.logger = logging.getLogger(
"{0}.{1}".format(__name__, self.__class__.__name__)
)

if "ZIPALIGN_PATH" in os.environ:
self.zipalign_path: str = os.environ["ZIPALIGN_PATH"]
else:
self.zipalign_path: str = "zipalign"

full_zipalign_path = shutil.which(self.zipalign_path)

# Make sure to use the full path of the executable (needed for cross-platform
# compatibility).
if full_zipalign_path is None:
raise RuntimeError(
'Something is wrong with executable "{0}"'.format(self.zipalign_path)
)
else:
self.zipalign_path = full_zipalign_path

def align(self, apk_path: str) -> str:

# Check if the apk file to align is a valid file.
if not os.path.isfile(apk_path):
self.logger.error('Unable to find file "{0}"'.format(apk_path))
raise FileNotFoundError('Unable to find file "{0}"'.format(apk_path))

# Since zipalign cannot be run inplace, a temp file will be created.
apk_copy_path = "{0}.copy.apk".format(
os.path.join(
os.path.dirname(apk_path),
os.path.splitext(os.path.basename(apk_path))[0],
)
)

try:
apk_copy_path = shutil.copy2(apk_path, apk_copy_path)

align_cmd = [self.zipalign_path, "-v", "-f", "4", apk_copy_path, apk_path]

self.logger.info('Running align command "{0}"'.format(" ".join(align_cmd)))
output = subprocess.check_output(
align_cmd, stderr=subprocess.STDOUT
).strip()
return output.decode(errors="replace")
except subprocess.CalledProcessError as e:
self.logger.error(
"Error during align command: {0}".format(
e.output.decode(errors="replace") if e.output else e
)
)
raise
except Exception as e:
self.logger.error("Error during aligning: {0}".format(e))
raise
finally:
# Remove the temp file used for zipalign.
if os.path.isfile(apk_copy_path):
os.remove(apk_copy_path)
2 changes: 1 addition & 1 deletion src/test/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def test_valid_basic_command_without_quotes(
# Mock the command line parser.
arguments = cli.get_cmd_args(
"-w {working_dir} -d {destination} "
"-o Rebuild -o NewSignature -o NewAlignment {apk_file}".format(
"-o Rebuild -o NewAlignment -o NewSignature {apk_file}".format(
working_dir=tmp_working_directory_path,
destination=obfuscated_apk_path,
apk_file=tmp_demo_apk_v10_original_path,
Expand Down
2 changes: 1 addition & 1 deletion src/test/test_obfuscation.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ def test_perform_full_obfuscation_valid_apk(
"Reorder",
"RandomManifest",
"Rebuild",
"NewSignature",
"NewAlignment",
"NewSignature",
],
tmp_working_directory_path,
obfuscated_apk_path,
Expand Down
Loading

0 comments on commit 6d99a9b

Please sign in to comment.