상세 컨텐츠

본문 제목

[Android Reversing] Weak Cryptography implementation (취약한 암호화 취약점)

REVERSING/Android

by koharin 2021. 7. 6. 22:37

본문

728x90
반응형

AES-CBC 알고리즘


  • 평문의 블록을 이전 암호문과 xor 연산으로 암호화를 진행한다.
  • 초기에는 이전 블록이 없으므로 초기화 벡터(IV, Initial Vector)와 xor 연산을 한다.
  • 암호문이 블록의 배수가 되므로, 복호화하여 평문을 얻기 위해서는 padding을 해야 한다.

 

 

Vulnerability


$ dex2jar-2.0\\d2j-dex2jar.bat Android-InsecureBankv2\\InsecureBankv2.apk

 

dex2jar로 디컴파일하여 얻은 jar 파일을 jd-gui 툴로 열면 다음과 같이 java 소스코드들을 얻을 수 있다.

암호화, 복호화 로직은 CryptoClass.class 코드에서 확인할 수 있다.

 

  • AES-256-CBC 암호화 알고리즘에서 사용하는 초기화 벡터(IV)가 모두 0의 값을 가지는데, 초기에는 이전 블록이 없으므로 IV로 초기 블록과 xor 연산을 하여 암호화를 한다. InsecureBankv2에서와 같이 모두 0의 값을 가지는 IV는 예측이 쉬워 암호문을 유추할 가능성이 높아진다.
  • 상수로 하드코딩된 key는 디컴파일 시 평문의 key를 쉽게 얻을 수 있다.
  • salt 값이 설정되어 있지 않아 공격자는 레인보우 테이블을 통해 brute force 공격으로 복호화한 비밀번호를 얻을 수도 있다.
  • 코드 난독화가 되어있지 않아, 암호화된 정보를 어떤 복호화 로직으로 평문을 얻을 수 있는지 알 수 있다.

 

 

Exploit


CryptoClass 코드에 있는 복호화 코드를 사용해서, superSecretPassword를 복호화해보자.

superSecretPassword의 암호화된 값은 이전에 WebView 취약점에서 얻었다.

superSecurePassword를 디코딩하는 앱을 작성해보자.

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_weight="1">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:textSize="20dp"
            android:text="AES cipherText:"/>
        <TextView
            android:id="@+id/cipherText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20dp"
            android:layout_weight="1"/>
    </LinearLayout>
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_weight="1">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:textSize="20dp"
            android:text="PlainText:"/>
        <TextView
            android:id="@+id/plainText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20dp"
            android:layout_weight="1"/>
    </LinearLayout>
    <Button
        android:id="@+id/decodeButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Decode cipherText"
        android:layout_gravity="center"/>

</LinearLayout>

 

MainActivity.java

package com.cookandroid.insecurebankv2_weakcrypto;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Base64;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import org.w3c.dom.Text;

import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class MainActivity extends AppCompatActivity {

    TextView cipherText, plainText;
    Button decodeBtn;
    byte[] cipherData;
    String plainPassword;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setTitle("Decode superSecretPassword");

        final byte[]  ivBytes = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
        final String key = "This is the super secret key 123";
        final String superSecretPassword = "DTrW2VXjSoFdg0e61fHxJg==";

        cipherText = (TextView)findViewById(R.id.cipherText);
        plainText = (TextView)findViewById(R.id.plainText);
        decodeBtn = (Button)findViewById(R.id.decodeButton);

        decodeBtn.setOnClickListener(new View.OnClickListener(){
           public void onClick(View view){
                try{
                    byte[] arrayOfByte = key.getBytes("UTF-8"); // unicode 문자열을 bytecode로 인코딩
                    cipherData = aes256decrypt(ivBytes, arrayOfByte, Base64.decode(superSecretPassword.getBytes("UTF-8"), Base64.DEFAULT));
                    plainPassword = new String(cipherData, "UTF-8");
                }catch(UnsupportedEncodingException e){
                    e.printStackTrace();
                }catch(InvalidKeyException e){
                    e.printStackTrace();
                }catch(NoSuchAlgorithmException e) {
                    e.printStackTrace();
                }catch(NoSuchPaddingException e){
                    e.printStackTrace();
                }catch(InvalidAlgorithmParameterException e){
                    e.printStackTrace();
                }catch(IllegalBlockSizeException e){
                    e.printStackTrace();
                }catch(BadPaddingException e){
                    e.printStackTrace();
                }finally {
                    cipherText.setText(superSecretPassword);
                    plainText.setText(plainPassword);
                }
           }
        });
    }
    public static byte[] aes256decrypt(byte[] paramArrayOfbyte1, byte[] paramArrayOfbyte2, byte[] paramArrayOfbyte3) throws UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
        IvParameterSpec ivParameterSpec = new IvParameterSpec(paramArrayOfbyte1);
        SecretKeySpec secretKeySpec = new SecretKeySpec(paramArrayOfbyte2, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(2, secretKeySpec, ivParameterSpec);
        return cipher.doFinal(paramArrayOfbyte3);
    }
}

 

Decode 앱 실행

 

 

 

대응 방안


  1. key 값 생성 시 해쉬 함수를 사용하여 디컴파일 해도 key 값이 노출되지 않도록 하거나, 외부 디렉터리에서 key 값을 불러온다.
  2. 초기화 벡터(IV)는 예측할 수 없는 값을 사용하고, 암호화마다 다른 값을 사용한다.
  3. 해쉬 함수가 역함수가 존재하지 않아 복호화가 불가능해도 공격자가 레인보우 테이블을 통해 brute force attack으로 암호문을 얻기 어렵도록 비밀번호 해싱 전 임의의 salt 값을 설정한다.

 

 

Reference


dineshshetty/Android-InsecureBankv2

Sana Yum : 인생의 3막을 채색하라. : 네이버 블로그

728x90
반응형

관련글 더보기