리버싱(리버스 엔지니어링, 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 프로젝트 생성
- Xcode를 실행하고 "New Project" → "App" 선택
- 프로젝트 이름: ReversingExample
- Interface: Storyboard, Language: Swift
- "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 프로젝트 생성
- Xcode에서 "New Project" → "App" 선택
- 프로젝트 이름: ReversingExampleSwiftUI
- Interface: SwiftUI, Language: Swift
- "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 앱의 내부 로직을 분석하고 조작하는 방법을 살펴보았습니다. 하지만, 실제 앱에서는 보안을 강화하기 위해 다음과 같은 방법을 사용할 수 있습니다:
- 코드 난독화: checkPassword 메서드명을 변경하고 난독화하여 분석을 어렵게 만듦.
- Jailbreak 탐지: Frida 및 lldb 사용을 감지하여 앱이 종료되도록 설정.
- 서버 측 인증: 비밀번호 검증을 서버에서 수행하여 클라이언트 코드 조작을 방지.
- Secure Enclave 활용: 민감한 데이터는 Secure Enclave에 저장하여 접근을 어렵게 함.
보안이 필요한 앱을 개발하는 경우 항상 공격자 입장에서 앱을 테스트해 보는 것이 중요합니다.
'클라이언트' 카테고리의 다른 글
[UIKit] UIColor Hex 초기화, 문자열 초기화 유틸 Extension 코드 (0) | 2025.02.16 |
---|---|
[GameKit] 간단한 가위바위보 멀티게임 만들기 (0) | 2025.02.16 |
[Combine] Combine의 기본 개념 및 예제 (0) | 2025.02.16 |
[iOS] iOS 앱 보안을 위한 최적화 방법 (0) | 2025.02.16 |
[iOS][Swift] iOS Swift에서 사용할 수 있는 암호화 방식 (0) | 2025.02.16 |