ai_root_shield 0.1.0

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.
@@ -0,0 +1,407 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+ require "openssl"
5
+
6
+ module AiRootShield
7
+ module Analyzers
8
+ # Checks application integrity and detects repackaging/tampering
9
+ class IntegrityChecker
10
+ # Known repackaging indicators
11
+ REPACKAGING_INDICATORS = %w[
12
+ test-keys
13
+ platform.pk8
14
+ shared.pk8
15
+ media.pk8
16
+ debug.keystore
17
+ androiddebugkey
18
+ unsigned
19
+ CERT.RSA
20
+ CERT.DSA
21
+ ].freeze
22
+
23
+ # Suspicious certificate authorities
24
+ SUSPICIOUS_CAS = %w[
25
+ CN=Android Debug
26
+ CN=Test
27
+ CN=Debug
28
+ O=Android
29
+ OU=Android
30
+ ].freeze
31
+
32
+ def analyze(device_data)
33
+ factors = []
34
+ risk_score = 0
35
+
36
+ # Check application signatures
37
+ signature_result = check_app_signatures(device_data)
38
+ factors.concat(signature_result[:factors])
39
+ risk_score += signature_result[:risk_score]
40
+
41
+ # Check for repackaging indicators
42
+ repackaging_result = check_repackaging_indicators(device_data)
43
+ factors.concat(repackaging_result[:factors])
44
+ risk_score += repackaging_result[:risk_score]
45
+
46
+ # Check file integrity
47
+ integrity_result = check_file_integrity(device_data)
48
+ factors.concat(integrity_result[:factors])
49
+ risk_score += integrity_result[:risk_score]
50
+
51
+ # Check for code injection
52
+ injection_result = check_code_injection(device_data)
53
+ factors.concat(injection_result[:factors])
54
+ risk_score += injection_result[:risk_score]
55
+
56
+ {
57
+ factors: factors,
58
+ risk_score: [risk_score, 100].min
59
+ }
60
+ end
61
+
62
+ private
63
+
64
+ def check_app_signatures(device_data)
65
+ factors = []
66
+ risk_score = 0
67
+
68
+ certificates = device_data[:certificates] || []
69
+
70
+ certificates.each do |cert|
71
+ next unless cert.is_a?(Hash)
72
+
73
+ # Check for debug certificates
74
+ if debug_certificate?(cert)
75
+ factors << "DEBUG_CERTIFICATE_DETECTED"
76
+ risk_score += 15
77
+ end
78
+
79
+ # Check for suspicious issuers
80
+ if suspicious_issuer?(cert)
81
+ factors << "SUSPICIOUS_CERTIFICATE_ISSUER"
82
+ risk_score += 12
83
+ end
84
+
85
+ # Check certificate validity
86
+ if expired_certificate?(cert)
87
+ factors << "EXPIRED_CERTIFICATE"
88
+ risk_score += 8
89
+ end
90
+
91
+ # Check for self-signed certificates
92
+ if self_signed_certificate?(cert)
93
+ factors << "SELF_SIGNED_CERTIFICATE"
94
+ risk_score += 10
95
+ end
96
+ end
97
+
98
+ # Check for missing signatures
99
+ if certificates.empty?
100
+ factors << "MISSING_SIGNATURES"
101
+ risk_score += 20
102
+ end
103
+
104
+ {
105
+ factors: factors,
106
+ risk_score: risk_score
107
+ }
108
+ end
109
+
110
+ def check_repackaging_indicators(device_data)
111
+ factors = []
112
+ risk_score = 0
113
+
114
+ # Check system info for repackaging signs
115
+ system_info = device_data[:system_info] || {}
116
+
117
+ # Check build fingerprint
118
+ fingerprint = system_info[:build_fingerprint].to_s
119
+ REPACKAGING_INDICATORS.each do |indicator|
120
+ if fingerprint.downcase.include?(indicator.downcase)
121
+ factors << "REPACKAGED_APP"
122
+ risk_score += 18
123
+ break
124
+ end
125
+ end
126
+
127
+ # Check installed packages for suspicious signatures
128
+ packages = device_data[:installed_packages] || []
129
+
130
+ packages.each do |package|
131
+ next unless package.is_a?(Hash)
132
+
133
+ signature = package["signature"] || package["cert"]
134
+ next unless signature
135
+
136
+ REPACKAGING_INDICATORS.each do |indicator|
137
+ if signature.to_s.downcase.include?(indicator.downcase)
138
+ factors << "PACKAGE_REPACKAGED"
139
+ risk_score += 15
140
+ break
141
+ end
142
+ end
143
+ end
144
+
145
+ # Check for multiple signatures on same package
146
+ signature_counts = {}
147
+ packages.each do |package|
148
+ next unless package.is_a?(Hash)
149
+
150
+ pkg_name = package["name"]
151
+ signature = package["signature"]
152
+
153
+ next unless pkg_name && signature
154
+
155
+ signature_counts[pkg_name] ||= []
156
+ signature_counts[pkg_name] << signature
157
+ end
158
+
159
+ signature_counts.each do |pkg_name, signatures|
160
+ if signatures.uniq.length > 1
161
+ factors << "MULTIPLE_SIGNATURES_DETECTED"
162
+ risk_score += 12
163
+ break
164
+ end
165
+ end
166
+
167
+ {
168
+ factors: factors,
169
+ risk_score: risk_score
170
+ }
171
+ end
172
+
173
+ def check_file_integrity(device_data)
174
+ factors = []
175
+ risk_score = 0
176
+
177
+ file_system = device_data[:file_system] || {}
178
+
179
+ # Check for modified system files
180
+ system_binaries = file_system[:system_binaries] || []
181
+
182
+ # Look for unexpected modifications in system directories
183
+ writable_system_dirs = file_system[:writable_system_dirs] || []
184
+
185
+ if writable_system_dirs.any?
186
+ factors << "SYSTEM_PARTITION_WRITABLE"
187
+ risk_score += 15
188
+ end
189
+
190
+ # Check for suspicious file permissions
191
+ suspicious_files = file_system[:suspicious_files] || []
192
+
193
+ suspicious_files.each do |file_info|
194
+ next unless file_info.is_a?(Hash)
195
+
196
+ permissions = file_info["permissions"]
197
+ path = file_info["path"]
198
+
199
+ # Check for world-writable system files
200
+ if permissions && permissions.include?("777") && path&.start_with?("/system")
201
+ factors << "SUSPICIOUS_FILE_PERMISSIONS"
202
+ risk_score += 10
203
+ break
204
+ end
205
+ end
206
+
207
+ # Check for DEX file modifications (Android)
208
+ if device_data[:platform] == "android"
209
+ dex_result = check_dex_integrity(device_data)
210
+ factors.concat(dex_result[:factors])
211
+ risk_score += dex_result[:risk_score]
212
+ end
213
+
214
+ # Check for bundle modifications (iOS)
215
+ if device_data[:platform] == "ios"
216
+ bundle_result = check_bundle_integrity(device_data)
217
+ factors.concat(bundle_result[:factors])
218
+ risk_score += bundle_result[:risk_score]
219
+ end
220
+
221
+ {
222
+ factors: factors,
223
+ risk_score: risk_score
224
+ }
225
+ end
226
+
227
+ def check_code_injection(device_data)
228
+ factors = []
229
+ risk_score = 0
230
+
231
+ processes = device_data[:processes] || []
232
+
233
+ processes.each do |process|
234
+ next unless process.is_a?(Hash)
235
+
236
+ # Check for unexpected loaded libraries
237
+ libraries = process["loaded_libraries"] || []
238
+ memory_maps = process["memory_maps"] || []
239
+
240
+ # Look for libraries loaded from suspicious locations
241
+ suspicious_locations = %w[/data/local/tmp /sdcard /cache /data/data]
242
+
243
+ libraries.each do |lib|
244
+ suspicious_locations.each do |location|
245
+ if lib.to_s.start_with?(location)
246
+ factors << "SUSPICIOUS_LIBRARY_INJECTION"
247
+ risk_score += 15
248
+ break
249
+ end
250
+ end
251
+ end
252
+
253
+ # Check for executable memory in data segments
254
+ memory_maps.each do |map|
255
+ next unless map.is_a?(Hash)
256
+
257
+ permissions = map["permissions"]
258
+ path = map["path"]
259
+
260
+ if permissions&.include?("x") && path&.start_with?("/data")
261
+ factors << "EXECUTABLE_DATA_SEGMENT"
262
+ risk_score += 12
263
+ break
264
+ end
265
+ end
266
+ end
267
+
268
+ # Check system logs for injection indicators
269
+ logs = device_data[:logs] || []
270
+
271
+ injection_keywords = %w[
272
+ "code injection"
273
+ "library injection"
274
+ "process injection"
275
+ "dll injection"
276
+ "dylib injection"
277
+ ]
278
+
279
+ injection_keywords.each do |keyword|
280
+ if logs.any? { |log| log.to_s.downcase.include?(keyword) }
281
+ factors << "CODE_INJECTION_DETECTED"
282
+ risk_score += 18
283
+ break
284
+ end
285
+ end
286
+
287
+ {
288
+ factors: factors,
289
+ risk_score: risk_score
290
+ }
291
+ end
292
+
293
+ def check_dex_integrity(device_data)
294
+ factors = []
295
+ risk_score = 0
296
+
297
+ packages = device_data[:installed_packages] || []
298
+
299
+ packages.each do |package|
300
+ next unless package.is_a?(Hash)
301
+
302
+ dex_hash = package["dex_hash"]
303
+ original_hash = package["original_dex_hash"]
304
+
305
+ if dex_hash && original_hash && dex_hash != original_hash
306
+ factors << "DEX_TAMPERED"
307
+ risk_score += 12
308
+ break
309
+ end
310
+ end
311
+
312
+ # Check for multiple DEX files (could indicate repackaging)
313
+ file_system = device_data[:file_system] || {}
314
+ suspicious_files = file_system[:suspicious_files] || []
315
+
316
+ dex_files = suspicious_files.select { |file| file.to_s.end_with?(".dex") }
317
+
318
+ if dex_files.length > 2 # Most apps have 1-2 DEX files normally
319
+ factors << "MULTIPLE_DEX_FILES"
320
+ risk_score += 8
321
+ end
322
+
323
+ {
324
+ factors: factors,
325
+ risk_score: risk_score
326
+ }
327
+ end
328
+
329
+ def check_bundle_integrity(device_data)
330
+ factors = []
331
+ risk_score = 0
332
+
333
+ packages = device_data[:installed_packages] || []
334
+
335
+ packages.each do |package|
336
+ next unless package.is_a?(Hash)
337
+
338
+ bundle_hash = package["bundle_hash"]
339
+ original_hash = package["original_bundle_hash"]
340
+
341
+ if bundle_hash && original_hash && bundle_hash != original_hash
342
+ factors << "BUNDLE_MODIFIED"
343
+ risk_score += 10
344
+ break
345
+ end
346
+ end
347
+
348
+ # Check for suspicious Info.plist modifications
349
+ file_system = device_data[:file_system] || {}
350
+ suspicious_files = file_system[:suspicious_files] || []
351
+
352
+ info_plist_files = suspicious_files.select { |file| file.to_s.include?("Info.plist") }
353
+
354
+ info_plist_files.each do |plist_file|
355
+ # This would typically check for modifications to critical plist values
356
+ # For now, we'll flag if there are multiple Info.plist files
357
+ if info_plist_files.length > 1
358
+ factors << "MULTIPLE_INFO_PLIST"
359
+ risk_score += 6
360
+ break
361
+ end
362
+ end
363
+
364
+ {
365
+ factors: factors,
366
+ risk_score: risk_score
367
+ }
368
+ end
369
+
370
+ def debug_certificate?(cert)
371
+ subject = cert["subject"].to_s
372
+ issuer = cert["issuer"].to_s
373
+
374
+ debug_indicators = ["debug", "test", "android debug", "development"]
375
+
376
+ debug_indicators.any? do |indicator|
377
+ subject.downcase.include?(indicator) || issuer.downcase.include?(indicator)
378
+ end
379
+ end
380
+
381
+ def suspicious_issuer?(cert)
382
+ issuer = cert["issuer"].to_s
383
+
384
+ SUSPICIOUS_CAS.any? { |ca| issuer.include?(ca) }
385
+ end
386
+
387
+ def expired_certificate?(cert)
388
+ not_after = cert["not_after"]
389
+ return false unless not_after
390
+
391
+ begin
392
+ expiry_date = Time.parse(not_after)
393
+ expiry_date < Time.now
394
+ rescue
395
+ false
396
+ end
397
+ end
398
+
399
+ def self_signed_certificate?(cert)
400
+ subject = cert["subject"].to_s
401
+ issuer = cert["issuer"].to_s
402
+
403
+ subject == issuer
404
+ end
405
+ end
406
+ end
407
+ end