본문 바로가기
mobile해킹/안드로이드

Uncrackable level 1 (정적분석 Part.2)

by HHack 2023. 5. 24.
반응형

Uncrackable level 1 (정적분석 Part.2)

오늘은 지난 시간에 이어 암복호화 과정을 정적분석으로 풀어보는 시간을 가져보도록 하겠습니다.

 

2. 암복호화

(1) jadx-gui로 디컴파일 하기

Step 1) jadx-gui를 실행하고 다운받은 UnCrackable-Level1.apk 파일을 드래그&드롭 한 뒤 메인 액티비티를 찾아간다. (sg.vantagepoint -> uncracakable1 -> MainActivity)

package sg.vantagepoint.uncrackable1;

public void verify(View view) {
	String str;
	String obj = ((EditText) findViewById(R.id.edit_text)).getText().toString();
	AlertDialog create = new AlertDialog.Builder(this).create();
	if (a.a(obj)) {
		create.setTitle("Success!");
		str = "This is the correct secret.";
	} else {
		create.setTitle("Nope...");
		str = "That's not it. Try again.";
	}
	create.setMessage(str);
	create.setButton(-3, "OK", new DialogInterface.OnClickListener() { // from class: sg.vantagepoint.uncrackable1.MainActivity.2
		@Override // android.content.DialogInterface.OnClickListener
		public void onClick(DialogInterface dialogInterface, int i) {
			dialogInterface.dismiss();
		}
	});
	create.show();
}

오늘은 MainActivity에서 사용자의 입력값을 검증하는 verify 메서드를 해석하고 이를 Frida가 아닌 smali 코드 변조를 통한 정적분석으로 풀어보도록 하겠습니다.

 

#5 : String obj = ((EditText) findViewById(R.id.edit_text)).getText().toString();

- 사용자가 입력한 문자열을 obj 변수에 저장합니다.

 

#7 ~ #13 : if (a.a(obj)) { ... } ~ else { ... }

- 만약 사용자가 입력한 값을 sg.vantagepoint.uncrackable1 패키지 안에 있는 a클래스의 a메서드에 넣었을 때 참일경우 Success, 아니면 Nope

 

그렇다면 if(a.a(obj))를 분석하기 위해 a.a 메서드를 확인해보자.

 

Step 2) a클래스를 찾아가서 메서드를 확인해 보자. a.a에서 a에 마우스를 올리고 Ctrl과 같이 클릭하면 해당 메서드로 한 번에 이동한다.

a클래스의 a메서드 확인

package sg.vantagepoint.uncrackable1;

import android.util.Base64;
import android.util.Log;

/* loaded from: classes.dex */
public class a {
    public static boolean a(String str) {
        byte[] bArr;
        byte[] bArr2 = new byte[0];
        try {
            bArr = sg.vantagepoint.a.a.a(b("8d127684cbc37c17616d806cf50473cc"), Base64.decode("5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=", 0));
        } catch (Exception e) {
            Log.d("CodeCheck", "AES error:" + e.getMessage());
            bArr = bArr2;
        }
        return str.equals(new String(bArr));
    }

    public static byte[] b(String str) {
        int length = str.length();
        byte[] bArr = new byte[length / 2];
        for (int i = 0; i < length; i += 2) {
            bArr[i / 2] = (byte) ((Character.digit(str.charAt(i), 16) << 4) + Character.digit(str.charAt(i + 1), 16));
        }
        return bArr;
    }
}

역시나 하나씩 해석해보도록 하겠습니다.

 

#12

bArr = sg.vantagepoint.a.a.a(b("8d127684cbc37c17616d806cf50473cc"), Base64.decode("5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=", 0));

- bArr이라는 byte 배열에 sg.vantagepoint.a라는 패키지 안에 있는 a클래스의 a메서드에 두개의 인자 b("8d127684cbc37c17616d806cf50473cc")과 Base64.decode("5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=", 0) 를 넣습니다.

 

이 부분이 핵심 부분이라 좀 더 자세히 보도록 하겠습니다.

① b("8d127684cbc37c17616d806cf50473cc")

- b 메서드에 문자열 "8d127684cbc37c17616d806cf50473cc"을 넣습니다.

 

그렇다면 b메서드는 무엇인지 보도록 하겠습니다.

public static byte[] b(String str) {
    int length = str.length();
    byte[] bArr = new byte[length / 2];
    for (int i = 0; i < length; i += 2) {
    	bArr[i / 2] = (byte) ((Character.digit(str.charAt(i), 16) << 4) + Character.digit(str.charAt(i + 1), 16));
    }
    return bArr;
}

#2 : int length = str.length();

- 입력한 문자열의 길이를 구해 length 변수에 저장한다.

#3 : byte[] bArr = new byte[length / 2];
- 위에서 구한 str.length는 헥사 문자열이므로 바이트로 변환하면 이 길이는 절반이 된다. 절반만큼의 길이를 가진 bArr 배열을 생성한다.

#5 : bArr[i / 2] = (byte) ((Character.digit(str.charAt(i), 16) << 4) + Character.digit(str.charAt(i + 1), 16));

- Character.digit(str.charAt(i), 16) << 4) : 문자열에서 i번째 문자를 가져와 16진수 숫자로 변환합니다. 이 숫자를 4비트 왼쪽으로 쉬프트 합니다.

- Character.digit(str.charAt(i + 1), 16) : 다음 문자를 가져와 16진수 숫자로 변환한다.
- 위 두 수를 더하여 하나의 바이트를 얻고 이 바이트는 bArr[i/2]에 저장됩니다.

 

이렇게 하면 결론적으로 "8D 12 76 84 CB C3 7C 17 61 6D 80 6C F5 04 73 CC" 라는 바이트 값으로 변환되게 됩니다.

 

② Base64.decode("5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=", 0)

- Base64로 "5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc="를 디코딩 한다.

 

그렇다면 다시 돌아와 sg.vantagepoint.a라는 패키지 안에 있는 a클래스의 a메서드를 확인해 보도록 하겠습니다.

Step 3) sg.vantagepoint.a.a.a에서 맨 끝 a에 마우스를 올리고 Ctrl과 같이 클릭하면 해당 메서드로 한 번에 이동한다.

sg.vantagepoint.a.a.a 확인

한 번 자세히 코드를 분석해보도록 하겠습니다.

package sg.vantagepoint.a;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

/* loaded from: classes.dex */
public class a {
    public static byte[] a(byte[] bArr, byte[] bArr2) {
        SecretKeySpec secretKeySpec = new SecretKeySpec(bArr, "AES/ECB/PKCS7Padding");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(2, secretKeySpec);
        return cipher.doFinal(bArr2);
    }
}

#9 : SecretKeySpec secretKeySpec = new SecretKeySpec(bArr, "AES/ECB/PKCS7Padding");

- 입력받은 bArr을 "AES/ECB/PKCS7Padding" 알고리즘을 이용하여 비밀키로 사용한다.

#10 : Cipher cipher = Cipher.getInstance("AES");

- 암복호화 알고리즘은 AES를 사용한다. AES는 대칭키이기 때문에 암호화 복호화 하는과정에서 같은 키를 사용한다.
#11 : cipher.init(2, secretKeySpec);

- Cipher 객체를 초기화합니다. 2는 Cipher.DECRYPT_MODE 상수를 나타내며, 이는 Cipher 객체를 복호화 모드로 설정한다는 것을 의미합니다. secretKeySpec는 복호화에 사용되는 비밀 키입니다.
#12 : return cipher.doFinal(bArr2);

- 입력받은 bArr2 배열을 bArr로 복호화 한다.

 

즉, verify 메서드를 최종 정리를 해보겠습니다.

① b("8d127684cbc37c17616d806cf50473cc")로 얻은 "8D 12 76 84 CB C3 7C 17 61 6D 80 6C F5 04 73 CC" 라는 바이트를 암복호화 하는 키로 사용합니다.

② "5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc="를 Base64로 디코딩한 값을 위의 키값으로 복호화 합니다.

③ 복호화한 값이 사용자가 입력한 값과 같다면 true를 반환하고 Success를 보여줍니다.

 

그렇다면 이번엔 복호화된 값이 무엇인지 로그로 확인해보도록 하겠습니다. 그러기 위해선 로그를 볼 수 있게 세팅을 해보도록 하겠습니다.

Step 4) nox_adb shell 실행 후 logcat 명령어를 실행해 nox에서 동작하는 것들을 로그로 찍어본다.

logcat 실행 시 cmd 화면

Step 5) 암복호화를 수행하는 sg.vantagepoint.a.a.a의 smail 코드를 확인한다.

경로 : ~\APK Easy Tool v1.60 Portable\1-Decompiled APKs\UnCrackable-Level1\smali\sg\vantagepoint\a\a.smali

AES 암복호화를 수행하는 sg.vantagepoint.a.a.a의 smail 코드

우린 여기서 return전에 로그를 찍어주는 smali코드를 넣어줄 것이다. 아래의 코드를 return-object v0 전에 넣어주자.

const-string v1, "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"

new-instance v2, Ljava/lang/String;

invoke-direct {v2, v0}, Ljava/lang/String;-><init>([B)V

invoke-static {v1, v2}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

수정된 smali코드

일반적으로 로그만 찍으면 알아보기 힘드니 로그에서 ZZZZZZZZZZZZZZZZZZ가 나온 부분을 확인해보면 쉽게 로그를 찾을 수 있다.

 

Step 6) smali코드를 수정한 후 리컴파일을 통해 코드를 조작한 apk 파일을 만들어준다.

APK Easy Tool을 활용해 리컴파일 시키면 수정된 apk파일을 얻을 수 있다.

Step 7) 수정된 apk 파일을 녹스에서 실행시켜 임의의 문자열을 입력하면 cmd창에 로그가 찍히게 된다.

임의의 문자열 입력 후 Nope창이 뜬다.

Nope창이 뜨고 로그를 확인해보면 ZZZZZZZZZZZZZ와 함께 복호화된 Secret값이 나오게 된다.

ZZZZZZZZZZZZZZZ 와 함께 로그에 복호화된 값이 나오게 된다.

Step 8) 얻은 Secret값을 대입해보면 Success와 함께 풀리게 된다.

Success! 알림창 출력

이렇게 Uncrackable level1을 Frida를 사용하지 않고 정적분석을 통한 smali 코드 변조를 이용한 풀이방법을 마무리하도록 하겠습니다.

반응형

'mobile해킹 > 안드로이드' 카테고리의 다른 글

Uncrackable level 2(Frida 활용)  (0) 2023.05.30
Uncrackable level 1 (정적분석 Part.1)  (0) 2023.05.23