Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions sigtool/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@
"PEMCertificateGenerator",
"ColonUpperCase",
"OutputFormatter",
"PEMSignatureExtractor"
]
"PEMSignatureExtractor",
]
23 changes: 12 additions & 11 deletions sigtool/apk_info_extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import subprocess


class APKInfoExtractor:
def __init__(self, apk_path: str):
self.apk_path = apk_path
Expand All @@ -10,23 +11,23 @@ def get_apk_info(self) -> dict:
result = subprocess.run(
["aapt", "dump", "badging", self.apk_path],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
stderr=subprocess.PIPE,
)
if result.returncode != 0:
raise RuntimeError(f"aapt error: {result.stderr.decode()}")

output = result.stdout.decode()
app_info = {}
for line in output.split('\n'):
if line.startswith('package:'):
for line in output.split("\n"):
if line.startswith("package:"):
package_info = line.split()
for info in package_info:
if info.startswith('name='):
app_info['package_name'] = info.split('=')[1].strip("'")
elif info.startswith('versionCode='):
app_info['version_code'] = info.split('=')[1].strip("'")
elif info.startswith('versionName='):
app_info['version_name'] = info.split('=')[1].strip("'")
elif line.startswith('application:'):
app_info['app_name'] = line.split("label='")[1].split("'")[0]
if info.startswith("name="):
app_info["package_name"] = info.split("=")[1].strip("'")
elif info.startswith("versionCode="):
app_info["version_code"] = info.split("=")[1].strip("'")
elif info.startswith("versionName="):
app_info["version_name"] = info.split("=")[1].strip("'")
elif line.startswith("application:"):
app_info["app_name"] = line.split("label='")[1].split("'")[0]
return app_info
37 changes: 20 additions & 17 deletions sigtool/apk_signature_extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from .pem_signature_extractor import PEMSignatureExtractor


class APKSignatureExtractor:
def __init__(self, apk_path):
self.apk_path = apk_path
Expand All @@ -21,41 +22,43 @@ def _find_eocd(self, file):
seek_offset = -min(65536, file_size)
file.seek(seek_offset, 2)
data = file.read(65536)
eocd_offset = data.rfind(b'PK\x05\x06')
eocd_offset = data.rfind(b"PK\x05\x06")
if eocd_offset == -1:
raise ValueError("Invalid APK: End of Central Directory signature not found")
eocd = data[eocd_offset:eocd_offset + 22]
return struct.unpack('<I', eocd[16:20])[0]
raise ValueError(
"Invalid APK: End of Central Directory signature not found"
)
eocd = data[eocd_offset : eocd_offset + 22]
return struct.unpack("<I", eocd[16:20])[0]
except Exception as e:
print(f"Error in _find_eocd: {repr(e)}")
raise e

def _find_apk_signing_block(self, file, cd_offset):
file.seek(cd_offset - 24)
block_size = struct.unpack('<Q', file.read(8))[0]
block_size = struct.unpack("<Q", file.read(8))[0]
magic = file.read(16)
if magic != b'APK Sig Block 42':
if magic != b"APK Sig Block 42":
raise ValueError("Invalid APK: APK Signing Block not found")
return block_size

def _extract_first_signature(self, signing_block):
index = 0
while index < len(signing_block):
index = signing_block.find(b'\x30\x82', index)
index = signing_block.find(b"\x30\x82", index)
if index == -1:
return None
length = struct.unpack_from('>H', signing_block, index + 2)[0]
length = struct.unpack_from(">H", signing_block, index + 2)[0]
sig_length = 4 + length
if index + sig_length <= len(signing_block):
return signing_block[index:index + sig_length]
return signing_block[index : index + sig_length]
index += sig_length
return None

def _extract_rsa(self):
try:
with zipfile.ZipFile(self.apk_path, 'r') as zip_file:
with zipfile.ZipFile(self.apk_path, "r") as zip_file:
for file_name in zip_file.namelist():
if file_name.startswith('META-INF/') and file_name.endswith('.RSA'):
if file_name.startswith("META-INF/") and file_name.endswith(".RSA"):
with zip_file.open(file_name) as rsa_file:
return rsa_file.read()
return None
Expand All @@ -68,11 +71,11 @@ def _extract_v1_signature(self):
rsa_file = self._extract_rsa()
if rsa_file is None:
return "No RSA file found"

with tempfile.NamedTemporaryFile(delete=False) as temp_rsa_file:
temp_rsa_file.write(rsa_file)
temp_rsa_path = temp_rsa_file.name

try:
rsa_extractor = PEMSignatureExtractor()
pem_data = rsa_extractor.convert_rsa_to_pem(temp_rsa_path)
Expand All @@ -81,10 +84,10 @@ def _extract_v1_signature(self):
return signature_hex
finally:
os.remove(temp_rsa_path)

def extract_signatures(self):
try:
with open(self.apk_path, 'rb') as file:
with open(self.apk_path, "rb") as file:
try:
cd_offset = self._find_eocd(file)
block_size = self._find_apk_signing_block(file, cd_offset)
Expand All @@ -104,4 +107,4 @@ def extract_signatures(self):
except FileNotFoundError:
return "Error: APK file not found"
except Exception as e:
return f"Unexpected error: {str(e)}"
return f"Unexpected error: {str(e)}"
7 changes: 4 additions & 3 deletions sigtool/base64_encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,22 @@

import base64


class Base64Encoder:
def __init__(self, signature_hex, hashes=None):
self.signature_hex = signature_hex
self.signature_bytes = bytes.fromhex(signature_hex)
self.hashes = hashes

def encode_signature(self):
encoded_signature = base64.b64encode(self.signature_bytes).decode('utf-8')
encoded_signature = base64.b64encode(self.signature_bytes).decode("utf-8")
return encoded_signature

def encode_hashes(self):
if self.hashes is None:
raise ValueError("Hashes are not provided.")
encoded_hashes = {
hash_type: base64.b64encode(bytes.fromhex(hash_value)).decode('utf-8')
hash_type: base64.b64encode(bytes.fromhex(hash_value)).decode("utf-8")
for hash_type, hash_value in self.hashes.items()
}
return encoded_hashes
return encoded_hashes
9 changes: 5 additions & 4 deletions sigtool/colon_uppercase.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# -*- coding: utf-8 -*-


class ColonUpperCase:
def __init__(self, apply_colons=False, convert_uppercase=False):
self.apply_colons = apply_colons
self.convert_uppercase = convert_uppercase

def add_colons_to_hex(self, hex_string: str) -> str:
if self.apply_colons:
return ':'.join(hex_string[i:i+2] for i in range(0, len(hex_string), 2))
return ":".join(hex_string[i : i + 2] for i in range(0, len(hex_string), 2))
return hex_string

def convert_to_uppercase(self, string: str) -> str:
Expand All @@ -16,8 +17,8 @@ def convert_to_uppercase(self, string: str) -> str:
return string.lower()

def convert_crc32_and_hashcode(self, value: str) -> str:
if value.startswith('0x'):
return '0x' + self.convert_to_uppercase(value[2:])
if value.startswith("0x"):
return "0x" + self.convert_to_uppercase(value[2:])
return self.convert_to_uppercase(value)

def process_signature_hashes(self, hashes: dict) -> dict:
Expand All @@ -34,4 +35,4 @@ def process_crc32_and_hashcode(self, results: dict) -> dict:
processed_results[key] = self.convert_crc32_and_hashcode(value)
else:
processed_results[key] = value
return processed_results
return processed_results
9 changes: 6 additions & 3 deletions sigtool/crc32_and_hashcode_calculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import zlib


class CRC32AndHashCodeCalculator:
def __init__(self, signature_hex):
self.signature_hex = signature_hex
Expand Down Expand Up @@ -32,11 +33,13 @@ def _calculate_hash_code(self):

def calculate_crc32_and_hash_code(self):
crc32 = self._calculate_crc32()
crc32 = f"0x{crc32:08x} ({crc32 - 0x100000000 if crc32 >= 0x80000000 else crc32})"

crc32 = (
f"0x{crc32:08x} ({crc32 - 0x100000000 if crc32 >= 0x80000000 else crc32})"
)

hashCode = self._calculate_hash_code()

return {
"CRC32": crc32,
"hashCode": hashCode,
}
}
Loading