APK反编译工具(APKINFO)及制作详解(附图)

转载请注明出处:https://blog.csdn.net/xiangxinzijiwonen/article/details/83547250(来自ZhongGuanYao的csdn博客)

目录

前言

一、查看APK信息

二、查看APK源代码

三、查看签名信息

四、反编译APK

五、重签名

六、打入Jar包代码


 

前言

从事游戏SDK接入和打包相关工作时,经常对要APK文件进行信息检查、打包测试、问题排查等等,难免要敲一些反编译、重签名等繁琐的命令行,为方便和提高工作效率,制作一款适合自己工作需要的APKINFO小工具是有必要的。

APKINFO工具下载:https://download.csdn.net/download/xiangxinzijiwonen/10766262

github源码:https://github.com/zhongguanyao/APKINFO

注意:无法在CSDN资源下载时,可到github下载源码工程,直接使用bin/Debug目录即可

先展示张图片看下效果:

使用语言:C#

涉及到的工具:aapt.exe、jarsigner.exe、zipalign.exe、apktool、java.exe、keytool.exe,第三方jadx等

APKINFO的功能是通过C#语言对这些工具的命令行进一步封装来实现的,你会发现APKINFO工具为什么有一百多M那么大,是因为它引用了JDK和ADT的部分工具。

项目结构如下:

 

一、查看APK信息

工具:aapt.exe

命令行:aapt dump badging apkFilePath

示例截图:

编程思路:C#调用命令行后获得APK信息,然后将应用名、包名、版本号、图标、权限等信息整理出来,最后展示到窗体的控件上。

注意点:

  • C#在调用命令行时,需要设置使用Shell执行,输出重定向,可以屏蔽Shell窗体的显示。
  • 截图上的中文应用名出现乱码,是因为Shell输出编码默认使用了gb2312,将标准输出编码设置为UTF-8即可。
  • 不同版本的aapt.exe,查看APK信息的速度不一样,输出来的信息结构也稍有不同,一般低版本的aapt执行速度比较快。
  • 图标读取是先在APK信息中找到图标路径,然后根据路径解压图标文件的,项目中是引入Ionic.Zip.dll解压文件。
  • 另外,增加了查看APK的assets目录下的配置,至于查看哪些个配置文件里的参数,由用户设置决定,这里只能读取内容格式为key=value的配置文件。

C#关键代码如下:

BrowseApkInfoBLL类:
 
       /// <summary>
        /// 读取APK信息
        /// </summary>
        /// <param name="apkFilePath"></param>
        /// <returns></returns>
        public static ApkInfo ReadApkInfo(string apkFilePath)
        {
            if (String.IsNullOrEmpty(apkFilePath))
                return null;

            string aaptPath = PathUtils.GetPath(Constants.PATH_AAPT);
            var startInfo = new ProcessStartInfo(aaptPath);
            string args = string.Format("dump badging \"{0}\"", apkFilePath);
            startInfo.Arguments = args;
            startInfo.UseShellExecute = false;
            startInfo.RedirectStandardOutput = true;
            startInfo.CreateNoWindow = true;
            startInfo.StandardOutputEncoding = Encoding.UTF8;
            
            // 读取命令行返回的所有txt文本和字符编码
            List<string> lineLst = new List<string>();
            using (var process = Process.Start(startInfo))
            {
                var sr = process.StandardOutput;
                while (!sr.EndOfStream)
                {
                    lineLst.Add(sr.ReadLine());
                }
                process.WaitForExit();
            }


            ApkInfo apkInfo = new ApkInfo();
            apkInfo.ApkFilePath = apkFilePath;
            apkInfo.CurrentFileName = Path.GetFileName(apkFilePath);
            apkInfo.AllInfoList = lineLst;

            // 解析相关的配置参数
            ParseTxtFile(ref apkInfo);
            ParseIcon(ref apkInfo);
            ParseAssetsConfig(ref apkInfo);

            return apkInfo;
        }


        /// <summary>
        /// 解压并读取icon图片
        /// </summary>
        /// <param name="apkInfo"></param>
        private static void ParseIcon(ref ApkInfo apkInfo)
        {
            if (apkInfo == null 
                || string.IsNullOrEmpty(apkInfo.ApkFilePath)
                || apkInfo.IconDic == null 
                || apkInfo.IconDic.Count <= 0) return;

            string icon = null;
            int i = 0;
            // 在多张尺寸的ICON里选择尺寸为240
            foreach (KeyValuePair<string, string> kv in apkInfo.IconDic)
            {
                if (kv.Key.Contains("240"))
                {
                    icon = kv.Value;
                    apkInfo.IconName = Path.GetFileName(icon);
                    break;
                }
                if (i == apkInfo.IconDic.Count - 1)
                {
                    icon = kv.Value;
                    apkInfo.IconName = Path.GetFileName(icon);
                    break;
                }
                i++;
            }

            // 指定解压目录
            string unZipPath = PathUtils.GetPath(Constants.DIR_TEMP_ICON);
            // 清空解压目录
            FileUtils.ClearDir(unZipPath);

            // 解压指定的icon图片文件
            ZipUtils.UnZip(apkInfo.ApkFilePath, unZipPath, icon);

            // 读取解压出来的图片文件
            string iconPath = PathUtils.JoinPath(unZipPath, icon);

            apkInfo.Icon = new Bitmap(iconPath);
        }

 

二、查看APK源代码

工具:jadx-0.7.1

命令行:jadx-gui.bat  apkFilePath 或jarFilePath 或dexFilePath

之前使用过jd-gui.exe查看源码,但它只能打开jar文件,dex和apk文件需要转换成jar文件才能打开,并不好用;这里推荐使用jadx工具,jar、dex和apk文件它都能直接打开。

编程思路:程序判断用户传递的文件类型,如果为jar或dex文件,则直接通过命令行调用jadx-0.7.1打开,如果为apk文件,则展示APK信息,让用户通过点击《查看源码》按钮打开,当jadx-0.7.1打开后,关闭程序。

C#关键代码如下:

BrowseSourceBLL类:

        /// <summary>
        /// 用jadx-gui.bat打开文件
        /// </summary>
        /// <param name="filePath">以.jar/.dex/.apk为后缀的文件路径</param>
        public static void OpenJadxGUI(string filePath) {

            if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath)) return;

            string jadxPath = PathUtils.GetPath(Constants.PATH_JADXGUI);
            if (!File.Exists(jadxPath))
            {
                LogUtils.WriteLine(jadxPath + "不存在!");
                return;
            }

            string cmdArgs = jadxPath + " " + filePath;
            CmdUtils.ExecCmd(cmdArgs);
        }    


CmdUtils类:

        /// <summary>
        /// 执行CMD命令,但不等待执行结果
        /// </summary>
        /// <param name="args"></param>
        public static void ExecCmd(string args)
        {
            try
            {
                Process proc = new Process();
                proc.StartInfo.WorkingDirectory = System.Windows.Forms.Application.StartupPath;
                proc.StartInfo.FileName = "cmd.exe";
                proc.StartInfo.Arguments = "/C " + args;
                proc.StartInfo.UseShellExecute = false;
                proc.StartInfo.RedirectStandardOutput = true;
                proc.StartInfo.RedirectStandardError = true;
                proc.StartInfo.CreateNoWindow = true;//不创建窗口  
                proc.Start();
               
            }
            catch (Exception ex)
            {
                LogUtils.WriteLine("CMD :" + args);
                LogUtils.WriteLine("CMD Exception Occurred :" + ex.Message + ", " + ex.StackTrace.ToString());
                Console.WriteLine("Exception Occurred :{0},{1}", ex.Message, ex.StackTrace.ToString());
            }

        }

 

三、查看签名信息

工具:keytool.exe

命令行:keytool  -printcert  -file  apk中META-INF目录下的.RSA签名文件路径  // 查看APK签名信息

               keytool  -list -v -keystore certFilePath  -storepass  password  // 查看.jks或.keystore签名库信息

示例截图:

编程思路:如果是查看APK签名信息,需要将APK中META-INF目录下的.RSA签名文件解压到临时目录下,调用命令行读取该.RSA文件的签名信息;如果是查看签名库文件(.jks或.keystore文件),需要用户拖放签名库文件至APKINFO.exe,获得签名库文件路径,再弹框让用户输入密码,最后调用上面第二个命令行即可。

C#关键代码如下:

BrowseCertificateBLL类:

        /// <summary>
        /// 查看APK签名信息
        /// </summary>
        /// <param name="apkFilePath"></param>
        /// <param name="logView"></param>
        /// <returns></returns>
        public static string BrowseCert(string apkFilePath, ILog logView) {

            if (string.IsNullOrEmpty(apkFilePath) || !File.Exists(apkFilePath)) {
                logView.Log("Apk文件不存在!");
                return null;
            }

            // 解压文件存放的临时目录
            string tempDir = PathUtils.GetPath(Constants.DIR_CERT);
            if (!Directory.Exists(tempDir))
            {
                Directory.CreateDirectory(tempDir);
            }
            FileUtils.ClearDir(tempDir);

            // 解压APK文件中META-INF目录下的.RSA签名文件
            ZipUtils.UnZipApkCertRSAFile(apkFilePath, tempDir, logView);

            tempDir = PathUtils.JoinPath(tempDir, "META-INF");
            if (!Directory.Exists(tempDir)) {
                logView.Log(">>>>>>查看签名信息失败: 解压APK文件中META-INF目录下的.RSA签名文件失败,META-INF目录未解压出来!");
                return null;
            }
                
            string[] files = Directory.GetFiles(tempDir);
            if (files == null || files.Length != 1 || !".RSA".Equals(Path.GetExtension(files[0])))
            {
                logView.Log(">>>>>>查看签名信息失败: 解压APK文件中META-INF目录下的.RSA签名文件失败!");
                return null;
            }

            string keyToolPath = PathUtils.GetPath(Constants.PATH_KEYTOOL);
            if (!File.Exists(keyToolPath)) {
                logView.Log(">>>>>>重新签名失败: " + keyToolPath + "不存在!");
                return null;
            }

            var startInfo = new ProcessStartInfo(keyToolPath);
            string args = "-printcert -file " + files[0];
            startInfo.Arguments = args;
            startInfo.UseShellExecute = false;
            startInfo.RedirectStandardOutput = true;
            startInfo.CreateNoWindow = true;

            StringBuilder sb = new StringBuilder();
            using (var process = Process.Start(startInfo))
            {
                var sr = process.StandardOutput;
                while (!sr.EndOfStream)
                {
                    sb.AppendLine(sr.ReadLine());
                }
                process.WaitForExit();
            }

            return sb.ToString();

        }



ZipUtils类:

        /// <summary>
        /// 解压APK文件中META-INF目录下的.RSA签名文件
        /// </summary>
        /// <param name="zipPath"></param>
        /// <param name="outPath"></param>
        /// <param name="logView"></param>
        public static void UnZipApkCertRSAFile(string zipPath, string outPath, ILog logView)
        {

            try
            {
                using (ZipFile zip = ZipFile.Read(zipPath))
                {
                    List<ZipEntry> entries = new List<ZipEntry>();

                    foreach (ZipEntry entry in zip.Entries)
                    {
                        if (string.IsNullOrEmpty(entry.FileName))
                            continue;

                        if (!entry.FileName.StartsWith("META-INF/"))
                            continue;

                        if (entry.FileName.EndsWith(".RSA"))
                        {
                            entry.Extract(outPath, ExtractExistingFileAction.OverwriteSilently);
                            break;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                logView.Log("UnZipApkCertRSAFile Exception : " + ex.Message);
                LogUtils.WriteLine("UnZipApkCertRSAFile Exception : " + ex.Message);
                Console.WriteLine("Exception Occurred :{0},{1}", ex.Message, ex.StackTrace.ToString());
                Console.ReadKey();
            }
        }



        // <summary>
        /// 查看签名库文件信息
        /// </summary>
        /// <param name="certFilePath">.keystore或.jks签名库文件</param>
        /// <param name="password">签名库密码</param>
        /// <param name="logView"></param>
        /// <returns></returns>
        public static string BrowseCert(string certFilePath, string password, ILog logView)
        {

            if (string.IsNullOrEmpty(certFilePath) || !File.Exists(certFilePath))
            {
                logView.Log(">>>>>>查看签名信息失败:签名库文件不存在!");
                return null;
            }

            if (string.IsNullOrEmpty(password))
            {
                logView.Log(">>>>>>查看签名信息失败:密码为空!");
                return null;
            }

            string keyToolPath = PathUtils.GetPath(Constants.PATH_KEYTOOL);
            if (!File.Exists(keyToolPath))
            {
                logView.Log(">>>>>>查看签名信息失败: " + keyToolPath + "不存在!");
                return null;
            }

            var startInfo = new ProcessStartInfo(keyToolPath);
            string args = " -list -v -keystore " + certFilePath + " -storepass " + password;
            startInfo.Arguments = args;
            startInfo.UseShellExecute = false;
            startInfo.RedirectStandardOutput = true;
            startInfo.CreateNoWindow = true;

            StringBuilder sb = new StringBuilder();
            using (var process = Process.Start(startInfo))
            {
                var sr = process.StandardOutput;
                while (!sr.EndOfStream)
                {
                    sb.AppendLine(sr.ReadLine());
                }
                process.WaitForExit();
            }

            return sb.ToString();

        }
    

 

四、反编译APK

工具:java.exe 、apktool.jar、apktool.bat

命令行:apktool.bat  d  apkFilePath  -o  输出目录

示例截图:

编程思路:由于反编译过程比较耗时,需要开启线程执行命令行操作,反编译结束后关闭程序,并打开输出目录。

注意:  输出目录不能是已存在的目录。

C#关键代码如下:

DecompileBLL类:

        /// <summary>
        /// 反编译
        /// </summary>
        /// <param name="apkFilePath"></param>
        /// <returns>返回保存反编译信息的目录,为空表示反编译失败</returns>
        public static string DecompileApk(string apkFilePath)
        {
            if (string.IsNullOrEmpty(apkFilePath) 
                || !File.Exists(apkFilePath)) return null;

            string apktoolPath = PathUtils.GetPath(Constants.PATH_APKTOOL);
            if (!File.Exists(apktoolPath))
            {
                LogUtils.WriteLine(apktoolPath + "不存在!");
                return null;
            }

            string decompileDir = PathUtils.GetPath(Constants.DIR_TEMP_DECOMPLIE);
            if (!Directory.Exists(decompileDir))
            {
                Directory.CreateDirectory(decompileDir);
            }
            FileUtils.ClearDir(decompileDir);
            decompileDir = PathUtils.JoinPath(decompileDir, DateTime.Now.ToString("yyyyMMdd_HHmmss"));

            string cmdArgs = apktoolPath + " d " + apkFilePath + " -o " + decompileDir;// -o后面的目录不能已存在
            string[] res = CmdUtils.ExecCmdAndWaitForExit(cmdArgs);
            if (res == null || res.Length < 2 || !string.IsNullOrEmpty(res[1]))
                return null;

            return decompileDir;
        }

 

五、重签名

工具:jarsigner.exe、zipalign.exe

命令行:

  1. jarsigner  -digestalg SHA1 -sigalg SHA1withRSA  -keystore  keystoreFilePath   -storepass  password   -keypass aliasPwd   apkFilePath  aliasKey
  2. zipalign  -f 4  apkFilePath   newApkFilePath

示例截图:

编程思路:由于重签名也比较耗时,需要开启线程执行命令行操作,因为是对APK文件进行重签名,所以在重签名之前需要删掉META-INF目录下旧的相关签名文件(以.MF、.SF、.RSA、.DSA后缀结尾的文件),重签名之后,使用zipalign.exe工具优化APK。

C#关键代码如下:

ResignApkBLL类:

        /// <summary>
        /// 重签名
        /// </summary>
        /// <param name="apkFilePath"></param>
        /// <param name="newApkPath"></param>
        /// <param name="config"></param>
        /// <param name="logView"></param>
        /// <returns></returns>
        public static bool ResignApk(string apkFilePath, string newApkPath, KeystoreConfig config, ILog logView)
        {
            if (string.IsNullOrEmpty(apkFilePath) || !File.Exists(apkFilePath))
            {
                logView.Log(">>>>>>重新签名失败: " + apkFilePath + "不存在!");
                return false;
            }

            string tempDir = PathUtils.GetPath(Constants.DIR_TEMP_RESIGNAPK);
            if (!Directory.Exists(tempDir))
            {
                Directory.CreateDirectory(tempDir);
            }
            FileUtils.ClearDir(tempDir);
            string tempApkFilePath = PathUtils.JoinPath(tempDir, "temp.apk");

            // 拷贝一份APK作为临时文件,在该临时文件上操作
            FileInfo fi = new FileInfo(apkFilePath);
            if (fi.Exists)
            {
                fi.CopyTo(tempApkFilePath, true);
            }

            logView.BlankLine();
            logView.Log("删除APK文件中META-INF目录下的签名文件");
            ZipUtils.RemoveApkCertFile(tempApkFilePath, logView);

            logView.BlankLine();
            logView.Log("↓↓↓↓↓↓ 重签名-开始 ↓↓↓↓↓↓");

            string jarsignerPath = PathUtils.GetPath(Constants.PATH_JARSIGNER);
            string args = jarsignerPath + " -digestalg SHA1 -sigalg SHA1withRSA -keystore " + config.KeystoreFilePath + " -storepass " + config.Password + " -keypass " + config.Aliaspwd + " " + tempApkFilePath + " " + config.Aliaskey;
            string[] ret = CmdUtils.ExecCmdAndWaitForExit(args, logView);
            if (ret == null || ret.Length < 2 || !string.IsNullOrEmpty(ret[1]))
            {
                logView.Log(">>>>>>重新签名失败: 执行CMD失败");
                return false;
            }
            logView.Log("↑↑↑↑↑↑ 重签名-结束 ↑↑↑↑↑↑");


            logView.BlankLine();
            logView.Log("↓↓↓↓↓↓ 对齐优化-开始 ↓↓↓↓↓↓");
            string zipalignPath = PathUtils.GetPath(Constants.PATH_ZIPALIGN);
            args = zipalignPath + " -f 4 " + tempApkFilePath + " " + newApkPath;

            ret = CmdUtils.ExecCmdAndWaitForExit(args, logView);
            if (ret == null || ret.Length < 2 || !string.IsNullOrEmpty(ret[1]))
            {
                logView.Log(">>>>>>对齐优化失败: 执行CMD失败");
                return false;
            }
            logView.Log("↑↑↑↑↑↑ 对齐优化-结束 ↑↑↑↑↑↑");
            logView.BlankLine();


            if (!File.Exists(newApkPath)) {
                logView.Log(">>>>>>重新签名失败: " + newApkPath + "导出的新apk文件不存在!");
                return false;
            }
            
            logView.Log("导出apk为: " + newApkPath);

            return true;
        }



ZipUtils类:

        /// <summary>
        /// 删除APK文件中META-INF目录下的签名文件
        /// </summary>
        /// <param name="zipPath"></param>
        /// <param name="logView"></param>
        public static void RemoveApkCertFile(string zipPath, ILog logView)
        {
            try
            {
                using (ZipFile zip = ZipFile.Read(zipPath))
                {
                    List<ZipEntry> entries = new List<ZipEntry>();

                    foreach (ZipEntry entry in zip.Entries)
                    {
                        if (string.IsNullOrEmpty(entry.FileName))
                            continue;

                        if (!entry.FileName.StartsWith("META-INF/"))
                            continue;

                        if (entry.FileName.EndsWith(".MF")
                            || entry.FileName.EndsWith(".SF")
                            || entry.FileName.EndsWith(".RSA")
                            || entry.FileName.EndsWith(".DSA"))
                        {

                            entries.Add(entry);
                            logView.Log("删除->" + entry.FileName);
                        }
                    }

                    if (entries.Count > 0)
                    {
                        zip.RemoveEntries(entries);
                    }
                    zip.Save();
                }
            }
            catch (Exception ex)
            {
                logView.Log("RemoveApkCertFile Exception : " + ex.Message);
                LogUtils.WriteLine("RemoveApkCertFile Exception : " + ex.Message);
                Console.WriteLine("Exception Occurred :{0},{1}", ex.Message, ex.StackTrace.ToString());
                Console.ReadKey();
            }
        }

 

六、打入Jar包代码

工具:java.exe 、apktool.jar、apktool.bat、dx.jar、baksmali.jar、android.jar、jarsigner.exe、zipalign.exe

命令行:

  1. apktool.bat  d  apkFilePath  -o  输出目录decompileDir   //反编译
  2. java  -jar -Xms1024m -Xmx1024m  dxJarFilePath  --dex --output=dexFilePath  jarFilePath  //将jar包转换成dex文件
  3. java  -jar baksmaliJarFilePath  -o  targetDir dexFilePath  //将dex文件转换成smali文件
  4. apktool.bat  b  decompileDir  -o  apkFilePath  //回编译生成apk
  5. jarsigner  -digestalg SHA1 -sigalg SHA1withRSA  -keystore  keystoreFilePath   -storepass  password   -keypass aliasPwd   apkFilePath  aliasKey  //签名
  6. zipalign  -f 4  apkFilePath   newApkFilePath  //对齐优化

示例截图:

编程思路:上面的命令行就是将Jar打入到Apk文件里需要操作的步骤,先是反编译Apk文件,生成反编译目录,里面有个包含.smali文件的smali目录,就是Apk的源码;我们要将jar包文件也转换成.smali文件(中间要经过转换为.dex文件),转换后的smali目录路径设置为Apk的smali目录路径,目的是将它里面的.smali文件覆盖掉Apk的smali目录下的文件,以实现源码替换;然后将反编译目录回编译生成新的Apk文件,最后给新的Apk文件签名、对齐优化。

注意点:

  • 反编译时,指定的输出目录必须是新的目录,不能是已存在的目录。
  • jar文件转换成dex文件时,如果是多个jar文件,可通过空格隔开。
  • dex文件转换成smali文件时,将输出的目标目录设置为Apk反编译目录下的smali目录,以实现smali文件替换。
  • 这里给Apk签名不是重新签名,不需要进行删掉META-INF目录下旧的相关签名文件的操作。
  • 整个操作流程比较耗时,要开线程跑。

C#关键代码如下:

MergeJarBLL类:

        /// <summary>
        /// 反编译
        /// </summary>
        /// <param name="apkFilePath"></param>
        /// <param name="logView"></param>
        /// <returns></returns>
        public static string DecompileApk(string apkFilePath, ILog logView)
        {
            logView.Log("↓↓↓↓↓↓ 反编译-开始 ↓↓↓↓↓↓");

            string res = DecompileBLL.DecompileApk(apkFilePath);

            if (res == null)
            {
                logView.Log(">>>>>>反编译失败");
                return res;
            } else {
                logView.Log(">>>>>>反编译成功:" + res);
            }

            logView.Log("↑↑↑↑↑↑ 反编译-结束 ↑↑↑↑↑↑");
            logView.BlankLine();

            return res;

        }


        /// <summary>
        /// Jar文件转换成Dex文件
        /// </summary>
        /// <param name="jarPath">Jar文件或目录</param>
        /// <param name="logView"></param>
        /// <returns>返回生成dex文件的路径,为null时表示转换dex文件失败</returns>
        public static string Jar2Dex(string jarPath, ILog logView)
        {
            string javaPath = PathUtils.GetPath(Constants.PATH_JAVA);// java.exe
            string dexToolPath = PathUtils.GetPath(Constants.PATH_DX);// dx.jar
            if (!File.Exists(javaPath))
            {
                logView.Log(">>>>>>Jar文件转换成Dex文件失败: " + javaPath + "文件未生成!");
                return null;
            }
            if (!File.Exists(dexToolPath))
            {
                logView.Log(">>>>>>Jar文件转换成Dex文件失败: " + dexToolPath + "文件未生成!");
                return null;
            }


            string dexFilePath = PathUtils.GetPath(Constants.DIR_DEX);
            if (!Directory.Exists(dexFilePath)) {
                Directory.CreateDirectory(dexFilePath);
            }
            FileUtils.ClearDir(dexFilePath);
            // 生成的dex文件路径
            dexFilePath = PathUtils.GetPath(Constants.PATH_CLASSDEX);
   
            string args = javaPath + " -jar -Xms1024m -Xmx1024m " + dexToolPath + " --dex --output=" + dexFilePath;

            logView.Log("↓↓↓↓↓↓ Jar文件转换成Dex文件-开始 ↓↓↓↓↓↓");
            if (Directory.Exists(jarPath))
            {
                string[] files = Directory.GetFiles(jarPath);
                if (files != null && files.Length > 0)
                {
                    for (int i = 0; i < files.Length; i++)
                    {
                        if (files[i].EndsWith(".jar"))
                        {
                            args += " " + files[i];
                        }
                    }
                }
            } else if (File.Exists(jarPath) && jarPath.EndsWith(".jar"))
            {
                args += " " + jarPath;
            }

            string[] ret = CmdUtils.ExecCmdAndWaitForExit(args, logView);
            if (ret == null || ret.Length < 2 || !string.IsNullOrEmpty(ret[1])) {
                logView.Log(">>>>>>Jar文件转换成Dex文件失败: 执行CMD失败" );
                return null;
            }

            logView.Log("↑↑↑↑↑↑ Jar文件转换成Dex文件-结束 ↑↑↑↑↑↑");
            logView.BlankLine();

            if (File.Exists(dexFilePath))
            {
                logView.Log("已生成dex文件:" + dexFilePath);
            } else {
                logView.Log("没生成dex文件:" + dexFilePath);
            }
            logView.BlankLine();

            return dexFilePath;
        }



        /// <summary>
        /// 将Dex文件转换为Smali文件
        /// </summary>
        /// <param name="decompileDir"></param>
        /// <param name="logView"></param>
        /// <returns></returns>
        public static bool Dex2Smali(string decompileDir, ILog logView)
        {
            if (string.IsNullOrEmpty(decompileDir) || !Directory.Exists(decompileDir))
            {
                logView.Log(">>>>>>将Dex文件转换为Smali文件失败: " + decompileDir + "目录不存在!");
                return false;
            }

            string dexFilePath = PathUtils.GetPath(Constants.PATH_CLASSDEX);
            if (!File.Exists(dexFilePath))
            {
                logView.Log(">>>>>>将Dex文件转换为Smali文件失败: " + dexFilePath + "文件未生成!");
                return false;
            }

            string javaPath = PathUtils.GetPath(Constants.PATH_JAVA);// java.exe
            string smaliTool = PathUtils.GetPath(Constants.PATH_BAKSMALI);// baksmali.jar
            if (!File.Exists(javaPath))
            {
                logView.Log(">>>>>>将Dex文件转换为Smali文件失败: " + javaPath + "不存在!");
                return false;
            }
            if (!File.Exists(smaliTool))
            {
                logView.Log(">>>>>>将Dex文件转换为Smali文件失败: " + smaliTool + "不存在!");
                return false;
            }


            // 生成smali位置,是Apk反编译目录下的smali目录,用于覆盖apk的smali文件
            string targetDir = PathUtils.JoinPath(decompileDir, Constants.DIR_SMALI);

            logView.Log("↓↓↓↓↓↓ 将Dex文件转换为Smali文件-开始 ↓↓↓↓↓↓");

            string args = javaPath + " -jar " + smaliTool + " -o " + targetDir + " " + dexFilePath;
            string[] ret = CmdUtils.ExecCmdAndWaitForExit(args, logView);
            if (ret == null || ret.Length < 2 || !string.IsNullOrEmpty(ret[1]))
            {
                logView.Log(">>>>>>将Dex文件转换为Smali文件失败: 执行CMD失败");
                return false;
            }

            logView.Log("↑↑↑↑↑↑ 将Dex文件转换为Smali文件-结束 ↑↑↑↑↑↑");
            logView.BlankLine();

            return true;
        }



        /// <summary>
        /// 回编译
        /// </summary>
        /// <param name="decompileDir"></param>
        /// <param name="logView"></param>
        /// <returns></returns>
        public static bool RecompileApk(string decompileDir, ILog logView)
        {
            if (string.IsNullOrEmpty(decompileDir) || !Directory.Exists(decompileDir))
            {
                logView.Log(">>>>>>回编译失败: " + decompileDir + "目录不存在!");
                return false;
            }

            string apktoolPath = PathUtils.GetPath(Constants.PATH_APKTOOL);
            if (!File.Exists(apktoolPath))
            {
                LogUtils.WriteLine(">>>>>>回编译失败: " + apktoolPath + "不存在!");
                return false;
            }

            string tempApkPath = PathUtils.GetPath(Constants.PATH_TEMPAPK);
            if (File.Exists(tempApkPath))
            {
                File.Delete(tempApkPath);
            }

            logView.Log("↓↓↓↓↓↓ 回编译-开始 ↓↓↓↓↓↓");

            string args = apktoolPath + " b " + decompileDir + " -o " + tempApkPath;
            string[] ret = CmdUtils.ExecCmdAndWaitForExit(args, logView);
            if (ret == null || ret.Length < 2 || !string.IsNullOrEmpty(ret[1]))
            {
                logView.Log(">>>>>>回编译失败: 执行CMD失败");
                return false;
            }

            logView.Log("↑↑↑↑↑↑ 回编译-结束 ↑↑↑↑↑↑");
            logView.BlankLine();

            return true;
        }


        /// <summary>
        /// 签名
        /// </summary>
        /// <param name="config"></param>
        /// <param name="logView"></param>
        /// <returns></returns>
        public static bool SignApk(KeystoreConfig config, ILog logView)
        {
            string tempApkPath = PathUtils.GetPath(Constants.PATH_TEMPAPK);
            if (!File.Exists(tempApkPath))
            {
                logView.Log(">>>>>>签名失败: " + tempApkPath + "未生成!");
                return false;
            }

            string jarsignerPath = PathUtils.GetPath(Constants.PATH_JARSIGNER);
            if (!File.Exists(jarsignerPath))
            {
                logView.Log(">>>>>>签名失败: " + jarsignerPath + "不存在!");
                return false;
            }

            logView.Log("↓↓↓↓↓↓ 签名-开始 ↓↓↓↓↓↓");

            string args = jarsignerPath + " -digestalg SHA1 -sigalg SHA1withRSA -keystore " + config.KeystoreFilePath + " -storepass " + config.Password + " -keypass " + config.Aliaspwd + " " + tempApkPath + " " + config.Aliaskey;
            string[] ret = CmdUtils.ExecCmdAndWaitForExit(args, logView);
            if (ret == null || ret.Length < 2 || !string.IsNullOrEmpty(ret[1]))
            {
                logView.Log(">>>>>>签名失败: 执行CMD失败");
                return false;
            }

            logView.Log("↑↑↑↑↑↑ 签名-结束 ↑↑↑↑↑↑");
            logView.BlankLine();

            return true;
        }


        /// <summary>
        /// 对齐优化
        /// </summary>
        /// <param name="newApkPath"></param>
        /// <param name="logView"></param>
        /// <returns></returns>
        public static bool AlignApk(string newApkPath, ILog logView)
        {
            string tempApkPath = PathUtils.GetPath(Constants.PATH_TEMPAPK);
            if (!File.Exists(tempApkPath))
            {
                logView.Log(">>>>>>对齐优化失败: " + tempApkPath + "未生成!");
                return false;
            }

            string zipalignPath = PathUtils.GetPath(Constants.PATH_ZIPALIGN);
            if (!File.Exists(zipalignPath))
            {
                logView.Log(">>>>>>对齐优化失败: " + zipalignPath + "不存在!");
                return false;
            }

            logView.Log("↓↓↓↓↓↓ 对齐优化-开始 ↓↓↓↓↓↓");

            string args = zipalignPath + " -f 4 " + tempApkPath + " " + newApkPath;
            string[] ret = CmdUtils.ExecCmdAndWaitForExit(args, logView);
            if (ret == null || ret.Length < 2 || !string.IsNullOrEmpty(ret[1]))
            {
                logView.Log(">>>>>>对齐优化失败: 执行CMD失败");
                return false;
            }

            logView.Log("↑↑↑↑↑↑ 对齐优化-结束 ↑↑↑↑↑↑");
            logView.BlankLine();
            logView.Log("导出apk为: " + newApkPath);
            logView.BlankLine();

            return true;
        }

 

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: APK反编译工具是一种用于查看和修改Android应用程序的工具。使用这些工具,可以将安装在设备上的APK文件还原为其源代码,并对其进行修改。 APK反编译工具的使用有多种用途。首先,它可以帮助开发人员了解其他应用程序的实现细节。通过查看其他应用程序的源代码,开发人员可以学习到新的编程技巧和方法,以优化他们自己的应用程序。 此外,APK反编译工具还可以用于安全研究和漏洞分析。通过分析应用程序的源代码,安全研究人员可以发现潜在的漏洞和安全隐患,并提供有关如何修复这些问题的建议。 然而,需要注意的是,APK反编译工具的使用也可能存在一些法律和道德问题。未经授权地修改其他应用程序的源代码可能会侵犯知识产权,违反用户协议或法律法规。因此,在使用APK反编译工具时,必须遵守法律规定,并获得相关方的授权。 总而言之,APK反编译工具是一种有用的工具,可以帮助开发人员了解其他应用程序的实现细节,同时也有助于安全研究和漏洞分析。但是,使用这些工具需要遵守法律规定,并遵循道德原则。 ### 回答2: APK反编译工具是一种用于查看和修改APK文件的工具APK文件是Android应用程序的安装包文件,其中包含了应用程序的源代码、资源文件和其他必要的文件。反编译工具允许开发者或者研究人员将APK文件解压和反编译为可读取的源代码和资源文件。 使用APK反编译工具可以帮助开发者了解和分析其他应用程序的实现方式和逻辑。通过查看反编译后的源代码,可以了解应用程序的代码结构、算法和逻辑处理。这对开发者来说是非常有价值的,可以借鉴和学习其他应用程序的实现方式,提高自己的开发水平。 此外,APK反编译工具也可以用于应用程序的逆向工程和修改。通过反编译APK文件,可以直接修改应用程序的代码或资源文件,从而实现一些自定义的功能或者修复错误。然而,需要注意的是,在修改他人的应用程序时,需要遵守相关的法律和道德规范,不能进行非法的操作,以免侵犯他人的权益。 总的来说,APK反编译工具是一个非常有用的工具,可以帮助开发者了解和学习其他应用程序的实现方式,并且在一定范围内可以对应用程序进行修改和优化。但是需要注意使用的合法性和道德规范,遵守相关法律的前提下使用该工具

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值