@leejungkiin/awkit 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/VERSION +1 -1
- package/bin/awk.js +1 -1
- package/core/GEMINI.md +4 -0
- package/package.json +1 -1
- package/skills/smali-to-kotlin/SKILL.md +521 -0
- package/skills/smali-to-kotlin/library-patterns.md +189 -0
- package/skills/smali-to-kotlin/smali-reading-guide.md +310 -0
- package/skills/smali-to-swift/SKILL.md +749 -0
- package/skills/smali-to-swift/framework-patterns.md +189 -0
- package/skills/smali-to-swift/objc-reading-guide.md +388 -0
- package/workflows/mobile/reverse-android.md +740 -0
- package/workflows/mobile/reverse-ios.md +674 -0
|
@@ -0,0 +1,674 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 🍎 Dịch ngược IPA iOS (class-dump, Hopper output) → App Swift hiện đại với SwiftUI, Clean Architecture, và Framework Scanner tự động.
|
|
3
|
+
skill: smali-to-swift
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /reverse-ios — iOS IPA Reverse Engineering Workflow
|
|
7
|
+
|
|
8
|
+
> **Skill được dùng:** `smali-to-swift`
|
|
9
|
+
> **Tech Stack:** Swift + SwiftUI + async/await + URLSession + SwiftData
|
|
10
|
+
> **Philosophy:** "Read ObjC headers to understand WHAT & WHY → Write Swift for HOW"
|
|
11
|
+
> **Sibling:** `/reverse-android` (cùng pipeline pattern, khác platform)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## ⚡ QUICK START
|
|
16
|
+
|
|
17
|
+
User cung cấp một trong các input sau:
|
|
18
|
+
- Decrypted `.app` bundle (từ frida-ios-dump / bagbak)
|
|
19
|
+
- Class-dump headers output
|
|
20
|
+
- Hopper/IDA pseudo-code
|
|
21
|
+
- Nói: "Tôi muốn reverse engineer IPA này"
|
|
22
|
+
|
|
23
|
+
Workflow dẫn dắt từng bước — **không bao giờ nhảy cóc**.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 🔵 Session Setup (Chạy 1 lần khi bắt đầu)
|
|
28
|
+
|
|
29
|
+
### Bước 0.1: Khởi tạo session state
|
|
30
|
+
|
|
31
|
+
```yaml
|
|
32
|
+
reverse_ios_session:
|
|
33
|
+
project_name: "[TBD - lấy từ Info.plist]"
|
|
34
|
+
app_bundle_dir: "[path user cung cấp]"
|
|
35
|
+
headers_dir: "[path to class-dump output]"
|
|
36
|
+
current_step: 0
|
|
37
|
+
framework_report_done: false
|
|
38
|
+
plist_analyzed: false
|
|
39
|
+
completed_screens: []
|
|
40
|
+
pending_screens: []
|
|
41
|
+
decisions: []
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Bước 0.2: Xác nhận input và hướng dẫn chuẩn bị
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
🍎 iOS Reverse Engineering bắt đầu!
|
|
48
|
+
|
|
49
|
+
Em cần biết:
|
|
50
|
+
1. Decrypted .app bundle ở đâu? (vd: ~/decrypted/App.app/)
|
|
51
|
+
2. Đã chạy class-dump chưa? Headers ở đâu?
|
|
52
|
+
3. Tên app gốc? Bundle ID?
|
|
53
|
+
|
|
54
|
+
Nếu chưa chuẩn bị, đây là flow chuẩn bị:
|
|
55
|
+
|
|
56
|
+
# 1. Decrypt IPA (cần jailbroken device)
|
|
57
|
+
bagbak -o ~/decrypted/ com.example.app
|
|
58
|
+
# hoặc
|
|
59
|
+
frida-ios-dump -u com.example.app
|
|
60
|
+
|
|
61
|
+
# 2. Dump headers
|
|
62
|
+
class-dump -H ~/decrypted/App.app -o ~/headers/
|
|
63
|
+
|
|
64
|
+
# 3. (Optional) Disassembly — mở trong Hopper hoặc IDA
|
|
65
|
+
open -a Hopper ~/decrypted/App.app/App
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 📦 Step 0: Framework Scanner (BẮT BUỘC — Không được bỏ qua)
|
|
71
|
+
|
|
72
|
+
> **Mục tiêu:** Nhận diện toàn bộ frameworks trước khi code.
|
|
73
|
+
> **Reference:** `skills/smali-to-swift/framework-patterns.md`
|
|
74
|
+
|
|
75
|
+
### Bước 0.3: Quét IPA structure
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# 1. Embedded frameworks
|
|
79
|
+
ls [app_bundle]/Frameworks/
|
|
80
|
+
|
|
81
|
+
# 2. Linked libraries (Mach-O)
|
|
82
|
+
otool -L [app_bundle]/App | grep -v /System | grep -v /usr/lib
|
|
83
|
+
|
|
84
|
+
# 3. Class-dump header imports
|
|
85
|
+
grep -rh "#import <" [headers_dir]/ | sort -u
|
|
86
|
+
grep -rh "@import " [headers_dir]/ | sort -u
|
|
87
|
+
grep -rh "import " [headers_dir]/ | grep -v Foundation | grep -v UIKit | sort -u
|
|
88
|
+
|
|
89
|
+
# 4. String search for SDK identifiers
|
|
90
|
+
strings [app_bundle]/App | grep -i "cocoapods\|carthage\|firebase\|facebook\|google"
|
|
91
|
+
|
|
92
|
+
# 5. Assets & resources
|
|
93
|
+
ls [app_bundle]/*.car 2>/dev/null # Asset catalogs
|
|
94
|
+
ls [app_bundle]/*.momd 2>/dev/null # Core Data models
|
|
95
|
+
ls [app_bundle]/*.storyboardc 2>/dev/null # Storyboards
|
|
96
|
+
find [app_bundle] -name "*.json" -o -name "*.plist" | grep -v Info.plist | sort
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Bước 0.4: Tạo Framework Detection Report
|
|
100
|
+
|
|
101
|
+
Dùng patterns từ `framework-patterns.md`:
|
|
102
|
+
|
|
103
|
+
```markdown
|
|
104
|
+
## 📦 Framework Detection Report — [App Name]
|
|
105
|
+
|
|
106
|
+
### ✅ Reuse (Add via SPM)
|
|
107
|
+
| Framework | Detected | Latest Version | Notes |
|
|
108
|
+
|-----------|----------|----------------|-------|
|
|
109
|
+
| Kingfisher | Frameworks/Kingfisher.framework | 7.12.0 | Keep |
|
|
110
|
+
| [...] | [...] | [...] | [...] |
|
|
111
|
+
|
|
112
|
+
### 🔄 Replace (Legacy → Modern Swift)
|
|
113
|
+
| Old Framework | Detected | Modern Replacement |
|
|
114
|
+
|---------------|----------|-------------------|
|
|
115
|
+
| AFNetworking | Frameworks/AFNetworking.framework | URLSession async/await |
|
|
116
|
+
| SDWebImage | header imports | AsyncImage + Kingfisher |
|
|
117
|
+
| SnapKit | header imports | SwiftUI layout |
|
|
118
|
+
| [...] | [...] | [...] |
|
|
119
|
+
|
|
120
|
+
### 🍏 Apple Frameworks Used
|
|
121
|
+
| Framework | Purpose | SwiftUI Equivalent |
|
|
122
|
+
|-----------|---------|-------------------|
|
|
123
|
+
| MapKit | Maps | Map (SwiftUI) |
|
|
124
|
+
| CoreLocation | GPS | LocationManager wrapper |
|
|
125
|
+
| AVFoundation | Camera | Camera view wrapper |
|
|
126
|
+
| CoreData | Database | SwiftData migration |
|
|
127
|
+
|
|
128
|
+
### 📱 Native Libraries — investigate
|
|
129
|
+
| File | Notes |
|
|
130
|
+
|------|-------|
|
|
131
|
+
| libcustom.dylib | C library — need bridging header |
|
|
132
|
+
|
|
133
|
+
### 🏷️ App Code (Rewrite in Swift)
|
|
134
|
+
| Class Prefix / Pattern | Estimated Module |
|
|
135
|
+
|------------------------|-----------------|
|
|
136
|
+
| MYAppUser*, MYAppAuth* | Auth module |
|
|
137
|
+
| MYAppHome*, MYAppFeed* | Home/Feed module |
|
|
138
|
+
|
|
139
|
+
### ❓ Unknown (investigate)
|
|
140
|
+
| Framework | Notes |
|
|
141
|
+
|-----------|-------|
|
|
142
|
+
| CustomSDK.framework | Proprietary? |
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Bước 0.5: User approval
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
📦 Framework Report sẵn sàng!
|
|
149
|
+
|
|
150
|
+
Anh review:
|
|
151
|
+
✅ "Reuse" list — còn thiếu gì không?
|
|
152
|
+
🔄 "Replace" list — có cái nào anh muốn giữ?
|
|
153
|
+
|
|
154
|
+
Xác nhận xong → em bắt đầu Step 1.
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
> **GATE:** Không tiếp tục khi chưa có user approval.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 📄 Step 1: Info.plist & Entitlements Analysis + Project Bootstrap
|
|
162
|
+
|
|
163
|
+
> **Input:** `[app_bundle]/Info.plist` + entitlements
|
|
164
|
+
|
|
165
|
+
### Bước 1.1: Đọc Info.plist
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
# Read Info.plist (may be binary format)
|
|
169
|
+
plutil -p [app_bundle]/Info.plist
|
|
170
|
+
|
|
171
|
+
# Entitlements
|
|
172
|
+
codesign -d --entitlements :- [app_bundle]/App 2>/dev/null
|
|
173
|
+
# hoặc
|
|
174
|
+
jtool2 --ent [app_bundle]/App
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Trích xuất:
|
|
178
|
+
```yaml
|
|
179
|
+
extract:
|
|
180
|
+
- bundle_id: "com.example.app"
|
|
181
|
+
- display_name: "My App"
|
|
182
|
+
- min_ios_version: "15.0"
|
|
183
|
+
- permissions:
|
|
184
|
+
camera: "NSCameraUsageDescription → [description]"
|
|
185
|
+
photos: "NSPhotoLibraryUsageDescription → [description]"
|
|
186
|
+
location: "NSLocationWhenInUseUsageDescription → [description]"
|
|
187
|
+
microphone: "NSMicrophoneUsageDescription → [description]"
|
|
188
|
+
notifications: "aps-environment → [production/development]"
|
|
189
|
+
tracking: "NSUserTrackingUsageDescription → [description]"
|
|
190
|
+
- url_schemes: ["myapp://"]
|
|
191
|
+
- universal_links: ["applinks:example.com"]
|
|
192
|
+
- capabilities:
|
|
193
|
+
push_notifications: true/false
|
|
194
|
+
apple_pay: true/false
|
|
195
|
+
sign_in_with_apple: true/false
|
|
196
|
+
app_groups: ["group.com.example.app"]
|
|
197
|
+
- supported_orientations: [portrait, landscape]
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Bước 1.2: Phân tích class hierarchy
|
|
201
|
+
|
|
202
|
+
Từ class-dump headers, xác định:
|
|
203
|
+
```bash
|
|
204
|
+
# Find all ViewControllers
|
|
205
|
+
grep -rl "UIViewController" [headers_dir]/ | sort
|
|
206
|
+
|
|
207
|
+
# Find AppDelegate
|
|
208
|
+
grep -rl "UIApplicationDelegate" [headers_dir]/
|
|
209
|
+
|
|
210
|
+
# Find tab bar structure
|
|
211
|
+
grep -rl "UITabBarController" [headers_dir]/
|
|
212
|
+
|
|
213
|
+
# Find navigation controllers
|
|
214
|
+
grep -rl "UINavigationController" [headers_dir]/
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Mapping ViewControllers → SwiftUI Screens:
|
|
218
|
+
```
|
|
219
|
+
SplashViewController → LaunchScreen (or SplashScreen.swift)
|
|
220
|
+
LoginViewController → Auth/LoginScreen.swift
|
|
221
|
+
MainTabBarController → TabView in ContentView.swift
|
|
222
|
+
HomeViewController → Screens/Home/HomeScreen.swift
|
|
223
|
+
ProfileViewController → Screens/Profile/ProfileScreen.swift
|
|
224
|
+
SettingsTableViewController → Screens/Settings/SettingsScreen.swift
|
|
225
|
+
DetailViewController → Screens/Detail/DetailScreen.swift
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Bước 1.3: Tạo Xcode project structure
|
|
229
|
+
|
|
230
|
+
Đề xuất structure (xem template trong SKILL.md Step 1).
|
|
231
|
+
|
|
232
|
+
### Bước 1.4: Package.swift setup (hoặc SPM via Xcode)
|
|
233
|
+
|
|
234
|
+
```swift
|
|
235
|
+
// Dependencies từ Framework Report
|
|
236
|
+
// Add via Xcode: File → Add Package Dependencies
|
|
237
|
+
|
|
238
|
+
// Firebase
|
|
239
|
+
"https://github.com/firebase/firebase-ios-sdk" // 11.0+
|
|
240
|
+
// Kingfisher (image loading)
|
|
241
|
+
"https://github.com/onevcat/Kingfisher" // 7.12+
|
|
242
|
+
// KeychainAccess
|
|
243
|
+
"https://github.com/kishikawakatsumi/KeychainAccess" // 4.2+
|
|
244
|
+
// Lottie
|
|
245
|
+
"https://github.com/airbnb/lottie-ios" // 4.4+
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### ✅ Checkpoint Step 1
|
|
249
|
+
|
|
250
|
+
```markdown
|
|
251
|
+
## ✅ Step 1 Complete: Info.plist & Bootstrap
|
|
252
|
+
|
|
253
|
+
### Extracted:
|
|
254
|
+
- Bundle ID: [bundle_id]
|
|
255
|
+
- Permissions: [count] total
|
|
256
|
+
- Screens to rebuild: [list from VCs]
|
|
257
|
+
- URL Schemes: [list]
|
|
258
|
+
|
|
259
|
+
### Created:
|
|
260
|
+
- Xcode project structure proposal
|
|
261
|
+
- SPM dependency list
|
|
262
|
+
|
|
263
|
+
### ⏭️ Next: Step 2 — Data Layer Reconstruction
|
|
264
|
+
- Cung cấp class-dump headers cho: Network/API classes, Model classes, Database/Storage
|
|
265
|
+
- Tìm trong headers: *Service, *Manager, *Client, *API, *Model, *Entity
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## 💾 Step 2: Data Layer Reconstruction
|
|
271
|
+
|
|
272
|
+
> **Input:** Class-dump headers cho network, models, storage classes
|
|
273
|
+
> **Reading help:** `skills/smali-to-swift/objc-reading-guide.md`
|
|
274
|
+
|
|
275
|
+
### Bước 2.1: Models
|
|
276
|
+
|
|
277
|
+
```objc
|
|
278
|
+
// From class-dump
|
|
279
|
+
@interface UserModel : NSObject
|
|
280
|
+
@property (nonatomic, copy) NSString *userId;
|
|
281
|
+
@property (nonatomic, copy) NSString *fullName;
|
|
282
|
+
@property (nonatomic, assign) NSInteger age;
|
|
283
|
+
@property (nullable, nonatomic, copy) NSString *avatarURL;
|
|
284
|
+
@end
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
```swift
|
|
288
|
+
// Swift Codable
|
|
289
|
+
struct User: Codable, Identifiable, Sendable {
|
|
290
|
+
let id: String
|
|
291
|
+
let fullName: String
|
|
292
|
+
let age: Int
|
|
293
|
+
let avatarURL: String?
|
|
294
|
+
|
|
295
|
+
enum CodingKeys: String, CodingKey {
|
|
296
|
+
case id = "user_id"
|
|
297
|
+
case fullName = "full_name"
|
|
298
|
+
case age
|
|
299
|
+
case avatarURL = "avatar_url"
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Bước 2.2: API Client
|
|
305
|
+
|
|
306
|
+
```swift
|
|
307
|
+
// Data/Network/APIClient.swift
|
|
308
|
+
actor APIClient {
|
|
309
|
+
private let session: URLSession
|
|
310
|
+
private let baseURL: URL
|
|
311
|
+
private let decoder = JSONDecoder()
|
|
312
|
+
|
|
313
|
+
init(baseURL: URL, session: URLSession = .shared) {
|
|
314
|
+
self.baseURL = baseURL
|
|
315
|
+
self.session = session
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
func request<T: Decodable>(_ endpoint: Endpoint) async throws -> T {
|
|
319
|
+
var request = URLRequest(url: baseURL.appendingPathComponent(endpoint.path))
|
|
320
|
+
request.httpMethod = endpoint.method.rawValue
|
|
321
|
+
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
322
|
+
endpoint.headers?.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) }
|
|
323
|
+
if let body = endpoint.body { request.httpBody = body }
|
|
324
|
+
|
|
325
|
+
let (data, response) = try await session.data(for: request)
|
|
326
|
+
guard let http = response as? HTTPURLResponse, (200...299).contains(http.statusCode) else {
|
|
327
|
+
throw APIError.invalidResponse
|
|
328
|
+
}
|
|
329
|
+
return try decoder.decode(T.self, from: data)
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Bước 2.3: SwiftData (nếu app có Core Data / SQLite)
|
|
335
|
+
|
|
336
|
+
```swift
|
|
337
|
+
// Data/Local/SwiftDataModels/UserEntity.swift
|
|
338
|
+
import SwiftData
|
|
339
|
+
|
|
340
|
+
@Model
|
|
341
|
+
final class UserEntity {
|
|
342
|
+
@Attribute(.unique) var id: String
|
|
343
|
+
var name: String
|
|
344
|
+
var email: String
|
|
345
|
+
var lastUpdated: Date
|
|
346
|
+
|
|
347
|
+
init(id: String, name: String, email: String) {
|
|
348
|
+
self.id = id
|
|
349
|
+
self.name = name
|
|
350
|
+
self.email = email
|
|
351
|
+
self.lastUpdated = .now
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Bước 2.4: Repository
|
|
357
|
+
|
|
358
|
+
```swift
|
|
359
|
+
// Domain/Repositories/UserRepository.swift
|
|
360
|
+
protocol UserRepository: Sendable {
|
|
361
|
+
func getUser(id: String) async throws -> User
|
|
362
|
+
func login(email: String, password: String) async throws -> AuthToken
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Data/Repositories/UserRepositoryImpl.swift
|
|
366
|
+
final class UserRepositoryImpl: UserRepository {
|
|
367
|
+
private let apiClient: APIClient
|
|
368
|
+
private let modelContext: ModelContext
|
|
369
|
+
|
|
370
|
+
func getUser(id: String) async throws -> User {
|
|
371
|
+
// offline-first: local → remote → cache
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### ✅ Checkpoint Step 2
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## 🧮 Step 3: Core Logic & Utils Reconstruction
|
|
381
|
+
|
|
382
|
+
> **CRITICAL:** Crypto output MUST match original
|
|
383
|
+
> **Input:** Hopper pseudo-code / headers cho encryption utils
|
|
384
|
+
|
|
385
|
+
### Bước 3.1: Crypto utils → Swift
|
|
386
|
+
|
|
387
|
+
```swift
|
|
388
|
+
import CryptoKit
|
|
389
|
+
import CommonCrypto
|
|
390
|
+
|
|
391
|
+
enum CryptoUtils {
|
|
392
|
+
// MD5 (legacy — use CommonCrypto)
|
|
393
|
+
static func md5(_ input: String) -> String {
|
|
394
|
+
let data = Data(input.utf8)
|
|
395
|
+
var digest = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
|
|
396
|
+
data.withUnsafeBytes { CC_MD5($0.baseAddress, CC_LONG(data.count), &digest) }
|
|
397
|
+
return digest.map { String(format: "%02x", $0) }.joined()
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// SHA256 (modern — use CryptoKit)
|
|
401
|
+
static func sha256(_ input: String) -> String {
|
|
402
|
+
let hash = SHA256.hash(data: Data(input.utf8))
|
|
403
|
+
return hash.compactMap { String(format: "%02x", $0) }.joined()
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// AES (match original algorithm exactly)
|
|
407
|
+
static func aesEncrypt(data: Data, key: Data, iv: Data) throws -> Data {
|
|
408
|
+
// Implement matching original parameters
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### Bước 3.2: XCTest verification
|
|
414
|
+
|
|
415
|
+
```swift
|
|
416
|
+
final class CryptoUtilsTests: XCTestCase {
|
|
417
|
+
func testMD5MatchesOriginal() {
|
|
418
|
+
XCTAssertEqual(CryptoUtils.md5("test"), "098f6bcd4621d373cade4e832627b4f6")
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### ✅ Checkpoint Step 3
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
## 🎨 Step 4: UI & ViewModel Reconstruction (Per Screen — Lặp lại)
|
|
428
|
+
|
|
429
|
+
> **Input:** Storyboard analysis + VC headers + disassembly
|
|
430
|
+
> **Reference:** `skills/smali-to-swift/SKILL.md` → Step 4
|
|
431
|
+
|
|
432
|
+
### Bước 4.0: Chọn màn hình theo ưu tiên
|
|
433
|
+
|
|
434
|
+
```
|
|
435
|
+
1. LaunchScreen / Splash (đơn giản nhất)
|
|
436
|
+
2. Auth screens (Login, Register)
|
|
437
|
+
3. Main TabView + Home
|
|
438
|
+
4. Detail screens
|
|
439
|
+
5. Settings / Profile
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### Bước 4.1: Resource extraction (on-demand)
|
|
443
|
+
|
|
444
|
+
```markdown
|
|
445
|
+
### Resources cho [ScreenName]:
|
|
446
|
+
- Images: [icon_logo, bg_login, ...]
|
|
447
|
+
- Colors: [primaryColor, backgroundColor, ...]
|
|
448
|
+
- Strings: [login_title, email_placeholder, ...]
|
|
449
|
+
- Fonts: [Inter-Regular.ttf, ...]
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### Bước 4.2: UIKit → SwiftUI
|
|
453
|
+
|
|
454
|
+
```swift
|
|
455
|
+
// Presentation/Screens/Auth/LoginScreen.swift
|
|
456
|
+
struct LoginScreen: View {
|
|
457
|
+
@State private var viewModel: LoginViewModel
|
|
458
|
+
@Environment(\.dismiss) private var dismiss
|
|
459
|
+
|
|
460
|
+
var body: some View {
|
|
461
|
+
NavigationStack {
|
|
462
|
+
ScrollView {
|
|
463
|
+
VStack(spacing: 24) {
|
|
464
|
+
// Logo
|
|
465
|
+
Image(.appLogo)
|
|
466
|
+
.resizable()
|
|
467
|
+
.scaledToFit()
|
|
468
|
+
.frame(height: 80)
|
|
469
|
+
|
|
470
|
+
// Form fields
|
|
471
|
+
VStack(spacing: 16) {
|
|
472
|
+
TextField("Email", text: $viewModel.email)
|
|
473
|
+
.textContentType(.emailAddress)
|
|
474
|
+
.keyboardType(.emailAddress)
|
|
475
|
+
.autocapitalization(.none)
|
|
476
|
+
|
|
477
|
+
SecureField("Password", text: $viewModel.password)
|
|
478
|
+
.textContentType(.password)
|
|
479
|
+
}
|
|
480
|
+
.textFieldStyle(.roundedBorder)
|
|
481
|
+
|
|
482
|
+
// Login button
|
|
483
|
+
Button {
|
|
484
|
+
Task { await viewModel.login() }
|
|
485
|
+
} label: {
|
|
486
|
+
Text("Login")
|
|
487
|
+
.frame(maxWidth: .infinity)
|
|
488
|
+
}
|
|
489
|
+
.buttonStyle(.borderedProminent)
|
|
490
|
+
.disabled(viewModel.isLoading)
|
|
491
|
+
}
|
|
492
|
+
.padding()
|
|
493
|
+
}
|
|
494
|
+
.overlay {
|
|
495
|
+
if viewModel.isLoading { ProgressView() }
|
|
496
|
+
}
|
|
497
|
+
.alert("Error", isPresented: $viewModel.showError) {
|
|
498
|
+
Button("OK") {}
|
|
499
|
+
} message: {
|
|
500
|
+
Text(viewModel.errorMessage)
|
|
501
|
+
}
|
|
502
|
+
.navigationTitle("Login")
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### Bước 4.3: ViewModel
|
|
509
|
+
|
|
510
|
+
```swift
|
|
511
|
+
@Observable
|
|
512
|
+
final class LoginViewModel {
|
|
513
|
+
var email = ""
|
|
514
|
+
var password = ""
|
|
515
|
+
var isLoading = false
|
|
516
|
+
var showError = false
|
|
517
|
+
var errorMessage = ""
|
|
518
|
+
|
|
519
|
+
private let authRepository: AuthRepository
|
|
520
|
+
|
|
521
|
+
init(authRepository: AuthRepository) {
|
|
522
|
+
self.authRepository = authRepository
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
func login() async {
|
|
526
|
+
isLoading = true
|
|
527
|
+
defer { isLoading = false }
|
|
528
|
+
|
|
529
|
+
do {
|
|
530
|
+
try await authRepository.login(email: email, password: password)
|
|
531
|
+
} catch {
|
|
532
|
+
errorMessage = error.localizedDescription
|
|
533
|
+
showError = true
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
### ✅ Checkpoint Step 4 (Per Screen)
|
|
540
|
+
|
|
541
|
+
> **Loop:** Lặp Step 4 cho từng screen → khi hết → Step 5.
|
|
542
|
+
|
|
543
|
+
---
|
|
544
|
+
|
|
545
|
+
## 📦 Step 5: Third-party SDK & Native Library Integration
|
|
546
|
+
|
|
547
|
+
### Bước 5.1: App entry point
|
|
548
|
+
|
|
549
|
+
```swift
|
|
550
|
+
@main
|
|
551
|
+
struct MyApp: App {
|
|
552
|
+
@UIApplicationDelegateAdaptor private var appDelegate: AppDelegate
|
|
553
|
+
|
|
554
|
+
var body: some Scene {
|
|
555
|
+
WindowGroup {
|
|
556
|
+
ContentView()
|
|
557
|
+
}
|
|
558
|
+
.modelContainer(for: [UserEntity.self, /* other models */])
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
class AppDelegate: NSObject, UIApplicationDelegate {
|
|
563
|
+
func application(_ application: UIApplication,
|
|
564
|
+
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
|
565
|
+
FirebaseApp.configure()
|
|
566
|
+
return true
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
func application(_ application: UIApplication,
|
|
570
|
+
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
|
571
|
+
Messaging.messaging().apnsToken = deviceToken
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
### Bước 5.2: Native C/C++ libraries (Bridging Header)
|
|
577
|
+
|
|
578
|
+
```c
|
|
579
|
+
// App-Bridging-Header.h
|
|
580
|
+
#include "native_lib.h"
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
```swift
|
|
584
|
+
// Swift usage
|
|
585
|
+
let result = native_function(param1, param2)
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
### ✅ Checkpoint Step 5
|
|
589
|
+
|
|
590
|
+
---
|
|
591
|
+
|
|
592
|
+
## ✅ Step 6: Parity Check & Quality Gate
|
|
593
|
+
|
|
594
|
+
### Bước 6.1: Test checklist từ disassembly branches
|
|
595
|
+
|
|
596
|
+
```markdown
|
|
597
|
+
### Edge cases (từ ObjC analysis):
|
|
598
|
+
- [ ] Login empty email → error message
|
|
599
|
+
- [ ] Login wrong password → error + retry limit?
|
|
600
|
+
- [ ] Network offline → cached data shown?
|
|
601
|
+
- [ ] App backgrounded during API call
|
|
602
|
+
- [ ] Deep link handling
|
|
603
|
+
- [ ] Push notification tap → correct screen
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
### Bước 6.2: Build & test
|
|
607
|
+
|
|
608
|
+
```bash
|
|
609
|
+
# Build
|
|
610
|
+
xcodebuild -scheme App -destination 'generic/platform=iOS' build
|
|
611
|
+
|
|
612
|
+
# Unit tests
|
|
613
|
+
xcodebuild -scheme App -destination 'platform=iOS Simulator,name=iPhone 16' test
|
|
614
|
+
|
|
615
|
+
# SwiftLint
|
|
616
|
+
swiftlint lint
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
### ✅ Final Checkpoint
|
|
620
|
+
|
|
621
|
+
```markdown
|
|
622
|
+
## 🎉 iOS Reverse Engineering Complete!
|
|
623
|
+
|
|
624
|
+
### Summary:
|
|
625
|
+
- Screens rebuilt: [count]
|
|
626
|
+
- Frameworks reused: [count]
|
|
627
|
+
- Frameworks replaced: [count]
|
|
628
|
+
- Native libs integrated: [count]
|
|
629
|
+
|
|
630
|
+
### ⏭️ Next Steps:
|
|
631
|
+
1. `/test` — Run full test suite
|
|
632
|
+
2. `/deploy` — When ready for TestFlight / App Store
|
|
633
|
+
3. `/code-janitor` — Clean up before merge
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
---
|
|
637
|
+
|
|
638
|
+
## 🚫 WORKFLOW RULES
|
|
639
|
+
|
|
640
|
+
```yaml
|
|
641
|
+
never_skip:
|
|
642
|
+
- Step 0 (Framework Scanner) — always first
|
|
643
|
+
- User approval of Framework Report
|
|
644
|
+
- Checkpoint after each step
|
|
645
|
+
|
|
646
|
+
never_do:
|
|
647
|
+
- Mass-copy assets from IPA
|
|
648
|
+
- Use UIKit when SwiftUI equivalent exists
|
|
649
|
+
- Use GCD for new async code (use async/await)
|
|
650
|
+
- Use ObjC in new code (Swift only, except bridging headers)
|
|
651
|
+
- Skip crypto parity testing
|
|
652
|
+
|
|
653
|
+
always_do:
|
|
654
|
+
- Document decisions in session state
|
|
655
|
+
- Present Framework Report before coding
|
|
656
|
+
- XCTest all crypto/hash functions
|
|
657
|
+
- Use @Observable for ViewModels (iOS 17+)
|
|
658
|
+
- Use NavigationStack for navigation
|
|
659
|
+
- Use SPM for all dependencies
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
---
|
|
663
|
+
|
|
664
|
+
## 🔗 Related
|
|
665
|
+
|
|
666
|
+
- **Skill:** `smali-to-swift` (core knowledge & rules)
|
|
667
|
+
- **Framework DB:** `skills/smali-to-swift/framework-patterns.md`
|
|
668
|
+
- **ObjC Guide:** `skills/smali-to-swift/objc-reading-guide.md`
|
|
669
|
+
- **Sibling:** `/reverse-android` (Android counterpart)
|
|
670
|
+
- **After RE done:** `/test`, `/deploy`, `/code-janitor`
|
|
671
|
+
|
|
672
|
+
---
|
|
673
|
+
|
|
674
|
+
*reverse-ios workflow v1.0.0 — iOS IPA RE Execution Flow*
|