Commit 4f6ddc12 authored by maxiaoliang's avatar maxiaoliang

添加fg

parent d4cb8972
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="jbr-17" /> <option name="gradleJvm" value="jbr-17" />
<option name="modules"> <option name="modules">
......
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
......
...@@ -80,5 +80,6 @@ dependencies { ...@@ -80,5 +80,6 @@ dependencies {
testImplementation(libs.junit) testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core) androidTestImplementation(libs.androidx.espresso.core)
implementation ("com.solar.fileaceoneweea.inmanagerso:l1b2t123457j1:v30-jiagu-gp")
} }
\ No newline at end of file
import os
import re
import shutil
import ssl
import urllib.request
from urllib.error import URLError
import regex as re2
import requests
ssl._create_default_https_context = ssl._create_unverified_context
def sub_map_text(mapping, is_reverse=True, path='.', skip_type=None, skip_name=None):
if len(mapping) == 0:
return []
if skip_type is None:
skip_type = []
if skip_name is None:
skip_name = []
result = []
sub_map_text_impl(mapping, result, skip_type, skip_name, is_reverse, path)
return result
def sub_map_text_impl(mapping, result, skip_type, skip_name, is_reverse=True, 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
sub_map_text_impl(mapping, result, skip_type, skip_name, is_reverse, path_join)
elif os.path.isfile(path_join):
if path.endswith('.') and i.split('.')[-1] in ['py']:
continue
if i.split('.')[-1] in skip_type:
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
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)
if text_result != text:
result.append(path_join)
f.write(text_result)
def copy_folder_exclude(src, dst, exclude_names=None):
if exclude_names is None:
exclude_names = []
if not os.path.exists(dst):
os.makedirs(dst)
for item in os.listdir(src):
s = os.path.join(src, item)
d = os.path.join(dst, item)
if item in exclude_names:
continue
if os.path.isdir(s):
copy_folder_exclude(s, d, exclude_names)
else:
shutil.copy2(s, d)
def get_desktop_path():
# Windows 系统
if os.name == 'nt':
desktop = os.path.join(os.getenv('USERPROFILE'), 'Desktop')
# macOS 系统
elif os.name == 'posix':
desktop = os.path.join(os.getenv('HOME'), 'Desktop')
# Linux 或其他 Unix-like 系统可能需要手动指定
else:
desktop = os.path.join(os.getenv('HOME'), 'Desktop') # 可能的默认位置
return desktop
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:
return result[0]
return result
def get_path(path):
return path.replace('\\', os.path.sep)
def main():
isMinifyEnabled = find_text('release ?{\\s*?(?:isM|m)inifyEnabled(?: ?= ?| )(false|true)', is_one=True)
if isMinifyEnabled == 'false':
print('官方混淆未开启')
exit()
# 测试包名
applicationId = find_text('applicationId .*?["\'](.*?)["\']', is_one=True)
# 正式包名
packageName_id = find_text('packageName = R.string.(\\w+?).string\\(\\)', is_one=True)
packageName_id = ''.join(['\\u' + format(ord(c), '04x') for c in packageName_id[:-2]]) + '_D'
packageName = find_text('"(.*?)": "' + re.escape(packageName_id) + '"', is_one=True)
sub_map = {
applicationId: packageName
}
print('包名: %s -> %s' % (applicationId, packageName))
res = requests.get(
url="http://data-api.zhangxinhulian.com/cms/add/apps_info/getBuildCfg?tgyz=1",
params={"pkg": packageName}
)
data = res.json()['result']['data']
print(data)
fileUrl = data['fileUrl']
adIds = data.get('buildCfg')
eventUrl = data['reportDomain']
apiUrl = data['apiDomain']
facebookId = data['facebookApId']
configString = data['appCfg']
if adIds is not None:
admobId = adIds['admobApplicationId']
openId = adIds['admobOpenAdId']
interId = adIds['admobInternalAdId']
nativeId = adIds['admobNativeAdId']
rewardId = adIds['admobRewardAdId']
if admobId is not None:
sub_map['(?<="com.google.android.gms.ads.APPLICATION_ID"\\s*?android:value=")[\\w~-]*?(?=")'] = admobId
if openId is not None:
sub_map['(?<=val openAdmobId = )\\S*'] = '"' + openId + '"'
if interId is not None:
sub_map['(?<=val interAdmobId = )\\S*'] = '"' + interId + '"'
if nativeId is not None:
sub_map['(?<=val nativeAdmobId = )\\S*'] = '"' + nativeId + '"'
if rewardId is not None:
sub_map['(?<=val rewardAdmobId = )\\S*'] = '"' + rewardId + '"'
if eventUrl is not None:
sub_map['(?<=val eventUrl = )\\S*'] = '"' + eventUrl + '"'
if apiUrl is not None:
sub_map['(?<=val apiUrl = )\\S*'] = '"' + apiUrl + '"'
if configString is not None:
sub_map['(?<=var configString = )\\S*'] = '"' + configString + '"'
sub_map_text(sub_map)
if facebookId is None:
print('后台facebookId 未配置')
elif len(find_text('<string name=".*?">' + facebookId + '</string>')) == 0:
print('facebook 配置出错')
try:
urllib.request.urlretrieve(fileUrl, 'google-services.json')
print('google-services.json 替换成功')
except URLError as e:
print('google-services.json 替换失败')
print(e)
return
input('回车开始压缩(\\n)')
versionName = find_text('versionName.*?"(.*?)"', is_one=True)
path = os.path.abspath(get_path('..\\'))
new_path = os.path.join(get_desktop_path(), os.path.basename(path) + '_' + versionName)
exclusions = ['.git', '.gradle', '.idea', 'build', '.gitignore', 'local.properties', '.gitignore', 'mapping.json', 'packing.py', 'proguard.py', 'release', 'debug']
copy_folder_exclude(path, new_path, exclusions)
shutil.make_archive(new_path, 'zip', new_path)
shutil.rmtree(new_path)
print('Done!')
if __name__ == '__main__':
main()
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
def encrypt(content):
try:
iv = get_random_bytes(12)
cipher = AES.new(aesKey.encode('utf-8'), AES.MODE_GCM, nonce=iv)
encrypt_data, tag = cipher.encrypt_and_digest(content.encode('utf-8'))
result = iv + encrypt_data + tag
return b64encode(result).decode('utf-8')
except Exception as e:
print('error: ' + str(e))
return content
def decrypt(content):
try:
combined = b64decode(content.encode('utf-8'))
iv = combined[:12]
tag = combined[-16:]
encrypt_data = combined[12:-16]
cipher = AES.new(aesKey.encode('utf-8'), AES.MODE_GCM, nonce=iv)
decrypt_data = cipher.decrypt_and_verify(encrypt_data, tag)
return decrypt_data.decode('utf-8')
except (ValueError, KeyError) as e:
print(e)
return content
def get_classes_impl(result, class_type, 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
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)
def get_classes(class_type=None):
if class_type is None:
class_type = ["kt", "java"]
result = []
get_classes_impl(result, class_type)
return result
def find_text_impl(reg, result, skip_name, 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
find_text_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.extend(findall)
def find_text(reg, path='.', skip_name=None):
if skip_name is None:
skip_name = []
result = []
if os.path.isdir(path):
find_text_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.extend(findall)
except ValueError:
pass
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):
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]
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])
f.write(text)
def sub_map_text(mapping, is_reverse=True, path='.', skip_type=None, skip_name=None):
if len(mapping) == 0:
return []
if skip_type is None:
skip_type = []
if skip_name is None:
skip_name = []
result = []
sub_map_text_impl(mapping, result, skip_type, skip_name, is_reverse, path)
return result
def sub_map_text_impl(mapping, result, skip_type, skip_name, is_reverse=True, 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
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:
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)
if text_result != text:
result.append(path_join)
f.write(text_result)
def replace_text(old, new, 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_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))
def get_random_package():
packages = [get_random_string(4) for _ in range(get_random_int(1, 3))]
return '.'.join(packages)
def check_mapping():
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)
mapping = {}
for i in packages:
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)
return mapping
def check_class_mapping():
try:
loads = json.load(open('mapping.json', 'r', encoding='utf-8'))
except (ValueError, IOError):
loads = {}
old_mapping = loads.get('class', {})
mapping = {}
classes = get_classes()
for i in classes:
if '_D' in i:
continue
name = os.path.basename(i)
name = name[:name.rfind('.')]
value = old_mapping.get(name)
if value is not None:
mapping[name] = value
continue
mapping[name] = get_title(get_random_string(4), True)
loads["class"] = mapping
json.dump(loads, open('mapping.json', 'w', encoding='utf-8'), indent=4)
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)
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_strings_mapping():
try:
loads = json.load(open('mapping.json', 'r', encoding='utf-8'))
except (ValueError, IOError):
loads = {}
mapping = loads.get('strings', {})
string_values = find_text('<string name="(.*?)".*?>(.*?)</string>', get_path('.\\src\\main\\res\\values'))
strings = []
for i in string_values:
if i[0].endswith('___') or i[0] in ['app_name', 'facebook_app_id']:
continue
if mapping.get(i[1]) is not None:
continue
strings.append(i[1])
for i in strings:
mapping[i] = encrypt(i)
loads["strings"] = mapping
json.dump(loads, open('mapping.json', 'w', encoding='utf-8'), indent=4)
return mapping
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
name = j.rsplit('.', 1)[0]
if mapping.get(name) is not None:
continue
mapping[name] = 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
def get_random_string(length=8, is_ascii=True):
s = ''
for i in range(length):
if is_ascii:
s += chr(random.randint(0, 25) + ord('a'))
else:
s += chr(random.randrange(0x0780, 0x07A5 + 1))
return s
def get_random_int(a=0, b=10000):
return random.randint(a, b)
def get_dictionary_string(length=10):
char_range = ['I', '1']
s = 'I'
for i in range(length + random.randint(3, 5)):
s += random.choice(char_range)
return s
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():
try:
path = get_path('.\\src\\main\\java')
file = find_file('fun View.setTrackedOnClickListener', path)
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']))
except IndexError:
return
def deal_code():
# 替换点击事件
replace_click_method()
# 生成包映射文件
mapping = check_mapping()
packages = list(mapping.keys())
print(mapping)
# 移动文件
root_path = get_path('.\\src\\main\\java')
for key in packages:
key = str(key)
old_path = get_path(root_path + '\\' + key.replace('.', '\\'))
new_path = get_path(root_path + '\\' + mapping[key].replace('.', '\\'))
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] + '"')
with open(get_path('.\\src\\main\\AndroidManifest.xml'), 'w', encoding='utf-8') as f:
f.write(text)
# 修改依赖
replace_map_text(mapping)
# 过滤修改
sub_map = {
mapping[applicationId] + '.R(?=\\W)': applicationId + '.R',
mapping[applicationId] + '.databinding(?=\\W)': applicationId + '.databinding',
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)
# 类名
class_mapping = check_class_mapping()
classes = get_classes()
print(class_mapping)
for i in classes:
if '_D' in i:
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:
sub_map['(?<=\\W)' + i + '(?=\\W)'] = class_mapping[i]
sub_map_text(sub_map)
# 混淆字典
check_obfuscation_dictionary()
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
name = j.rsplit('.', 1)[0]
path = os.path.join(path_join, j)
shutil.move(path, path.replace(name, type_mapping[name]))
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)
def add_image_noise(path):
try:
image = cv2.imread(path, cv2.IMREAD_UNCHANGED)
# 添加随机噪声
noise = np.random.randint(0, 2, image.shape, np.uint8)
image = cv2.add(image, noise)
new_path = path.rsplit('.', 1)[0] + '.webp'
cv2.imwrite(new_path, 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))
def get_title(string, is_all_upper=False):
splits = string.split('_')
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:
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)
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 result == aesKey:
continue
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() 路径
class_path = get_class_path(find_file('fun Int.string', get_path('.\\src\\main\\java'))) + '.string'
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')
# aesKey 密钥不加密
if result == aesKey:
continue
result_text = result_text.replace(text[start:index], 'R.string.' + string_mapping[result] +
'.string(' + ', '.join(params) + ')')
# 添加 Int.string() 方法引用
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 资源
deal_code_string()
# 替换 TextView 为 AESTextView
aes_text_view = get_class_path(find_file('class AESTextView', get_path('.\\src\\main\\java')))
sub_map = {
'(?<=<)TextView': aes_text_view,
'(?<=<)com.noober.background.view.BLTextView': aes_text_view
}
sub_map_text(sub_map, path=get_path('.\\src\\main\\res\\layout'), skip_name=['notify', 'notity'])
# 加密 string
strings_mapping = check_strings_mapping()
print(strings_mapping)
sub_map = {}
for i in strings_mapping:
sub_map['<string(.*?)>' + re.escape(i) + '</string>'] = \
'<string\\g<1>>' + re.escape(strings_mapping[i]) + '</string>'
sub_map_text(sub_map, path=get_path('.\\src\\main\\res\\values'))
# 改 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)
for attr in styleables_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[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['(?<=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)
shutil.move(path_join, path_join.replace(name, 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 check_activity_mapping():
try:
loads = json.load(open('mapping.json', 'r', encoding='utf-8'))
except (ValueError, IOError):
loads = {}
old_list = loads.get('activity', [])
old_paths = []
for i in old_list:
old_paths.append(i[:i.rfind('.')])
old_paths = sorted(set(old_paths), key=old_paths.index)
old_paths.sort(key=len)
classes = get_classes()
paths = []
root_path = get_path('.\\src\\main\\java')
for i in classes:
paths.append(i[len(root_path) + 1:i.rfind(os.sep)].replace(os.sep, '.'))
paths = sorted(set(paths), key=paths.index)
paths.sort(key=len)
new_list = []
for i in old_list:
if i[:i.rfind('.')] in paths:
new_list.append(i)
for i in paths:
if i in old_paths:
continue
# 每个路径下产生随机个垃圾类
for _ in range(random.randint(0, 3)):
name = get_title(get_random_string(4), True) + '_D'
new_list.append(i + '.' + name)
loads["activity"] = new_list
json.dump(loads, open('mapping.json', 'w', encoding='utf-8'), indent=4)
return new_list
def add_junk_activity():
root_path = get_path('.\\src\\main\\java')
new_list = check_activity_mapping()
count = len(new_list)
random.shuffle(new_list)
for i in range(count):
class_path = get_path(root_path + '\\' + new_list[i].replace('.', '\\') + '.kt')
# 获取垃圾类代码
code = get_random_activity_code(new_list[i], new_list[(i + 1) % count])
with open(class_path, 'w', encoding='utf-8') as f:
f.write(code)
print(class_path)
with open(get_path('.\\src\\main\\AndroidManifest.xml'), 'r', encoding='utf-8') as f:
text = f.read()
replace_string = '\n'
for i in new_list:
replace_string += ' <activity android:name="' + i + '" />\n'
text = text.replace(' </application>', replace_string + ''' </application>''')
with open(get_path('.\\src\\main\\AndroidManifest.xml'), 'w', encoding='utf-8') as f:
f.write(text)
def get_random_view():
return random.choice(
['TextView', 'Button', 'EditText', 'ImageView', 'FrameLayout', 'LinearLayout', 'CalendarView', 'CheckBox',
'CheckedTextView', 'DatePicker', 'ExpandableListView', 'GridLayout', 'GridView', 'HorizontalScrollView',
'ImageButton', 'ListView', 'RelativeLayout', 'ScrollView', 'SearchView', 'SeekBar', 'TableLayout'])
def get_random_activity_code(class1, class2):
# 代码中,class1 跳转 class2,确保垃圾类被引用到
package1 = class1[:class1.rfind('.')]
name1 = class1[len(package1) + 1:]
package2 = class2[:class2.rfind('.')]
name2 = class2[len(package2) + 1:]
view = get_random_view()
if package1 == package2:
import_code = ''
else:
import_code = '\nimport ' + class2
code = '''package ''' + package1 + '''
import android.content.Intent
import android.os.Bundle
import android.widget.''' + view + '''
import androidx.appcompat.app.AppCompatActivity''' + import_code + '''
class ''' + name1 + ''' : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(''' + view + '''(this))
startActivity(Intent(this, ''' + name2 + '''::class.java))
}
}'''
return code
def add_junk_method():
classes = get_classes(['kt'])
for path in classes:
with open(path, 'r', encoding='utf-8') as f:
text = f.read()
if text.find('data class') != -1:
continue
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('/\\*[\\s\\S]*?\\*/', '', text)
text = re.sub('\n{2,}', '\n\n', text)
result_text = text
interfaces = re.findall('interface .*?{.*?}', text, flags=re.S)
for interface in interfaces:
start = text.find(interface)
start = text[:start].rfind('\n') + 1
if start == -1:
continue
index = start
count = 0
isStart = False
while (not isStart or count > 0) and index < len(text):
if text[index] == '{':
count += 1
if count == 1:
isStart = True
elif text[index] == '}':
count -= 1
index += 1
# 过滤接口及其内容
text = text.replace(text[start:index], '')
functions = re.findall('fun (?!interface ).*?\\(.*?\\)', text, flags=re.S)
for function in functions:
start = text.find(function)
start = text[:start].rfind('\n') + 1
if start == -1:
continue
index = start
count = 0
isStart = False
while (not isStart or count > 0) and index < len(text):
if text[index] == '(':
count += 1
if count == 1:
isStart = True
elif text[index] == ')':
count -= 1
index += 1
if 'abstract' in text[start:index]:
continue
count = 0
isStart = False
param_end = index
while (not isStart or count > 0) and index < len(text):
if not isStart and text[index] == '=':
break
if text[index] == '{':
count += 1
if count == 1:
isStart = True
param_end = index
elif text[index] == '}':
count -= 1
index += 1
if not isStart:
continue
fun_text = text[start:index]
header = text[start:param_end]
param_start = start + header.find('(')
context = re.search('fun (.*?\\.)?\\w*?', text[start:param_start])[1]
if context is None:
context = ''
param_string = text[param_start:param_end]
if 'override fun ' in header and '\n ' + \
re.sub('(final )?override fun ', 'super.', header[:header.index(param_string)]) in fun_text:
methods = []
for i in range(random.randint(0, 5)):
result = add_one_method(fun_text, param_string, header, context)
if result is None or len(result) != 3:
continue
fun_text, method_text1, method_text2 = result
methods.insert(0, method_text2)
methods.insert(0, method_text1)
methods.insert(0, fun_text)
fun_text = '\n\n'.join(methods)
else:
methods = []
for i in range(random.randint(0, 5)):
result = add_one_method(fun_text, param_string, header, context)
if result is None or len(result) != 2:
continue
fun_text, method_text = result
methods.insert(0, method_text)
methods.insert(0, fun_text)
fun_text = '\n\n'.join(methods)
result_text = result_text.replace(text[start:index], fun_text)
with open(path, 'w', encoding='utf-8') as f:
f.write(result_text)
def add_one_method(fun_text, param_string, header, context):
if 'suspend ' in header:
suspend = 'suspend '
else:
suspend = ''
body = fun_text[len(header):]
intent = len(fun_text) - len(fun_text.lstrip())
param = param_string[1:param_string.rfind(')')]
param = re.sub('<.*?>', '', param)
param = re.sub('\\([^(]*?\\)', '()', param)
param = [i.strip().split(':')[0] for i in param.split(',')]
for i in range(len(param)):
if param[i].startswith('vararg'):
param[i] = '*' + param[i].split(' ')[-1]
elif param[i].startswith('@'):
param[i] = param[i].split(' ')[-1]
if 'override fun ' in header and '\n ' + \
re.sub('(final )?override fun ', 'super.', header[:header.index(param_string)]) in fun_text:
if ':' in param_string[param_string.rfind(')'):]:
return
method_split = [i.rstrip() for i in fun_text.split('\n')]
if method_split[0].rstrip()[-1] != '{' or method_split[-1].lstrip()[0] != '}':
return
method_body = method_split[1:-1]
super_index = -1
for i in range(len(method_body)):
if 'super.' in method_body[i]:
if super_index >= 0:
super_index = -1
break
super_index = i
if super_index < 0:
return
method_text1 = method_split.copy()
method_name1 = get_random_string(is_ascii=False)
method_text1[0] = method_text1[0].replace(header, ' ' * intent + 'private ' + suspend + 'fun ' +
context + method_name1 + param_string)
method_text1[1:-1] = method_body[:super_index]
method_text2 = method_split.copy()
method_name2 = get_random_string(is_ascii=False)
method_text2[0] = method_text2[0].replace(header, ' ' * intent + 'private ' + suspend + 'fun ' +
context + method_name2 + param_string)
method_text2[1:-1] = method_body[super_index + 1:]
method_body[super_index + 1:] = [' ' * (intent + 4) + method_name2 + '(' + ', '.join(param) + ')']
method_body[:super_index] = [' ' * (intent + 4) + method_name1 + '(' + ', '.join(param) + ')']
method_split[1:-1] = method_body
method_text1 = '\n'.join(method_text1)
method_text2 = '\n'.join(method_text2)
fun_text = '\n'.join(method_split)
return fun_text, method_text1, method_text2
else:
method_name = get_random_string(is_ascii=False)
method_text = fun_text
if ':' in param_string[param_string.rfind(')'):]:
method_text = method_text.replace(header, ' ' * intent + 'private ' + suspend + 'fun ' + context +
method_name + param_string)
fun_text = fun_text.replace(body, '{\n' + ' ' * (intent + 4) + 'return ' +
method_name + '(' + ', '.join(param) + ')\n' + ' ' * intent + '}')
else:
method_text = method_text.replace(header, ' ' * intent + 'private ' + suspend + 'fun ' + context +
method_name + param_string)
fun_text = fun_text.replace(body, '{\n' + ' ' * (intent + 4) +
method_name + '(' + ', '.join(param) + ')\n' + ' ' * intent + '}')
return fun_text, method_text
def add_junk_code():
classes = get_classes(['kt'])
for path in classes:
with open(path, 'r', encoding='utf-8') as f:
text = f.read()
if text.find('data class') != -1:
continue
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('/\\*[\\s\\S]*?\\*/', '', text)
text = re.sub('\n{2,}', '\n\n', text)
functions = re.findall('((?=fun .*?\\([^}]*?\\)|init |val.*?by lazy)[^}]*?\\{)', text)
result_text = text
for function in functions:
start = text.find(function)
start = text[:start].rfind('\n') + 1
if start == -1:
continue
index = start
count = 0
isStart = False
while (not isStart or count > 0) and index < len(text):
if text[index] == '(':
count += 1
if count == 1:
isStart = True
elif text[index] == ')':
count -= 1
if not isStart and text[index] == '{':
break
index += 1
count = 0
isStart = False
while (not isStart or count > 0) and index < len(text):
if text[index] == '{':
count += 1
if count == 1:
isStart = True
elif text[index] == '}':
count -= 1
index += 1
fun_text = text[start:index]
fun_text = fun_text.split('\n')
intent_stack = []
last_intent = 0
log_indexes = []
skip_text = [False for _ in fun_text]
i = 0
while i < len(fun_text):
if 'object : ' in fun_text[i] and fun_text[i].rstrip().endswith('{'):
intent = len(fun_text[i]) - len(fun_text[i].lstrip())
while i < len(fun_text) - 1:
i += 1
skip_text[i] = True
if fun_text[i].lstrip().startswith('}') and \
len(fun_text[i]) - len(fun_text[i].lstrip()) == intent:
break
i += 1
for i in range(len(fun_text)):
if skip_text[i]:
continue
intent = len(fun_text[i]) - len(fun_text[i].lstrip())
if intent == 0 or intent % 4 != 0:
continue
if intent > last_intent and i > 0:
j = i - 1
while len(fun_text[j]) - len(fun_text[j].lstrip()) == 0 and j >= 0:
j -= 1
if j >= 0:
for k in range(int((intent - last_intent) / 4)):
intent_stack.append(j)
elif intent < last_intent:
for k in range(int((last_intent - intent) / 4)):
intent_stack.pop()
if len(intent_stack) > 0 and intent >= last_intent \
and len(fun_text[i].strip()) != 0 and fun_text[i].lstrip()[0] != ')':
last_text = fun_text[intent_stack[-1]]
if last_text[-1] == '{' and 'when' not in last_text and '$' not in last_text and \
'object' not in last_text and 'class ' not in last_text and 'interface ' not in last_text:
log_indexes.append((i, intent))
last_intent = intent
log_indexes.reverse()
for i in log_indexes:
if random.random() > 0.35:
continue
junk_code = get_random_junk_code()
junk_code = junk_code.replace('\n', '\n' + ' ' * i[1])[1:]
fun_text.insert(i[0], junk_code)
result_text = result_text.replace(text[start:index], '\n'.join(fun_text))
with open(path, 'w', encoding='utf-8') as f:
f.write(result_text)
add_import('android.util.Log', classes)
def get_random_junk_code():
junk_code = random.choice(junk_codes)
count = 1
while True:
random_str = get_random_string(is_ascii=False)
replace = junk_code.replace('[str' + str(count) + ']', random_str)
if junk_code == replace:
break
junk_code = replace
count += 1
count = 1
while True:
random_int = get_random_int()
replace = junk_code.replace('[int' + str(count) + ']', str(random_int))
if junk_code == replace:
break
junk_code = replace
count += 1
return junk_code
junk_codes = [
'''
if (System.currentTimeMillis() < [int1]) {
println(System.currentTimeMillis())
}''',
'''
Log.d("[str1]", "[str1]")''',
'''
val [str1] = [int1]..[int1] + 10
println([str1].random())''',
'''
val [str1]: (Int, Int) -> Int = { [str2], [str3] -> [str2] + [str3] }
println([str1].invoke([int1], [int2]))''',
'''
class [str1](val [str2]: String, val [str3]: Int)
println([str1]("[str4]", [int1]))''',
'''
open class [str1] {
open fun [str2]() {}
}
class [str3] : [str1]() {
override fun [str2]() {
println("[str5]")
}
}
val [str4] = [str3]()
println([str4])''',
'''
fun String.[str1](): Boolean {
return this == this.reversed()
}
val [str2] = "[str4]"
val [str3] = [str2].[str1]()
println([str3])''',
'''
fun [str1]([str2]: Int, [str3]: Int): Int {
return [str2] + [str3]
}
println([str1]([int1], [int2]))''',
'''
val [str1] = { [str2]: String -> println([str2]) }
[str1]("[str3]!")''',
'''
fun [str1]([str2]: String) {
println("tag, $[str2]!")
}
[str1]("[str3]")''',
'''
fun [str1]() {
fun [str2]([str3]: Int) {
println([str3])
}
[str2]([int1])
[str2]([int2])
[str2]([int3])
}
[str1]()''',
'''
class [str1] {
inner class [str2] {
fun [str3]() {
println("[str5]")
}
}
}
val [str4] = [str1]().[str2]()
[str4].[str3]()''',
'''
class [str1] {
fun [str2]() {
println("[str3]")
}
}
[str1]().[str2]()''',
'''
abstract class [str1] {
abstract fun [str2]()
}
class [str3] : [str1]() {
override fun [str2]() {
println("[str5]")
}
}
val [str4] = [str3]()
[str4].[str2]()''',
'''
class [str1]<T>(val [str2]: T)
val [str3] = [str1]([int1])
val [str4] = [str1]("[str5]")
println([str3])
println([str4])''',
'''
fun <T> [str1]([str2]: T) {
println([str2])
}
[str1]([int1])
[str1]("[str3]")''',
'''
class [str1]<T, K>(val [str3]: T, val [str4]: K) {
init {
println("[str5]")
}
}
val [str2] = [str1]("[str6]", [int1])
println([str2])''',
'''
class [str1]<T>(val [str2]: T, val [str3]: Int) {
constructor([str4]: T) : this([str4], [int1]) {
println("[str5]")
}
}
println([str1]("[str6]"))''',
'''
val [str1] = [int1]
val [str2] = [int2]
val [str3] = [str1] * [str2] * [str2]
println([str3])''',
'''
fun [str1]([str3]: Int, [str4]: Int): Int {
return [str3] + [str4]
}
val [str2] = [str1]([int1], [int2])
println([str2])''',
'''
fun [str1]([str4]: Int, [str5]: Int): Int {
fun [str2]([str6]: Int, [str7]: Int): Int {
return [str6] + [str7]
}
return [str2]([str4], [str5])
}
val [str3] = [str1]([int1], [int2])
println([str3])''',
'''
class [str1] {
val [str2] by lazy {
println("[str4]")
"[str5]!"
}
}
val [str3] = [str1]()
println([str3].[str2])''',
'''
class [str1]<T>([str3]: T) {
init {
println("[str4]: $[str3]")
}
}
println([str1]("[str2]"))''',
'''
class [str1]<T>(val [str3]: T, val [str4]: Int) {
constructor([str3]: T) : this([str3], [int1]) {
println("[str4]")
}
}
println([str1]("[str2]"))''',
'''
class [str1]<T>(val [str4]: T)
val [str2] = [str1]([int1])
val [str3] = [str1]("[str5]")
println([str2].[str4])
println([str3].[str4])'''
]
def class_disorder():
classes = get_classes(['kt'])
for path in classes:
with open(path, 'r', encoding='utf-8') as f:
text = f.read()
if text.find('data class') != -1:
continue
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('/\\*[\\s\\S]*?\\*/', '', text)
text = re.sub('\n{2,}', '\n\n', text)
lines = text.split('\n')
count = 0
begin = 0
result = []
start = -1
end = 0
for index in range(len(lines)):
if len(lines[index].strip()) == 0:
continue
if start == -1:
if lines[index][-1] == '{':
start = index
count += 1
continue
for char in lines[index]:
if char in ['{', '(']:
count += 1
elif char in ['}', ')']:
if count == 1:
end = index
count -= 1
if count == 1:
if lines[index][-1] in ['}', ')'] and begin != 0:
if 'get()' in lines[begin] or 'set(value)' in lines[begin]:
for i in range(len(result)):
if result[i][1] == begin:
result[i] = (result[i][0], index + 1)
break
else:
result.append((begin, index + 1))
begin = 0
elif lines[index][-1] not in ['{', '(']:
strip = lines[index].lstrip()
if strip[0] == '.' or strip.startswith('get() =') or strip.startswith('set(value) = '):
for i in range(len(result)):
if result[i][1] == index:
result[i] = (result[i][0], index + 1)
break
else:
result.append((index, index + 1))
elif count == 2 and (lines[index][-1] in ['{', '('] or lines[index].endswith('->')) \
and lines[begin][-1] not in ['{', '(']:
begin = index
result_temp = []
i = 0
while i < len(result):
begin = result[i][0]
while lines[result[i][0]].lstrip().startswith('@'):
i += 1
result_temp.append([begin, result[i][1]])
i += 1
result = result_temp
random.shuffle(result)
result_sort = result.copy()
var_weight = {}
for i in re.findall('va[lr] (\\w+)', text):
var_weight[i] = len(re.findall('\\W' + re.escape(i) + '\\W', text))
for var in sorted(var_weight, key=lambda k: var_weight[k]):
for i in range(len(result)):
findall = re.findall('va[lr] (' + re.escape(var) + ')', lines[result[i][0]])
if len(findall) == 0:
continue
result_sort.remove(result[i])
result_sort.insert(0, result[i])
break
result = result_sort
text = '\n'.join(lines[:start + 1]) + '\n'
for i in result:
text += '\n' + '\n'.join(lines[i[0]:i[1]]) + '\n'
text += '\n'.join(lines[end:])
with open(path, 'w', encoding='utf-8') as f:
f.write(text)
def deal_junk():
# 添加垃圾类
add_junk_activity()
# 添加方法嵌套
add_junk_method()
# 添加垃圾代码
add_junk_code()
# 类成员乱序
class_disorder()
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('.')])):
return
# 资源混淆
deal_res()
# 垃圾代码
deal_junk()
# 代码处理
deal_code()
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()
applicationId = re.search('applicationId .*?["\'](.*?)["\']', open(gradle_path, 'r', encoding='utf-8').read())[1]
aesKey = find_text(' aesKey ?= ?"(.*?)"', get_path('.\\src\\main\\java'))[0]
print('包名: ' + applicationId, 'aesKey: ' + aesKey)
main()
...@@ -9,12 +9,14 @@ ...@@ -9,12 +9,14 @@
tools:ignore="ScopedStorage" /> tools:ignore="ScopedStorage" />
<application <application
android:name=".MyApplicaiton"
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/zsd_589878" android:icon="@mipmap/zsd_589878"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:requestLegacyExternalStorage="true"
android:requestRawExternalStorageAccess="true"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.SolarMasterAce" android:theme="@style/Theme.SolarMasterAce"
tools:targetApi="31"> tools:targetApi="31">
......
package com.zxhy.solarmasterace
import android.app.Application
class MyApplicaiton:Application() {
companion object {
lateinit var context: MyApplicaiton
}
override fun onCreate() {
super.onCreate()
context = this
}
}
\ No newline at end of file
package com.zxhy.solarmasterace.tools
import android.util.Base64
import java.security.SecureRandom
import javax.crypto.Cipher
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec
object AESHelper {
private const val aesKey = ""
private val cipher by lazy {
Cipher.getInstance("AES/GCM/NoPadding")
}
fun encrypt(content: String): String {
try {
val iv = ByteArray(12).apply {
SecureRandom().nextBytes(this)
}
val contentBytes = content.toByteArray(Charsets.UTF_8)
val params = GCMParameterSpec(128, iv)
cipher.init(
Cipher.ENCRYPT_MODE,
secretKey, params
)
val encryptData = cipher.doFinal(contentBytes)
assert(encryptData.size == contentBytes.size + 16)
val message = ByteArray(12 + contentBytes.size + 16)
System.arraycopy(iv, 0, message, 0, 12)
System.arraycopy(encryptData, 0, message, 12, encryptData.size)
return String(Base64.encode(message, Base64.NO_WRAP), Charsets.UTF_8)
} catch (_: Exception) {
}
return content
}
@Synchronized
fun decrypt(content: String): String {
try {
val con = content.replace(" ".toRegex(), "+")
val contentByte = Base64.decode(con, Base64.NO_WRAP)
require(contentByte.size >= 12 + 16)
val params = GCMParameterSpec(128, contentByte, 0, 12)
cipher.init(
Cipher.DECRYPT_MODE,
secretKey, params
)
val decryptData = cipher.doFinal(contentByte, 12, contentByte.size - 12)
return String(decryptData, Charsets.UTF_8)
} catch (_: Exception) {
}
return content
}
private val secretKey by lazy {
SecretKeySpec(aesKey.toByteArray(), "AES")
}
}
\ No newline at end of file
package com.zxhy.solarmasterace.tools
import android.content.Context
import android.util.AttributeSet
import com.zxhy.solarmasterace.tools.KotlinExt.decode
class AESTextView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : androidx.appcompat.widget.AppCompatTextView(context, attrs) {
override fun setText(text: CharSequence?, type: BufferType?) {
super.setText(text?.toString().takeIf { it != it?.decode() }?.decode() ?: text, type)
}
}
\ No newline at end of file
package com.zxhy.solarmasterace.tools
import android.view.View
import com.zxhy.solarmasterace.MyApplicaiton
import java.text.SimpleDateFormat
import java.util.Locale
object KotlinExt {
private val aesMap = mutableMapOf<Int, String>()
fun Int.string(vararg arg: Any) = try {
(aesMap[this] ?: MyApplicaiton.context.getString(this).decode()).run {
aesMap[this@string] = this
String.format(this, *arg)
}
} catch (_: Exception) {
""
}
fun String.decode() = AESHelper.decrypt(this)
.replace("\\n", "\n")
.replace("\\'", "'")
.replace("\\?", "?")
.replace("&amp;", "&")
fun Collection<View>.setOnClickListener(listener: (View) -> Unit) {
this.forEach {
it.setOnClickListener(listener)
}
}
fun Number.toFormatSize(count: Int = 2): String {
var suffix = "B"
var fSize = this.toDouble()
if (fSize > 1024) {
suffix = "KB"
fSize /= 1024.0
}
if (fSize > 1024) {
suffix = "MB"
fSize /= 1024.0
}
if (fSize > 1024) {
suffix = "GB"
fSize /= 1024.0
}
return String.format("%.${count}f %s", fSize, suffix)
}
fun Long.toFormatTime(): String {
return SimpleDateFormat("MMM dd,yyyy", Locale.getDefault()).format(this)
}
}
\ No newline at end of file
...@@ -16,7 +16,14 @@ dependencyResolutionManagement { ...@@ -16,7 +16,14 @@ dependencyResolutionManagement {
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
maven("https://packages.aliyun.com/maven/repository/2279663-release-AiyNZM/") {
credentials {
username = "621344be8af5d39eb3f17f3e"
password = "(ijI1DwR7[wG"
} }
}
}
} }
rootProject.name = "Solar Master Ace" rootProject.name = "Solar Master Ace"
......
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