취약점 개요

바이너리 패칭 취약점은 애플리케이션이 관리자 상태, 기능 접근과 같은 권한 확인을 클라이언트 측 로직과 하드코딩된 검사에 의존하기 때문에 발생합니다. 이러한 검사는 서버 측 검증, 코드 무력성 확인과 같은 보호 메커니즘 없이 애플리케이션의 APK 내에 구현되어 있습니다.

그 결과, 공격자는 APK를 디컴파일하거나 디스어셈블하고, 명령어 및 변수를 수정한 뒤, 애플리케이션을 리패키징하여 보안 제한을 우회할 수 있습니다.

바이너리를 패치함으로써 공격자는 인증 확인 절차를 무력화하거나, 관리자 권한을 부여하고 남용할 수 있습니다.

취약점 분석

현재 앱에서는 ADMINISTRATION 버튼이 비활성화되어 있습니다.

PS C:\Users\WIN11> adb -s 2c838d0cfa0b7ece shell dumpsys window | findstr 'mCurrentFocus'
  mCurrentFocus=Window{996e86f u0 owasp.sat.agoat/owasp.sat.agoat.BinaryPatchingActivity}

액티비티명을 확인한 뒤 디컴파일한 코드를 분석해보겠습니다.

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(C1278R.layout.activity_binary_patching);
        TextView isAdminText = (TextView) findViewById(C1278R.id.isAdminText);
        Button adminButton = (Button) findViewById(C1278R.id.adminButton);
        if (this.isAdmin) {
            isAdminText.setText("You are Admin Now");
            adminButton.setBackgroundColor(Color.parseColor("#000000"));
            adminButton.setTextColor(Color.parseColor("#FFFFFF"));
            adminButton.setEnabled(true);
        }
        adminButton.setOnClickListener(new View.OnClickListener() { // from class: owasp.sat.agoat.BinaryPatchingActivity$$ExternalSyntheticLambda0
            @Override // android.view.View.OnClickListener
            public final void onClick(View view) {
                BinaryPatchingActivity.onCreate$lambda$0(this.f$0, view);
            }
        });
    }
    public static final void onCreate$lambda$0(BinaryPatchingActivity this$0, View it) {
        Intrinsics.checkNotNullParameter(this$0, "this$0");
        Toast.makeText(this$0, "You clicked on Administration button", 1).show();
    }

이 코드에서는 기능에 대한 권한을 확인하기 위해 클라이언트 측에서 제어할 수 있는 isAdmin 변수에 따라 할당합니다.

초기 변수 선언 시 isAdminfinal로 선언되어, 별도의 보안 메커니즘이 존재하지 않고 onCreate()함수 내에서 사용됩니다.

이는 공격자가 디컴파일 후 바이너리 패칭이나 런타임 조작을 수행해 isAdmin 값을 true로 변경할 수 있습니다. 결과적으로 공격자가 권한이 없더라도 버튼을 활성화 시킬 수 있습니다.

바이너리 패칭 우회 실습

먼저, 명령어 또는 프로그램을 사용하여 APK 파일을 디컴파일해 Smali 코드를 획득합니다.

  • Smali는 안드로이드 Dalvik 바이트코드의 사람이 읽을 수 있는 저수준 표현입니다. 이를 통해 Java나 Kotlin으로 컴파일된 앱의 내부 로직을 분석하고 수정할 수 있습니다.

그다음 smali_classes2/owasp/sat/agoat/ 경로로 이동하여 BinaryPatchingActivity.smali 파일을 수정합니다.

현재는 if-eqz로 설정되어 있습니다. 이는 "값이 0(거짓)과 같다면"을 의미하므로, 코드는 조건이 거짓일 때 관리자 버튼이 활성화됩니다.

그러므로 if-eqz로 설정된 코드를 if-nez로 변경하여 조건 검사의 논리를 반전시킬 수 있습니다. 이러한 코드 패칭을 통해 적절한 권한 부여 없이 Admin 기능을 활성화 시킬 수 있었습니다.

코드를 변경한 뒤 컴파일을 진행하고, 새롭게 컴파일한 앱을 기기 내 설치합니다. 설치 후, Binary Patching메뉴로 이동하면:

이전에 활성화되지 않았던 ADMINISTRATION 버튼이 활성화되어있고, 클릭 시 동작하는 것을 볼 수 있습니다.

취약점 발생 원인

바이너리 패칭은 안드로이드 애플리케이션 내 사용자가 APK 파일에 대한 완전한 접근 권한을 갖는 클라이언트 제어 환경에 배포되기 때문에 발생합니다. 앱이 설치되면 공격자는 APK를 추출하고 디컴파일하여 내부 로직을 분석할 수 있습니다.

컴파일된 코드는 본질적으로 수정으로부터 보호되지 않아, 클라이언트 측에만 구현된 보안 로직은 우회가 가능합니다.

또한, 안드로이드 앱은 인증 및 권한 부여 같은 제한을 강제하기 위해 종종 변수, 하드코딩된 값 또는 클라이언트 측 검사에 의존합니다. 서버 측 검증, 코드 무결성 확인 및 변조 방지 메커니즘과 같은 보호 조치 없이는 앱의 바이트 코드를 수정하고, 조건 검사를 변경하고, APK를 리패키징할 수 있습니다.

이는 특히 애플리케이션이 자신의 클라이언트 측 로직을 맹목적으로 신뢰할 때 바이너리 패칭을 가능하고 효과적으로 만듭니다.

Mitigation

안드로이드 애플리케이션에서 바이너리 패칭 취약점을 완화하기 위해 다음과 같은 보안 조치를 적용할 수 있습니다.

  • 중요 로직은 서버 측 검증: 인증 및 권한 부여와 같은 부분은 클라이언트 측 검사에 의존하지 않고 신뢰할 수 있는 백엔드에서 진행해야 합니다.
  • 무결성 및 변조 탐지: 앱의 서명이나 체크섬을 확인하는 등 APK가 수정되었는지 감지하기 위해 런타임 무결성 검사를 진행합니다.
  • 코드 난독화: ProGuard나 R8과 같은 도구를 적용하여 클래스 이름, 메서드 및 로직 흐름을 명확하지 않게 만듦으로써 리버스 엔지니어링 및 Smali 코드 수정을 어렵게 만들 수 있습니다.
  • 루팅 및 변조된 환경 탐지: 런타임 조작 위험을 줄이기 위해 루팅된 기기나 후킹 프레임워크를 탐지하는 검사를 추가합니다.

결론

결론적으로, 바이너리 패칭 취약점은 안드로이드 애플리케이션이 앱 코드가 수정될 수 없다고 가정하고 보안 결정을 강제하기 위해 클라이언트 측 로직에 의존할 때 발생합니다.

APK 파일은 추출, 디컴파일 및 변경이 가능하여 공격자는 조건부 검사, 하드코딩된 값 또는 권한 부여 로직을 조작해 제한을 우회하고 제한된 접근 권한을 획득할 수 있습니다.

이는 클라이언트 측의 신뢰만으로 보안에 불충분함을 보여줍니다. 이 문제를 해결하기 위해 중요 로직은 서버 측에서 검증되어야 하며, 바이너리 패칭 공격의 영향을 줄이기 위해 무결성 확인, 난독화 및 변조 탐지와 같은 추가적인 보호 조치가 필요합니다.