블로그 이미지

ICEB34R

공부하는 아이스베어는 귀엽습니다..!

이번에 볼 것은 SharedPreferences와 암호화 키가 약한 경우입니다.

 

1. SharedPreferences가 무엇인가?

간단한 string이나 setting값 등 DB를 사용하기에는 부담스러운 정보를 저장하기 위해 사용하는 데이터 관리 기법.

 

SharedPreferences는 xml파일형태로 앱의 데이터폴더에 저장이 됩니다.

data/data/(package_name)/shared_prefs/SharedPreference

따라서 앱을 삭제하는 경우 데이터 또한 함께 삭제됩니다.

 

2. InsecureBank의 SharedPreferences를 확인해보자

 

adb shell

 

cd /data/data/com.android.insecurebankv2/shared_prefs

(/data/data에 접근이 안되는 경우, su를 입력해서 root shell로 접근해야 합니다.)

 

cat, vi 등등 에디터를 이용해서 mySharedPreferences.xml파일을 열어줍니다.

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <string name="superSecurePassword">DTrW2VXjSoFdg0e61fHxJg==&#10;    </string>
    <string name="EncryptedUsername">ZGluZXNo&#13;&#10;    </string>
</map>

 superSecurePassword와 EncryptedUsername이 존재하는 것을 볼 수 있습니다.

 

username과 password가 암호화되어서 저장된다고 유추할 수 있습니다. 

 

EncryptedUsername의 값은 ZGluZXNo인데, 이 값을 Base64Decode를 해보면, dinesh가 나오는 것을 알 수 있습니다.

 

3. 앱 내부에서 어떻게 사용되는지 알아보자.

 

이전과 마찬가지로 Bytecode Viewer를 통해 apk를 분석합니다.

 

왼쪽 하단의 Search에서 Regex로 getSharedPreferences를 검색합니다.

 

그중 첫 결과를 보면 Dologin클래스의 saveCreds메소드에서 사용하는 것을 확인할 수 있습니다.

 

  private void saveCreds(String paramString1, String paramString2)
    throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException
  {
    SharedPreferences.Editor localEditor = this.this$0.getSharedPreferences("mySharedPreferences", 0).edit();
    this.this$0.rememberme_username = paramString1;
    this.this$0.rememberme_password = paramString2;
    paramString2 = new String(Base64.encodeToString(this.this$0.rememberme_username.getBytes(), 4));
    paramString1 = new CryptoClass();
    this.this$0.superSecurePassword = paramString1.aesEncryptedString(this.this$0.rememberme_password);
    localEditor.putString("EncryptedUsername", paramString2);
    localEditor.putString("superSecurePassword", this.this$0.superSecurePassword);
    localEditor.commit();
  }

처음 getSharedPreferences를 이용해 "mySharedPreferences"를 만듭니다.

 

메소드의 인자1(아이디)는, Base64로 encode하고, 인자2(비밀번호)는 CryptoClass의 aesEncryptedString을 이용해서 aes암호화를 하는 것 같습니다.

 

SharedPreferences를 자동로그인을 구현하기위해 사용할 땐, 위와같이 암호화를 하여 저장합니다.

 

CryptoClass를 보면, 취약한 부분을 볼 수 있습니다. 

 

public class CryptoClass
{
  String base64Text;
  byte[] cipherData;
  String cipherText;
  byte[] ivBytes = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
  String key = "This is the super secret key 123";
  String plainText;
  
  public static byte[] aes256decrypt(byte[] paramArrayOfByte1, byte[] paramArrayOfByte2, byte[] paramArrayOfByte3)
    throws UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException
  {
    paramArrayOfByte1 = new IvParameterSpec(paramArrayOfByte1);
    paramArrayOfByte2 = new SecretKeySpec(paramArrayOfByte2, "AES");
    Cipher localCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    localCipher.init(2, paramArrayOfByte2, paramArrayOfByte1);
    return localCipher.doFinal(paramArrayOfByte3);
  }
  
  public static byte[] aes256encrypt(byte[] paramArrayOfByte1, byte[] paramArrayOfByte2, byte[] paramArrayOfByte3)
    throws UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException
  {
    paramArrayOfByte1 = new IvParameterSpec(paramArrayOfByte1);
    paramArrayOfByte2 = new SecretKeySpec(paramArrayOfByte2, "AES");
    Cipher localCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    localCipher.init(1, paramArrayOfByte2, paramArrayOfByte1);
    return localCipher.doFinal(paramArrayOfByte3);
  }

AES의 key가 하드코딩되어 있기 때문에, 이를 이용해서 AES를 decrypt할 수 있습니다.

 

https://www.devglan.com/online-tools/aes-encryption-decryption 온라인 AES툴을 이용하여 처음에 알아냈던

DTrW2VXjSoFdg0e61fHxJg== AES encrypt되어있는 값을 해당 key로 decrypt하게되면

Base64결과 값이 나오게 되며, decode해주면 Dinesh@123$로 비밀번호를 알아낼 수 있습니다.

Posted by ICEB34R

컨텐츠 프로바이더의 접근에 있어서 권한 검증이 미흡한 경우, 앱 내의 민감한 정보들을 유출할 수 있는 취약점 입니다.

 

우선 컨텐츠 프로바이더란?

앱에서 다른 앱의 데이터가 필요할 때 사용할 수 있도록 해주는 역할을 합니다.

 

예를들어 uri content://sms/inbox를 통해서 메세지의 데이터 베이스에 접근할 수 있습니다. 하지만 READ_SMS 권한이 없는데도 접근이 가능하면, 메세지 정보들이 유출 될 수 있는 것 입니다. 

 

이전과 마찬가지로 apktool을 이용해 디컴파일 합니다.

./apktool d InsecureBankv2.apk

그 뒤 AndroidManifest.xml파일을 열어줍니다.

 

<?xml version="1.0" encoding="utf-8" standalone="no"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.insecurebankv2" platformBuildVersionCode="22" platformBuildVersionName="5.1.1-1819727">
	...중략
    <application android:allowBackup="true" android:debuggable="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:theme="@android:style/Theme.Holo.Light.DarkActionBar">
        <activity android:label="@string/app_name" android:name="com.android.insecurebankv2.LoginActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <activity android:label="@string/title_activity_file_pref" android:name="com.android.insecurebankv2.FilePrefActivity" android:windowSoftInputMode="adjustNothing|stateVisible"/>
        <activity android:label="@string/title_activity_do_login" android:name="com.android.insecurebankv2.DoLogin"/>
        <activity android:exported="false" android:label="@string/title_activity_post_login" android:name="com.android.insecurebankv2.PostLogin"/>
        <activity android:label="@string/title_activity_wrong_login" android:name="com.android.insecurebankv2.WrongLogin"/>
        <activity android:exported="true" android:label="@string/title_activity_do_transfer" android:name="com.android.insecurebankv2.DoTransfer"/>
        <activity android:exported="true" android:label="@string/title_activity_view_statement" android:name="com.android.insecurebankv2.ViewStatement"/>
        
        //provider
        <provider android:authorities="com.android.insecurebankv2.TrackUserContentProvider" android:exported="true" android:name="com.android.insecurebankv2.TrackUserContentProvider"/>
        
        
        <receiver android:exported="true" android:name="com.android.insecurebankv2.MyBroadCastReceiver">
            <intent-filter>
                <action android:name="theBroadcast"/>
            </intent-filter>
	...중략
</manifest>

provider항목을 보게 되면, TrackUserContentProvider라는 이름의 컨텐츠 프로바이더가 있고, exported가 true인것을 확인할 수 있었습니다.

 

#2 에서 다룬 Activity의 exported가 true인 것 처럼 adb에서 접근이 가능합니다.

 

content query --uri content://com.android.insecurebankv2.TrackUserContentProvider/trackerusers

위의 명령어를 adb shell에서 입력하게 되면, 

com.android.insecurebankv2.TrackUserContentProvider/trackerusers              <
Row: 0 id=1, name=devadmin
Row: 1 id=2, name=devadmin
Row: 2 id=3, name=jack

로그인을 한 기록들이 보여지게 됩니다.

 

#2처럼 exported를 false로 변경함으로써 이 문제를 해결 할 수 있습니다.

 

AndroidManifest.xml에서 provider의 exported를 false로 변경한 뒤, apktool로 리컴파일 합니다.

./apktool b InsecureBankv2

그 뒤, sign.jar로 sign을 해주고 설치합니다.

java -jar sign.jar InsecureBankv2.apk

다시 adb shell에서 query를 날려보면

com.android.insecurebankv2.TrackUserContentProvider/trackerusers              <
Error while accessing provider:com.android.insecurebankv2.TrackUserContentProvider
java.lang.SecurityException: Permission Denial: opening provider com.android.insecurebankv2.TrackUserContentProvider from (null) (pid=7518, uid=2000) that is not exported from UID 10095
	at android.os.Parcel.createException(Parcel.java:1950)
	at android.os.Parcel.readException(Parcel.java:1918)
	at android.os.Parcel.readException(Parcel.java:1868)
	at android.app.IActivityManager$Stub$Proxy.getContentProviderExternal(IActivityManager.java:6550)
	at com.android.commands.content.Content$Command.execute(Content.java:464)
	at com.android.commands.content.Content.main(Content.java:690)
	at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
	at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:340)
Caused by: android.os.RemoteException: Remote stack trace:
	at com.android.server.am.ActivityManagerService.getContentProviderImpl(ActivityManagerService.java:12195)
	at com.android.server.am.ActivityManagerService.getContentProviderExternalUnchecked(ActivityManagerService.java:12606)
	at com.android.server.am.ActivityManagerService.getContentProviderExternal(ActivityManagerService.java:12601)
	at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:1659)
	at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:3291)

 

에러가 발생하게 됩니다.

 

 

Posted by ICEB34R

adb를 이용하여 액티비티를 임의로 조작할 수  있는 취약점에 대해서 알아보겠습니다.

 

이전과 마찬가지로 apktool을 이용해서 디컴파일 해줍니다. 

./apktool d InsecureBankv2.apk

 

/InsecureBankv2/AndroidManifest.xml을 열어 보면,

<?xml version="1.0" encoding="utf-8" standalone="no"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.insecurebankv2" platformBuildVersionCode="22" platformBuildVersionName="5.1.1-1819727">
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.SEND_SMS"/>
	...중략
        <activity android:label="@string/title_activity_file_pref" android:name="com.android.insecurebankv2.FilePrefActivity" android:windowSoftInputMode="adjustNothing|stateVisible"/>
        <activity android:label="@string/title_activity_do_login" android:name="com.android.insecurebankv2.DoLogin"/>
        <activity android:exported="true" android:label="@string/title_activity_post_login" android:name="com.android.insecurebankv2.PostLogin"/>
        <activity android:label="@string/title_activity_wrong_login" android:name="com.android.insecurebankv2.WrongLogin"/>
        <activity android:exported="true" android:label="@string/title_activity_do_transfer" android:name="com.android.insecurebankv2.DoTransfer"/>
        <activity android:exported="true" android:label="@string/title_activity_view_statement" android:name="com.android.insecurebankv2.ViewStatement"/>
        <provider android:authorities="com.android.insecurebankv2.TrackUserContentProvider" android:exported="true" android:name="com.android.insecurebankv2.TrackUserContentProvider"/>
        <receiver android:exported="true" android:name="com.android.insecurebankv2.MyBroadCastReceiver">
            <intent-filter>
                <action android:name="theBroadcast"/>
            </intent-filter>
        </receiver>
        <activity android:exported="true" android:label="@string/title_activity_change_password" android:name="com.android.insecurebankv2.ChangePassword"/>
        <activity android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode" android:name="com.google.android.gms.ads.AdActivity" android:theme="@android:style/Theme.Translucent"/>
        <activity android:name="com.google.android.gms.ads.purchase.InAppPurchaseActivity" android:theme="@style/Theme.IAPTheme"/>
        <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version"/>
        <meta-data android:name="com.google.android.gms.wallet.api.enabled" android:value="true"/>
        <receiver android:exported="false" android:name="com.google.android.gms.wallet.EnableWalletOptimizationReceiver">
            <intent-filter>
                <action android:name="com.google.android.gms.wallet.ENABLE_WALLET_OPTIMIZATION"/>
            </intent-filter>
        </receiver>
    </application>
</manifest>

 

activity의 정보들이 보이게 됩니다. 

 

그중 로그인 완료 화면인 PostLogin으로 임의 접근 해보겠습니다.

 

adb shell로 shell에 접속합니다.

am start -n com.android.insecurebankv2/.PostLogin

am(Activity Manager)명령어는 http://www.dreamy.pe.kr/zbxe/CodeClip/163972에 설명이 잘 되어있습니다.

 

PostLogin 액티비티가 실행되었음

앱이 실행중이 아니더라도 am을 통해서 원하는 액티비티를 띄울 수 있습니다.

 

<activity android:exported="true"
android:label="@string/title_activity_post_login" 
android:name="com.android.insecurebankv2.PostLogin"/>

이 부분에 exported 가 true이면, 앱 밖에서도 액티비티의 호출이 가능합니다.

 

따라서 exported="false"로 변경한 뒤, 리컴파일 , sign까지 한 어플을 설치한다면, 

 

./apktool b InsecureBankv2
java -jar sign.jar InsecureBankv2.apk
generic_x86_arm:/ $ am start -n com.android.insecurebankv2/.PostLogin                     
Starting: Intent { cmp=com.android.insecurebankv2/.PostLogin }
Security exception: Permission Denial: starting Intent { flg=0x10000000 cmp=com.android.insecurebankv2/.PostLogin } from null (pid=11059, uid=2000) not exported from uid 10093

java.lang.SecurityException: Permission Denial: starting Intent { flg=0x10000000 cmp=com.android.insecurebankv2/.PostLogin } from null (pid=11059, uid=2000) not exported from uid 10093
at com.android.server.am.ActivityStackSupervisor.checkStartAnyActivityPermission(ActivityStackSupervisor.java:1788)
at com.android.server.am.ActivityStarter.startActivity(ActivityStarter.java:717)
at com.android.server.am.ActivityStarter.startActivity(ActivityStarter.java:544)
at com.android.server.am.ActivityStarter.startActivityMayWait(ActivityStarter.java:1099)
at com.android.server.am.ActivityStarter.execute(ActivityStarter.java:486)
at com.android.server.am.ActivityManagerService.startActivityAsUser(ActivityManagerService.java:5120)
at com.android.server.am.ActivityManagerService.startActivityAsUser(ActivityManagerService.java:5094)
at com.android.server.am.ActivityManagerShellCommand.runStartActivity(ActivityManagerShellCommand.java:479)
at com.android.server.am.ActivityManagerShellCommand.onCommand(ActivityManagerShellCommand.java:161)
at android.os.ShellCommand.exec(ShellCommand.java:103)
at com.android.server.am.ActivityManagerService.onShellCommand(ActivityManagerService.java:16012)
at android.os.Binder.shellCommand(Binder.java:634)
at android.os.Binder.onTransact(Binder.java:532)
at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:3592)
at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:3291)
at android.os.Binder.execTransact(Binder.java:731)

위와같이 에러가 발생하며 액티비티의 실행이 안되는 것을 확인할 수 있었습니다.

Posted by ICEB34R

처음으로 해볼 것은 앱 패치입니다.

 

필요한 것은 apktool과 SignApk입니다. 

인터넷에 apktool과 SignApk설치 관련 자료가 많으니 생략하겠습니다.

./apktool d InsecureBankv2.apk

 

apktool경로에 InsecureBankv2.apk를 옮긴 뒤, 명령을 이용해서 앱을 디컴파일 해줍니다. 

 

그 뒤 /InsecureBankv2/res/values/strings.xml파일을 열어줍니다.

 

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="abc_action_bar_home_description">Navigate home</string>
    <string name="abc_action_bar_up_description">Navigate up</string>
    <string name="abc_action_menu_overflow_description">More options</string>
    <string name="abc_action_mode_done">Done</string>
    <string name="abc_activity_chooser_view_see_all">See all</string>
	... 중략
    <string name="hello_world">Hello world!</string>
    <string name="is_admin">no</string>
    <string name="loginscreen_password">Password:</string>
    <string name="loginscreen_username">Username:</string>
    <string name="pref_submit">Submit:</string>
    <string name="server_ip">Server IP:</string>
    <string name="server_port">Server Port:</string>

</resources>

strings.xml은 앱 내부에 들어가는 문자열들이 모여있는 파일입니다.

 

파일 내용을 잘 살펴보면, is_admin이라는 요소가 보입니다. no라고 지정이 되어있는데, 이 값을 통해서 어드민인지 아닌지 앱에서 판단하는 것 같아 보입니다.

 

실제로 저 부분이 어디서 쓰이는지 확인해보겠습니다.

 

우선 https://github.com/Konloch/bytecode-viewer/releases링크를 통해 Bytecode-Viewer.jar를 다운로드 받습니다.

 

가장위의 jar파일을 받아줍니다.

jar파일을 더블클릭 혹은 java -jar Bytecode-Viewer-[버전명].jar로 실행시켜줍니다. 

 

bytecode viewer실행화면

왼쪽 상단에 InsecureBankv2.apk를 드래그 드롭해줍니다. (파일이 로드되는데 시간이 조금 걸립니다.)

 

com.android.insecurebankv2.R$string.class를 클릭하여 열어줍니다.

 

방금 우리가 확인했던 is_admin이 보입니다. is_admin의 값은 2131165258이고, 앱 내부에서 is_admin을 사용할 때 2131165258를 이용하여 참조합니다. 따라서 왼쪽 하단의 search로 2131165258를 검색해봅시다.

 

2131165258검색 결과

검색결과가 하나가 나오는데 위치는 LoginActivity이고, onCreate메소드에서 참조됩니다. 

 

if문을 보게되면, is_admin에 해당하는 2131165258번 리소스를 가져와서, "no"인지 검사합니다. 

 

만약 "no"라면, 2131558510리소스에 setVisibility(8)을 해주는것을 알 수 있습니다.

 

0 is for VISIBLE
4 is for INVISIBLE 
8 is for GONE

이때 8은 GONE이므로  2131558510리소스를 없애버린다고 이해할 수 있습니다. 

 

 

com.android.insecurebankv2.R$id.class파일을 열어서 검색을 해보면 button_CreateUser라는 것을 알 수 있습니다. 

 

CreateUser버튼이 없는 모습

그렇다면 아마 is_admin의 값이 "no"만 아니면, CreateUser버튼이 활성화 된다 라고 생각해 볼 수 있겠습니다.

 

no 였던 값을 ICEBEAR로 변경

다시 apktool폴더로 돌아와서 /InsecureBankv2/res/values/strings.xml의 is_admin값을 임의로 바꿔 봅시다.

 

저장한 뒤 re compile 해줍니다.

 

./apktool b InsecureBankv2

 

작업이 완료되면, InsecureBank/dist/ 에 apk파일이 생성되는데, adb install을 통해 이 파일을 설치하려고 하면 에러가 발생합니다.

Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES: Failed to collect certificates from /data/app/vmdl883768825.tmp/base.apk: Attempt to get length of null array]

 

그 이유는 리컴파일 한 뒤, signing을 해주지 않았기 때문입니다. 

 

https://github.com/appium/sign

 

appium/sign

Sign.jar automatically signs an apk with the Android test certificate. - appium/sign

github.com

여기서 sign.jar를 받아준 뒤, 

java -jar sign.jar InsecureBankv2.apk

를 해주시면, sign이 완료되고 InsecureBankv2.s.apk가 생성됩니다.

 

sign과정에서 BASE64 관련 오류가 난다면 Java 8이하의 버전으로 실행하셔야합니다.

 

adb install InsecureBankv2.s.apk

adb를 통해 앱을 설치를 하게되면 정상적으로 설치가 됩니다.

 

Create User버튼이 활성화된 모습

예상대로 Create User버튼이 활성화 되었습니다. 

Posted by ICEB34R