보안 공부/모바일 보안

안드로이드 취약점 진단(1) - 브로드캐스트 리시버 취약점

H.J.World 2024. 2. 17. 21:36
728x90
반응형

브로드캐스트 리시버(Broadcast Receiver)는 안드로이드 디바이스에서 이벤트가 발생하면 브로드캐스트 리시버 신호를 보내게 되는데, 이 신호를 받아 처리하는 역할을 수행한다.

신호를 받는 경우 애플리케이션에 정의해 놓은 작업을 수행한다. => 이 작업은 브로드캐스트 리시버를 상속받은 메서드에서 처리한다.

AndroidManifest.xml의 <receivcer>, </receiver> 항목에 선언된다.

-- 취약점 진단 방법 --

1. Manifest 확인

        <receiver android:exported="true" android:name="com.android.insecurebankv2.MyBroadCastReceiver">
                        
            <intent-filter>
                                
                <action android:name="theBroadcast"/>
                            
            </intent-filter>
                    
        </receiver>

- 브로드캐스트 리시버 이름 : theBroadcast

- 신호를 받으면 MybroadcastReceiver에 설정된 작업을 수행

- exported : true => 그렇기 때문에 외부 애플리케이션으로 부터 intent를 받을수 있다.

 

2. MybroadcastReceiver smali 파일

.class public Lcom/android/insecurebankv2/MyBroadCastReceiver;
.super Landroid/content/BroadcastReceiver;
.source "MyBroadCastReceiver.java"


# static fields
.field public static final MYPREFS:Ljava/lang/String; = "mySharedPreferences"


# instance fields
.field usernameBase64ByteString:Ljava/lang/String;


# direct methods
.method public constructor <init>()V
    .locals 0

    .prologue
    .line 16
    invoke-direct {p0}, Landroid/content/BroadcastReceiver;-><init>()V

    return-void
.end method


# virtual methods
.method public onReceive(Landroid/content/Context;Landroid/content/Intent;)V
    .locals 16
    .param p1, "context"    # Landroid/content/Context;
    .param p2, "intent"    # Landroid/content/Intent;

    .prologue
    .line 24
    const-string v3, "phonenumber"

    move-object/from16 v0, p2

    invoke-virtual {v0, v3}, Landroid/content/Intent;->getStringExtra(Ljava/lang/String;)Ljava/lang/String;

    move-result-object v12

    .line 25
    .local v12, "phn":Ljava/lang/String;
    const-string v3, "newpass"

    move-object/from16 v0, p2

    invoke-virtual {v0, v3}, Landroid/content/Intent;->getStringExtra(Ljava/lang/String;)Ljava/lang/String;

    move-result-object v10

    .line 27
    .local v10, "newpass":Ljava/lang/String;
    if-eqz v12, :cond_0

    .line 29
    :try_start_0
    const-string v3, "mySharedPreferences"

    const/4 v5, 0x1

    move-object/from16 v0, p1

    invoke-virtual {v0, v3, v5}, Landroid/content/Context;->getSharedPreferences(Ljava/lang/String;I)Landroid/content/SharedPreferences;

    move-result-object v13

    .line 30
    .local v13, "settings":Landroid/content/SharedPreferences;
    const-string v3, "EncryptedUsername"

    const/4 v5, 0x0

    invoke-interface {v13, v3, v5}, Landroid/content/SharedPreferences;->getString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;

    move-result-object v14

    .line 31
    .local v14, "username":Ljava/lang/String;
    const/4 v3, 0x0

    invoke-static {v14, v3}, Landroid/util/Base64;->decode(Ljava/lang/String;I)[B

    move-result-object v15

    .line 32
    .local v15, "usernameBase64Byte":[B
    new-instance v3, Ljava/lang/String;

    const-string v5, "UTF-8"

    invoke-direct {v3, v15, v5}, Ljava/lang/String;-><init>([BLjava/lang/String;)V

    move-object/from16 v0, p0

    iput-object v3, v0, Lcom/android/insecurebankv2/MyBroadCastReceiver;->usernameBase64ByteString:Ljava/lang/String;

    .line 33
    const-string v3, "superSecurePassword"

    const/4 v5, 0x0

    invoke-interface {v13, v3, v5}, Landroid/content/SharedPreferences;->getString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;

    move-result-object v11

    .line 34
    .local v11, "password":Ljava/lang/String;
    new-instance v7, Lcom/android/insecurebankv2/CryptoClass;

    invoke-direct {v7}, Lcom/android/insecurebankv2/CryptoClass;-><init>()V

    .line 35
    .local v7, "crypt":Lcom/android/insecurebankv2/CryptoClass;
    invoke-virtual {v7, v11}, Lcom/android/insecurebankv2/CryptoClass;->aesDeccryptedString(Ljava/lang/String;)Ljava/lang/String;

    move-result-object v8

    .line 36
    .local v8, "decryptedPassword":Ljava/lang/String;
    invoke-virtual {v12}, Ljava/lang/String;->toString()Ljava/lang/String;

    move-result-object v2

    .line 37
    .local v2, "textPhoneno":Ljava/lang/String;
    new-instance v3, Ljava/lang/StringBuilder;

    invoke-direct {v3}, Ljava/lang/StringBuilder;-><init>()V

    const-string v5, "Updated Password from: "

    invoke-virtual {v3, v5}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    move-result-object v3

    invoke-virtual {v3, v8}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    move-result-object v3

    const-string v5, " to: "

    invoke-virtual {v3, v5}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    move-result-object v3

    invoke-virtual {v3, v10}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    move-result-object v3

    invoke-virtual {v3}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object v4

    .line 38
    .local v4, "textMessage":Ljava/lang/String;
    invoke-static {}, Landroid/telephony/SmsManager;->getDefault()Landroid/telephony/SmsManager;

    move-result-object v1

    .line 39
    .local v1, "smsManager":Landroid/telephony/SmsManager;
    sget-object v3, Ljava/lang/System;->out:Ljava/io/PrintStream;

    new-instance v5, Ljava/lang/StringBuilder;

    invoke-direct {v5}, Ljava/lang/StringBuilder;-><init>()V

    const-string v6, "For the changepassword - phonenumber: "

    invoke-virtual {v5, v6}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    move-result-object v5

    invoke-virtual {v5, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    move-result-object v5

    const-string v6, " password is: "

    invoke-virtual {v5, v6}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    move-result-object v5

    invoke-virtual {v5, v4}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    move-result-object v5

    invoke-virtual {v5}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object v5

    invoke-virtual {v3, v5}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V

    .line 40
    const/4 v3, 0x0

    const/4 v5, 0x0

    const/4 v6, 0x0

    invoke-virtual/range {v1 .. v6}, Landroid/telephony/SmsManager;->sendTextMessage(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Landroid/app/PendingIntent;Landroid/app/PendingIntent;)V
    :try_end_0
    .catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0

    .line 48
    .end local v1    # "smsManager":Landroid/telephony/SmsManager;
    .end local v2    # "textPhoneno":Ljava/lang/String;
    .end local v4    # "textMessage":Ljava/lang/String;
    .end local v7    # "crypt":Lcom/android/insecurebankv2/CryptoClass;
    .end local v8    # "decryptedPassword":Ljava/lang/String;
    .end local v11    # "password":Ljava/lang/String;
    .end local v13    # "settings":Landroid/content/SharedPreferences;
    .end local v14    # "username":Ljava/lang/String;
    .end local v15    # "usernameBase64Byte":[B
    :goto_0
    return-void

    .line 41
    :catch_0
    move-exception v9

    .line 42
    .local v9, "e":Ljava/lang/Exception;
    invoke-virtual {v9}, Ljava/lang/Exception;->printStackTrace()V

    goto :goto_0

    .line 46
    .end local v9    # "e":Ljava/lang/Exception;
    :cond_0
    sget-object v3, Ljava/lang/System;->out:Ljava/io/PrintStream;

    const-string v5, "Phone number is null"

    invoke-virtual {v3, v5}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V

    goto :goto_0
.end method

- 브로드캐스트가 발생하는 경우 intent-filter에 의해 걸러진 Intent들이 onReceiver() 메서드로 들어와 입력된 전화번호에 문자 메시지를 전송하도록 정의

 

3. ADB를 사용한 브로드캐스트 생성

- am 명령을 사용해서 브로드캐스트 생성 시도

명령어 : am broadcast -a theBroadcast -n com.android.insecurebankv2/.MyBroadCastReceiver

명령어 구성

  • am: Activity Manager의 약자로, 안드로이드 시스템에서 액티비티, 서비스, 브로드캐스트 리시버 등을 제어하는 데 사용됩니다.
  • broadcast: 브로드캐스트 인텐트를 보내는 명령입니다.
  • -a: 인텐트의 액션을 지정합니다. 여기서 theBroadcast는 브로드캐스트 인텐트의 액션 이름입니다.
  • -n: 구체적인 컴포넌트 이름을 지정합니다. 여기서 com.android.insecurebankv2/.MyBroadCastReceiver는 호출할 브로드캐스트 리시버의 전체 경로입니다.

해당 명령어 수행 시 발생하는 로그를 logcat을 통해서 확인해보면

2번에서 있는 smali 코드에서 있던 폰 번호 없이 실행 시킬때 발생하는 오류 인 "Phone number is null" 이라는 결과를 발생

 

4. 드로저를 사용한 브로드캐스트 생성

- 앱 자체에 취약점이 존재하는지 확인

 

- 브로드캐스트 리시버 정보 확인

dz> run app.broadcast.info -a com.android.insecurebankv2
Package: com.android.insecurebankv2
  com.android.insecurebankv2.MyBroadCastReceiver
    Permission: null

 >> 결과 :  MyBroadcastReceiver가 포함되어 있고, 권한은 설정되어있지 않음

 

명령어 : run app.broadcast.send --component com.android.insecurebankv2 com.android.insecurebankv2.MyBroadcastReceiver --extra string phonenumber 1111 --extra string newpass test

>> 결과 : --extra 옵션을 사용해 변수명, 값을 입력해서 변경을 시도

 

5. 대응방안

- androidmanifest.xml 내 android:exported 를 false로 설정

- 각 리시버에 별도의 권한을 설정

728x90
반응형