def old_mode_generate_subpackage(self, task: Task, ext, auto_test: AutoTestInfo = None) -> bool: channel_id = task.params_info.common_params.channel_id for package_id in task.params_info.common_params.package_chanle: start_time = int(time.time()) # 统计处理分包起始时间 Flog.i("正在打第{0}/{1}个分包,分包ID={2}".format(self.__current_index + 1, self.__package_count, package_id)) if task.bag_info.group_id == "5": if not invoke_script.game(task, ext): self.__wx_err_msg = "打分包执行游戏脚本失败" return False if not pack_core.handle_common_params(task, channel_id, package_id): self.__wx_err_msg = "打分包执行handle_v3_common_params失败" return False if not self.__compile(task): self.__wx_err_msg = "打分包回编译失败" return False signed, new_apk = self.__sign(task, package_id) if not signed: self.__wx_err_msg = "打分包重签名失败" return False if not self.__upload_bag(task, new_apk, package_id, start_time, auto_test): return False return True
def del_system_label(label): # 开始删除字段 filelist = ["colors", "dimens", "ids", "public", "strings", "styles"] for f in filelist: fpath = FPath.DECOMPILE_PATH + "/res/values/" + f + ".xml" if os.path.exists(fpath): tree = elementTree.parse(fpath) root = tree.getroot() for node in list(root): attrib_name = node.attrib.get('name') if attrib_name is None: continue if attrib_name.lower().startswith(label): root.remove(node) Flog.i("remove debug res index name:" + attrib_name + " from" + fpath) tree.write(fpath, "UTF-8") res_path = FPath.DECOMPILE_PATH + "/res" filelist = [] fio.list_file(res_path, filelist, []) for f in filelist: if os.path.basename(f).lower().startswith(label): fio.del_file_folder(f) Flog.i("remove debug res file:" + f)
def handle_origin_res() -> bool: try: files = [] fio.list_file(FPath.DECOMPILE_PATH + "/res", files) for file in files: if "authsdk_" in file and os.path.exists(file): os.remove(file) if "umcsdk_" in file and os.path.exists(file): os.remove(file) if "kkk_" in file and os.path.exists(file): os.remove(file) xml_list = ["colors", "dimens", "ids", "public", "strings", "styles"] for f in xml_list: xml_path = FPath.DECOMPILE_PATH + "/res/values/" + f + ".xml" if os.path.exists(xml_path): tree = elementTree.parse(xml_path) root = tree.getroot() for node in list(root): attrib_name = node.attrib.get('name') if attrib_name is None: continue if attrib_name.lower().startswith("authsdk_"): root.remove(node) if attrib_name.lower().startswith("kkk_"): root.remove(node) tree.write(xml_path, "UTF-8") return True except Exception as e: Flog.i("handle_origin_res() error " + str(e)) return False
def handle_v3_origin() -> bool: if handle_origin_assets() and handle_origin_res() and handle_smali(): Flog.i("handle_v3_origin() success") return True else: Flog.i("handle_v3_origin() error") return False
def removeMinifestComponentByName(decompileDir, typeName, componentName): manifestFile = decompileDir + "/AndroidManifest.xml" ET.register_namespace('android', androidNS) key = '{' + androidNS + '}name' tree = ET.parse(manifestFile) root = tree.getroot() applicationNode = root.find('application') if applicationNode is None: return activityNodeLst = applicationNode.findall(typeName) if activityNodeLst is None: return for activityNode in activityNodeLst: name = activityNode.get(key) if name == componentName: applicationNode.remove(activityNode) break tree.write(manifestFile, 'UTF-8') Flog.i("remove " + componentName + " from AndroidManifest.xml") return componentName
def post(url: str, raw: str, upload: bool = False, file_link: str = "") -> ResultInfo: """ 加密流程: 1)当前时间戳+10位长度Digits(0-9)随机数进行32位小写的md5加密得到原始秘钥raw_key 2)把原始秘钥raw_key正序和倒序拼接成64位长度字符串进行16位小写的md5加密得到AES秘钥aes_key 3)使用AES秘钥aes_key对数据进行AES/CBC/PKCS5Padding加密得到数据密文p """ Flog.i("请求参数 : " + raw) time_stamp = str(int(time.time())) raw_key = cipher.get_32low_md5(time_stamp + cipher.get_random_str()) aes_key = cipher.get_16low_md5(raw_key + raw_key[::-1]) # p = cipher.urlencode(cipher.AesCipher.encrypt(raw, aes_key)) p = cipher.urlencode(cipher.AesCipher.encrypt(raw, aes_key)) ts = raw_key enc_url = url + "&p=" + p + "&ts=" + ts Flog.i("logTag " + time_stamp + " : url = " + enc_url) try: if upload: Flog.i("post upload") with open(file_link, encoding="utf-8") as f: result = requests.post(enc_url, files={"log_file": f}) else: result = requests.post(enc_url, headers={"Connection": "close"}, verify=False, timeout=5) Flog.i("返回内容 : " + result.text) return ResultInfo(result.text) except Exception as e: info = { "status": -1, "msg": str(e), "result": {} } Flog.i("请求异常 : " + str(e)) return ResultInfo(json.dumps(info))
def check_game_resource(task: Task) -> bool: Flog.i("检查游戏资源") try: icon_url = task.params_info.game_params.game_icon_url logo_url = task.params_info.game_params.game_logo_url splash_url = task.params_info.game_params.game_splash_url background_url = task.params_info.game_params.game_background_url loading_url = task.params_info.game_params.game_loading_url resource_url = task.params_info.game_params.game_resource_url if not is_empty(icon_url): icon = Download.download_file(icon_url, FPath.RESOURCE_PATH, 1) if not is_empty(logo_url): logo = Download.download_file(logo_url, FPath.RESOURCE_PATH, 2) if not is_empty(splash_url): splash = Download.download_file(splash_url, FPath.RESOURCE_PATH, 3) if not is_empty(background_url): background = Download.download_file(background_url, FPath.RESOURCE_PATH, 4) if not is_empty(background_url): loading = Download.download_file(loading_url, FPath.RESOURCE_PATH, 5) if not is_empty(resource_url): resource = Download.download_file(resource_url, FPath.RESOURCE_PATH) if fio.unzip(FPath.RESOURCE_PATH + "/" + resource, FPath.RESOURCE_PATH): os.remove(FPath.RESOURCE_PATH + "/" + resource) return True except Exception as e: Flog.i(str(e)) return False
def _replace(self, file_name, key: str, value: str): # tempfile默认模式是w+b,写入的是byte,需要改成w+ 才能写入字符串 # 或者也可以不改这个模式,在写入时候转成byte,取出时再传string tfile = tempfile.TemporaryFile(mode="w+") from_regex = key + ".*=.*" if os.path.exists(file_name): r_open = open(file_name, 'r', encoding="utf-8") pattern = re.compile(r'' + from_regex) found = None for line in r_open: if pattern.search(line) and not line.strip().startswith('#'): found = True save_value = key + "=" + value line = re.sub(from_regex, save_value, line) tfile.writelines(line) if not found and self.append_on_not_exists: kav = "\n" + key + "=" + str(value) tfile.writelines(kav) r_open.close() tfile.seek(0) content = tfile.read() if os.path.exists(file_name): fio.remove_files(file_name) w_open = open(file_name, 'w', encoding="utf-8") w_open.writelines(content) w_open.close() tfile.close() else: Flog.i("file %s not found" % file_name)
def __upload_bag(self, task: Task, new_apk: str, package_id: str, start_time: int, compile_time: int = 0, auto_test: AutoTestInfo = None) -> bool: # 上传包 bag_url = self.__upload(task, new_apk) if bag_url is None and bag_url == '': self.__wx_err_msg = "打分包上传包体失败" return False if os.path.exists(new_apk): # 分包大小 self.__data["package_size"] = os.path.getsize(new_apk) # 删除新包 os.remove(new_apk) # 上报打包状态 self.__data["package_id"] = package_id self.__data["status_code"] = 1210 self.__data["bag_url"] = bag_url if self.__current_index + 1 == 1: self.__data["ext"]["from_time"] = int( time.time()) - start_time + compile_time # 分包处理时间 else: self.__data["ext"]["from_time"] = int( time.time()) - start_time # 分包处理时间 self.__random_link_list.append(bag_url) self.__package_id_list.append(package_id) is_stop, msg = self.__report_status(self.__data) if not msg: # 上报打包状态接口正常 # 是否中止执行 if is_stop: Flog.i("中断任务") return True # 判断是否最后一个子包 self.__current_index += 1 if self.__current_index == self.__package_count: # 上报任务状态(成功) random_link = random.choice(self.__random_link_list) random_package_id = self.__package_id_list[ self.__random_link_list.index(random_link)] result, err_msg = self.report_task(task, 1210, random_package_id, random_link, auto_test) if err_msg: self.__wx_err_msg = err_msg return result return True else: # 上报打包状态接口异常 Flog.i("上报打包成功接口异常") self.__wx_err_msg = msg return False
def send_msg_2_wx(url: str, data: dict) -> str: headers = {"Content-Type": "application/json; charset=UTF-8"} try: result = requests.post(url, json.dumps(data), headers=headers) Flog.i(result.text) return result.text except Exception as e: Flog.i("请求异常 : " + str(e)) return str(e)
def insert_file_2_apk(apk_path: str, target_path: str, data: bytes) -> bool: try: with zipfile.ZipFile(apk_path, "a") as zf: with zf.open(target_path, "w") as f: f.write(data) return True except Exception as e: Flog.i(str(e)) return False
def modifyRootApplicationExtends(decompileDir, applicationClassName): applicationSmali = find_root_application_smali(decompileDir) if applicationSmali is None: Flog.i("the applicationSmali get failed.") return Flog.i("modifyRootApplicationExtends: root application smali: " + applicationSmali) modifyApplicationExtends(applicationSmali, applicationClassName)
def __decompile(task: Task): """ 反编译母包 :param task: :return: """ Flog.i("反编译母包") if android.decompile_apk(task): return True else: return False
def create_app() -> Flask: app = Flask(__name__) app.response_class = JSONResponse impl = ImplManager() Flog.init() task_cache.init_cache(app) task_scheduler.init_scheduler(app, impl) return app
def dex2smali(src_dir: str, dst_dir: str) -> bool: baksmali_tool = FPath.BAKSMALI_JAR if not os.path.exists(src_dir): Flog.i("classes.dex is can not found , where : " + src_dir) return False if not os.path.exists(dst_dir): os.makedirs(dst_dir) cmd = get_java_shell() + " -jar %s -o %s %s" % (baksmali_tool, dst_dir, src_dir) return command.exec_command(cmd)
def parse_class(line): """ parse class line :param line: :return: """ if not line.startswith(".class"): Flog.i("line parse error. not startswith .class : " + line) return None blocks = line.split() return blocks[len(blocks) - 1]
def handle_origin() -> bool: """ 处理母包 :return: """ if is_v3_mode(): Flog.i("v3版本母包") result = handle_v3_origin() else: Flog.i("v2版本母包") result = handle_v2_origin() return result
def parse_method_invoke(line): if not line.startswith("invoke-"): Flog.i("the line parse error in parse_method_invoke:" + line) blocks = line.split("->") method = blocks[len(blocks) - 1] pre_blocks = blocks[0].split(",") class_name = pre_blocks[len(pre_blocks) - 1] class_name = class_name.strip() return class_name, method
def parse_method_default(line): """ parse default method :param line: :return: """ if not line.startswith(".method"): Flog.i("the line parse error in parse_method_default:" + line) return None blocks = line.split() return blocks[len(blocks) - 1]
def handle_v2_origin() -> bool: if del_smali_code() and delete_so_file() and del_3k_res( ) and del_manifest_infos(FPath.DECOMPILE_PATH): del_system_label('$') del_system_label('ic_launcher_foreground') del_system_label('ic_launcher_background') fio.del_file_folder(FPath.DECOMPILE_PATH + "/res/mipmap-anydpi-v26") fio.del_file_folder(FPath.DECOMPILE_PATH + "/res/drawable-v24") Flog.i("handle_v2_origin() success") return True else: Flog.i("handle_v2_origin() error") return False
def check_keystore(task: Task) -> bool: Flog.i("检查签名文件资源") try: keystore_info = task.keystore_info file_name = Download.download_file(keystore_info.file_url, FPath.WORKSPACE_PATH) if file_name != keystore_info.keystore_name: os.rename(FPath.WORKSPACE_PATH + "/" + file_name, FPath.WORKSPACE_PATH + "/" + keystore_info.keystore_name) return True except Exception as e: Flog.i(str(e)) return False
def handle_v3_common_params(channel_id: str, package_id: str, new_mode: bool = False): if new_mode: fuse_prop_file = FPath.WORKSPACE_PATH + "/fuse_cfg.properties" else: fuse_prop_file = FPath.DECOMPILE_PATH + "/assets/fuse_cfg.properties" if not os.path.exists(fuse_prop_file): Flog.i("fuse_cfg.properties not exists") return False try: prop = fprop.parse(fuse_prop_file) # 写入融合渠道id if is_empty(channel_id): Flog.i("channel_id 不能为空") return False else: prop.put("3KWAN_Platform_ChanleId", channel_id) # 写入融合渠道id if is_empty(package_id): Flog.i("package_id 不能为空") return False else: prop.put("3KWAN_PackageID", package_id) prop.save() return True except Exception as e: Flog.i("handle_v3_common_params() error " + str(e)) return False
def handle_smali() -> bool: try: if os.path.exists(FPath.DECOMPILE_PATH + "/smali/com/didi"): shutil.rmtree(FPath.DECOMPILE_PATH + "/smali/com/didi") if os.path.exists(FPath.DECOMPILE_PATH + "/smali/com/tencent"): shutil.rmtree(FPath.DECOMPILE_PATH + "/smali/com/tencent") if os.path.exists(FPath.DECOMPILE_PATH + "/smali/cn/impl"): shutil.rmtree(FPath.DECOMPILE_PATH + "/smali/cn/impl") if os.path.exists(FPath.DECOMPILE_PATH + "/smali/cn/kkk"): shutil.rmtree(FPath.DECOMPILE_PATH + "/smali/cn/kkk") return True except Exception as e: Flog.i("handle_smali() error " + str(e)) return False
def del_3k_res() -> bool: """ delete 3k res eg:kkk_ :return: """ try: res_path = FPath.DECOMPILE_PATH + "/res" if not os.path.exists(res_path): Flog.i("can't find this res path : " + res_path) return False res_files = [] fio.list_file(res_path, res_files, []) if res_files is None or len(res_files) <= 0: return True for res in res_files: if "kkk_" in res: fio.del_file_folder(res) # 开始删除字段 # decompile_dir = path_utils.get_full_path(decompile_dir) file_list = ["colors", "dimens", "ids", "public", "strings", "styles"] for f in file_list: fpath = FPath.DECOMPILE_PATH + "/res/values/" + f + ".xml" if os.path.exists(fpath): tree = elementTree.parse(fpath) root = tree.getroot() for node in list(root): attrib_name = node.attrib.get("name") if attrib_name is None: continue if attrib_name.lower().startswith( "kkk_") or attrib_name.lower().startswith("tk_"): root.remove(node) tree.write(fpath, "UTF-8") res_path = FPath.DECOMPILE_PATH + "/res" xml_list = [] fio.list_file(res_path, xml_list, []) for xml in xml_list: if os.path.basename(xml).lower().startswith( "kkk_") or os.path.basename(xml).lower().startswith("tk_"): fio.del_file_folder(xml) return True except Exception as e: Flog.i("del_3k_res() error " + str(e)) return False
def handle_result(result) -> dict: p = "" ts = "" if result: if "p" in result: p = result["p"] if "ts" in result: ts = result["ts"] aes_key = cipher.get_16low_md5(ts + ts[::-1]) raw = cipher.urldecode( cipher.AesCipher.decrypt(cipher.urldecode(p), aes_key)) result = json.loads(raw) Flog.i("解析数据 : " + dict_2_str(result)) return result
def copyResource(task: Task, sdkDir): """ Copy comsdk resources to the apk decompile dir Merge manifest.xml Merge all res xml if the xml already exists in target apk. copy all others resources :return: """ # start to merge manifest.xml manifestFrom = sdkDir + "/SDKManifest.xml" manifestFromTemp = manifestFrom manifestTo = FPath.DECOMPILE_PATH + "/AndroidManifest.xml" if task.params_info.game_params.game_orientation == 1: # 'portrait' manifestFrom = manifestFrom[:-4] + "_portrait.xml" else: manifestFrom = manifestFrom[:-4] + "_landscape.xml" if not os.path.exists(manifestFrom): manifestFrom = manifestFromTemp if os.path.exists(manifestFrom): # merge into xml bRet = mergeManifest(manifestTo, manifestFrom) if bRet: Flog.i("merge manifest file success.") else: Flog.i("merge manifest file failed.") return 1 # copyRes copyFrom = sdkDir + "/res" copyTo = FPath.DECOMPILE_PATH + "/res" if os.path.exists(copyFrom): copyResToApk(copyFrom, copyTo) # copyAssets assetsFrom = sdkDir + "/assets" assetsTo = FPath.DECOMPILE_PATH + "/assets" if os.path.exists(assetsFrom): copyResToApk(assetsFrom, assetsTo) # copyLibs libFrom = sdkDir + "/libs" libTo = FPath.DECOMPILE_PATH + "/lib" if os.path.exists(libFrom): copyLibs(libFrom, libTo)
def get_smali_method_count(smaliFile, allMethods): """ get smali methods count :param smaliFile: :param allMethods: :return: """ if not os.path.exists(smaliFile): return 0 f = open(smaliFile, encoding='UTF-8', errors='ignore') lines = f.readlines() f.close() if lines is None or len(lines) <= 0: return 0 class_line = lines[0] class_line = class_line.strip() if not class_line.startswith(".class"): Flog.i(smaliFile + " not startswith .class") return 0 class_name = parse_class(class_line) count = 0 for line in lines: line = line.strip() method = None temp_class_name = class_name if line.startswith(".method"): method = parse_method_default(line) elif line.startswith("invoke-"): temp_class_name, method = parse_method_invoke(line) if method is None: continue if temp_class_name not in allMethods: allMethods[temp_class_name] = list() if method not in allMethods[temp_class_name]: count = count + 1 allMethods[temp_class_name].append(method) else: pass return count
def __download(download_name: str, file_link: str, save_path: str) -> str: print("down file_link:" + file_link) try: with DownloadHook(unit="B", unit_scale=True, unit_divisor=1024, miniters=1, desc=download_name) as hook: path, msg = request.urlretrieve( file_link, save_path + "/" + download_name, hook.update_to) file_name = str(os.path.basename(path)) return file_name except Exception as e: Flog.i("下载失败:" + str(e)) return ""
def sign(task: Task, new_mode: bool, sigalg: str = "SHA1withRSA") -> bool: if new_mode: apkfile = FPath.WORKSPACE_PATH + "/new_mode_output.apk" else: apkfile = FPath.WORKSPACE_PATH + "/output.apk" keystore = FPath.WORKSPACE_PATH + "/" + task.keystore_info.keystore_name if not os.path.exists(keystore): Flog.i("the keystore file is not exists. " + keystore) return False sign_shell = java.get_jarsigner_shell( ) + " -digestalg SHA1 -sigalg %s -keystore %s -storepass %s -keypass %s %s %s" % ( sigalg, keystore, task.keystore_info.keystore_password, task.keystore_info.keystore_alias_password, apkfile, task.keystore_info.keystore_alias) return command.exec_command(sign_shell)
def remove_files(target: str) -> bool: """ 删除文件或文件夹 :param target: 目标文件或文件夹路径 :return: 是否成功 """ try: if isfile(target): os.remove(target) return True else: shutil.rmtree(target) return True except Exception as e: Flog.i(str(e)) return False