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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +56 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +88 -0
- data/LICENSE +21 -0
- data/README.md +310 -0
- data/Rakefile +36 -0
- data/examples/device_logs/clean_device.json +74 -0
- data/examples/device_logs/rooted_android.json +93 -0
- data/exe/ai_root_shield +155 -0
- data/lib/ai_root_shield/analyzers/emulator_detector.rb +331 -0
- data/lib/ai_root_shield/analyzers/hooking_detector.rb +375 -0
- data/lib/ai_root_shield/analyzers/integrity_checker.rb +407 -0
- data/lib/ai_root_shield/analyzers/network_analyzer.rb +352 -0
- data/lib/ai_root_shield/analyzers/root_detector.rb +292 -0
- data/lib/ai_root_shield/detector.rb +78 -0
- data/lib/ai_root_shield/device_log_parser.rb +118 -0
- data/lib/ai_root_shield/risk_calculator.rb +161 -0
- data/lib/ai_root_shield/version.rb +5 -0
- data/lib/ai_root_shield.rb +36 -0
- metadata +179 -0
@@ -0,0 +1,93 @@
|
|
1
|
+
{
|
2
|
+
"platform": "android",
|
3
|
+
"system_info": {
|
4
|
+
"os_version": "Android 11",
|
5
|
+
"kernel_version": "4.19.95-g0123456789ab",
|
6
|
+
"build_fingerprint": "google/flame/flame:11/RQ3A.210905.001/7511028:user/test-keys",
|
7
|
+
"bootloader_status": "unlocked",
|
8
|
+
"selinux_status": "permissive",
|
9
|
+
"developer_options": true
|
10
|
+
},
|
11
|
+
"installed_packages": [
|
12
|
+
{
|
13
|
+
"name": "com.topjohnwu.magisk",
|
14
|
+
"signature": "test-keys"
|
15
|
+
},
|
16
|
+
{
|
17
|
+
"name": "eu.chainfire.supersu",
|
18
|
+
"signature": "debug.keystore"
|
19
|
+
},
|
20
|
+
{
|
21
|
+
"name": "com.android.chrome",
|
22
|
+
"signature": "platform.pk8"
|
23
|
+
}
|
24
|
+
],
|
25
|
+
"file_system": {
|
26
|
+
"suspicious_files": [
|
27
|
+
"/system/bin/su",
|
28
|
+
"/system/xbin/su",
|
29
|
+
"/system/app/Superuser.apk",
|
30
|
+
"/data/local/tmp/frida-server"
|
31
|
+
],
|
32
|
+
"system_binaries": [
|
33
|
+
"/system/bin/magisk",
|
34
|
+
"/system/xbin/daemonsu"
|
35
|
+
],
|
36
|
+
"writable_system_dirs": [
|
37
|
+
"/system/app"
|
38
|
+
]
|
39
|
+
},
|
40
|
+
"running_processes": [
|
41
|
+
{
|
42
|
+
"name": "magisk",
|
43
|
+
"pid": 1234
|
44
|
+
},
|
45
|
+
{
|
46
|
+
"name": "frida-server",
|
47
|
+
"pid": 5678,
|
48
|
+
"loaded_libraries": [
|
49
|
+
"libfrida-gadget.so"
|
50
|
+
]
|
51
|
+
}
|
52
|
+
],
|
53
|
+
"network": {
|
54
|
+
"proxy_settings": {
|
55
|
+
"enabled": true,
|
56
|
+
"host": "127.0.0.1",
|
57
|
+
"port": 8080
|
58
|
+
},
|
59
|
+
"vpn_active": false,
|
60
|
+
"certificates": [
|
61
|
+
{
|
62
|
+
"subject": "CN=PortSwigger CA, O=PortSwigger, L=Knaresborough",
|
63
|
+
"issuer": "CN=PortSwigger CA, O=PortSwigger, L=Knaresborough",
|
64
|
+
"user_installed": true
|
65
|
+
}
|
66
|
+
]
|
67
|
+
},
|
68
|
+
"security": {
|
69
|
+
"screen_lock_enabled": true,
|
70
|
+
"encryption_enabled": true,
|
71
|
+
"unknown_sources": true,
|
72
|
+
"usb_debugging": true
|
73
|
+
},
|
74
|
+
"hardware": {
|
75
|
+
"device_model": "Pixel 4",
|
76
|
+
"manufacturer": "Google",
|
77
|
+
"sensors": ["accelerometer", "gyroscope", "magnetometer"],
|
78
|
+
"baseband_version": "g7250-00042-200421-B-6404797",
|
79
|
+
"serial_number": "HT1ABC123456"
|
80
|
+
},
|
81
|
+
"certificates": [
|
82
|
+
{
|
83
|
+
"subject": "CN=Android Debug, O=Android, C=US",
|
84
|
+
"issuer": "CN=Android Debug, O=Android, C=US",
|
85
|
+
"not_after": "2025-01-01T00:00:00Z"
|
86
|
+
}
|
87
|
+
],
|
88
|
+
"system_logs": [
|
89
|
+
"magisk: Magisk v23.0 daemon started",
|
90
|
+
"frida: Frida gadget loaded successfully",
|
91
|
+
"xposed: Xposed framework initialized"
|
92
|
+
]
|
93
|
+
}
|
data/exe/ai_root_shield
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "../lib/ai_root_shield"
|
5
|
+
require "optparse"
|
6
|
+
require "json"
|
7
|
+
|
8
|
+
# CLI interface for AI Root Shield
|
9
|
+
class AiRootShieldCLI
|
10
|
+
def initialize
|
11
|
+
@options = {
|
12
|
+
config: {},
|
13
|
+
output_format: "json",
|
14
|
+
verbose: false
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
def run(args)
|
19
|
+
parse_options(args)
|
20
|
+
|
21
|
+
if args.empty?
|
22
|
+
puts "Error: Please provide a device logs file path"
|
23
|
+
puts "Usage: ai_root_shield [options] <device_logs.json>"
|
24
|
+
exit 1
|
25
|
+
end
|
26
|
+
|
27
|
+
device_logs_path = args.first
|
28
|
+
|
29
|
+
unless File.exist?(device_logs_path)
|
30
|
+
puts "Error: Device logs file not found: #{device_logs_path}"
|
31
|
+
exit 1
|
32
|
+
end
|
33
|
+
|
34
|
+
begin
|
35
|
+
result = AiRootShield.scan_device_with_config(device_logs_path, @options[:config])
|
36
|
+
output_result(result)
|
37
|
+
rescue AiRootShield::Error => e
|
38
|
+
puts "Error: #{e.message}"
|
39
|
+
exit 1
|
40
|
+
rescue StandardError => e
|
41
|
+
puts "Unexpected error: #{e.message}"
|
42
|
+
puts e.backtrace if @options[:verbose]
|
43
|
+
exit 1
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def parse_options(args)
|
50
|
+
OptionParser.new do |opts|
|
51
|
+
opts.banner = "Usage: ai_root_shield [options] <device_logs.json>"
|
52
|
+
opts.separator ""
|
53
|
+
opts.separator "Options:"
|
54
|
+
|
55
|
+
opts.on("-f", "--format FORMAT", ["json", "text", "summary"],
|
56
|
+
"Output format (json, text, summary)") do |format|
|
57
|
+
@options[:output_format] = format
|
58
|
+
end
|
59
|
+
|
60
|
+
opts.on("-v", "--verbose", "Enable verbose output") do
|
61
|
+
@options[:verbose] = true
|
62
|
+
end
|
63
|
+
|
64
|
+
opts.on("-t", "--threshold SCORE", Integer,
|
65
|
+
"Risk threshold (0-100, default: 50)") do |threshold|
|
66
|
+
@options[:config][:risk_threshold] = threshold
|
67
|
+
end
|
68
|
+
|
69
|
+
opts.on("--no-root", "Disable root detection") do
|
70
|
+
@options[:config][:enable_root_detection] = false
|
71
|
+
end
|
72
|
+
|
73
|
+
opts.on("--no-emulator", "Disable emulator detection") do
|
74
|
+
@options[:config][:enable_emulator_detection] = false
|
75
|
+
end
|
76
|
+
|
77
|
+
opts.on("--no-hooking", "Disable hooking detection") do
|
78
|
+
@options[:config][:enable_hooking_detection] = false
|
79
|
+
end
|
80
|
+
|
81
|
+
opts.on("--no-integrity", "Disable integrity checks") do
|
82
|
+
@options[:config][:enable_integrity_checks] = false
|
83
|
+
end
|
84
|
+
|
85
|
+
opts.on("--no-network", "Disable network analysis") do
|
86
|
+
@options[:config][:enable_network_analysis] = false
|
87
|
+
end
|
88
|
+
|
89
|
+
opts.on("-h", "--help", "Show this help message") do
|
90
|
+
puts opts
|
91
|
+
exit
|
92
|
+
end
|
93
|
+
|
94
|
+
opts.on("--version", "Show version") do
|
95
|
+
puts "AI Root Shield v#{AiRootShield::VERSION}"
|
96
|
+
exit
|
97
|
+
end
|
98
|
+
end.parse!(args)
|
99
|
+
end
|
100
|
+
|
101
|
+
def output_result(result)
|
102
|
+
case @options[:output_format]
|
103
|
+
when "json"
|
104
|
+
puts JSON.pretty_generate(result)
|
105
|
+
when "text"
|
106
|
+
output_text_format(result)
|
107
|
+
when "summary"
|
108
|
+
output_summary_format(result)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def output_text_format(result)
|
113
|
+
puts "AI Root Shield Security Scan Results"
|
114
|
+
puts "=" * 40
|
115
|
+
puts "Risk Score: #{result[:risk_score]}/100 (#{AiRootShield::RiskCalculator.risk_level_description(result[:risk_score])})"
|
116
|
+
puts "Scan Time: #{Time.at(result[:timestamp]).strftime('%Y-%m-%d %H:%M:%S')}"
|
117
|
+
puts "Version: #{result[:version]}"
|
118
|
+
puts ""
|
119
|
+
|
120
|
+
if result[:factors].any?
|
121
|
+
puts "Detected Security Factors:"
|
122
|
+
result[:factors].each do |factor|
|
123
|
+
puts " • #{factor.gsub('_', ' ').downcase.capitalize}"
|
124
|
+
end
|
125
|
+
puts ""
|
126
|
+
|
127
|
+
recommended_actions = AiRootShield::RiskCalculator.recommended_actions(result[:factors])
|
128
|
+
if recommended_actions.any?
|
129
|
+
puts "Recommended Actions:"
|
130
|
+
recommended_actions.each do |action|
|
131
|
+
puts " → #{action}"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
else
|
135
|
+
puts "No security threats detected."
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def output_summary_format(result)
|
140
|
+
risk_level = AiRootShield::RiskCalculator.risk_level_description(result[:risk_score])
|
141
|
+
|
142
|
+
puts "Risk Level: #{risk_level} (#{result[:risk_score]}/100)"
|
143
|
+
puts "Threats: #{result[:factors].length} detected"
|
144
|
+
|
145
|
+
if result[:factors].any?
|
146
|
+
puts "Primary Concerns: #{result[:factors].first(3).join(', ')}"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Run CLI if this file is executed directly
|
152
|
+
if __FILE__ == $0
|
153
|
+
cli = AiRootShieldCLI.new
|
154
|
+
cli.run(ARGV)
|
155
|
+
end
|
@@ -0,0 +1,331 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AiRootShield
|
4
|
+
module Analyzers
|
5
|
+
# Detects emulator and simulator environments
|
6
|
+
class EmulatorDetector
|
7
|
+
# Known emulator indicators
|
8
|
+
EMULATOR_FILES = %w[
|
9
|
+
/dev/socket/qemud
|
10
|
+
/dev/qemu_pipe
|
11
|
+
/system/lib/libc_malloc_debug_qemu.so
|
12
|
+
/sys/qemu_trace
|
13
|
+
/system/bin/qemu-props
|
14
|
+
/dev/socket/baseband_genyd
|
15
|
+
/dev/socket/genyd
|
16
|
+
/proc/tty/drivers
|
17
|
+
/proc/cpuinfo
|
18
|
+
/system/lib/libdvm.so
|
19
|
+
/system/bin/androVM-prop
|
20
|
+
/system/bin/microvirt-prop
|
21
|
+
/system/lib/libhoudini.so
|
22
|
+
/system/lib/arm/libhoudini.so
|
23
|
+
].freeze
|
24
|
+
|
25
|
+
EMULATOR_PROPERTIES = %w[
|
26
|
+
ro.kernel.qemu
|
27
|
+
ro.bootmode
|
28
|
+
ro.hardware
|
29
|
+
ro.product.device
|
30
|
+
ro.product.model
|
31
|
+
ro.product.name
|
32
|
+
ro.product.brand
|
33
|
+
ro.build.product
|
34
|
+
ro.build.fingerprint
|
35
|
+
init.svc.qemud
|
36
|
+
qemu.hw.mainkeys
|
37
|
+
qemu.sf.fake_camera
|
38
|
+
qemu.sf.lcd_density
|
39
|
+
ro.kernel.android.qemud
|
40
|
+
].freeze
|
41
|
+
|
42
|
+
EMULATOR_PACKAGES = %w[
|
43
|
+
com.google.android.launcher.layouts.genymotion
|
44
|
+
com.bluestacks
|
45
|
+
com.bignox.app
|
46
|
+
com.vphone.launcher
|
47
|
+
com.microvirt.guide
|
48
|
+
com.microvirt.market
|
49
|
+
com.microvirt.memuplay
|
50
|
+
com.android.development_settings
|
51
|
+
com.android.development
|
52
|
+
com.android.emulator.smoketests
|
53
|
+
].freeze
|
54
|
+
|
55
|
+
# iOS Simulator indicators
|
56
|
+
IOS_SIMULATOR_INDICATORS = %w[
|
57
|
+
i386
|
58
|
+
x86_64
|
59
|
+
Simulator
|
60
|
+
iPhone Simulator
|
61
|
+
iPad Simulator
|
62
|
+
AppleTV Simulator
|
63
|
+
].freeze
|
64
|
+
|
65
|
+
def analyze(device_data)
|
66
|
+
factors = []
|
67
|
+
risk_score = 0
|
68
|
+
|
69
|
+
case device_data[:platform]
|
70
|
+
when "android"
|
71
|
+
android_result = check_android_emulator(device_data)
|
72
|
+
factors.concat(android_result[:factors])
|
73
|
+
risk_score += android_result[:risk_score]
|
74
|
+
when "ios"
|
75
|
+
ios_result = check_ios_simulator(device_data)
|
76
|
+
factors.concat(ios_result[:factors])
|
77
|
+
risk_score += ios_result[:risk_score]
|
78
|
+
end
|
79
|
+
|
80
|
+
# Cross-platform checks
|
81
|
+
cross_platform_result = check_cross_platform_indicators(device_data)
|
82
|
+
factors.concat(cross_platform_result[:factors])
|
83
|
+
risk_score += cross_platform_result[:risk_score]
|
84
|
+
|
85
|
+
{
|
86
|
+
factors: factors,
|
87
|
+
risk_score: [risk_score, 100].min
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def check_android_emulator(device_data)
|
94
|
+
factors = []
|
95
|
+
risk_score = 0
|
96
|
+
|
97
|
+
# Check for emulator files
|
98
|
+
emulator_files = check_emulator_files(device_data[:file_system])
|
99
|
+
factors.concat(emulator_files)
|
100
|
+
risk_score += emulator_files.length * 15
|
101
|
+
|
102
|
+
# Check system properties
|
103
|
+
property_factors = check_system_properties(device_data[:system_info])
|
104
|
+
factors.concat(property_factors)
|
105
|
+
risk_score += property_factors.length * 12
|
106
|
+
|
107
|
+
# Check for emulator packages
|
108
|
+
package_factors = check_emulator_packages(device_data[:installed_packages])
|
109
|
+
factors.concat(package_factors)
|
110
|
+
risk_score += package_factors.length * 18
|
111
|
+
|
112
|
+
# Check hardware characteristics
|
113
|
+
hardware_factors = check_hardware_indicators(device_data[:hardware_info])
|
114
|
+
factors.concat(hardware_factors)
|
115
|
+
risk_score += hardware_factors.length * 10
|
116
|
+
|
117
|
+
{
|
118
|
+
factors: factors,
|
119
|
+
risk_score: risk_score
|
120
|
+
}
|
121
|
+
end
|
122
|
+
|
123
|
+
def check_ios_simulator(device_data)
|
124
|
+
factors = []
|
125
|
+
risk_score = 0
|
126
|
+
|
127
|
+
# Check hardware info for simulator indicators
|
128
|
+
hardware_info = device_data[:hardware_info] || {}
|
129
|
+
|
130
|
+
IOS_SIMULATOR_INDICATORS.each do |indicator|
|
131
|
+
device_model = hardware_info[:device_model].to_s
|
132
|
+
if device_model.include?(indicator)
|
133
|
+
factors << "SIMULATOR_IOS"
|
134
|
+
risk_score += 20
|
135
|
+
break
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Check for simulator-specific file paths
|
140
|
+
file_system = device_data[:file_system] || {}
|
141
|
+
suspicious_files = file_system[:suspicious_files] || []
|
142
|
+
|
143
|
+
if suspicious_files.any? { |file| file.to_s.include?("Simulator") }
|
144
|
+
factors << "SIMULATOR_FILES_DETECTED"
|
145
|
+
risk_score += 15
|
146
|
+
end
|
147
|
+
|
148
|
+
{
|
149
|
+
factors: factors,
|
150
|
+
risk_score: risk_score
|
151
|
+
}
|
152
|
+
end
|
153
|
+
|
154
|
+
def check_cross_platform_indicators(device_data)
|
155
|
+
factors = []
|
156
|
+
risk_score = 0
|
157
|
+
|
158
|
+
# Check for missing baseband (common in emulators)
|
159
|
+
baseband_factor = check_baseband_presence(device_data[:hardware_info])
|
160
|
+
if baseband_factor
|
161
|
+
factors << baseband_factor
|
162
|
+
risk_score += 12
|
163
|
+
end
|
164
|
+
|
165
|
+
# Check sensor anomalies
|
166
|
+
sensor_factors = check_sensor_anomalies(device_data[:hardware_info])
|
167
|
+
factors.concat(sensor_factors)
|
168
|
+
risk_score += sensor_factors.length * 8
|
169
|
+
|
170
|
+
# Check for virtualization indicators in processes
|
171
|
+
process_factors = check_virtualization_processes(device_data[:processes])
|
172
|
+
factors.concat(process_factors)
|
173
|
+
risk_score += process_factors.length * 10
|
174
|
+
|
175
|
+
{
|
176
|
+
factors: factors,
|
177
|
+
risk_score: risk_score
|
178
|
+
}
|
179
|
+
end
|
180
|
+
|
181
|
+
def check_emulator_files(file_system)
|
182
|
+
factors = []
|
183
|
+
suspicious_files = file_system[:suspicious_files] || []
|
184
|
+
system_binaries = file_system[:system_binaries] || []
|
185
|
+
|
186
|
+
all_files = suspicious_files + system_binaries
|
187
|
+
|
188
|
+
EMULATOR_FILES.each do |emulator_file|
|
189
|
+
if all_files.any? { |file| file.to_s.include?(emulator_file) }
|
190
|
+
case emulator_file
|
191
|
+
when /qemu/i
|
192
|
+
factors << "EMULATOR_QEMU"
|
193
|
+
when /genyd/i
|
194
|
+
factors << "EMULATOR_GENYMOTION"
|
195
|
+
when /microvirt/i
|
196
|
+
factors << "EMULATOR_MICROVIRT"
|
197
|
+
else
|
198
|
+
factors << "EMULATOR_FILE_DETECTED"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
factors
|
204
|
+
end
|
205
|
+
|
206
|
+
def check_system_properties(system_info)
|
207
|
+
factors = []
|
208
|
+
|
209
|
+
# Check build fingerprint
|
210
|
+
fingerprint = system_info[:build_fingerprint].to_s
|
211
|
+
if fingerprint.include?("generic") || fingerprint.include?("emulator")
|
212
|
+
factors << "EMULATOR_BUILD_GENERIC"
|
213
|
+
end
|
214
|
+
|
215
|
+
# Check kernel version for emulator indicators
|
216
|
+
kernel_version = system_info[:kernel_version].to_s
|
217
|
+
if kernel_version.include?("goldfish") || kernel_version.include?("ranchu")
|
218
|
+
factors << "EMULATOR_KERNEL_GOLDFISH"
|
219
|
+
end
|
220
|
+
|
221
|
+
factors
|
222
|
+
end
|
223
|
+
|
224
|
+
def check_emulator_packages(packages)
|
225
|
+
factors = []
|
226
|
+
package_names = packages.map { |pkg| pkg.is_a?(Hash) ? pkg["name"] : pkg.to_s }
|
227
|
+
|
228
|
+
EMULATOR_PACKAGES.each do |emulator_pkg|
|
229
|
+
if package_names.any? { |pkg| pkg&.include?(emulator_pkg) }
|
230
|
+
case emulator_pkg
|
231
|
+
when /genymotion/i
|
232
|
+
factors << "EMULATOR_GENYMOTION"
|
233
|
+
when /bluestacks/i
|
234
|
+
factors << "EMULATOR_BLUESTACKS"
|
235
|
+
when /nox/i
|
236
|
+
factors << "EMULATOR_NOX"
|
237
|
+
when /memu/i
|
238
|
+
factors << "EMULATOR_MEMU"
|
239
|
+
else
|
240
|
+
factors << "EMULATOR_PACKAGE_DETECTED"
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
factors
|
246
|
+
end
|
247
|
+
|
248
|
+
def check_hardware_indicators(hardware_info)
|
249
|
+
factors = []
|
250
|
+
|
251
|
+
device_model = hardware_info[:device_model].to_s.downcase
|
252
|
+
manufacturer = hardware_info[:manufacturer].to_s.downcase
|
253
|
+
|
254
|
+
# Check for generic device names
|
255
|
+
if device_model.include?("generic") || device_model.include?("emulator")
|
256
|
+
factors << "EMULATOR_GENERIC_DEVICE"
|
257
|
+
end
|
258
|
+
|
259
|
+
# Check for known emulator manufacturers
|
260
|
+
if manufacturer.include?("genymotion") || manufacturer.include?("android")
|
261
|
+
factors << "EMULATOR_MANUFACTURER"
|
262
|
+
end
|
263
|
+
|
264
|
+
# Check serial number patterns
|
265
|
+
serial = hardware_info[:serial_number].to_s
|
266
|
+
if serial.include?("android") || serial == "unknown"
|
267
|
+
factors << "EMULATOR_SERIAL_PATTERN"
|
268
|
+
end
|
269
|
+
|
270
|
+
factors
|
271
|
+
end
|
272
|
+
|
273
|
+
def check_baseband_presence(hardware_info)
|
274
|
+
baseband_version = hardware_info[:baseband_version]
|
275
|
+
|
276
|
+
# Missing or null baseband is common in emulators
|
277
|
+
if baseband_version.nil? || baseband_version.to_s.empty? || baseband_version.to_s.downcase.include?("unknown")
|
278
|
+
return "MISSING_BASEBAND"
|
279
|
+
end
|
280
|
+
|
281
|
+
nil
|
282
|
+
end
|
283
|
+
|
284
|
+
def check_sensor_anomalies(hardware_info)
|
285
|
+
factors = []
|
286
|
+
sensors = hardware_info[:sensors] || []
|
287
|
+
|
288
|
+
# Check for missing common sensors
|
289
|
+
common_sensors = %w[accelerometer gyroscope magnetometer proximity light]
|
290
|
+
missing_sensors = common_sensors - sensors.map(&:downcase)
|
291
|
+
|
292
|
+
if missing_sensors.length >= 3
|
293
|
+
factors << "VIRTUAL_SENSORS"
|
294
|
+
end
|
295
|
+
|
296
|
+
# Check for emulator-specific sensor patterns
|
297
|
+
if sensors.any? { |sensor| sensor.to_s.downcase.include?("goldfish") }
|
298
|
+
factors << "EMULATOR_GOLDFISH_SENSORS"
|
299
|
+
end
|
300
|
+
|
301
|
+
factors
|
302
|
+
end
|
303
|
+
|
304
|
+
def check_virtualization_processes(processes)
|
305
|
+
factors = []
|
306
|
+
process_names = processes.map { |proc| proc.is_a?(Hash) ? proc["name"] : proc.to_s }
|
307
|
+
|
308
|
+
virtualization_indicators = %w[
|
309
|
+
qemu
|
310
|
+
vbox
|
311
|
+
vmware
|
312
|
+
virtualbox
|
313
|
+
genymotion
|
314
|
+
bluestacks
|
315
|
+
nox
|
316
|
+
memu
|
317
|
+
ldplayer
|
318
|
+
]
|
319
|
+
|
320
|
+
virtualization_indicators.each do |indicator|
|
321
|
+
if process_names.any? { |proc| proc&.downcase&.include?(indicator) }
|
322
|
+
factors << "VIRTUALIZATION_PROCESS_DETECTED"
|
323
|
+
break
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
factors
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|