상세 컨텐츠

본문 제목

암호 복호화

CTF

by ser-ser 2025. 4. 20. 14:38

본문

UnCrackable-Level1에서 비밀 문자열 찾기

 

이전 포스팅에서 UnCrackable-Level1.apk의 루팅 탐지를 우회하여 앱 종료를 방지했습니다.  이번 포스팅에서는 앱의 "Enter the Secret String"입력란에 필요한 비밀 문자열을 찾아 입력하여 문제를 완전히 해결하는 과정을 알아보겠습니다.


문제 설명

문제: 앱 실행 후 "Enter the Secret String"입력란이 나타나며, 올바른 비밀 문자열을 입력해야 합니다.

비밀문자열을 찾아 입력란에 입력하여 "Success!" 메시지를 확인해야 합니다.


소스코드 분석

비밀 문자열 검증 로직을 찾기 위해 UnCrackable-Level1.apk를 jadx로 열어 분석하겠습니다.

  • 검증 메시지 검색


  • 입력란에 임의의 값을 입력했을 때 출력되는 "Nope... That's not it Try again"메시지를 검색하여 관련 코드를 찾습니다.

소스코드 확인 (verify)

  • 검색 기능에 "Nope..."입력합니다. 그리고  MainActivity 클래스의 verify 메서드에서 해당 메시지를 확인합니다.
    public void verify(View view) {
        String str;
        String obj = ((EditText) findViewById(R.id.edit_text)).getText().toString();
        AlertDialog create = new AlertDialog.Builder(this).create();
        if (a.a(obj)) {
            create.setTitle("Success!");
            str = "This is the correct secret.";
        } else {
            create.setTitle("Nope...");
            str = "That's not it. Try again.";
        }
    }

    • findViewById(R.id.edit_text)
      • 입력란(EditText)에서 사용자가 입력한 문자열(obj)을 가져옵니다.
    • a.a(obj)
      • 입력 문자열을 검증하여 true 또는 false를 반환합니다.
    • 검증결과
      • true: "Success!"와 "This is the correct secret." 메시지를 출력합니다.
      • false: "Nope..."와 "That's not it. Try again."메시지를 출력합니다.
    • 목표
      • a.a(obj)가 true를 반환하도록 올바른 비밀 문자열을 찾아야합니다.


소스코드 확인 (a.a)

  • a.a 메서드를 클릭하여 sg.vantagepoint.uncrackable1.a 클래스의 코드를 확인합니다.
    public class a {
        public static boolean a(String str) {
            byte[] bArr;
            byte[] bArr2 = new byte[0];
            try {
                bArr = sg.vantagepoint.a.a.a(b("8d127684cbc37c17616d806cf50473cc"), Base64.decode("5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=", 0));
            } catch (Exception e) {
                Log.d("CodeCheck", "AES error:" + e.getMessage());
                bArr = bArr2;
            }
            return str.equals(new String(bArr));
        }
    
        public static byte[] b(String str) {
            int length = str.length();
            byte[] bArr = new byte[length / 2];
            for (int i = 0; i < length; i += 2) {
                bArr[i / 2] = (byte) ((Character.digit(str.charAt(i), 16) << 4) + Character.digit(str.charAt(i + 1), 16));
            }
            return bArr;
        }
    }

    • b("8d127684cbc37c17616d806cf50473cc")
      • 16진수 무자열을 바이트 배열로 변환하여 AES 키를 생성합니다.
    • Base64.decode ("5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=", 0)
      • Base64로 인코딩된 암호화 데이터를 바이트 배열로 디코딩합니다.
    • sg.vantagepoint.a.a.a
      • AES 복호화를 수행하여 복호화된 바이트 배열(bArr)을 반환합니다.
    • str.equals(new String(bArr))
      • 입력 문자열(str)이 복호화된 문자열과 일치하면 true를 반환합니다.
    • 목표
      • AES 복호화를 통해 bArr의 문자열 값을 찾아 입력란에 입력합니다.

복호화 로직 분석

  • a.a 메서드는 AES 암호화 방식으로 복호화된 문자열과 입력값을 비교합니다. 주어진 키와 암호화 데이터를 복호화하여 비밀 문자열을 찾아야 합니다.
    public class a {
        public static byte[] a(byte[] bArr, byte[] bArr2) {
            SecretKeySpec secretKeySpec = new SecretKeySpec(bArr, "AES/ECB/PKCS7Padding");
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(2, secretKeySpec);
            return cipher.doFinal(bArr2);
        }
    }

    • AES 키
      • "8d127684cbc37c17616d806cf50473cc" (16진수 문자열)
    • 암호화 데이터
      • "5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=" (Base64 인코딩)
    • 복호화 방식
      • sg.vantagepoint.a.a.a는 AES/ECB/PKCS7Padding 모드로 복호화를 수행합니다.
    • 목표
      • 키와 데이터를 사용하여 복호화된 문자열을 얻습니다.

복호화 과정

파이썬과 pycryptodome 라이브러리를 사용하여 AES 복호화를 수행하겠습니다. 단계별 과정은 다음과 같습니다.

 

1. 도구 가져오기

  • 먼저, 암호를 풀기 위해 필요한 도구를 가져옵니다. 이 도구들은 암호문을 풀고 데이터를 처리하는 데 필요합니다.
    from Crypto.Cipher import AES
    from base64 import b64decode

    • AES는 암호를 푸는 도구입니다.
    • b64decode는 암호문의 특수 포장을 푸는 도구입니다.
    • 이 도구들을 코드 맨 위에 적어 준비합니다.

2. 암호문과 키 넣기

  • 암호문과 키를 코드에 넣어 준비합니다.
    from Crypto.Cipher import AES
    from base64 import b64decode
    
    encrypted = "5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc="
    key_hex = "8d127684cbc37c17616d806cf50473cc"

    • encrypted는 암호화된 텍스트입니다.
    • key_hex는 암호를 풀기 위한 키입니다.
    • 이 데이터를 코드에 적어 준비합니다.
    • 실행하면 암호문: 5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=가 출력됩니다.

 


3. 암호문 포장 풀기

  • 암호문은 특수 포장(Base64)으로 되어 있어 컴퓨터가 이해할 수 있는 숫자(바이트)로 바꿉니다.
    from Crypto.Cipher import AES
    from base64 import b64decode
    
    encrypted = "5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc="
    key_hex = "8d127684cbc37c17616d806cf50473cc"
    
    encrypted_bytes = b64decode(encrypted)
    print("포장 푼 암호문:", encrypted_bytes.hex())

    • b64decod(encryted)는 암호문의 포장을 풀어 숫자 형태로 만듭니다.
    • encrypted_bytes.hex()는 숫자를 보기 쉽게 16진수로 보여줍니다.
    • 출력 예시: e54262715dcb5b9a06c3a0b5e6a43d779a49e8e074f82eff1d96ab7c17147618e7
    • 암호문이 숫자로 바뀌었습니다.

4. 키 준비하기

  • 키는 숫자와 문자로 된 코드(16진수)라서 컴퓨터가 이해할 수 있는 숫자(바이트)로 바꿉니다.
    from Crypto.Cipher import AES
    from base64 import b64decode
    
    encrypted = "5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc="
    key_hex = "8d127684cbc37c17616d806cf50473cc"
    
    encrypted_bytes = b64decode(encrypted)
    key_bytes = bytes.fromhex(key_hex)
    
    print("포장 푼 암호문:", encrypted_bytes.hex())
    print("준비된 키:", key_bytes.hex())

    • bytes.fromhex(key_hex)는 키를 숫자 형태로 바꿉니다.
    • key_bytes.hex()는 키를 보기 쉽게 16진수로 보여줍니다.
    • 암호문과 키가 모두 준비되었습니다.

5. 암호풀기

  • 암호문과 키를 사용해 암호를 풉니다. AES 도구로 암호를 푸는 기계를 만들고 실행합니다.
    from Crypto.Cipher import AES
    from base64 import b64decode
    
    encrypted = "5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc="
    key_hex = "8d127684cbc37c17616d806cf50473cc"
    
    encrypted_bytes = b64decode(encrypted)
    key_bytes = bytes.fromhex(key_hex)
    
    cipher = AES.new(key_bytes, AES.MODE_ECB)
    decrypted_bytes = cipher.decrypt(encrypted_bytes)
    
    print("푼 암호:", decrypted_bytes.hex())

    • AES.new(key_bytes, AES.MODE_ECB)는 암호 푸는 기계를 만듭니다.
    • cipher.decrypt(encryted_bytes)는 암호문과 키를 넣어 암호를 풉니다.
    • decryted_bytes.hex()는 푼 결과를 16진수로 보여줍니다.
    • 암호가 풀렸지만 메시지 끝에 불필요한 부분(패딩)이 붙어 있습니다.

6. 불필요한 부분 제거

  • 암호를 풀면 메시지 끝에 불필요한 숫자(패딩)가 붙어 있어 이를 제거합니다.
    from Crypto.Cipher import AES
    from base64 import b64decode
    
    encrypted = "5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc="
    key_hex = "8d127684cbc37c17616d806cf50473cc"
    
    encrypted_bytes = b64decode(encrypted)
    key_bytes = bytes.fromhex(key_hex)
    
    cipher = AES.new(key_bytes, AES.MODE_ECB)
    decrypted_bytes = cipher.decrypt(encrypted_bytes)
    
    padding_len = decrypted_bytes[-1]
    plain_bytes = decrypted_bytes[:-padding_len]
    
    print("불필요한 부분 제거 후:", plain_bytes.hex())

    • padding_len = decryted_btyes[-1]은 불필요한 부분의 크기를 알려줍니다.
    • plain_bytes = decryted_bytes[:-padding_len]은 그 크기만큼 뒤에서 잘라냅니다.
    • plain_bytes.hex()는 깨끗한 메시지를 16진수로 보여줍니다.
    • 메시지의 숫자 형태가 깨끗해졌습니다.

7. 정답 읽기

  • 숫자 형태의 메시지를 사람이 읽을 수 있는 글자로 바꿉니다.
    from Crypto.Cipher import AES
    from base64 import b64decode
    
    encrypted = "5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc="
    key_hex = "8d127684cbc37c17616d806cf50473cc"
    
    encrypted_bytes = b64decode(encrypted)
    key_bytes = bytes.fromhex(key_hex)
    
    cipher = AES.new(key_bytes, AES.MODE_ECB)
    decrypted_bytes = cipher.decrypt(encrypted_bytes)
    
    padding_len = decrypted_bytes[-1]
    plain_bytes = decrypted_bytes[:-padding_len]
    
    plain_text = plain_bytes.decode('utf-8')
    print("복호화된 평문:", plain_text)

    • decode('utf-8')는 숫자를 글자로 바꿉니다.
    • print는 최종 메시지를 보여줍니다.

정답 입력 및 확인

  • 앱의 "Enter the Secret String" 입력란에 "I want to believe"를 입력합니다.
  • 입력이 올바르면 "Success!" 제목과 "This is the correct secret." 메시지가 표시됩니다.

문제 해결 과정에서의 주의점

  • AES 복호화 설정
    • AES 모드는 ECB이며, 패딩은 PKCS7 방식입니다. 파이썬 코드에서 AES.MODE_ECB와 패딩 제거를 정확히 처리해야 합니다.
  • ase64와 16진수 처리
    • 암호문은 Base64로, 키는 16진수로 제공되므로 b64decode와 bytes.fromhex로 올바르게 변환해야 합니다.
  • 패딩 제거
    • PKCS7 패딩은 마지막 바이트를 참조하여 제거하므로, 올바른 패딩 길이를 계산해야 합니다.

이번 포스팅에서는 UnCrackable-Level1.apk의 비밀 문자열을 AES 복호화를 통해 찾아 입력하는 방법을 배웠습니다. jadx-gui로 검증 로직을 분석하고, 파이썬과 pycrytodome 라이브러리를 사용하여 AES/ECB/PKCS7Padding 복호화를 수행하여 비밀 문자열 "I want to believe"를 도출했습니다. 이 과정을 통해 앱의 암호화된 데이터를 분석하고 문제를 해결하는 방법을 익혔습니다.

'CTF' 카테고리의 다른 글

브루트포스 활용  (0) 2025.04.23
로그인 우회  (0) 2025.04.22
루팅 탐지 우회  (0) 2025.04.19
FridaLab_08  (0) 2025.04.18
FridaLab_07  (1) 2025.04.17

관련글 더보기