본문 바로가기
안드로이드 해킹/InsecureBankv2

[인시큐어뱅크] 하드코딩된 중요 정보 확인 실습

by jwcs 2024. 12. 31.
728x90

앱 분석을 하기 위해 실제 소스 코드가 있는 경우와 apk 파일만 있는 경우가 있다. 기업에서 직접 소스 코드를 주지 않는 이상 소스 코드를 보면서 분석을 진행하는 경우는 드물기 때문에 apk 파일만 주어진 경우로 가정하여 분석 진행하겠다.

 

분석 도구는 JADX-gui를 사용한다.

jadx-gui

그럼 이런 화면을 볼 수 있다. 여기서 분석을 진행하면 된다.

ctrl + shift + f

위 단축키를 이용하면 검색을 할 수 있다. 이걸로 주요 키워드를 검색해가면서 하드코딩된 정보를 찾아보자.

[계정 정보 하드코딩]

검색창

앞서 설명한 검색 방법으로 `username`을 검색하게 되면 `devadmin`이라는 username이 하드코딩되어 사용되는 것을 확인할 수 있다. 더블클릭하여 자세한 내용을 확인해보자.

 

        public void postData(String valueIWantToSend) throws ClientProtocolException, IOException, JSONException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
            HttpResponse responseBody;
            HttpClient httpclient = new DefaultHttpClient();
            HttpPost httppost = new HttpPost(DoLogin.this.protocol + DoLogin.this.serverip + ":" + DoLogin.this.serverport + "/login");
            HttpPost httppost2 = new HttpPost(DoLogin.this.protocol + DoLogin.this.serverip + ":" + DoLogin.this.serverport + "/devlogin");
            List<NameValuePair> nameValuePairs = new ArrayList<>(2);
            nameValuePairs.add(new BasicNameValuePair("username", DoLogin.this.username));
            nameValuePairs.add(new BasicNameValuePair("password", DoLogin.this.password));
            if (DoLogin.this.username.equals("devadmin")) {
                httppost2.setEntity(new UrlEncodedFormEntity(nameValuePairs));
                responseBody = httpclient.execute(httppost2);
            } else {
                httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
                responseBody = httpclient.execute(httppost);
            }
            InputStream in = responseBody.getEntity().getContent();
            DoLogin.this.result = convertStreamToString(in);
            DoLogin.this.result = DoLogin.this.result.replace("\n", "");
            if (DoLogin.this.result != null) {
                if (DoLogin.this.result.indexOf("Correct Credentials") != -1) {
                    Log.d("Successful Login:", ", account=" + DoLogin.this.username + ":" + DoLogin.this.password);
                    saveCreds(DoLogin.this.username, DoLogin.this.password);
                    trackUserLogins();
                    Intent pL = new Intent(DoLogin.this.getApplicationContext(), (Class<?>) PostLogin.class);
                    pL.putExtra("uname", DoLogin.this.username);
                    DoLogin.this.startActivity(pL);
                    return;
                }
                Intent xi = new Intent(DoLogin.this.getApplicationContext(), (Class<?>) WrongLogin.class);
                DoLogin.this.startActivity(xi);
            }
        }

해당 내용이 사용되는 전체 코드다. 일반 로그인(/login)과 개발자 로그인(/devlogin)으로 나누어서 처리하고 있는 것을 확인할 수 있다.

 

nox

녹스에서 devadmin과 아무런 비밀번호를 입력해서 로그인해보면

 

로그인 성공

다음과 같이 로그인에 성공하는 것을 확인할 수 있다.

 

[키 값 하드코딩]

검색창

아까와 같은 검색창을 열어서 key로 검색하면 아주 많은 검색량을 확인할 수 있다. JADX-gui가 한 번에 모든 결과를 보여주지는 않기때문에 왼쪽 하단에 `모두 로드`를 눌러줘야한다.

그럼 위와 같은 하드코딩된 키값을 확인할 수 있다. 이런 키 값으로 복호화가 일어날 수 있기 때문에 이런 키 하드코딩은 지양해야한다.

 

추가적으로 이 키가 어떻게 쓰이는지 좀 더 확인좀 해봤다.

 

package com.android.insecurebankv2;

import android.util.Base64;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.AlgorithmParameterSpec;
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;

/* loaded from: classes.dex */
public class CryptoClass {
    String base64Text;
    byte[] cipherData;
    String cipherText;
    String plainText;
    String key = "This is the super secret key 123";
    byte[] ivBytes = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

    public static byte[] aes256encrypt(byte[] ivBytes, byte[] keyBytes, byte[] textBytes) throws UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
        AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        SecretKeySpec newKey = new SecretKeySpec(keyBytes, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(1, newKey, ivSpec);
        return cipher.doFinal(textBytes);
    }

    public static byte[] aes256decrypt(byte[] ivBytes, byte[] keyBytes, byte[] textBytes) throws UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
        AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        SecretKeySpec newKey = new SecretKeySpec(keyBytes, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(2, newKey, ivSpec);
        return cipher.doFinal(textBytes);
    }

    public String aesDeccryptedString(String theString) throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
        byte[] keyBytes = this.key.getBytes("UTF-8");
        this.cipherData = aes256decrypt(this.ivBytes, keyBytes, Base64.decode(theString.getBytes("UTF-8"), 0));
        this.plainText = new String(this.cipherData, "UTF-8");
        return this.plainText;
    }

    public String aesEncryptedString(String theString) throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
        byte[] keyBytes = this.key.getBytes("UTF-8");
        this.plainText = theString;
        this.cipherData = aes256encrypt(this.ivBytes, keyBytes, this.plainText.getBytes("UTF-8"));
        this.cipherText = Base64.encodeToString(this.cipherData, 0);
        return this.cipherText;
    }
}

접근해보면 이런 전체 키를 확인해볼 수 있다.

 

사용 검색

지금 보고있는 변수 혹은 함수가 어디서 사용되는지 보고싶으면 `x`키를 눌러 확인할 수 있다. `aesDeccryptedString`는 앞서 하드코딩된 key의 사용을 추적하면서 확인했다.

 

    protected void fillData() throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
        SharedPreferences settings = getSharedPreferences("mySharedPreferences", 0);
        String username = settings.getString("EncryptedUsername", null);
        String password = settings.getString("superSecurePassword", null);
        if (username != null && password != null) {
            byte[] usernameBase64Byte = Base64.decode(username, 0);
            try {
                this.usernameBase64ByteString = new String(usernameBase64Byte, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            this.Username_Text = (EditText) findViewById(C0230R.id.loginscreen_username);
            this.Password_Text = (EditText) findViewById(C0230R.id.loginscreen_password);
            this.Username_Text.setText(this.usernameBase64ByteString);
            CryptoClass crypt = new CryptoClass();
            String decryptedPassword = crypt.aesDeccryptedString(password);
            this.Password_Text.setText(decryptedPassword);
            return;
        }
        if (username == null || password == null) {
            Toast.makeText(this, "No stored credentials found!!", 1).show();
        } else {
            Toast.makeText(this, "No stored credentials found!!", 1).show();
        }
    }

여기서 사용을 확인해보면 `mySharedPreferences`에서 base64로 인코딩된 username과 암호화된 비밀번호를 가져오고 있다. 암호화된 비밀번호를 앞서 살펴본 `aesDeccryptedString`으로 복호화하고 있다. key값은 로그인에 사용되는 것을 알 수 있었다.

 

주의해야할 점이 apk 파일에서 이렇게 복호화하고 있다면 공격자는 smali 코드 변조로 복호화된 값을 확인할 수 있다.

 

[대응 방안]

1. 개발자 계정 같은 경우는 배포 시, 반드시 제거하여 배포한다.

2. 키 정보같은 경우는 keystore에 저장한다.

728x90
반응형