성능 최적화 (7) APK 보강 Dex 복호화, 역컴파일은 프로젝트의 주요 코드를 볼 수 없습니다.

13909 단어
간단한 소개
현재 임의로 응용 시장에서 APK 파일을 다운로드한 후에 역컴파일하고 95% 이상은 기본적으로 혼동, 암호화, 또는 제3자 보강(제3자 보강도 이 원리)이다. 그러면 오늘 우리는 Dex에 대해 암호화 해독을 진행할 것이다.프로젝트 원본 코드를 제대로 읽을 수 없도록 역컴파일합니다.
암호화된 패브릭
APK 분석
AS 도구를 통해 암호화된 APK 파일을 분석하고 dex를 보는 것은 오류가 발생했습니다. 이 효과가 필요합니다.
역컴파일 효과
Dex를 암호화하려면 먼저 64K 문제를 파악해야 합니다.
64k에 대해 자세히 알고 싶으면 홈페이지를 참고하세요.
안드로이드 플랫폼이 지속적으로 성장함에 따라 안드로이드 앱의 크기도 증가하고 있다.응용 프로그램과 인용된 라이브러리가 특정 시간에 이르면 구축 오류가 발생합니다. 응용 프로그램이 안드로이드 응용 프로그램 구축 구조의 한계에 도달했음을 의미합니다.이전 버전의 빌드 시스템은 다음과 같이 오류를 보고합니다.
Conversion to Dalvik format failed:
Unable to execute dex: method ID not in [0, 0xffff]: 65536

최신 버전의 Android 구축 시스템과는 다른 오류가 표시되지만 동일한 문제를 나타냅니다.
trouble writing output:
Too many field references: 131000; max is 65536.
You may try using --multi-dex option.

이 오류 상황들은 아래의 숫자를 보여 줍니다: 65536.이 숫자는 Dalvik Executable (DEX) 바이트 파일 내의 코드가 호출할 수 있는 인용 총수를 대표하기 때문에 매우 중요하다.이 절에서는 Dalvik 실행 파일 하도급이라고 불리는 응용 프로그램 설정을 사용해서 이 제한을 넘어서 응용 프로그램이 Dalvik 실행 파일 하도급 DEX 파일을 구축하고 읽을 수 있도록 하는 방법을 소개합니다.
64K 참조 제한 정보
Android 5.0 이전 버전의 Dalvik 실행 파일 패키지 지원
Android 5.0(API 레벨 21) 이전 플랫폼 버전에서는 Dalvik이 실행될 때 응용 코드를 실행했습니다.기본적으로 Dalvik 제한 적용의 각 APK는 단일 바이트classes.dex 파일만 사용할 수 있습니다.이 제한을 무시하려면 Dalvik 실행 가능한 파일 패키지 지원 라이브러리를 사용하십시오. 이것은 응용 프로그램의 주요 DEX 파일의 일부분이 되고 다른 DEX 파일과 포함된 코드에 대한 접근을 관리할 수 있습니다.
Android 5.0 이상 Dalvik 실행 파일 패키지 지원
Android 5.0(API 레벨 21) 이상 버전은 ART라는 이름으로 실행될 때 APK 파일에서 여러 DEX 파일을 로드할 수 있습니다.ART는 응용 프로그램을 설치할 때 미리 컴파일하고 classesN.dex 파일을 스캔하여 안드로이드 장치가 실행할 수 있도록 단일 .oat 파일로 컴파일합니다.따라서 minSdkVersion가 21 이상이면 Dalvik 실행 가능한 파일 패키지 지원 라이브러리가 필요하지 않습니다.
64K 제한 사항 해결
  • 만약에 minSdkVersion를 21 이상으로 설정하면 모듈 레벨build.gradle 파일에서 multiDexEnabledtrue로 설정하면 다음과 같습니다.
    android {
        defaultConfig {
            ...
            minSdkVersion 21 
            targetSdkVersion 28
            multiDexEnabled true
        }
        ...
    }
    
    그러나 minSdkVersion를 20 이하로 설정하면 Dalvik 실행 가능한 파일 패키지 지원 라이브러리를 다음과 같이 사용해야 합니다.
  • 모듈 레벨build.gradle 파일을 수정하여 Dalvik 실행 가능한 파일 하도급을 사용하고 Dalvik 실행 가능한 파일 하도급 라이브러리를 의존항으로 추가합니다
    android {
        defaultConfig {
            ...
            minSdkVersion 15 
            targetSdkVersion 28
            multiDexEnabled true
        }
        ...
    }
    
    dependencies {
      compile 'com.android.support:multidex:1.0.3'
    }
    
  • 현재 Application extends MultiDexApplication {...}또는 MultiDex.install(this);

  • 혼동을 통해 ProGuard를 켜서 사용하지 않은 코드를 제거하고 코드 압축을 구축합니다.
  • 제3자 라이브러리의 직접적인 의존을 줄이고 가능한 한 원본 코드를 다운로드하고 필요한 대로 사용하며 전체 프로젝트에 의존할 필요가 없다.

  • Dex 암호화 및 복호화
    프로세스:
  • APK에서 모든 dex 파일을 압축해제합니다.
  • Tools를 통해 암호화하고 암호화된 dex와 프록시 응용class.dex 합병하고 다시 서명하고 정렬하고 포장합니다.
  • 사용자가 APK를 설치하여 프록시 복호화에 들어가는 Application을 열 때 dexElements를 반사하고 복호화된 dex를 DexPathList의 dexElements로 대체합니다.

  • Dex 파일 로드 프로세스
    Dex 불러오는 과정을 찾으려면 어떤 원본 코드인class부터 시작해야 하는지 알아야 한다. 모르면 ClassLoader를 출력하자.
    다음 Dex 로드 프로세스에 대한 자세한 내용은 다음 로드맵을 참조하십시오.
    마지막으로findClass(String name, List sup)에서 dex Elements를 옮겨다니며 Class를 찾아 안드로이드에 불러오는 것을 알았습니다.
    Dex 복호화
    이제 dex 불러오는 프로세스를 알았습니다. 그러면 dex 해밀러를 어떻게 진행해야 하는지, 방금 dex Elements를 옮겨다니며 Class를 찾아야 한다는 것을 알았습니다. 그러면 옮겨다니기 전에 dex Elements를 초기화할 수 있는지 알았습니다.반사된 dexElements는 우리가 복호화한 dex를 dexElements에 건네줍니다.다음은 코드를 통해 dex를 복호화하고 DexPathList의 dexElements를 교체합니다.
  • 현재 암호화된 APK 파일을 받아서 압축을 풀기
    //        APK  
    File apkFile=new File(getApplicationInfo().sourceDir);
    // apk     app_name+"_"+app_version        boot     
    File versionDir = getDir(app_name+"_"+app_version,MODE_PRIVATE);
    File appDir=new File(versionDir,"app");
    File dexDir=new File(appDir,"dexDir");
    
  • 로드해야 하는 Dex 파일
    // apk   appDir
    Zip.unZip(apkFile,appDir);
    //          
    File[] files=appDir.listFiles();
    for (File file : files) {
         String name=file.getName();
         if(name.endsWith(".dex") && !TextUtils.equals(name,"classes.dex")){
         try{
            AES.init(AES.DEFAULT_PWD);
            //      
            byte[] bytes=Utils.getBytes(file);
            //  
            byte[] decrypt=AES.decrypt(bytes);
            //       
            FileOutputStream fos=new FileOutputStream(file);
            fos.write(decrypt);
            fos.flush();
            fos.close();
            dexFiles.add(file);
    
         }catch (Exception e){
             e.printStackTrace();
         }
      }
    }
    
  • 복호화된 dex를 시스템에 불러오기
    private void loadDex(List dexFiles, File versionDir) throws Exception{
            //1.  pathlist
            Field pathListField = Utils.findField(getClassLoader(), "pathList");
            Object pathList = pathListField.get(getClassLoader());
            //2.    dexElements
            Field dexElementsField=Utils.findField(pathList,"dexElements");
            Object[] dexElements=(Object[])dexElementsField.get(pathList);
            //3.      dexElements   
            Method makeDexElements=Utils.findMethod(pathList,"makePathElements",List.class,File.class,List.class);
    
            ArrayList suppressedExceptions = new ArrayList();
            Object[] addElements=(Object[])makeDexElements.invoke(pathList,dexFiles,versionDir,suppressedExceptions);
    
            //    
            Object[] newElements= (Object[])Array.newInstance(dexElements.getClass().getComponentType(),dexElements.length+addElements.length);
            System.arraycopy(dexElements,0,newElements,0,dexElements.length);
            System.arraycopy(addElements,0,newElements,dexElements.length,addElements.length);
    
            //   DexPathList    element   
            dexElementsField.set(pathList,newElements);
        }
    
    복호화가 완료되었습니다. 다음은 암호화를 봅시다. 여기서 왜 복호화러를 먼저 말합니까? 암호화는 서명, 포장, 정렬과 관련이 있기 때문입니다.그러니까 끝까지 남아서 얘기해.

  • Dex 암호화
  • 복호화 코드만 포함하는 dex
    1. sdk\build-tools               dex   jar
    dx --dex --output out.dex in.jar
    2.    exec   
    File aarFile=new File("proxy_core/build/outputs/aar/proxy_core-debug.aar");
            File aarTemp=new File("proxy_tools/temp");
            Zip.unZip(aarFile,aarTemp);
            File classesJar=new File(aarTemp,"classes.jar");
            File classesDex=new File(aarTemp,"classes.dex");
            String absolutePath = classesDex.getAbsolutePath();
            String absolutePath1 = classesJar.getAbsolutePath();
            //dx --dex --output out.dex in.jar
            //dx --dex --output //D:\Downloads\android_space\DexDEApplication\proxy_tools\temp\classes.dex //D:\Downloads\android_space\DexDEApplication\proxy_tools\temp\classes.jar
            Process process=Runtime.getRuntime().exec("cmd /c dx --dex --output "+classesDex.getAbsolutePath()
                                        +" "+classesJar.getAbsolutePath());
            process.waitFor();
            if(process.exitValue()!=0){
                throw new RuntimeException("dex error");
            }
    
  • 제작
  • 암호화 apk의 dex 파일
     File apkFile=new File("app/build/outputs/apk/debug/app-debug.apk");
            File apkTemp=new File("app/build/outputs/apk/debug/temp");
            Zip.unZip(apkFile,apkTemp);
            //  dex       
            File[] dexFiles=apkTemp.listFiles(new FilenameFilter() {
                @Override
                public boolean accept(File file, String s) {
                    return s.endsWith(".dex");
                }
            });
            //AES   
            AES.init(AES.DEFAULT_PWD);
            for (File dexFile : dexFiles) {
                byte[] bytes = Utils.getBytes(dexFile);
                byte[] encrypt = AES.encrypt(bytes);
                FileOutputStream fos=new FileOutputStream(new File(apkTemp,
                        "secret-"+dexFile.getName()));
                fos.write(encrypt);
                fos.flush();
                fos.close();
                dexFile.delete();
    }
    
  • dex를 apk 가압 디렉터리에 넣고 apk 파일로 다시 눌러라
     File apkTemp=new File("app/build/outputs/apk/debug/temp");
            File aarTemp=new File("proxy_tools/temp");
            File classesDex=new File(aarTemp,"classes.dex");
            classesDex.renameTo(new File(apkTemp,"classes.dex"));
            File unSignedApk=new File("app/build/outputs/apk/debug/app-unsigned.apk");
            Zip.zip(apkTemp,unSignedApk);
    
    현재 암호화된 파일과 암호화되지 않은 파일을 볼 수 있다. 암호화되지 않은 apk: 암호화된 apk(현재 프록시 Application만 보인다)
  • 포장하다
    정렬
    //apk                                  ,        。
    zipalign -f 4 in.apk out.apk 
    
    //   apk     
    zipalign -c -v 4 output.apk
    
    //     Verification succesful        
      236829 res/mipmap-xxxhdpi-v4/ic_launcher.png (OK - compressed)
      245810 res/mipmap-xxxhdpi-v4/ic_launcher_round.png (OK - compressed)
      260956 resources.arsc (OK - compressed)
      317875 secret-classes.dex (OK - compressed)
     2306140 secret-classes2.dex (OK - compressed)
     2477544 secret-classes3.dex (OK - compressed)
    Verification succesful
    

    서명 포장 apksigner
    //sdk\build-tools\24.0.3   ,apk    
    apksigner sign  --ks jks     --ks-key-alias    --ks-pass pass:jsk   --key-pass pass:     --out  out.apk in.apk
    

    총결산
    사실 원리는 주요 코드를 명령 dx를 통해 dex 파일을 생성한 다음에 암호화된 dex를 프록시class에 통합시키는 것이다.dex 중.이렇게 하면 에이전트의 코드를 볼 수 있지만 주요 코드는 드러나지 않고 우리가 원하는 효과를 실현할 수 있다.잘 봉인되면 (JNI에서 주요 복호화 코드를 실현한다) 기본적으로 하도 보이지 않는다.ClassLoader는 여전히 중요합니다. 열 복구와 열 로드가 모두 이 원리입니다.여기까지 배웠어요. DEX 해독은 다 배웠어요. 직접 해보고 싶으면 제 코드를 참고하세요.
    코드 전송진
    전재 대상:https://juejin.im/post/5cf3ee295188256aa76bb1e1

    좋은 웹페이지 즐겨찾기