Commit f25c3f74 authored by wanglei's avatar wanglei

[打包]提交混淆脚本

parent 8f8f01a0
import requests
import json
import os
import re
def fetch_app_info(pkg):
"""向指定 URL 发送请求并获取内容"""
base_url = "http://data-api.zhangxinhulian.com/anonymous/getappinfoAndroid"
params = {"pkg": pkg}
key_mapping = {
"packageName": "PACKAGE_NAME",
"urlEvent": "URL_EVENT",
"urlApi": "URL_API",
"urlPrivacy": "URL_PRIVACY",
"urlUse": "URL_USE",
"keyAes": "KEY_AES",
"keySolar": "KEY_SOLAR",
"keyMax": "KEY_MAX",
"idAdmobInter": "ID_ADMOB_INTER",
"idAdmobNative": "ID_ADMOB_NATIVE",
"idAdmobOpen": "ID_ADMOB_OPEN",
"idAdmobBanner": "ID_ADMOB_BANNER",
"idAdmobReward": "ID_ADMOB_REWARD",
"idMaxInter": "ID_MAX_INTER",
"idMaxNative": "ID_MAX_NATIVE",
"idMaxOpen": "ID_MAX_OPEN",
"idMaxBanner": "ID_MAX_BANNER",
"idMaxReward": "ID_MAX_REWARD",
# "idMaxReward": "ID_ADMOB_APPLICATION"
}
try:
response = requests.get(base_url, params=params, timeout=10)
if response.status_code == 200:
data = response.json()
if data.get("status") == 200 and data.get("result") and data["result"].get("data"):
app_data = data["result"]["data"]
# 设置 packageName 为用户输入的 pkg
app_data["packageName"] = pkg
# 将 null 转换为 ""
app_data = {key: (value if value is not None else "") for key, value in app_data.items()}
# 打印从服务器获取到的数据字段
print("从服务器获取到的数据字段值:")
for key, value in app_data.items():
print(f"{key}: {value}")
# 替换 key
transformed_data = {key_mapping.get(key, key): value for key, value in app_data.items()}
# 写入 JSON 文件
with open("applicationid.json", "w", encoding="utf-8") as file:
json.dump(transformed_data, file, ensure_ascii=False, indent=4)
with open("applicationid.json", "r", encoding="utf-8") as file:
json_data = json.load(file)
# 打印 applicationid.json 文件中的数据
print("\napplicationid.json 中的数据:")
print(json_data)
print("数据已成功以 JSON 格式写入 applicationid.json 文件。")
else:
print(f"未找到有效的 data 数据: {data}")
else:
print(f"请求失败,状态码: {response.status_code}, 响应内容: {response.text}")
except requests.RequestException as e:
print(f"请求发生错误: {e}")
def find_global_config(directory):
"""遍历目录查找 GlobalConfig.kt 文件"""
for root, dirs, files in os.walk(directory):
if 'GlobalConfig.kt' in files:
file_path = os.path.join(root, 'GlobalConfig.kt')
print(f"找到 GlobalConfig.kt 文件: {file_path}")
return file_path
print("未找到 GlobalConfig.kt 文件")
return None
def update_global_config(file_path, json_data):
"""更新 GlobalConfig.kt 文件中的变量值"""
try:
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()
# 获取 json_data 中的 adsApplicationId
ads_application_id = json_data.get("adsApplicationId")
if ads_application_id:
# 使用正则表达式查找并替换 ID_ADMOB_APPLICATION 的值
pattern = r'\binline\s+val\s+ID_ADMOB_APPLICATION\s+get\(\)\s*=\s*.*'
replacement = f'inline val ID_ADMOB_APPLICATION get() = "{ads_application_id}"'
content = re.sub(pattern, replacement, content)
facebook_app_id = json_data.get("facebookAppId")
if facebook_app_id:
# 使用正则表达式查找并替换 ID_ADMOB_APPLICATION 的值
pattern = r'\binline\s+val\s+ID_FACEBOOK\s+get\(\)\s*=\s*.*'
replacement = f'inline val ID_FACEBOOK get() = "{facebook_app_id}"'
content = re.sub(pattern, replacement, content)
# 遍历 JSON 数据中的每个变量名,并在 GlobalConfig.kt 中更新对应的值
for var_name, var_value in json_data.items():
# 替换 const val 的值
pattern_const = rf'\bconst\s+val\s+{re.escape(var_name)}\s*=\s*.*'
replacement_const = f'const val {var_name} = "{var_value}"'
content = re.sub(pattern_const, replacement_const, content)
pattern_inline = rf'\binline\s+val\s+{re.escape(var_name)}\s+get\(\)\s*=\s*.*'
replacement_inline = f'inline val {var_name} get() = "{var_value}"'
content = re.sub(pattern_inline, replacement_inline, content)
# 将修改后的内容写回到 GlobalConfig.kt 文件
with open(file_path, 'w', encoding='utf-8') as file:
file.write(content)
print("\nGlobalConfig.kt 文件已成功更新。")
except Exception as e:
print(f"更新文件时发生错误: {e}")
def update_google_services(json_data):
"""更新 google-services.json 文件内容"""
try:
google_services_path = os.path.join(os.getcwd(), "google-services.json")
if os.path.exists(google_services_path):
print(f"找到 google-services.json 文件: {google_services_path}")
google_services_raw = json_data.get("googleServices")
package_name = json_data.get("PACKAGE_NAME")
if google_services_raw:
try:
# 如果是 URL,就去下载内容
if google_services_raw.startswith("https://") or google_services_raw.startswith("http://"):
print(f"检测到 URL,开始下载: {google_services_raw}")
response = requests.get(google_services_raw, verify=False)
response.raise_for_status()
google_services_raw = response.text
# 反序列化
google_services_data = json.loads(google_services_raw)
# 写入文件
with open(google_services_path, "w", encoding="utf-8") as file:
json.dump(google_services_data, file, ensure_ascii=False, indent=4)
# 打印写入内容
with open(google_services_path, "r", encoding="utf-8") as file:
written_data = file.read()
print("\ngoogle-services.json 文件已更新,写入的内容如下:")
print(written_data)
except requests.RequestException as e:
print(f"下载 googleServices URL 失败: {e}")
except json.JSONDecodeError as e:
print(f"googleServices 值的反序列化失败: {e}")
else:
if package_name:
print("googleServices 的值为空,尝试更新 package_name。")
with open(google_services_path, "r", encoding="utf-8") as file:
google_services_data = json.load(file)
if "client" in google_services_data:
for client in google_services_data["client"]:
if "client_info" in client and "android_client_info" in client["client_info"]:
client["client_info"]["android_client_info"]["package_name"] = package_name
print(f"已将 package_name 更新为: {package_name}")
with open(google_services_path, "w", encoding="utf-8") as file:
json.dump(google_services_data, file, ensure_ascii=False, indent=4)
print("\ngoogle-services.json 文件已更新(更新 package_name)。")
else:
print("未找到 PACKAGE_NAME 值,无法更新 package_name。")
else:
print("未找到 google-services.json 文件。")
except Exception as e:
print(f"更新 google-services.json 文件时发生错误: {e}")
def update_build_gradle(json_data):
"""更新 build.gradle 或 build.gradle.kts 文件中的 applicationId 值"""
try:
# 查找 build.gradle.kts 文件
build_gradle_kts_path = os.path.join(os.getcwd(), "build.gradle.kts")
build_gradle_path = os.path.join(os.getcwd(), "build.gradle")
build_file_path = None
if os.path.exists(build_gradle_kts_path):
build_file_path = build_gradle_kts_path
print(f"找到 build.gradle.kts 文件: {build_file_path}")
elif os.path.exists(build_gradle_path):
build_file_path = build_gradle_path
print(f"找到 build.gradle 文件: {build_file_path}")
else:
print("未找到 build.gradle 或 build.gradle.kts 文件。")
return
# 从 applicationid.json 中获取 PACKAGE_NAME 的值
package_name = json_data.get("PACKAGE_NAME")
if package_name:
with open(build_file_path, "r", encoding="utf-8") as file:
content = file.read()
# 根据文件名选择不同的正则表达式
if build_file_path.endswith(".kts"):
# 匹配 applicationId 的值,无论其格式如何(字符串或变量引用)
pattern = r'applicationId\s*=\s*[^\s]+'
replacement = f'applicationId = "{package_name}"'
new_content = re.sub(pattern, replacement, content)
else: # build.gradle
pattern = r'applicationId\s*\'[^\']+\'' # 匹配单引号字符串
replacement = f'applicationId \'{package_name}\''
new_content = re.sub(pattern, replacement, content)
if new_content == content: # 如果单引号没有匹配到则匹配双引号字符串
pattern = r'applicationId\s*"[^"]+"'
replacement = f'applicationId "{package_name}"'
new_content = re.sub(pattern, replacement, content)
# 写入更新后的内容
with open(build_file_path, "w", encoding="utf-8") as file:
file.write(new_content)
# 打印更新后的内容
print(f"\n{os.path.basename(build_file_path)} 文件已更新,更新后的内容如下:")
print(new_content)
else:
print("applicationid.json 中未找到 PACKAGE_NAME 的值。")
except Exception as e:
print(f"更新 {os.path.basename(build_file_path)} 文件时发生错误: {e}")
def update_android_manifest(pkg):
'''更新 AndroidManifest.xml 文件中的 Facebook App ID'''
try:
# 查找 AndroidManifest.xml 文件
file_path = os.path.join('.', 'src', 'main', 'AndroidManifest.xml')
if os.path.exists(file_path):
print(f"找到 AndroidManifest.xml 文件: {file_path}")
# 从 applicationid.json 中获取 facebookAppId 的值
facebookAppId = json_data.get("facebookAppId")
if facebookAppId:
with open(file_path, "r", encoding="utf-8") as file:
content = file.read()
# 替换 facebookAppId 的值,无论其格式如何(字符串或变量引用)
pattern = r'<meta-data\s*android:name="com\.facebook\.sdk\.ApplicationId"\s*android:value\s*=\s*[^\s]+'
replacement = f'<meta-data android:name="com.facebook.sdk.ApplicationId"\n android:value = "{facebookAppId}"'
new_content = re.sub(pattern, replacement, content)
# 写入更新后的内容
with open(file_path, "w", encoding="utf-8") as file:
file.write(new_content)
print("\nAndroidManifest.xml 文件已更新,facebookAppId 更新后的内容如下:")
print(new_content)
else:
print("applicationid.json 中未找到 facebookAppId 的值。")
# 从 applicationid.json 中获取 adsApplicationId 的值
adsApplicationId = json_data.get("adsApplicationId")
if adsApplicationId:
with open(file_path, "r", encoding="utf-8") as file:
content = file.read()
pattern = r'<meta-data\s*android:name="com\.google\.android\.gms\.ads\.APPLICATION_ID"\s*android:value\s*=\s*[^\s]+'
replacement = f'<meta-data android:name="com.google.android.gms.ads.APPLICATION_ID"\n android:value = "{adsApplicationId}"'
new_content = re.sub(pattern, replacement, content)
# 写入更新后的内容
with open(file_path, "w", encoding="utf-8") as file:
file.write(new_content)
print("\nAndroidManifest.xml 文件已更新,adsApplicationId 更新后的内容如下:")
print(new_content)
else:
print("applicationid.json 中未找到 adsApplicationId 的值。")
# 从 applicationid.json 中获取 PACKAGE_NAME 的值
PACKAGE_NAME = json_data.get("PACKAGE_NAME")
if PACKAGE_NAME:
with open(file_path, "r", encoding="utf-8") as file:
content = file.read()
# 更宽松的正则表达式模式
pattern = r"""
android:permission="com\.google\.android\.c2dm\.permission\.SEND"\s*>
\s*<intent-filter>\s*
\s*<action\s+android:name="com\.google\.firebase\.MESSAGING_EVENT"\s*/>\s*
\s*<action\s+android:name="com\.google\.android\.c2dm\.intent\.RECEIVE"\s*/>\s*
\s*<category\s+android:name="([^"]+)"\s*/>\s* # 捕获 category 值
\s*</intent-filter>
"""
replacement = f"""
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="{PACKAGE_NAME}" />
</intent-filter>
"""
# 执行替换操作,使用 re.VERBOSE 忽略空白和注释,使用 re.DOTALL 匹配换行
new_content = re.sub(pattern, replacement, content, flags=re.VERBOSE | re.DOTALL)
# 判断是否匹配到并进行了替换
if new_content != content:
print("替换成功,内容已更新。")
# 将修改后的内容写回文件
with open(file_path, 'w', encoding='utf-8') as file:
file.write(new_content)
else:
print("没有找到匹配的内容,文件未更改。")
# pattern = r"""
# android:permission="com.google.android.c2dm.permission.SEND">
# <intent-filter>(.*?)<category android:name="\s*([^"]+)"
# """
#
# replacement = f"""
# android:permission="com.google.android.c2dm.permission.SEND">
# <intent-filter>\1
# <category android:name="{PACKAGE_NAME}"
# """
#
# #pattern = r'android:permission="com.google.android.c2dm.permission.SEND">'
# #r'<intent-filter>'
# #r'<action android:name="com.google.firebase.MESSAGING_EVENT" />'
# #r'<action android:name="com.google.android.c2dm.intent.RECEIVE" />'
#
# #r'<category android:name\s*=\s*[^\s]+'
# #replacement = f' android:permission="com.google.android.c2dm.permission.SEND">'
# #f'<intent-filter>'
# #f'<action android:name="com.google.firebase.MESSAGING_EVENT" />'
# #f'<action android:name="com.google.android.c2dm.intent.RECEIVE" /> android:name = "{PACKAGE_NAME}"'
# new_content = re.sub(pattern, replacement, content)
# 写入更新后的内容
with open(file_path, "w", encoding="utf-8") as file:
file.write(new_content)
print("\nAndroidManifest.xml 文件已更新,c2dm.permission.SEND 更新后的内容如下:")
print(new_content)
else:
print("applicationid.json 中未找到 PACKAGE_NAME 的值。")
else:
print("未找到 AndroidManifest.xml 文件。")
except Exception as e:
print(f"更新 AndroidManifest.xml 文件时发生错误: {e}")
if __name__ == "__main__":
pkg_input = input("请输入 pkg 的值:")
if pkg_input.strip():
fetch_app_info(pkg_input.strip())
# 查找 GlobalConfig.kt 文件
java_src_dir = os.path.join(os.getcwd(), "src", "main", "java")
global_config_path = find_global_config(java_src_dir)
# 读取 applicationid.json 文件并获取数据
if global_config_path and os.path.exists("applicationid.json"):
with open("applicationid.json", "r", encoding="utf-8") as file:
json_data = json.load(file)
# 更新 GlobalConfig.kt 文件
update_global_config(global_config_path, json_data)
# 更新 google-services.json 文件
update_google_services(json_data)
# 更新 build.gradle.kts 文件
update_build_gradle(json_data)
# 更新 AndroidManifest.xml
update_android_manifest(json_data["PACKAGE_NAME"])
else:
print("pkg 不能为空!")
import json
import os
import random
import re
import shutil
from base64 import b64encode, b64decode
import cv2
import numpy as np
import regex as re2
# from Cryptodome.Cipher import AES
# from Cryptodome.Random import get_random_bytes
# import chardet
import string
import random
def detect_encoding(file_path):
with open(file_path, 'rb') as f:
raw_data = f.read(1024)
result = chardet.detect(raw_data)
return result['encoding']
def get_classes_impl(result, class_type, path='.'):
print("get_classes_impl start")
listdir = os.listdir(path)
for i in listdir:
print(i)
path_join = os.path.join(path, i)
if os.path.isdir(path_join):
if path.endswith('.') and i in ["build", "debug", "release"]:
continue
if path.endswith('src') and i in ["androidTest", "test"]:
continue
get_classes_impl(result, class_type, path_join)
elif os.path.isfile(path_join):
if os.path.splitext(i)[-1][1:] in class_type:
result.append(path_join)
print(result)
print("get_classes_impl over")
def get_classes(class_type=None):
print("get_classes start")
if class_type is None:
class_type = ["kt", "java"]
result = []
get_classes_impl(result, class_type)
print(result)
print("get_classes over")
return result
def find_text_impl(reg, result, skip_name, path, is_one):
listdir = os.listdir(path)
for i in listdir:
if is_one and len(result) > 0:
return
path_join = os.path.join(path, i)
if os.path.isdir(path_join):
if path.endswith('.') and i in ["build", "debug", "release"]:
continue
if path.endswith('src') and i in ["androidTest", "test"]:
continue
find_text_impl(reg, result, skip_name, path_join, is_one)
elif os.path.isfile(path_join):
if path.endswith('.') and i.split('.')[-1] in ['py']:
continue
is_skip = False
for j in skip_name:
if j in i:
is_skip = True
break
if is_skip:
continue
with open(path_join, 'r', encoding='utf-8') as f:
if not f.readable():
continue
try:
text = f.read()
except ValueError:
continue
findall = re.findall(reg, text)
if len(findall) > 0:
result.extend(findall)
def find_text(reg, path='.', is_one=False, skip_name=None):
if skip_name is None:
skip_name = []
result = []
if os.path.isdir(path):
find_text_impl(reg, result, skip_name, path, is_one)
else:
with open(path, 'r', encoding='utf-8') as f:
if f.readable():
try:
text = f.read()
findall = re.findall(reg, text)
if len(findall) > 0:
result.extend(findall)
except ValueError:
pass
if is_one:
if len(result) > 0:
return result[0]
else:
return None
return result
def find_file_impl(reg, result, skip_name, path='.'):
listdir = os.listdir(path)
for i in listdir:
if len(result) > 0:
return
path_join = os.path.join(path, i)
if os.path.isdir(path_join):
if path.endswith('.') and i in ["build", "debug", "release"]:
continue
if path.endswith('src') and i in ["androidTest", "test"]:
continue
find_file_impl(reg, result, skip_name, path_join)
elif os.path.isfile(path_join):
if path.endswith('.') and i not in ["pro"]:
continue
is_skip = False
for j in skip_name:
if j in i:
is_skip = True
break
if is_skip:
continue
with open(path_join, 'r', encoding='utf-8') as f:
if not f.readable():
continue
try:
text = f.read()
except ValueError:
continue
findall = re.findall(reg, text)
if len(findall) > 0:
result.append(path_join)
def find_file(reg, path='.', skip_name=None):
print(find_file)
if skip_name is None:
skip_name = []
result = []
if os.path.isdir(path):
find_file_impl(reg, result, skip_name, path)
else:
with open(path, 'r', encoding='utf-8') as f:
if f.readable():
try:
text = f.read()
findall = re.findall(reg, text)
if len(findall) > 0:
result.append(path)
except ValueError:
pass
return result[0] if len(result) > 0 else ""
def get_class_path(path):
with open(path, 'r') as f:
text = f.read()
package = re.search('package (.*?)\\s', text).group(1)
name = os.path.basename(path)
name = name[:name.rfind('.')]
return package + '.' + name
def replace_map_text(mapping, path='.'):
listdir = os.listdir(path)
for i in listdir:
path_join = os.path.join(path, i)
if os.path.isdir(path_join):
if path.endswith('.') and i in ["build", "debug", "release"]:
continue
if path.endswith('src') and i in ["androidTest", "test"]:
continue
replace_map_text(mapping, path_join)
elif os.path.isfile(path_join):
if path.endswith('.') and os.path.splitext(i)[-1][1:] not in ["pro"]:
continue
with open(path_join, 'r', encoding='utf-8') as f:
if not f.readable():
continue
try:
text = f.read()
except ValueError:
continue
with open(path_join, 'w', encoding='utf-8') as f:
reverse = sorted(list(mapping.keys()), key=len, reverse=True)
for name in reverse:
# text = text.replace(str(name), mapping[name])
text_result = re.sub(rf'\b{name}\b', mapping[name], text_result)
f.write(text)
# 修复某些字符无法匹配的问题,如android:OEM
def sub_map_text(mapping, is_reverse=True, path='.', skip_type=None, skip_name=None):
print("sub_map_text start")
if len(mapping) == 0:
return []
if skip_type is None:
skip_type = []
if skip_name is None:
skip_name = []
skip_name.append('databinding') # 跳过 databinding
result = []
sub_map_text_impl(mapping, result, skip_type, skip_name, is_reverse, path)
print(result)
print("sub_map_text over")
return result
# def sub_map_text_impl(mapping, result, skip_type, skip_name, is_reverse=True, path='.'):
# print("sub_map_text_impl start")
# listdir = os.listdir(path)
# for i in listdir:
# path_join = os.path.join(path, i)
# if os.path.isdir(path_join):
# if path.endswith('.') and i in ["build", "debug", "release"]:
# continue
# if path.endswith('src') and i in ["androidTest", "test"]:
# continue
# sub_map_text_impl(mapping, result, skip_type, skip_name, is_reverse, path_join)
# elif os.path.isfile(path_join):
# if path.endswith('.') and os.path.splitext(i)[-1][1:] not in ["pro"]:
# continue
# if os.path.splitext(i)[-1][1:] in skip_type:
# continue
# is_skip = False
# for j in skip_name:
# if j in i:
# print(j)
# is_skip = True
# break
# if is_skip:
# continue
# with open(path_join, 'r', encoding='utf-8') as f:
# if not f.readable():
# continue
# try:
# text = f.read()
# except ValueError:
# continue
# with open(path_join, 'w', encoding='utf-8') as f:
# reverse = sorted(list(mapping.keys()), key=len, reverse=is_reverse)
# text_result = text
# for name in reverse:
# text_result = re2.sub(str(name), mapping[name], text_result)
# print(text_result)
# if text_result != text:
# result.append(path_join)
# print(result)
# f.write(text_result)
# print("sub_map_text_impl over")
# 修复datbinding问题
def sub_map_text_impl(mapping, result, skip_type, skip_name, is_reverse=True, path='.'):
print("sub_map_text_impl start")
listdir = os.listdir(path)
for i in listdir:
path_join = os.path.join(path, i)
if os.path.isdir(path_join):
if path.endswith('.') and i in ["build", "debug", "release"]:
continue
if path.endswith('src') and i in ["androidTest", "test"]:
continue
sub_map_text_impl(mapping, result, skip_type, skip_name, is_reverse, path_join)
elif os.path.isfile(path_join):
if path.endswith('.') and os.path.splitext(i)[-1][1:] not in ["pro"]:
continue
if os.path.splitext(i)[-1][1:] in skip_type:
continue
is_skip = False
for j in skip_name:
if j in i:
print(j)
is_skip = True
break
if is_skip:
continue
# 开始读取文件内容
with open(path_join, 'r', encoding='utf-8') as f:
if not f.readable():
continue
try:
text = f.read()
except ValueError:
continue
# 替换逻辑
with open(path_join, 'w', encoding='utf-8') as f:
reverse = sorted(list(mapping.keys()), key=len, reverse=is_reverse)
text_result = text
# 遍历映射规则替换
for name in reverse:
text_result = re2.sub(str(name), mapping[name], text_result)
# 方案 3:针对特定 import 的直接替换
text_result = re.sub(
r'import varietyed\.scratched\.assemblyed\.(R|databinding\.\w+)',
r'import com.pots.qasdf.phonemanager.\1',
text_result
)
# 检查替换结果
if text_result != text:
result.append(path_join)
f.write(text_result)
print("sub_map_text_impl over")
def replace_text(old, new, path='.'):
print("replace_text")
listdir = os.listdir(path)
for i in listdir:
path_join = os.path.join(path, i)
if os.path.isdir(path_join):
if path.endswith('.') and i in ["build", "debug", "release"]:
continue
if path.endswith('src') and i in ["androidTest", "test"]:
continue
replace_text(old, new, path_join)
elif os.path.isfile(path_join):
if path.endswith('.') and os.path.splitext(i)[-1][1:] not in ["pro"]:
continue
with open(path_join, 'r', encoding='utf-8') as f:
if not f.readable():
continue
try:
text = f.read()
except ValueError:
continue
with open(path_join, 'w', encoding='utf-8') as f:
f.write(text.replace(old, new))
print(text)
def get_random_package():
packages = [get_random_string(4) for _ in range(get_random_int(1, 3))]
return '.'.join(packages)
def check_mapping():
print("check_mapping start")
try:
loads = json.load(open('mapping.json', 'r', encoding='utf-8'))
except (ValueError, IOError):
loads = {}
old_mapping = loads.get('dir', {})
packages = find_text('package (.*?)[;]?\\s', 'src')
packages.append(applicationId)
packages = sorted(set(packages), key=packages.index)
packages.sort(key=len)
print(packages)
mapping = {}
for i in packages:
# 解决databingding包被混淆
if 'databinding' in i: # 跳过 databinding 包
mapping[i] = i
continue
if applicationId not in i:
continue
value = old_mapping.get(i)
if value is not None:
mapping[i] = value
continue
mapping[i] = get_random_package()
value = mapping.get(i[:i.rfind('.')])
if value is not None:
mapping[i] = value + '.' + mapping[i]
loads["dir"] = mapping
json.dump(loads, open('mapping.json', 'w', encoding='utf-8'), indent=4)
print(json)
print("check_mapping over")
return mapping
def check_class_mapping():
print("check_class_mapping start")
try:
loads = json.load(open('mapping.json', 'r', encoding='utf-8'))
print(loads)
except (ValueError, IOError):
loads = {}
old_mapping = loads.get('class', {})
mapping = {}
classes = get_classes()
for i in classes:
if '_D' in i or os.path.basename(i) == "GlobalConfig.kt": # 跳过 GlobalConfig.kt
continue
print(i)
name = os.path.basename(i)
name = name[:name.rfind('.')]
value = old_mapping.get(name)
print(value)
if value is not None:
mapping[name] = value
continue
mapping[name] = get_title(get_random_string(4), True)
print(mapping[name])
# method_variable_mapping = generate_method_variable_mapping(i) # 生成方法名和变量名的混淆映射
# for old, new in method_variable_mapping.items():
# mapping[old] = new # 添加方法名和变量名的混淆映射
loads["class"] = mapping
json.dump(loads, open('mapping.json', 'w', encoding='utf-8'), indent=4)
print(json)
print("check_class_mapping over")
return mapping
# def generate_method_variable_mapping(class_file):
# # 生成方法名和变量名的混淆映射
# mapping = {}
#
# with open(class_file, 'r', encoding='utf-8') as f:
# text = f.read()
#
# methods = re.findall(
# r'\b(public|private|protected|static|final|abstract|synchronized)?\s*(<[^>]+>\s*)?(\w+)\s+(\w+)\s*\(',
# text
# )
# variables = re.findall(
# r'\b(public|private|protected|static|final|transient|volatile)?\s*(<[^>]+>\s*)?(\w+)\s+(\w+)\s*[;=]',
# text
# )
#
# for method in methods:
# old_name = method[3]
# new_name = get_random_string(6)
# mapping[old_name] = new_name
#
# for variable in variables:
# old_name = variable[3]
# new_name = get_random_string(6)
# mapping[old_name] = new_name
#
# return mapping
def check_type_ids_mapping(ids_type):
try:
loads = json.load(open('mapping.json', 'r', encoding='utf-8'))
except (ValueError, IOError):
loads = {}
old_mapping = loads.get(ids_type + "_ids", {})
mapping = {}
type_ids = find_text('<' + ids_type + ' name="(.*?)".*?>', get_path('.\\src\\main\\res'))
for i in type_ids:
if i.endswith('_D'):
continue
value = old_mapping.get(i)
if value is not None:
mapping[i] = value
continue
mapping[i] = get_random_string(5, False)
type_ids = find_text('<item name="(.*?)".*? type="' + ids_type + '">', get_path('.\\src\\main\\res'))
for i in type_ids:
value = old_mapping.get(i)
if value is not None:
mapping[i] = value
continue
mapping[i] = get_random_string(5, False)
if len(mapping) != 0:
loads[ids_type + "_ids"] = mapping
json.dump(loads, open('mapping.json', 'w', encoding='utf-8'), indent=4)
return mapping
def check_styleable_mapping():
try:
loads = json.load(open('mapping.json', 'r', encoding='utf-8'))
except (ValueError, IOError):
loads = {}
old_mapping = loads.get("styleable", {})
mapping = {}
styleables = find_text('<declare-styleable name=".*?">[\\s\\S]*?</declare-styleable>')
for i in styleables:
styleable_id = re.findall('<declare-styleable name="(.*?)">', i)[0]
mapping[styleable_id] = old_mapping.get(styleable_id, get_random_string(5, False))
# styleables_attr = re.findall('<attr name="(.*?)".*?/>', i)
styleables_attr = re.findall('<attr name="(.*?)".*?>', i) # 包括单行和多行定义
for attr in styleables_attr:
mapping[attr + '_A'] = old_mapping.get(attr + '_A', get_random_string(5))
if len(mapping) != 0:
loads["styleable"] = mapping
json.dump(loads, open('mapping.json', 'w', encoding='utf-8'), indent=4)
return mapping
def deal_ids_type(ids_type):
type_ids_mapping = check_type_ids_mapping(ids_type)
if len(type_ids_mapping) == 0:
return
print(type_ids_mapping)
sub_map = {}
for i in type_ids_mapping:
sub_map['<' + ids_type + ' name="' + i + '"'] = '<' + ids_type + ' name="' + type_ids_mapping[i] + '"'
sub_map['<item name="' + i + '"(.*? type="' + ids_type + '")>'] = \
'<item name="' + type_ids_mapping[i] + '"\\g<1>>'
sub_map['(?<=[^\\.])R\\.' + ids_type + '\\.' + i + '(?=\\W)'] = 'R.' + ids_type + '.' + type_ids_mapping[i]
sub_map['(?<=>|")@' + ids_type + '/' + i + '(?=<|")'] = '@' + ids_type + '/' + type_ids_mapping[i]
sub_map['<' + ids_type + '(.*?) parent="' + i + '"(.*?)>'] = \
'<' + ids_type + '\\g<1> parent="' + type_ids_mapping[i] + '"\\g<2>>'
sub_map_text(sub_map)
def check_view_ids_mapping():
try:
loads = json.load(open('mapping.json', 'r', encoding='utf-8'))
except (ValueError, IOError):
loads = {}
mapping = loads.get('view_ids', {})
view_ids = find_text('"\\@\\+id/(.*?)"', get_path('.\\src\\main\\res'))
for i in view_ids:
if i == 'root':
continue
key = get_title(i)
if mapping.get(key) is not None:
continue
mapping[key] = get_random_string(7)
loads["view_ids"] = mapping
json.dump(loads, open('mapping.json', 'w', encoding='utf-8'), indent=4)
return mapping
def check_layout_mapping():
try:
loads = json.load(open('mapping.json', 'r', encoding='utf-8'))
except (ValueError, IOError):
loads = {}
mapping = loads.get('layout', {})
layout_path = get_path('.\\src\\main\\res\\layout')
listdir = os.listdir(layout_path)
for i in listdir:
name = i[:i.rfind('.')]
if mapping.get(name) is not None:
continue
mapping[name] = get_random_string(6)
loads["layout"] = mapping
json.dump(loads, open('mapping.json', 'w', encoding='utf-8'), indent=4)
return mapping
def check_res_mapping(res_type):
try:
loads = json.load(open('mapping.json', 'r', encoding='utf-8'))
except (ValueError, IOError):
loads = {}
mapping = loads.get(res_type, {})
res_path = get_path('.\\src\\main\\res')
listdir = os.listdir(res_path)
for i in listdir:
if not i.startswith(res_type):
continue
for j in os.listdir(os.path.join(res_path, i)):
if j.startswith('book_'):
continue
if j.endswith('.9.png'):
name = j[:-len('.9.png')]
else:
name = j.rsplit('.', 1)[0]
if mapping.get(name) is not None:
continue
mapping[name] = normalize_filename(get_random_string(8))
if len(mapping) != 0:
loads[res_type] = mapping
json.dump(loads, open('mapping.json', 'w', encoding='utf-8'), indent=4)
return mapping
used_words = set()
def get_random_string(length=8, is_ascii=True):
global used_words # 使用全局变量 used_words
words = load_words_from_file('word_file.json')
if words:
while words:
random_word = random.choice(words)
if random_word not in used_words:
used_words.add(random_word)
if is_ascii and all(ord(c) < 128 for c in random_word):
return random_word
elif not is_ascii:
return random_word
return None
def load_words_from_file(file_path):
try:
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
return data.get("word", []) # 返回单词列表
except FileNotFoundError:
print(f"Error: Word file not found at: {file_path}")
return []
except json.JSONDecodeError:
print(f"Error: Invalid JSON format in: {file_path}")
return []
def get_random_int(a=0, b=10000):
return random.randint(a, b)
def get_dictionary_string(length=10):
random_str = random.choice(string.ascii_letters)
random_str += ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(length - 1))
return random_str
def check_obfuscation_dictionary():
if not os.path.exists('dictionary.txt'):
dictionary = set()
while len(dictionary) != 5000:
dictionary.add(get_dictionary_string())
with open('dictionary.txt', 'w', encoding='utf-8') as f:
f.write('\n'.join(dictionary))
with open('proguard-rules.pro', 'r', encoding='utf-8') as f:
text = f.read()
if '-obfuscationdictionary' in text:
return
text += '\n-obfuscationdictionary dictionary.txt'
text += '\n-classobfuscationdictionary dictionary.txt'
text += '\n-packageobfuscationdictionary dictionary.txt'
with open('proguard-rules.pro', 'w', encoding='utf-8') as f:
f.write(text)
def mkdir(dir_path):
try:
os.makedirs(dir_path)
except OSError:
pass
def replace_click_method():
print("replace_click_method start")
try:
path = get_path('.\\src\\main\\java')
file = find_file('fun View.setTrackedOnClickListener', path)
if file:
class_path = get_class_path(file) + '.setTrackedOnClickListener'
# java类不能直接使用扩展函数
sub_map_text({
'([.@])setOnClickListener': '\\g<1>setTrackedOnClickListener',
}, path=path, skip_type=['java'])
add_import(class_path, get_classes(['kt']))
print("replace_click_method over")
except IndexError:
return
def replace_package_in_imports(root_path, old_package, new_package):
# 匹配旧的 import 包名部分的正则表达式
patterns = [
(re.compile(rf'import {re.escape(old_package)}(\.databinding\.\w+)'), rf'import {new_package}\1'),
(re.compile(rf'import {re.escape(old_package)}\.R\b'), rf'import {new_package}.R'),
(re.compile(rf'import {re.escape(old_package)}\.BuildConfig'), rf'import {new_package}.BuildConfig')
]
# 遍历目录中的所有文件
for dirpath, _, filenames in os.walk(root_path):
for filename in filenames:
# 只处理 .kt 和 .java 文件
if filename.endswith(('.kt', '.java')):
file_path = os.path.join(dirpath, filename)
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()
# 替换匹配到的旧包名
updated_content = content
for pattern, replacement in patterns:
updated_content = pattern.sub(replacement, updated_content)
# 如果内容发生了改变,写回文件
if content != updated_content:
with open(file_path, 'w', encoding='utf-8') as file:
file.write(updated_content)
print(f"已更新文件: {file_path}")
# def get_file_names_without_extension(root_path):
# # 遍历 root_path 下的所有 .kt 和 .java 文件
# all_files = []
# for dirpath, _, filenames in os.walk(root_path):
# for filename in filenames:
# if filename.endswith(('.kt', '.java')):
# all_files.append(os.path.join(dirpath, filename))
#
# return all_files
# def get_class_name(text, filename):
# """
# 获取文件中定义的类名(适用于 Java 和 Kotlin 文件)
# :param text: 文件内容
# :param filename: 文件名
# :return: 类名列表
# """
# if filename.endswith('.kt'):
# class_pattern = r'\bclass\s+(\w+)'
# elif filename.endswith('.java'):
# class_pattern = r'\bclass\s+(\w+)'
# else:
# return []
#
# # 获取类名
# return re.findall(class_pattern, text)
def get_method_names(text, filename):
"""
获取 Java 或 Kotlin 文件中的所有方法名
:param text: 文件内容
:param filename: 文件名
:return: 方法名列表
"""
if filename.endswith('.kt'):
# Kotlin 方法名:匹配包括访问修饰符(private/public)和 fun 关键字,不排除 override
method_pattern = r'\b(?:private|protected|public|internal|inline|suspend|)?\s*fun\s+(\w+)\s*\((.*?)\)\s*(?::\s*([\w<>,\s?*]*))?\s*{'
elif filename.endswith('.java'):
# Java 方法名:包括常见的修饰符和方法名
method_pattern = r'\b(?:public|private|protected|static|final|void|int|boolean|float|long|double|char|short|byte|void|[\w<>]+)\s+(\w+)\s*\(.*\)\s*\{'
else:
return []
# 匹配所有方法名
return re.findall(method_pattern, text)
def get_variable_names(text, filename):
"""
获取 Java 或 Kotlin 文件中的所有变量名
:param text: 文件内容
:param filename: 文件名
:return: 变量名列表
"""
if filename.endswith('.kt'):
# Kotlin 变量名:支持 `val` 和 `var` 关键字的变量声明
variable_pattern = r'\b(?:val|var)\s+(\w+)\s*(?=[;=])'
elif filename.endswith('.java'):
# Java 变量名:包括常见的类型声明
variable_pattern = r'\b(?:int|boolean|float|long|double|char|short|byte|String|[\w<>]+)\s+(\w+)\s*(?=[;=])'
else:
return []
# 匹配所有变量名
return re.findall(variable_pattern, text)
def get_file_names_without_extension(root_path):
"""
获取所有 `.kt` 和 `.java` 文件的路径(无扩展名)
:param root_path: 根目录路径
:return: 所有文件的路径列表
"""
all_files = []
for dirpath, _, filenames in os.walk(root_path):
for filename in filenames:
if filename.endswith(('.kt', '.java')):
all_files.append(os.path.join(dirpath, filename))
return all_files
# 存储每个文件名的映射关系
file_to_class_map = {}
def get_constant_names(text, filename):
"""
获取 Java 或 Kotlin 文件中的所有常量名
:param text: 文件内容
:param filename: 文件名
:return: 常量名列表
"""
if filename.endswith('.kt'):
# Kotlin 常量名:匹配以 `const val` 声明的常量
constant_pattern = r'const\s+val\s+(\w+)\s*=\s*.*'
elif filename.endswith('.java'):
# Java 常量名:匹配以 `static final` 声明的常量
constant_pattern = r'public\s+static\s+final\s+\w+\s+(\w+)\s*=\s*.*'
else:
return []
# 匹配所有常量名
return re.findall(constant_pattern, text)
def obfuscate_code_in_files(all_files):
"""
遍历所有文件,混淆其中的方法名、变量名和常量名,并返回混淆名称的映射。
:param all_files: 所有文件的路径列表。
:return: 一个字典,键为原始名称,值为混淆后的名称。
"""
name_mapping = {} # 用于存储原始名称和混淆名称的映射
constant_mapping = {} # 用于存储常量的映射
for file_path in all_files:
current_file_name = os.path.basename(file_path).split('.')[0] # 获取文件名
# 读取文件内容
with open(file_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
# 获取文件中的常量名
constants = get_constant_names("".join(lines), file_path)
# 为常量名生成混淆名称
for constant in constants:
if constant not in name_mapping:
name_mapping[constant] = get_random_string()
constant_mapping[constant] = name_mapping[constant]
# 获取类名(用于判断常量是否在类内引用)
new_lines = []
for line in lines:
if line.startswith("import "): # 跳过 import 行
new_lines.append(line)
else:
# 替换常量名:进行统一的替换,不区分引用的类型
for constant, obfuscated_name in constant_mapping.items():
# 直接替换常量,无论是在类内还是外部引用
line = re.sub(r'\b' + re.escape(constant) + r'\b', obfuscated_name, line)
# 替换方法名和变量名
for original_name, obfuscated_name in name_mapping.items():
line = re.sub(r'\b' + re.escape(original_name) + r'\b', obfuscated_name, line)
new_lines.append(line)
# 写回文件
with open(file_path, 'w', encoding='utf-8') as f:
f.writelines(new_lines)
return constant_mapping
def replace_with_mapped_names(all_files, name_mapping, constant_mapping):
"""
在所有文件中替换已经混淆的名称,保证不同文件间共享混淆名称。
:param all_files: 所有文件的路径列表。
:param name_mapping: 名称映射字典。
:param constant_mapping: 常量名称映射字典。
"""
for file_path in all_files:
# 使用在 obfuscate_code_in_files 中存储的文件名
current_file_name = file_to_class_map.get(file_path, None)
if not current_file_name:
print(f"未找到文件 {file_path} 的类名映射,跳过。")
continue
with open(file_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
new_lines = []
excluded_constants = set()
# 第一步:提取 `const val` 定义的常量名并排除混淆
for line in lines:
match = re.match(r'^\s*const\s+val\s+(\w+)\s*=.*$', line)
if match:
constant_name = match.group(1)
excluded_constants.add(constant_name)
for line in lines:
if line.startswith("import "): # 跳过 import 行
new_lines.append(line)
continue
# 跳过 context 或 activity 的调用
if 'context.' in line or 'activity.' in line:
new_lines.append(line)
continue
# 替换变量和方法名
for original_name, obfuscated_name in name_mapping.items():
if isinstance(original_name, tuple):
original_name = ''.join(original_name)
# 如果是 `excluded_constants` 中的常量名,跳过替换
if original_name in excluded_constants:
continue
# 替换代码中的变量或方法名
line = re.sub(r'\b' + re.escape(original_name) + r'\b', obfuscated_name, line)
new_lines.append(line)
with open(file_path, 'w', encoding='utf-8') as f:
f.writelines(new_lines)
print(f"文件 {file_path} 引用的名称已进行统一混淆处理。")
def deal_code():
print("deal_code start")
# 替换点击事件
replace_click_method()
# 生成包映射文件
mapping = check_mapping()
packages = list(mapping.keys())
print("mapping: " + json.dumps(mapping, indent=4))
# 移动文件
root_path = get_path('.\\src\\main\\java')
for key in packages:
key = str(key)
print(key)
old_path = get_path(root_path + '\\' + key.replace('.', '\\'))
new_path = get_path(root_path + '\\' + mapping[key].replace('.', '\\'))
print("mapping[key]" + mapping[key])
if not os.path.exists(old_path):
mkdir(old_path)
if not os.path.exists(new_path):
mkdir(new_path)
listdir = os.listdir(old_path)
for i in listdir:
path_join = os.path.join(old_path, i)
if os.path.isdir(path_join):
continue
shutil.move(path_join, os.path.join(new_path, i))
shutil.rmtree(get_path(root_path + '\\' + applicationId[:applicationId.find('.')]))
# 修改manifest
with open(get_path('.\\src\\main\\AndroidManifest.xml'), 'r', encoding='utf-8') as f:
text = f.read()
findall = re.findall('android:name=(".*?")', text)
for i in findall:
if i.startswith('".'):
text = text.replace(i, '"' + applicationId + i[1:-1] + '"')
print(text)
print(applicationId)
with open(get_path('.\\src\\main\\AndroidManifest.xml'), 'w', encoding='utf-8') as f:
f.write(text)
# 修改依赖
sub_map = {}
for i in mapping:
sub_map['(?<=\\W)' + i + '(?=\\W)'] = mapping[i]
print(sub_map['(?<=\\W)' + i + '(?=\\W)'])
print(mapping[i])
# 过滤修改
sub_map['(?<=\\W)' + mapping[applicationId] + '.R(?=\\W)'] = applicationId + '.R'
sub_map['(?<=\\W)' + mapping[applicationId] + '.databinding(?=\\W)'] = applicationId + '.databinding'
sub_map['(?<=\\W)' + mapping[applicationId] + '.BuildConfig(?=\\W)'] = applicationId + '.BuildConfig'
sub_map_text(sub_map)
# 根包名下 R 单独处理
new_path = get_path(root_path + '\\' + mapping[packages[0]].replace('.', '\\'))
listdir = os.listdir(new_path)
result_path = []
for i in listdir:
if os.path.isdir(os.path.join(new_path, i)):
continue
result_path.append(os.path.join(new_path, i))
# add_import(applicationId + '.R', result_path)
# add_import(applicationId + '.BuildConfig', result_path)
# 类名
print("类名 start")
class_mapping = check_class_mapping()
classes = get_classes()
print(class_mapping)
for i in classes:
if '_D' in i or os.path.basename(i) == "GlobalConfig.kt": # 跳过 GlobalConfig.kt
continue
name = os.path.basename(i)
name = name[:name.rfind('.')]
shutil.move(i, i.replace(name, class_mapping[name]))
sub_map = {}
for i in class_mapping:
# 原规则匹配整个单词
# 添加负向环视以排除前后是引号的情况,解决匹配attrs的问题,
# 添加排除前缀 "styleable." 的逻辑
# 原规则匹配整个单词,添加排除前缀 "styleable." 的逻辑,并允许后面有引号的匹配
regex_pattern = r'(?<!")(?<!\bstyleable\.)\b' + i + r'\b'
# 保持原有规则的匹配,优先处理非引号的情况
sub_map[regex_pattern] = class_mapping[i]
print(class_mapping[i])
sub_map_text(sub_map)
print("类名 over")
# 混淆字典
check_obfuscation_dictionary()
print(root_path)
print(mapping[applicationId])
print(applicationId)
# 获取混淆名称映射
# all_files = get_file_names_without_extension(root_path)
# name_mapping = obfuscate_code_in_files(all_files)
#
# # 获取常量映射
# constant_mapping = {} # 获取常量的映射
# for file_path in all_files:
# with open(file_path, 'r', encoding='utf-8') as f:
# lines = f.readlines()
# constants = get_constant_names("".join(lines), file_path)
# for constant in constants:
# if constant not in name_mapping:
# name_mapping[constant] = get_random_string()
# constant_mapping[constant] = name_mapping[constant]
#
# # 第一步:混淆代码,并获取所有混淆的名称映射
# name_mapping = obfuscate_code_in_files(all_files)
#
# # 第二步:替换其他文件中的引用,保证同样的混淆名
# replace_with_mapped_names(all_files, name_mapping, constant_mapping)
replace_package_in_imports(root_path, mapping[applicationId], applicationId)
def deal_res_type(res_type):
type_mapping = check_res_mapping(res_type)
if len(type_mapping) == 0:
return
print(type_mapping)
res_path = get_path('.\\src\\main\\res')
listdir = os.listdir(res_path)
for i in listdir:
if not i.startswith(res_type):
continue
path_join = os.path.join(res_path, i)
for j in os.listdir(path_join):
if j.startswith('book_'):
continue
if j.endswith('.9.png'):
name = j[:-len('.9.png')]
else:
name = j.rsplit('.', 1)[0]
path = os.path.join(path_join, j)
if '.DS_Store' not in path:
# shutil.move(path, path.replace(name, type_mapping[name]))
# 仅替换文件名部分
dir_path, file_name = os.path.split(path)
new_file_name = file_name.replace(name, type_mapping[name])
new_path = os.path.join(dir_path, new_file_name)
# 打印路径调试信息
print(f"源路径: {path}")
print(f"目标路径: {new_path}")
# 移动文件
shutil.move(path, new_path)
sub_map = {}
for i in type_mapping:
sub_map['R\\.' + res_type + '\\.' + i + '(\\W)'] = 'R.' + res_type + '.' + type_mapping[i] + '\\g<1>'
sub_map['(>|")@' + res_type + '/' + i + '(<|")'] = '\\g<1>@' + res_type + '/' + type_mapping[i] + '\\g<2>'
sub_map_text(sub_map)
# 解决mipmip命名中含有大写字母造成的报错
# 'E' is not a valid file-based resource name character: File-based resource names must contain only lowercase a-z, 0-9, or underscore
def generate_random_letter():
# 生成一个随机小写字母
return random.choice(string.ascii_lowercase)
def normalize_filename(name):
# 将名称转为小写
normalized_name = name.lower()
# 替换所有非法字符为随机字母
normalized_name = re.sub(r'[^a-z]', lambda match: generate_random_letter(), normalized_name)
# 确保名称以字母开头,如果不是,则以随机字母开头
if not normalized_name[0].isalpha():
normalized_name = generate_random_letter() + normalized_name[1:]
return normalized_name
def add_image_noise(path):
try:
image = cv2.imread(path, cv2.IMREAD_UNCHANGED)
if path.endswith('.9.png'):
return
# # 添加随机噪声
# noise = np.random.randint(0, 2, image.shape, np.uint8)
# image = cv2.add(image, noise)
# 添加 ±10 的随机噪声
noise = np.random.randint(0, 2, image.shape, np.int16) # 噪声范围 [-10, 10]
noisy_image = np.clip(image.astype(np.int16) + noise, 0, 255).astype(np.uint8) # 防止溢出
#计算原始图像与添加噪声后图像的差异:如果差值的范围包含 10(噪声范围),说明噪声添加成功。
difference = np.abs(image.astype(np.int16) - noisy_image.astype(np.int16))
print("Difference range:", difference.min(), "-", difference.max())
new_path = path.rsplit('.', 1)[0] + '.webp'
cv2.imwrite(new_path, noisy_image, [cv2.IMWRITE_WEBP_QUALITY, 75])
if new_path != path:
os.remove(path)
except AttributeError:
return
def deal_image():
res_path = get_path('.\\src\\main\\res')
listdir = os.listdir(res_path)
for i in listdir:
path_join = os.path.join(res_path, i)
if os.path.isdir(path_join):
for j in os.listdir(path_join):
# add_image_noise(os.path.join(path_join, j))
if j.endswith(('.png', '.jpg', '.jpeg', '.bmp', '.tiff', '.webp')):
add_image_noise(os.path.join(path_join, j))
def get_title(string, is_all_upper=False):
splits = string.split('_')
print(splits)
s = ''
for i in range(len(splits)):
if i == 0 and not is_all_upper:
s = splits[i]
elif len(splits[i]) > 0:
s += splits[i][0].upper() + splits[i][1:]
return s
def add_import(path, listfile):
for i in listfile:
if 'databinding' in path: # 跳过 databinding
continue
with open(i, 'r', encoding='utf-8') as f:
text = f.read()
if len(re.findall('import ' + path, text)) != 0:
continue
if i.endswith('kt'):
text = re.sub('(package \\S+?\\s+?)(?=\\S)', '\\g<1>import ' + path + '\n', text, flags=re.S)
print(text)
elif i.endswith('java'):
text = re.sub('(package \\S+?\\s+?)(?=\\S)', '\\g<1>import ' + path + ';\n', text, flags=re.S)
with open(i, 'w', encoding='utf-8') as f:
f.write(text)
def check_class_string_mapping():
try:
loads = json.load(open('mapping.json', 'r', encoding='utf-8'))
except (ValueError, IOError):
loads = {}
mapping = loads.get('class_string', {})
classes = get_classes(['kt'])
for path in classes:
if os.path.basename(path).startswith('AESHelper'):
continue
with open(path, 'r', encoding='utf-8') as f:
text = f.read()
text = re.sub('@SuppressLint.*?\n', '\n', text)
text = re.sub('//[^"]*?(("[^"]*?){2})*?\n', '\n', text)
text = re.sub(' +\n', '\n', text)
text = re.sub('=\\s+', '= ', text)
text = re.sub(':\\s+', ': ', text)
text = re.sub(',\\s+', ', ', text)
text = re.sub('\n{2,}', '\n\n', text)
result_text = text
strings = re.findall('".*?[^\\\\]"', re.sub(r'@[^)]*', lambda x: x.group().replace('"', "'"), result_text))
last_string = ''
for string in strings:
if len(string) <= 4 or string.isspace() or \
string[1:-1] in ['\\\\n', '\\\\r', '\\\\\'', '\\\\\\"', '\\\\?', '&amp;', 'UTF-8']:
continue
if string in last_string:
continue
start = text.find(string)
if start == -1:
continue
index = start + 1
sign_stack = ['"']
while len(sign_stack) != 0:
if text[index] == '\\':
index += 1
elif text[index] == '"' and sign_stack[-1] == '"':
sign_stack.pop()
elif text[index] == '"':
sign_stack.append('"')
elif text[index] == '}' and sign_stack[-1] == '${':
sign_stack.pop()
elif text[index - 1:index + 1] == '${':
sign_stack.append('${')
index += 1
string = text[start:index]
last_string = string
params = []
result = string
if '$' in string:
count = 0
last = -1
res = []
for i in range(len(string)):
if last == -1 and string[i] == '$':
if count == 0:
last = i
elif last != -1:
if string[i] == '{':
count += 1
elif string[i] == '}':
count -= 1
if count == 0:
res.append((last, i + 1))
last = -1
elif count == 0:
last = -1
for i in re.findall('\\$\\w*', string):
if i == '$':
continue
find = string.find(i)
res.append((find, find + len(i)))
res = sorted(res, key=lambda l: l[0])
last_res = None
for i in res:
if last_res is not None and i[0] < last_res[1]:
continue
params.append(string[i[0]:i[1]])
last_res = i
for i in params:
result = result.replace(i, "[str]")
result = result[1:-1].replace('%', '%%').replace('\\\\', '\\').replace('[str]', '%s')
if mapping.get(result) is not None:
continue
mapping[result] = get_random_string(5, False) + '_D'
loads['class_string'] = mapping
json.dump(loads, open('mapping.json', 'w', encoding='utf-8'), indent=4)
return mapping
def deal_class_string():
# 获取映射
string_mapping = check_class_string_mapping()
print(string_mapping)
with open(get_path('.\\src\\main\\res\\values\\strings.xml'), 'r', encoding='utf-8') as f:
text = f.read()
rfind = text.rfind('\n')
text_result = text[:rfind]
for i in string_mapping:
string = i
string = string.replace('&', '&amp;')
string = string.replace('?', '\\?')
string = string.replace('\'', '\\\'')
text_result += '\n <string name="' + string_mapping[i] + '" translatable="false">' + string + '</string>'
text_result += text[rfind:]
with open(get_path('.\\src\\main\\res\\values\\strings.xml'), 'w', encoding='utf-8') as f:
f.write(text_result)
# 搜索 Int.string() 路径
file_path = find_file('fun Int.string', get_path('.\\src\\main\\java'))
class_path = get_class_path(file_path) + '.string' if file_path else ""
classes = get_classes(['kt'])
for path in classes:
# 跳过 AESHelper 类中的字符串,避免嵌套
if os.path.basename(path).startswith('AESHelper'):
continue
with open(path, 'r', encoding='utf-8') as f:
text = f.read()
text = re.sub('@SuppressLint.*?\n', '\n', text)
text = re.sub('//[^"]*?(("[^"]*?){2})*?\n', '\n', text)
text = re.sub(' +\n', '\n', text)
text = re.sub('=\\s+', '= ', text)
text = re.sub(':\\s+', ': ', text)
text = re.sub(',\\s+', ', ', text)
text = re.sub('\n{2,}', '\n\n', text)
result_text = text
strings = re.findall('".*?[^\\\\]"', re.sub(r'@[^)]*', lambda x: x.group().replace('"', "'"), result_text))
last_string = ''
for string in strings:
if len(string) <= 4 or string.isspace() or \
string[1:-1] in ['\\\\n', '\\\\r', '\\\\\'', '\\\\\\"', '\\\\?', '&amp;', 'UTF-8']:
continue
if string in last_string:
continue
start = text.find(string)
if start == -1:
continue
index = start + 1
sign_stack = ['"']
while len(sign_stack) != 0:
if text[index] == '\\':
index += 1
elif text[index] == '"' and sign_stack[-1] == '"':
sign_stack.pop()
elif text[index] == '"':
sign_stack.append('"')
elif text[index] == '}' and sign_stack[-1] == '${':
sign_stack.pop()
elif text[index - 1:index + 1] == '${':
sign_stack.append('${')
index += 1
# 得到完整字符串
string = text[start:index]
last_string = string
# 参数
params = []
result = string
# 处理字符串拼接
if '$' in string:
count = 0
last = -1
res = []
for i in range(len(string)):
if last == -1 and string[i] == '$':
if count == 0:
last = i
elif last != -1:
if string[i] == '{':
count += 1
elif string[i] == '}':
count -= 1
if count == 0:
res.append((last, i + 1))
last = -1
elif count == 0:
last = -1
for i in re.findall('\\$\\w*', string):
if i == '$':
continue
find = string.find(i)
res.append((find, find + len(i)))
res = sorted(res, key=lambda l: l[0])
last_res = None
for i in res:
if last_res is not None and i[0] < last_res[1]:
continue
params.append(string[i[0]:i[1]])
last_res = i
for i in params:
result = result.replace(i, "[str]")
for i in range(len(params)):
if params[i][1] == '{':
params[i] = params[i][2:-1]
else:
params[i] = params[i][1:]
# 每个参数加上 toString
params[i] = '(' + params[i] + ').toString()'
# 拼接部分替换为 %s
result = result[1:-1].replace('%', '%%').replace('\\\\', '\\').replace('[str]', '%s')
# 添加 Int.string() 方法引用
if class_path:
if len(re.findall('import ' + class_path, text)) == 0:
result_text = re.sub('(package \\S+?\\s+?)(?=\\S)',
'\\g<1>import ' + class_path + '\n', result_text, flags=re.S)
# 添加资源 R 引用
if len(re.findall('import ' + applicationId + '.R', text)) == 0:
result_text = re.sub('(package \\S+?\\s+?)(?=\\S)',
'\\g<1>import ' + applicationId + '.R' + '\n', result_text, flags=re.S)
# 去除 const
result_text = re.sub('const (val.*?R\\.string\\.\\S+?\\.string\\(\\))', '\\g<1>', result_text)
with open(path, 'w', encoding='utf-8') as f:
f.write(result_text)
def check_xml_string_mapping():
try:
loads = json.load(open('mapping.json', 'r', encoding='utf-8'))
except (ValueError, IOError):
loads = {}
old_mapping = loads.get('xml_string', {})
strings = find_text('android:text="(?<!@string/)([^@"]+?)"', get_path('.\\src\\main\\res\\layout'),
skip_name=['notify', 'notity'])
mapping = {}
for i in strings:
value = old_mapping.get(i)
if value is not None:
mapping[i] = value
continue
# 添加 '_D' 结尾,资源混淆时过滤不用再次改名
mapping[i] = get_random_string(5, False) + '_D'
loads['xml_string'] = mapping
json.dump(loads, open('mapping.json', 'w', encoding='utf-8'), indent=4)
return mapping
def deal_xml_string():
# 获取映射
string_mapping = check_xml_string_mapping()
print(string_mapping)
with open(get_path('.\\src\\main\\res\\values\\strings.xml'), 'r', encoding='utf-8') as f:
text = f.read()
rfind = text.rfind('\n')
text_result = text[:rfind]
for i in string_mapping:
string = i
string = string.replace('&', '&amp;')
string = string.replace('?', '\\?')
string = string.replace('\'', '\\\'')
text_result += '\n <string name="' + string_mapping[i] + '" translatable="false">' + string + '</string>'
text_result += text[rfind:]
with open(get_path('.\\src\\main\\res\\values\\strings.xml'), 'w', encoding='utf-8') as f:
f.write(text_result)
sub_map = {}
for i in string_mapping:
sub_map['(?<=android:text=")' + re.escape(i) + '(?=")'] = '@string/' + re.escape(string_mapping[i])
# 替换时跳过通知布局
sub_map_text(sub_map, path=get_path('.\\src\\main\\res\\layout'), skip_name=['notify', 'notity'])
def deal_code_string():
# 处理布局文件中的明文字符串
deal_xml_string()
# 处理代码文件中的明文字符串
deal_class_string()
def deal_res():
# 改 string id
deal_ids_type('string')
# 改 color id
deal_ids_type('color')
# 改 dimen id
deal_ids_type('dimen')
# 改 style id
deal_ids_type('style')
# 改 declare-styleable
styleable_mapping = check_styleable_mapping()
print(styleable_mapping)
sub_map = {}
styleables = find_text('<declare-styleable name=".*?">[\\s\\S]*?</declare-styleable>')
for i in styleables:
result = i
styleable_id = re.findall('<declare-styleable name="(.*?)">', i)[0]
result = result.replace('<declare-styleable name="' + styleable_id + '">',
'<declare-styleable name="' + styleable_mapping[styleable_id] + '">')
sub_map['R\\.styleable\\.' + styleable_id + '(\\W)'] = \
'R.styleable.' + styleable_mapping[styleable_id] + '\\g<1>'
# styleables_attr = re.findall('<attr name="(.*?)".*?/>', i)
styleables_attr = re.findall('<attr name="(.*?)".*?>', i) # 包括单行和多行定义
for attr in styleables_attr:
# else:
# new_attr = attr
result = result.replace('<attr name="' + attr + '"', '<attr name="' + styleable_mapping[attr + '_A'] + '"')
sub_map['R\\.styleable\\.' + styleable_id + '_' + attr + '(\\W)'] = \
'R.styleable.' + styleable_mapping[styleable_id] + '_' + styleable_mapping[attr + '_A'] + '\\g<1>'
sub_map['app:' + attr + '='] = 'app:' + styleable_mapping[attr + '_A'] + '='
sub_map['\\?attr/' + attr] = '?attr/' + styleable_mapping[attr + '_A'] # 新增的替换规则
sub_map['<item name=\"' + attr +'\"'] = '<item name=\"' + styleable_mapping[attr + '_A'] +'\"' # 新增的替换规则
# 判断 attr 是否以 "android:" 开头
if attr.startswith("android:"):
new_attr = "android_" + attr[8:] # 去掉 "android:" 并替换为 "android_"
sub_map['R\\.styleable\\.' + styleable_id + '_' + new_attr + '(\\W)'] = \
'R.styleable.' + styleable_mapping[styleable_id] + '_' + styleable_mapping[attr + '_A'] + '\\g<1>'
sub_map[re.escape(i)] = result
sub_map_text(sub_map)
# 改 view id
view_ids = find_text('"\\@\\+id/(.*?)"', get_path('.\\src\\main\\res'))
view_ids_mapping = check_view_ids_mapping()
print(view_ids_mapping)
sub_map = {}
for i in view_ids:
if i == 'root':
continue
key = get_title(i)
sub_map['(>|")@(\\+)?id/' + i + '(<|")'] = '\\g<1>@\\g<2>id/' + view_ids_mapping[key] + '\\g<3>'
sub_map['R\\.id\\.' + i + '(\\W)'] = 'R.id.' + view_ids_mapping[key] + '\\g<1>'
sub_map['([bB]inding\\??(?:\\.\\w+)?\\??)\\.' + key + '(\\W)'] = '\\g<1>.' + view_ids_mapping[key] + '\\g<2>'
sub_map['([bB]inding\\??(?:\\.\\w+)?\\??)\\.' + key +'\\.'+ key + '(\\W)'] = '\\g<1>.' + view_ids_mapping[key] +'.' +view_ids_mapping[key] +'\\g<2>'
sub_map[r'\(binding as (\w+Binding)\)\.' + key + r'(\W)'] = r'(binding as \1).' + view_ids_mapping[key] + r'\2'
sub_map['(?<=app:constraint_referenced_ids=".*?)' + i + '(?=.*?")'] = view_ids_mapping[key]
sub_map_text(sub_map)
# 改 layout 文件名
layout_mapping = check_layout_mapping()
print(layout_mapping)
layout_path = get_path('.\\src\\main\\res\\layout')
listdir = os.listdir(layout_path)
for i in listdir:
name = i[:i.rfind('.')]
if layout_mapping.get(name) is None:
continue
path_join = os.path.join(layout_path, i)
#解决layout出现大写的问题
shutil.move(path_join, path_join.replace(name, normalize_filename(layout_mapping[name])))
sub_map = {}
for i in layout_mapping:
sub_map['R\\.layout\\.' + i + '(\\W)'] = 'R.layout.' + layout_mapping[i] + '\\g<1>'
# ViewBinding 同步修改
sub_map[get_title(i, True) + 'Binding'] = get_title(layout_mapping[i], True) + 'Binding'
sub_map['(>|")@layout/' + i + '(<|")'] = '\\g<1>@layout/' + layout_mapping[i] + '\\g<2>'
sub_map_text(sub_map)
# 改 drawable 文件名
deal_res_type('drawable')
# 改 mipmap 文件名
deal_res_type('mipmap')
# 改 raw 文件名
deal_res_type('raw')
# 改 raw 文件名
deal_res_type('xml')
# 改图片内容
deal_image()
def get_path(path):
paths = path.split('\\')
return str(os.path.join(*paths))
def main():
if not os.path.exists(get_path('.\\src\\main\\java\\' + applicationId[:applicationId.find('.')])):
print(get_path('.\\src\\main\\java\\' + applicationId[:applicationId.find('.')]))
return
# 资源混淆
deal_res()
deal_code()
def load_mapping(file_path):
"""加载 mapping.json 文件并打印内容."""
try:
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
print("Loaded Mapping:")
print(json.dumps(data, indent=4, ensure_ascii=False)) # 打印映射内容
return data.get("view_ids", {})
except (FileNotFoundError, json.JSONDecodeError) as e:
print(f"Error loading mapping file: {e}")
return {}
def replace_identifiers_in_file(file_path, mapping, replacements_log):
"""替换单个文件中的标识符,并存储替换的内容到 JSON 日志."""
with open(file_path, 'r', encoding='utf-8') as f:
original_text = f.read()
print(f"\nProcessing File: {file_path}")
print("Original Content:")
print(original_text) # 打印文件原始内容
modified_text = original_text
replacements = []
# 替换所有绑定相关的引用
def replace_binding_references(match):
identifier = match.group(2) or match.group(3)
return f"{match.group(1)}.{mapping.get(identifier, identifier)}"
# 替换类似 `binding.tvTest1` 和 `it.tvTest1` 的结构
modified_text = re.sub(
r'\b(binding|it)\.([a-zA-Z_0-9]+)\b',
replace_binding_references,
modified_text
)
# 替换无前缀的变量名,如 `tvTest1` 和 `tvTest2`,根据 mapping 文件中的 view_ids 替换
def replace_variable_names(match):
identifier = match.group(1)
# 使用 mapping 中的 view_ids 替换变量名
new_identifier = mapping.get(identifier, identifier)
if new_identifier != identifier:
replacements.append({"original": identifier, "modified": new_identifier})
return new_identifier
# 先提取所有引号中的内容
quoted_texts = re.findall(r'"([^"]*)"', modified_text)
# 将引号中的内容临时替换为占位符
placeholders = [f"__PLACEHOLDER_{i}__" for i in range(len(quoted_texts))]
for i, quoted in enumerate(quoted_texts):
modified_text = modified_text.replace(f'"{quoted}"', placeholders[i])
# 过滤掉方法调用中的标识符(例如 taichiPref.edit() 不替换 edit)
def is_function_call(match):
# 检查匹配的标识符后是否跟着 `()`
return f"{match.group(1)}()" if match.group(2) else None
# 替换变量名,排除函数调用
modified_text = re.sub(
r'\b(' + '|'.join(mapping.keys()) + r')\b(?!\()',
replace_variable_names,
modified_text
)
# 将占位符替换回原始引号内容
for i, quoted in enumerate(quoted_texts):
modified_text = modified_text.replace(placeholders[i], f'"{quoted}"')
# 过滤重复的替换项:根据 (original, modified) 进行去重
seen_replacements = set()
unique_replacements = []
for replacement in replacements:
replacement_tuple = (replacement["original"], replacement["modified"])
if replacement_tuple not in seen_replacements:
unique_replacements.append(replacement)
seen_replacements.add(replacement_tuple)
if unique_replacements:
replacements_log[file_path] = unique_replacements
# 写入修改后的内容
with open(file_path, 'w', encoding='utf-8') as f:
f.write(modified_text)
else:
print("No replacements made.")
def process_files_in_directory(directory, mapping, replacements_log):
"""处理目录下的所有文件并打印文件路径."""
for root, _, files in os.walk(directory):
for file in files:
if file.endswith(".kt"): # 仅处理 Kotlin 文件
file_path = os.path.join(root, file)
replace_identifiers_in_file(file_path, mapping, replacements_log)
def solve_runapplyalso():
# 配置路径
mapping_file = "mapping.json"
src_directory = "./src/main/java"
log_file = "replacement_log.json"
# 加载映射表
mapping = load_mapping(mapping_file)
# 检查路径
if not os.path.exists(src_directory):
print(f"Source directory not found: {src_directory}")
elif not mapping:
print("Mapping file is empty or not found.")
else:
# 存储替换日志
replacements_log = {}
# 开始处理文件
process_files_in_directory(src_directory, mapping, replacements_log)
# 将替换日志保存为 JSON 文件
with open(log_file, 'w', encoding='utf-8') as log:
json.dump(replacements_log, log, ensure_ascii=False, indent=4)
print(f"\nReplacement completed. Log saved to {log_file}.")
if __name__ == '__main__':
if os.path.exists('build.gradle'):
gradle_path = 'build.gradle'
elif os.path.exists('build.gradle.kts'):
gradle_path = 'build.gradle.kts'
else:
exit('找不到 build.gradle 文件')
applicationId = re.search('namespace .*?["\'](.*?)["\']', open(gradle_path, 'r', encoding='utf-8').read())[1]
print(applicationId)
main()
# solve_runapplyalso()
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment