본문 바로가기
4. 모바일 앱 취약점 진단/Android

[Android] 1.2. 앱 무결성 검증 취약점

by Robert8478 2024. 6. 19.

개요

앱 무결성 검증 취약점은 앱 소스코드, 리소스 및 XML 파일 등이 변조되었을때 무결성을 검증하지 않아 변조된 앱을 실행할 수 있게 되는 취약점으로 볼 수 있다.

기본적으로 앱 변조의 경우 디컴파일 및 리패키징이 가능해야하며, 1.1 암호화 및 난독화 취약점 존재로 인해 내부 코드 자체가 어느정도 분석이 가능해야 원활한 앱 변조가 가능해지게 된다.

점검 방법

점검 방법에는 기본적으로 앱의 아이콘을 변경하거나 내부에 Toast 메시지를 출력하는 코드를 삽입하여 출력이 되는지 확인하는 방법으로 진행했다.

AndroidManifest.xml

앱 아이콘 변경의 경우 Manifest.xml 파일에서 변조할 수 있다. 보통 mipmap 경로 내부에 아이콘 이미지가 존재하는데 해당 코드를 변조함으로써 확인하는 간단한 방식이지만 보통은 앱 아이콘 변경이 아닌 Toast 메시지 출력을 주로 사용한다.

[Toast 메시지 삽입]

앱 변조를 위한 Toast 메시지 삽입하기

일반적으로는 Toast 메시지를 삽입할 때 MainActivity의 onCreate 함수를 검색한다. 해당 함수는

앱의 메인이 시작될 때 반드시 호출되는 부분이기 때문에 해당 함수 밑에 코드를 삽입하는 것이다.

위 사진은 Toast 메시지가 삽입된 코드이며, 삽입한 코드는 아래와 같다.

const-string v0, "\uc774\uae00\ub8e8\u0020\ubb34\uacb0\uc131\u0020\uac80\uc99d"

const/4 v1, 0x0

invoke-static {p0, v0, v1}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;

move-result-object v0

invoke-virtual {v0}, Landroid/widget/Toast;->show()V

const-string의 경우 Toast 메시지 내용인데 \uc…로 된 부분은 한글 출력을 위한 유니코드다.

 

바로 삽입하고 사용하면 앱에 오류가 생겨 리패키징이 실패하는 경우가 존재하는데,

이는 보통 Toast 메시지 삽입 코드로 인해 p(매개변수) 혹은 v(레지스터)가 중복되거나

locals(레지스터 사용량)로 지정된 값이 Toast 메시지 코드로 인해 오버되었을떄 발생한다.

이런경우에는 locals 값을 올려주거나 p, v 값이 중복되지 않도록 수정하면 정상적으로 컴파일된다.

코드 변조 후 앱을 실행하면 Toast 메시지가 출력되는 것을 알 수 있을것이다.

 

대응 방안

앱의 무결성이 침해되었는지 여부를 파악하기 위해서는 대략 아래의 3가지의 방법이 존재한다.

 

1. Hash 값 비교

먼저 앱의 Hash값을 계산하고 이를 서버에 저장하는데 리눅스의 경우 아래의 명령어를 사용한다.

# sha256sum <apk file> | awk {'print $1}' > <hash file>

여기서 출력된 Hash값을 저장하고 아래와 같은 로직을 구현하여 앱이 변조되었는지 Hash값을 통해 비교하는 작업을 진행한다.

@Override
protected void onCreate(Bundle savedInstanceState) {
...
    try {
       PackageManager packageManager = getPackageManager();
       info = packageManager.getApplicationInfo(getPackageName(), 0);
       new Thread(new Runnable() {
              @Override
              public void run() {
                     HttpClient client = SessionControl.getHttpClient();
                     try {
                            HttpGet httpget = new HttpGet();
                            httpget.setURI(new URI("https://igloo.co.kr/security/apkhash.txt"));
                            HttpResponse res = client execute(httpget);
                            String response = EntityUtils.toString(res.getEntity(), HTTP.UTF_8);

                            if(getFileHash(info.sourceDir).equalsIgnoreCase(response.toString().trim())){
                               LinearLayout l = (LinearLayout) findViewById(R.id.l);
                               l.setOnClickListener(new View.OnClickListener() { 
                                   public void onClick(View v){
                                      Intent intent = new Intent(IGLogo.this, IGLogin.class);
                                      startActivity(intent);
                                      finish();
                                   }			 
                               });
                             } else {
                                 moveTaskToBack(true); 
                                 finish(); 
                                 android.os.Process.killProcess(android.os.Process.myPid());
                             }
...
public static String getFileHash(String filePath) {
	    String returnVal = "";
	    try {
	        InputStream input   = new FileInputStream(filePath); 
	        byte[] buffer  = new byte[1024];
	          MessageDigest md = MessageDigest.getInstance("SHA-256"); //SHA256, SHA386, SHA512 사용
	        int numRead = 0;
	        while (numRead != -1){
	            numRead = input.read(buffer);
	            if (numRead > 0){
	            	md.update(buffer, 0, numRead);
	            }
	        }
	        input.close();
	        byte [] mdBytes = md.digest();
	        for (int i=0; i < mdBytes.length; i++){
	            returnVal += Integer.toString( ( mdBytes[i] & 0xff ) + 0x100, 16).substring( 1 );
	        }
	    } catch(Throwable t) { t.printStackTrace(); }
	    return returnVal.toUpperCase(Locale.getDefault());
	}

 

2. 플레이 스토어 앱 점검

앱 개발 시 대부분의 앱은 구글 플레이 스토어를 통해 배포하게 될 것이다. 앱 로직에 구글 플레이 스토어에서 다운로드 받은 앱이 맞는지 검증하면, 이 또한 무결성 검증이 되는것이다.

public static boolean checkGooglePlayStore(Context context) {
    String installerPackageName = context.getPackageManager()
    .getInstallerPackageName(context.getPackageName());
    return installerPackageName != null && installerPackageName.startsWith(“com.google.android”);
}

구글 플레이스토어 여부 검증은 위와 같은 코드를 추가하여 대응하도록 한다.

 

3. 서명 키 검증

앱에 별도로 서명키를 생성하여 원본 Signature를 저장하고 이를 비교하는 방식도 존재한다.

// 원본 시그니쳐 저장
private static String CERTIFICATE_SHA1 = “45120AC9486E087DCBF5C7F6FEC95213585BCC5”;
public static Boolean validateAppSignature(Context context) {
    try {
    // 패키지 매니저에서 시그니처를 구함
    PackageInfo packageInfo = context.getPackageManager()
    .getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
    Signature[] appSignatures = packageInfo.signatures);

    For (Signature signature : appSignatures) {
       Byte[] signatureBytes = signature.toByteArray();
       // 16진수 변환 함수 호출
       String currentSignature = calcSHA1(signatureBytes);
       // 시그니쳐 비교 수행
       return CERTIFICATE_SHA1.equalsIgnoreCase(currentSignature);
    }
} catch (Exception e) {
}
return false;
}