개발/안드로이드 rooting

Reversing DexGuard’s String Encryption

개복치 개발자 2019. 12. 2. 15:20

Reversing DexGuard’s String Encryption의 변역본입니다. 이 취약점은 개선되었다고 합니다.

 

DexGuard is a commercial tool used for protecting android binaries (APK) mainly from reversing and tampering. It provides features like code obfuscation, class encryption, string encryption, asset/resource encryption, tamper protection, anti-debugger checks, VM/Environment checks, SSL pinning etc. This blog post explains the decryption/reversing of DexGuard 6.1's string encryption.

 

DexGuard는 주로 안드로이드 바이너리 (apk) 리버싱과 변조를 보호하는데 사용되는 상용 툴입니다. 이 것은 코드난독화, 클래스 암호화, 문자열 암호화, 리소스 암호화, 변조방지, 디버거 방지 검사, 가상환경 체크, SSL 피닝 등의 기능을 제공합니다. 이 블로그 포스트는 DexGuard 6.1 문자열 암호화의 리버싱을 설명합니다.

 

 

My colleague, Sachinraj collaborated with me for this research. After analysis, we found that DexGuard string encryption is more of an obfuscation that can be reversed with some effort. A sample android application is compiled with DexGuard String and Class encryption enabled. As per DexGuard documentation, You can encrypt entire classes by specifying them with the option -encryptclasses. For example:

 

-encryptclasses mypackage.MySecretClass

 

저의 동료인 Sachinraj는 이 연구를 함께 진행했습니다. 분석 후, Dexguard 문자열 암호화는 약간의 노력으로 되돌릴 수 암호화에 가깝습니다. 샘플 안드로이드 애플리케이션은 dexguard 문자열과 클래스 암호화를 사용하여 컴파일됩니다. dexguard 문서에 따라 우리는 -encryptclasses옵션을 통해서 전체 클래스를 암호화 할 수 있습니다. 예를 들어

 

-encryptclasses mypackage.MySecretClass

 

There are a few alternative ways to specify which constant strings in the code should be encrypted, with the option -encryptstrings. The shortest way is to specify the strings literally:

 

-encryptstrings 옵션을 사용해서 상수 문자열을 지정하는 몇 가지 대체 방법이 있습니다. 가장 짧은 방법은 문자열을 그대로 명시하는 것입니다.

 

-encryptstrings "Some secret string", "Some other secret string"

 

The sample android application contains two activity. The first activity contains an OK button which on press will invoke the second activity using Intent. We pass a hardcoded secret string from first activity to the second activity via Intent extra. The  source code of the first activity (MainActivity.java) is shown below:

 

샘플 안드로이드 애플리케이션에는 2개의 액티비티가 있습니다. 첫 번째 액티비에는 ok 버튼이 있는데 이 버튼을 누르면 두번째 액티비티로 이동합니다. intent를 통해서 하드코딩된 비밀 문자열을 첫번째 액티비티에서 두번째 액티비로 전달합니다. 첫 번째 액티비티의 소스 코드는 아래와 같습니다.

 

package opensecurity.dexreversedemo;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
public class MainActivity extends ActionBarActivity 
{
public Button bt;
 private final static String secret="SuperZS3cur!ty0R0CK3S";
 @Override
  protected void onCreate(Bundle savedInstanceState) 
   {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    bt=(Button)findViewById(R.id.button);
    bt.setOnClickListener(new View.OnClickListener() {
 @Override
 public void onClick(View v)
 {
   Intent ask = new Intent(MainActivity.this, Secret.class);
   ask.putExtra("SECURE",secret);
  startActivity(ask);
  }
 });
 }
-----SNIPPED------
}

 

 We defined the following rules in dexguard-project.txt

 

우리는 dexguard-project.txt 에 다음 규칙을 정의했습니다.

 

-encryptstrings "SuperZS3cur!ty0R0CK3S"
-encryptclasses opensecurity.dexreversedemo.MainActivity,opensecurity.dexreversedemo.Secret

 

The above rules should encrypt the string SuperZS3cur!ty0R0CK3S and encrypt the classes MainActivity and Secret. We compiled the code with DexGuard enabled and analysed the APK using Mobile Security Framework which is configured to use the CFR decompiler. You can also use CFR decompiler in standalone to decompile the APK. Due to the protection enforced by DexGuard, we got large chunk of decompiled java files with strange names.

 

위의 규칙은 SuperZS3cur를 암호화하고, 메인액티비티와 Secret 클래스를 암호화해야 합니다. 우리는 dexguard를 활성화해서 컴파일했고, CFR 디컴파일러를 사용하는 모바일 보안 프레임워크를 사용하여 APK를 분석했습니다.

 

 

우리는 메인액티비티가 있으므로, 거기서부터 분석을 시작합니다.

 

Here if you observe the above decompiled code, there are two strange imports. import o.\u062f; import o.\u706c;

As the decompiled source contains the following code, public class MainActivity extends \u062f { public Button \u02c9;

 

위의 디컴파일된 코드를 보면 import o.\u062f; import o.\u706c; 라는 이상한게 있습니다. 디컴파일된 소스에 다음 코드가 포함되어 있으므로 MainActivity extends \u062f; { public Button \u706c;

 

 

If you compare it with our initial source code, you can easily conclude that  \u062f is nothing but ActionBarActivity and \u02c9 is the button variable bt.

Lets look into the second import file o.\u706c. If you convert \u706c to unicode text, you will get 灬. Now we need to find the file o/灬.java

 

 

초기 소스 코드와 비교하면 \u062f는 ActionBarActivity이고 \ u02c9는 버튼 변수 bt라고 쉽게 결론 지을 수 있습니다. 파일 o. \ u706c를 살펴 보겠습니다. \ u706c를 유니 코드 텍스트로 변환하면 灬가됩니다. 이제 o / 灬 .java 파일을 찾아야합니다.

 

 

 

The file contains the following code

 

파일은 다음 코드를 포함합니다.

 

package o;
import android.content.Context;
import android.content.Intent;
import android.view.View;
import o.\ufb59$\u0640;
import opensecurity.dexreversedemo.MainActivity;
public class \u706c
implements View.OnClickListener {
    private static final byte[] \u02cb = new byte[]{110, -49, 71, -112, 33, -6, -12, 12, -25, -8, -33, 47, 17, -4, -82, 82, 4, -74, 33, -35, 18, 7, -25, 31};
    private static int \u02ce = 62;
    final /* synthetic */ MainActivity \u02ca;
    public \u706c(MainActivity mainActivity) {
        this.\u02ca = mainActivity;
    }
    /*
     * Unable to fully structure code
     * Enabled aggressive block sorting
     * Lifted jumps to return sites
     */
    private static String \u02ca(intvar0, intvar1_1, intvar2_2) {
        var2_2 = var2_2 * 4+ 4;
        var7_3 = var0 * 3+ 83;
        var6_4 = -1;
        var3_5 = \u706c.\u02cb;
        var8_6 = var1_1 * 3+ 21;
        var4_7 = newbyte[var8_6];
        var1_1 = var6_4;
        var5_8 = var7_3;
        var0 = var2_2;
        if(var3_5 != null) ** GOTO lbl19
        var0 = var2_2;
        var5_8 = var7_3;
        var7_3 = var2_2;
        var1_1 = var6_4;
        var2_2 = var0;
        do{
            var0 = var7_3 + 1;
            var5_8 = var2_2 + var5_8 + 1;
lbl19: // 2 sources:
            var4_7[++var1_1] = (byte)var5_8;
            if(var1_1 == var8_6 - 1) {
                return new String(var4_7, 0);
            }
            var2_2 = var5_8;
            var5_8 = var3_5[var0];
            var7_3 = var0;
        } while(true);
    }
    public void onClick(View view) {
        view = newIntent((Context)this.\u02ca, \ufb59$\u0640.\u141d("o.\ufb59"));
        view.putExtra("SECURE", \u706c.\u02ca(0, 0, 0).intern());
        this.\u02ca.startActivity((Intent)view);
    }
}

 

This decompiled code looks like CFR decompiler failed at some places to decompile it completely but gave us a good hint about the logic. If you analyse the code, you can see the line which contains the code.

 

이 디컴파일된 코드는 CFR 디컴파일러가 디컴파일을 하는데 실패한 것 처럼 보이지만 로직에 대한 힌트를 제공합니다. 만약 코드를 분석하면 코드가 포함된 줄을 볼 수 있습니다.

 

view.putExtra("SECURE", \u706c.\u02ca(0, 0, 0).intern());

 

This looks similar to ask.putExtra("SECURE",secret); in our initial source code. Now it's clear that \u706c.\u02ca(0, 0, 0).intern() method is responsible for decrypting the encrypted secret at Runtime. If we can successfully recreate this method, then we will be able to decrypt the secret. CFR decompiler did half the job for us and remaining is on us. We created a Java program with the above decryption method and it welcomed us with lots of errors. We added appropriate data types but still we had some missing code there. After looking into this code, We figured out the pseudo logic of the decryption method. This line if(var3_5 != null) ** GOTO lbl19 gave us a hint about the logic. var3_5 is nothing but the byte array \u02cb. It will never become null. So the following code (marked in red) will never get invoked.

 

이 것은 ask.putExtra("SECURE",secret); 와 유사합니다. 우리의 처음 소스 코드에서 \u706c.\u02ca(0, 0, 0).intern() 메소드가 런타임시 암호화된 비밀의 해독을 담당하고 있습니다. 우리는 이 메소드를 성공적으로 만들 수 있다면, 비밀을 해독할 수 있습니다. CFR 디컴파일러는 절반의 작업을 수행했으며 나머지는 우리에게 달려있습니다. 우리는 위의 해독 메소드로 자바 프로그램을 만들었고, 이 것은 많은 오류로 우리를 반겨주었습니다. 우리는 적절한 데이터 타입을 추가했지만 여전히 놓친 코드가 있습니다. 이 코드를 살펴본 이후 우리는 암호화 메소드의 로직을 알아냈습니다. if(var3_5 != null) ** GOTO lbl19는 우리에게 로직에 대한 힌트를 주었습니다. var3_5는 바이트 배열 \ u02cb입니다. 절대 null이 되지 않습니다. 그러니깐 다음 코드(빨간색으로 표시된)는 호출되지 않습니다.

 

 

So we removed those unnecessary code and again if you carefully observe the code, you can see that GOTO lbl19 goes inside the do-while loop and there is two lines of code (marked in blue) which is skipped for the first time execution of the loop and when the first loop is finished, the successive loops will include the code. So we quickly wrote a PoC with that logic. File: DexRev.java

 

그러니깐 우리는 불필요한 코드를 제거하고, 만약 주의깊게 코드를 관찰하면, GOTO lbl19가 do-while 루프 안에 들어가고 두 줄의 코드가(파란색으로 표시된) 있을음 알 수 있습니다. 이 것은 루프의 첫 번째 실행을 건너 뛰고 첫 번째 루프가 완료되면 연속 루프에 코드가 포함됩니다. 그러니깐 우리는 빨리 PoC를 이 논리로 썻습니다 DexRev.java

 

public class DexRev {
private static final byte[] \u02cb = new byte[]{110, -49, 71, -112, 33, -6, -12, 12, -25, -8, -33, 47, 17, -4, -82, 82, 4, -74, 33, -35, 18, 7, -25, 31};
    public static void main(String args[]) throws Exception
    {
    System.out.println("Decrypted: " + DexRev.\u02ca(0, 0, 0).intern());
    }
    private static String \u02ca(int var0, int var1_1, int var2_2) {
        var2_2 = var2_2 * 4 + 4;
        int var7_3 = var0 * 3 + 83;
        int var6_4 = -1;
        byte[] var3_5 = DexRev.\u02cb;
        int var8_6 = var1_1 * 3 + 21;
        byte [] var4_7 = new byte[var8_6];
        var1_1 = var6_4;
        int var5_8 = var7_3;
        var0 = var2_2;
        boolean firsttime=true;
        do {
        if(!firsttime){
            var0 = var7_3 + 1;
            var5_8 = var2_2 + var5_8 + 1;
        }
        firsttime=false;
            var4_7[++var1_1] = (byte)var5_8;
            if (var1_1 == var8_6 - 1) {
                return new String(var4_7, 0);
            }
            var2_2 = var5_8;
            var5_8 = var3_5[var0];
            var7_3 = var0;
        } while (true);
    }
}

If you run the above PoC, you will get the output.

Decrypted: SuperZS3cur!ty0R0CK3S

 

위의 PoC를 실행하면 아래와 같은 결과물을 얻을 수 있습니다.

해독 : SuperZS3cur!ty0R0CK3S

 

 

 

 

'개발 > 안드로이드 rooting' 카테고리의 다른 글

루팅 어플 체크  (0) 2019.11.17
root detection in android device  (0) 2019.11.16
무결성검사  (0) 2019.11.14
코드 난독화  (0) 2019.11.13
후킹(hooking)  (0) 2019.11.13