[안드로이드 모의해킹] 코드 패치와 앱 무결성 검증 (smali 코드 분석)

직접 연구하여 작성한 자료입니다. 공식 출처가 명시되지 않은 자료의 무단 복제, 사용을 금지합니다.
공격 기법은 학습용, 허가된 환경에서 실습 바랍니다. 실 운영망 대상 공격은 처벌받습니다. (정보통신망법 제48조 1항)

 

 

개요

 

안드로이드 단말의 무결성만큼 앱의 무결성도 매우 중요하다. 공격자가 앱 내부 코드를 변조하여 리패키징을 수행하면, 악성코드가 앱 내부에 삽입될 수 있다. 안드로이드 설치 파일은 구글 플레이 스토어뿐만 아니라 제3자 제공 스토어에서도 다운로드 받을 수 있으며, 단말에서 허용하는 경우 apk 파일을 별도로 다운로드 하여 설치할 수 있다.

 

 

이 과정에서 위변조된 앱이 설치되면 사용자 데이터에 접근하거나 민감정보가 유출될 수 있다. 예를 들어 기존 앱과 동일한 뷰로 로그인 기능을 구현하고 계정정보를 앱 서버가 아닌 공격자의 서버로 전송하도록 변조한 앱을 사용자가 사용하면, 계정정보가 유출된다.

 

 

또한 게임 앱에서 점수를 조작하거나 피해량 수치를 조작하여 죽지 않는 캐릭터를 만드는 등 변조가 가능하다. 이와 같은 위협을 방지하기 위해 앱의 코드가 변조되지 않았음을 검증하고 기능을 사용해야 한다. apk 파일은 디컴파일과 배포가 쉬워 신뢰할 수 없는 코드 실행을 방지하고 변조된 앱의 설치 및 실행을 금지하는 조치가 필요하다. 디지털 서명, 앱 해시값 검증, 안전한 배포 채널 사용 등을 통해 앱의 무결성을 보장해야 한다.

 

     smali 코드 수정

smali 코드는 자바 바이트 코드를 사람이 읽기 쉽도록 변환한 것이다. smali 코드의 특징과 해석 방법을 간략히 나타내면 다음과 같다. 자주 사용되는 명령어에 해당하는 경우이며, 정확한 명령어 사용 명세는 공식 문서를 참고한다.

  • smali 코드는 '명령어 인자'의 구조로 구성한다. 명령어에 따라 인자는 없거나 하나 이상 존재한다.
  • 명령어 인자는 '대상-소스' 순서로 배열한다. move vA, vB인 smali 코드는 vB의 값을 vA로 이동한다는 의미이다.
  • 명령어 또는 인수에 접두사, 접미사가 붙은 경우는 주로 자료형을 나타내며, 자료형마다 크기가 다르기 때문에 몇 바이트까지 인자로 해석할 것인지에 대한 직관성을 보여준다.

 

💡 smali 코드 형식
바이트 코드를 smali 코드로 해석하는 규칙은 안드로이드 공식 문서를 참고할 수 있다.
https://source.android.com/docs/core/runtime/dalvik-bytecode?hl=ko

 

smali 코드 패치를 위해 분석하는 경우 조건문이 어떻게 해석되는지 살펴본다. 다음은 조건문을 사용하는 자바 코드이다.

 

public class Example {
    public int cal(int a, int b) {
        if (a > 0) {
            return a + b;
        } else {
            return a - b;
        }
    }
}

 

예시의 자바 코드의 보면 Example 클래스를 선언하고 내부에 cal 함수를 선언하여 기능을 구현하였다. 정수형 인자 두 개를 받아 a변수가 0보다 큰 경우 덧셈, 그렇지 않은 경우 뺄셈 연산을 수행한다. 이 코드를 smali 코드로 변환하면 다음과 같다.

 

.class public Lcom/example/Example;
.super Ljava/lang/Object;
.method public cal(II)I
    .locals 3
    .parameter "a"
    .parameter "b"
    .prologue
    .line 4
    .load-param v0, "a"  ; v0 레지스터에 지역변수 'a' 할당
    .line 5
    const/4 v1, 0  ; v1 레지스터에 상수 0 할당
    const/4 v2, "b"; v2 레지스터에 지역변수 'b' 할당
    if-gt v0, v1, :cond_positive  ; v0 > v1의 결과가 참일 경우 :cond_positive로 이동
    .line 8
    sub-int/2addr v0, v1  ; v0 - v1의 결과를 v0에 할당
    .line 9
    goto :end  ; end 라벨이 있는 줄로 이동
    :cond_positive
    .line 12
    add-int/2addr v0, v2  ; v2 + v0의 결과를 v0에 할당
    :end
    .line 14
    return v0
.end method

 

  • smali 코드는 가장 첫 번째 줄에서부터 순차적으로 실행되므로 분석 또한 기능이 시작되는 가장 첫 줄부터 순차적으로 진행한다.
  • .class public Lcom/example/Example : 클래스선언을 나타낸다.
  • .super Ljava/lang/Object: 부모클래스를 나타낸다. Java.lang.Object클래스를 상속받았다는 의미이다.
  • .method public calc(II)I : calc메서드의 선언을 나타낸다. (II)I는 메서드의 인자와 반환 값의 자료형을 나타낸다. II는 두 개의 int 인자를 나타내고 I는 int 반환 값의 자료형을 나타낸다.
  • .locals 3: 지역 변수를 선언한다. 3개의 변수를 선언하였다.
  • .load-param v0, "a"': v0 레지스터에 지역변수 a를 할당한다.
  • if-gt v0, v1, :cond_positive: v0(a), v1(0)을 비교하여 v0가 더 큰 경우 :cond_positive 라벨이 붙은 라인으로 이동(jump)한다.

 

💡 smali 코드의 조건문


smali 코드의 조건문은 다음과 같다. 영문 설명의 약어를 딴 것이기 때문에 영문 설명을 함께 보면 이해가 쉽다. 앞의 조건절이 참인 경우 명령문 내 라벨이 있는 줄으로 이동하거나 추가 명령을 수행할 수 있다. 조건절에 부합하지 않는 경우 다음 줄에 있는 코드를 이어서 실행한다.

smali 코드 연산자 영문 설명
if-eq A==B equal to
if-ne A!=B not equal to
if-lt A<B less than
if-le A<=B less than or equal to
if-ge A>=B greater than or equal to
if-gt A>B greater than

 

  • :cond_positive: 라벨이 있는 코드를 먼적 분석하면, 조건문이 참일 경우 실행되는 구문이다. smali 코드는 순차적으로 실행되다가 조건절이나 이동구문(goto 등)을 평가하여 특정라벨로 건너뛰어 실행한다. 라벨의 구문 실행이 완료되면, 이후 코드를 계속해서 실행한다.
  • add-int/2addr v0, v2: 조건절이 참일 때 실행되는 구문이다. 두 개의 인자를 받아 덧셈의 결과를 v0에 저장한다.

 

💡 smali 코드의 명령어와 레지스터


명령어에 따라 같은 기능을 수행함에도 필요한 레지스터의 개수가 다를 수 있다. 예를 들어 바이너리 연산(binary operator, binop)  add-int/2addr add-int는 둘다 정수 덧셈 명령어이지만, 사용 방식과 목적에 차이가 있다.

● add-int/2addr vAA, VBB
는 레지스터 두 개의 값을 더하여 그 결과를 다시 vAA에 저장하는 명령이다. 이 명령은 덧셈 연산 후 결과를 피연산자에 다시 저장하기 때문에 추가 명령어나 레지스터를 사용하지 않아 주로 간단한 연산을 수행할 때 사용한다.


● add-int vAA, vBB, vCC
는 두 개의 레지스터(vBB, vCC)의 값을 더하고 그 결과를 다른 레지스터(vAA)에 저장하는 명령어이다. 이 명령어는 연산 결과를 vAA 레지스터에 저장하므로 추가 레지스터를 사용한다. 복잡한 계산 또는 중간 결과를 보존해야 하는 경우 사용한다.

 

  • :end: 연산 이후 실행되는 ':end' 라벨의 명령어와 .line 9에서 이동하여 실행되는 명령 모두 이 라벨이 있는 줄로 이동한다. 연산의 결과는 v0 레지스터에 보관되어 있으므로 return v0 명령을 통해 cal 함수를 호출한 함수에게 v0의 값을 반환하고 함수를 종료한다.

 

 

smali 코드의 분석은 이와 같은 방식으로 진행한다. 처음 접하는 부분이기 때문에 클래스와 함수 선언부터 다루었지만, 많은 코드를 분석하게 되면 분석에 필요한 로직을 찾고 해당 코드의 실행 전후 코드를 분석하면 된다.

 

이번 분석을 통해 넓은 범위에서의 리버싱을 다루었다. 완성된 앱 파일을 디컴파일하여 사람이 읽을 수 있는 smali 코드로 변환하고 분석하였다. 코드의 실행을 한 줄씩 따라가보고 어떻게 구성되어 있는지 파악하다 보면 개발자가 어떤 기능을 의도하면서 개발했는지 보이는 순간이 있다. 분석의 즐거움을 느껴보길 바란다.

분석을 통해 구조를 파악한 후, 코드 패치를 통해 내용을 수정하여 다른 기능을 수행하도록 변경할 수 있다. 공식 문서를 참고하여 분석을 충분히 수행하고 명령어에서 필요로 하고 있는 인자의 자료형과 개수에 맞게 코드를 수정한다.

 

예제 코드에서는 조건문을 이용하여 함수의 인자를 비교한 다음 그 결과에 따라 연산자를 다르게 하여 결과를 반환한다. 입력 받는 변수의 위치를 변경하거나, 조건문의 조건절을 변경하는 방법 등을 사용하여 코드의 실행흐름을 바꿀 수 있다. smali 코드는 수정이 가능하고 리패키징을 통해 다시 앱 설치 파일로 생성이 가능하기 때문에 코드 패치가 가능하다. 다음과 같이 코드 패치를 수행할 수 있다.

구분 코드
기존 코드 if-gt v0, v1, :cond_positive;
변경 코드 if-le v0, v1, :cond_positive;

 

v0의 값과 v1의 값을 비교하여 v0이 더 큰 경우 덧셈연산을 수행하는 기존 코드를 수정하여, v0 v1보다 작거나 같은 경우에 덧셈연산을 수행하도록 한다. 분석 과정을 통해서 어떤 기능을 하는 코드인지 정확하게 파악을 했기 때문에 수정이 필요한 부분에 적절한 코드 패치를 적용할 수 있다. 조건문 패치를 통한 검사 구문 우회는 루팅 탐지 우회, 무결성 검사 등 앱에 구현된 많은 기능들에 활용할 수 있다.

반응형