클라이언트

[iOS] iOS 앱 리버싱 간단한 예제

애기공룡훈련병 2025. 2. 16. 07:28
반응형

리버싱(리버스 엔지니어링, Reverse Engineering)은 소프트웨어의 내부 동작을 분석하고 이해하는 과정입니다. iOS 앱 리버싱을 통해 보안 취약점을 탐색하거나 앱의 동작을 변경할 수 있습니다. 본 글에서는 간단한 iOS 예제 앱을 만든 후 이를 리버싱하고, 일부 기능을 조작하는 방법을 다룹니다.

주의: 타인의 앱을 무단으로 리버싱하거나 조작하는 것은 불법이며 윤리적으로도 문제가 될 수 있습니다.

1. 환경 구축

리버싱을 위해 다음과 같은 도구가 필요합니다:

  • macOS (iOS 개발 및 리버싱 환경 필수)
  • Xcode (Swift 기반 예제 앱 개발)
  • lldb (디버깅 및 런타임 메모리 조작)
  • Hopper Disassembler (바이너리 분석)
  • Frida (런타임 코드 주입 및 분석)
  • class-dump (클래스 인터페이스 추출)
  • otool (Mach-O 파일 분석)
  • jtool2 (추가적인 Mach-O 분석 도구)

설치는 brew를 이용하여 간편하게 진행할 수 있습니다

brew install frida class-dump

2. 간단한 iOS 예제 앱 만들기

간단한 로그인 화면을 가진 iOS 앱을 생성합니다. 이 앱은 사용자가 특정한 비밀번호를 입력하면 성공 메시지를 띄우는 구조입니다.

우리는 리버싱을 통해 이 비밀번호를 알아내는 것이 목표입니다.

UIKit 기반 Xcode 프로젝트 생성

  1. Xcode를 실행하고 "New Project" → "App" 선택
  2. 프로젝트 이름: ReversingExample
  3. Interface: Storyboard, Language: Swift
  4. "Next"를 눌러 프로젝트 생성

UIKit 기반 코드 작성

import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var passwordTextField: UITextField!
    @IBOutlet weak var resultLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction func loginButtonTapped(_ sender: UIButton) {
        let password = passwordTextField.text ?? ""
        if checkPassword(password) {
            resultLabel.text = "로그인 성공!"
        } else {
            resultLabel.text = "비밀번호가 틀렸습니다."
        }
    }
    
    private func checkPassword(_ password: String) -> Bool {
        return password == "Secret1234"
    }
}

SwiftUI 기반 Xcode 프로젝트 생성

  1. Xcode에서 "New Project" → "App" 선택
  2. 프로젝트 이름: ReversingExampleSwiftUI
  3. Interface: SwiftUI, Language: Swift
  4. "Next"를 눌러 프로젝트 생성

SwiftUI 기반 코드 작성

import SwiftUI

struct ContentView: View {
    @State private var password: String = ""
    @State private var resultMessage: String = ""
    
    var body: some View {
        VStack {
            SecureField("비밀번호 입력", text: $password)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding()
            
            Button("로그인") {
                if checkPassword(password) {
                    resultMessage = "로그인 성공!"
                } else {
                    resultMessage = "비밀번호가 틀렸습니다."
                }
            }
            .padding()
            
            Text(resultMessage)
                .padding()
        }
        .padding()
    }
    
    private func checkPassword(_ password: String) -> Bool {
        return password == "Secret1234"
    }
}

@main
struct ReversingExampleSwiftUIApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

3. iOS 앱 리버싱

바이너리 덤프 (class-dump 이용)

앱을 디바이스 또는 시뮬레이터에서 실행한 후, class-dump를 사용하여 앱의 메타데이터를 추출합니다.

class-dump -H ReversingExample -o dumped_headers

이를 통해 checkPassword: 메서드를 찾을 수 있습니다.

디버깅 및 메모리 패치 (lldb 이용)

앱을 실행한 후, lldb를 이용하여 런타임에서 비밀번호 검증 로직을 우회할 수 있습니다.

(lldb) breakpoint set -n checkPassword:
(lldb) run
(lldb) expr (bool)[self checkPassword:@"WrongPass"] = YES

이렇게 하면 어떤 비밀번호를 입력해도 성공할 수 있습니다.

코드 인젝션 (Frida 이용)

Frida를 이용하면 코드 주입을 통해 비밀번호 검사를 우회할 수 있습니다.

frida -U -n ReversingExample -e '
Interceptor.attach(Module.findExportByName(null, "checkPassword"), {
    onEnter: function(args) {
        args[1] = ObjC.classes.NSString.stringWithString_("Secret1234");
    }
});
'

위 코드를 실행하면 입력한 비밀번호가 무엇이든 "Secret1234"로 변경되어 항상 로그인 성공하게 됩니다.

 

이 예제에서는 iOS 앱의 내부 로직을 분석하고 조작하는 방법을 살펴보았습니다. 하지만, 실제 앱에서는 보안을 강화하기 위해 다음과 같은 방법을 사용할 수 있습니다:

  1. 코드 난독화: checkPassword 메서드명을 변경하고 난독화하여 분석을 어렵게 만듦.
  2. Jailbreak 탐지: Frida 및 lldb 사용을 감지하여 앱이 종료되도록 설정.
  3. 서버 측 인증: 비밀번호 검증을 서버에서 수행하여 클라이언트 코드 조작을 방지.
  4. Secure Enclave 활용: 민감한 데이터는 Secure Enclave에 저장하여 접근을 어렵게 함.

보안이 필요한 앱을 개발하는 경우 항상 공격자 입장에서 앱을 테스트해 보는 것이 중요합니다.

반응형