dex-oracle 1.0.4 → 1.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +6 -1
- data/Gemfile.lock +36 -27
- data/LICENSE.txt +1 -1
- data/README.md +43 -8
- data/bin/dex-oracle +1 -1
- data/dex-oracle.gemspec +1 -1
- data/driver/src/main/java/org/cf/oracle/Driver.java +2 -3
- data/lib/dex-oracle/driver.rb +119 -78
- data/lib/dex-oracle/plugin.rb +7 -7
- data/lib/dex-oracle/plugins/bitwise_antiskid.rb +61 -0
- data/lib/dex-oracle/plugins/string_decryptor.rb +6 -6
- data/lib/dex-oracle/plugins/undexguard.rb +28 -23
- data/lib/dex-oracle/plugins/unreflector.rb +25 -28
- data/lib/dex-oracle/resources.rb +4 -2
- data/lib/dex-oracle/smali_file.rb +30 -8
- data/lib/dex-oracle/smali_input.rb +27 -21
- data/lib/dex-oracle/version.rb +1 -1
- data/lib/oracle.rb +21 -4
- data/res/driver.dex +0 -0
- data/res/dx.jar +0 -0
- data/spec/data/plugins/clinit.smali +14 -0
- data/spec/dex-oracle/driver_spec.rb +1 -2
- data/spec/dex-oracle/plugins/string_decryptor_spec.rb +11 -0
- data/spec/dex-oracle/plugins/unreflector_spec.rb +1 -1
- data/spec/dex-oracle/smali_file_spec.rb +2 -1
- data/spec/dex-oracle/smali_input_spec.rb +12 -3
- data/spec/spec_helper.rb +3 -0
- data/update_driver +7 -1
- metadata +9 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 29bb9c9cc9066101108219d27ca91119656d9a17
|
4
|
+
data.tar.gz: 9d709fc5ca90c911a3898a1ed0e67767fb9e44b4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d9eac46906217db1dcc2fcf4dc20a93a0a4ab98afb94fe523b2080c8a023a8acef3c55a5219fd19ff2b28a2c14cd36dabd626e418b24cdd7b8ae3dc3224fe8f6
|
7
|
+
data.tar.gz: 18057a22e1d35a71d34bc12da469929f2ce6d71924a806f4a43fbc3f1c42f87e16b146e5788854f0fae78230d8cd1724a6d474c24e4f97a659163e42ec9c0414
|
data/Gemfile
CHANGED
@@ -1,4 +1,9 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
gemspec name: 'dex-oracle'
|
3
3
|
|
4
|
-
|
4
|
+
group :development, :test do
|
5
|
+
gem 'rubocop', require: false
|
6
|
+
gem 'codeclimate-test-reporter', require: nil
|
7
|
+
gem 'fakefs', require: 'fakefs/safe'
|
8
|
+
gem 'rspec'
|
9
|
+
end
|
data/Gemfile.lock
CHANGED
@@ -1,56 +1,65 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
dex-oracle (1.0.
|
4
|
+
dex-oracle (1.0.5)
|
5
5
|
rubyzip (~> 1.1, >= 1.1.0)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
|
-
ast (2.
|
11
|
-
|
12
|
-
|
10
|
+
ast (2.3.0)
|
11
|
+
codeclimate-test-reporter (0.6.0)
|
12
|
+
simplecov (>= 0.7.1, < 1.0.0)
|
13
13
|
diff-lcs (1.2.5)
|
14
|
-
|
15
|
-
|
14
|
+
docile (1.1.5)
|
15
|
+
fakefs (0.9.0)
|
16
|
+
json (2.0.1)
|
17
|
+
parser (2.3.1.2)
|
18
|
+
ast (~> 2.2)
|
16
19
|
powerpack (0.1.1)
|
17
|
-
rainbow (2.
|
18
|
-
rspec (3.
|
19
|
-
rspec-core (~> 3.
|
20
|
-
rspec-expectations (~> 3.
|
21
|
-
rspec-mocks (~> 3.
|
22
|
-
rspec-core (3.
|
23
|
-
rspec-support (~> 3.
|
24
|
-
rspec-expectations (3.
|
20
|
+
rainbow (2.1.0)
|
21
|
+
rspec (3.5.0)
|
22
|
+
rspec-core (~> 3.5.0)
|
23
|
+
rspec-expectations (~> 3.5.0)
|
24
|
+
rspec-mocks (~> 3.5.0)
|
25
|
+
rspec-core (3.5.1)
|
26
|
+
rspec-support (~> 3.5.0)
|
27
|
+
rspec-expectations (3.5.0)
|
25
28
|
diff-lcs (>= 1.2.0, < 2.0)
|
26
|
-
rspec-support (~> 3.
|
29
|
+
rspec-support (~> 3.5.0)
|
27
30
|
rspec-its (1.2.0)
|
28
31
|
rspec-core (>= 3.0.0)
|
29
32
|
rspec-expectations (>= 3.0.0)
|
30
|
-
rspec-mocks (3.
|
33
|
+
rspec-mocks (3.5.0)
|
31
34
|
diff-lcs (>= 1.2.0, < 2.0)
|
32
|
-
rspec-support (~> 3.
|
33
|
-
rspec-support (3.
|
34
|
-
rubocop (0.
|
35
|
-
|
36
|
-
parser (>= 2.2.3.0, < 3.0)
|
35
|
+
rspec-support (~> 3.5.0)
|
36
|
+
rspec-support (3.5.0)
|
37
|
+
rubocop (0.41.2)
|
38
|
+
parser (>= 2.3.1.1, < 3.0)
|
37
39
|
powerpack (~> 0.1)
|
38
40
|
rainbow (>= 1.99.1, < 3.0)
|
39
41
|
ruby-progressbar (~> 1.7)
|
40
|
-
|
41
|
-
ruby-progressbar (1.
|
42
|
-
rubyzip (1.
|
43
|
-
|
42
|
+
unicode-display_width (~> 1.0, >= 1.0.1)
|
43
|
+
ruby-progressbar (1.8.1)
|
44
|
+
rubyzip (1.2.0)
|
45
|
+
simplecov (0.12.0)
|
46
|
+
docile (~> 1.1.0)
|
47
|
+
json (>= 1.8, < 3)
|
48
|
+
simplecov-html (~> 0.10.0)
|
49
|
+
simplecov-html (0.10.0)
|
50
|
+
unicode-display_width (1.1.0)
|
44
51
|
|
45
52
|
PLATFORMS
|
46
53
|
ruby
|
47
54
|
|
48
55
|
DEPENDENCIES
|
56
|
+
codeclimate-test-reporter
|
49
57
|
dex-oracle!
|
50
|
-
|
58
|
+
fakefs
|
59
|
+
rspec
|
51
60
|
rspec-its (~> 1.2, >= 1.2.0)
|
52
61
|
rspec-mocks (~> 3.4, >= 3.4.0)
|
53
62
|
rubocop
|
54
63
|
|
55
64
|
BUNDLED WITH
|
56
|
-
1.
|
65
|
+
1.12.5
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,29 +1,50 @@
|
|
1
1
|
# Oracle
|
2
|
+
|
2
3
|
A pattern based Dalvik deobfuscator which uses limited execution to improve semantic analysis. Also, the inspiration for another Android deobfuscator: [Simplify](https://github.com/CalebFenton/simplify).
|
3
4
|
|
5
|
+
[![Gem Version](https://badge.fury.io/rb/dex-oracle.svg)](https://badge.fury.io/rb/dex-oracle)
|
6
|
+
[![Code Climate](https://codeclimate.com/github/CalebFenton/dex-oracle/badges/gpa.svg)](https://codeclimate.com/github/CalebFenton/dex-oracle)
|
7
|
+
[![Test Coverage](https://codeclimate.com/github/CalebFenton/dex-oracle/badges/coverage.svg)](https://codeclimate.com/github/CalebFenton/dex-oracle/coverage)
|
8
|
+
|
4
9
|
**Before**
|
5
10
|
![before](http://i.imgur.com/nICE4N4.png)
|
6
11
|
|
7
12
|
**After**
|
8
13
|
![after](http://i.imgur.com/aFFd9eM.png)
|
9
14
|
|
10
|
-
|
15
|
+
_sha1: a68d5d2da7550d35f7dbefc21b7deebe3f4005f3_
|
16
|
+
|
17
|
+
_md5: 2dd2eeeda08ac8c15be8a9f2d01adbe8_
|
11
18
|
|
12
19
|
## Installation
|
13
20
|
|
14
21
|
### Step 1. Install Smali / Baksmali
|
15
|
-
|
22
|
+
|
23
|
+
Since you're an elite Android reverser, I'm sure you already have Smali and Baksmali on your path. If for some strange reason it's not already installed, this should get you started, but please examine it carefully before running:
|
24
|
+
|
25
|
+
```bash
|
26
|
+
mkdir ~/bin || cd ~/bin
|
27
|
+
curl --location -O https://bitbucket.org/JesusFreke/smali/downloads/smali-2.1.2.jar && mv smali-*.jar smali.jar
|
28
|
+
curl --location -O https://bitbucket.org/JesusFreke/smali/downloads/baksmali-2.1.2.jar && mv baksmali-*.jar baksmali.jar
|
29
|
+
curl --location -O https://bitbucket.org/JesusFreke/smali/downloads/smali
|
30
|
+
curl --location -O https://bitbucket.org/JesusFreke/smali/downloads/baksmali
|
31
|
+
chmod +x ./smali ./baksmali
|
32
|
+
export PATH=$PATH:$PWD
|
33
|
+
```
|
16
34
|
|
17
35
|
### Step 2. Install Android SDK / ADB
|
36
|
+
|
18
37
|
Make sure `adb` is on your path.
|
19
38
|
|
20
39
|
### Step 3. Install the Gem
|
21
|
-
|
40
|
+
|
41
|
+
```bash
|
22
42
|
gem install dex-oracle
|
23
43
|
```
|
24
44
|
|
25
45
|
Or, if you prefer to build from source:
|
26
|
-
|
46
|
+
|
47
|
+
```bash
|
27
48
|
git clone https://github.com/CalebFenton/dex-oracle.git
|
28
49
|
cd dex-oracle
|
29
50
|
gem install bundler
|
@@ -31,16 +52,19 @@ bundle install
|
|
31
52
|
```
|
32
53
|
|
33
54
|
### Step 4. Connect a Device or Emulator
|
55
|
+
|
34
56
|
_You must have either an emulator running or a device plugged in for Oracle to work._
|
35
57
|
|
36
58
|
Oracle needs to execute methods on an live Android system. This can either be on a device or an emulator (preferred). If it's a device, _make sure you don't mind running potentially hostile code on it_.
|
37
59
|
|
38
60
|
If you'd like to use an emulator, and already have the Android SDK installed, you can create and start emulator images with:
|
39
|
-
|
61
|
+
|
62
|
+
```bash
|
40
63
|
android avd
|
41
64
|
```
|
42
65
|
|
43
66
|
## Usage
|
67
|
+
|
44
68
|
```
|
45
69
|
Usage: dex-oracle [opts] <APK / DEX / Smali Directory>
|
46
70
|
-h, --help Display this screen
|
@@ -58,18 +82,20 @@ Usage: dex-oracle [opts] <APK / DEX / Smali Directory>
|
|
58
82
|
|
59
83
|
For example, to only deobfuscate methods in a class called `Lcom/android/system/admin/CCOIoll;` inside of an APK called `obad.apk`:
|
60
84
|
|
61
|
-
```
|
85
|
+
```bash
|
62
86
|
dex-oracle -i com/android/system/admin/CCOIoll obad.apk
|
63
87
|
```
|
64
88
|
|
65
89
|
## How it Works
|
90
|
+
|
66
91
|
Oracle takes Android apps (APK), Dalvik executables (DEX), and Smali files as inputs. First, if the input is an APK or DEX, it is disassembled into Smali files. Then, the Smali files are passed to various plugins which perform analysis and modifications. Plugins search for patterns which can be transformed into something easier to read. In order to understand what the code is doing, some Dalvik methods are actually executed with and the output is collected. This way, some method calls can be replaced with constants. After that, all of the Smali files are updated. Finally, if the input was an APK or a DEX file, the modified Smali files are recompiled and an updated APK or DEX is created.
|
67
92
|
|
68
|
-
Method execution is performed by the [Driver](driver/src/main/java/org/cf/oracle/Driver.java). The input APK, DEX, or Smali is combined with the Driver into a single DEX using dexmerge and pushed onto a device or emulator.
|
93
|
+
Method execution is performed by the [Driver](driver/src/main/java/org/cf/oracle/Driver.java). The input APK, DEX, or Smali is combined with the Driver into a single DEX using dexmerge and is pushed onto a device or emulator. Plugins can then use Driver which uses Java reflection to execute methods from the input DEX. The return values can be used to improve semantic analysis beyond mere pattern recognition. This is especially useful for many string decryption methods, which usually take an encrypted string or some byte array. One limitation is that execution is limited to static methods.
|
69
94
|
|
70
95
|
## Hacking
|
71
96
|
|
72
97
|
### Creating Your Own Plugin
|
98
|
+
|
73
99
|
There are three [plugins](lib/dex-oracle/plugins) which come with Oracle:
|
74
100
|
|
75
101
|
1. [Undexguard](lib/dex-oracle/plugins/undexguard.rb) - removes certain types of Dexguard obfuscations
|
@@ -93,10 +119,19 @@ The included plugins should be a good guide for understanding steps #3 and #4. D
|
|
93
119
|
Of course, you're always welcome to share whatever obfuscation you come across and someone may eventually get to it.
|
94
120
|
|
95
121
|
### Updating Driver
|
122
|
+
|
96
123
|
First, ensure `dx` is on your path. This is part of the Android SDK, but it's probably not on your path unless you're hardcore.
|
97
124
|
|
98
125
|
The [driver](driver) folder is a Java project managed by Gradle. Import it into Eclipse, IntelliJ, etc. and make any changes you like. To finish updating the driver, run `./update_driver`. This will rebuild the driver and convert the output JAR into a DEX.
|
99
126
|
|
127
|
+
### Troubleshooting
|
128
|
+
|
129
|
+
If there's a problem executing driver code on your emulator or device, be sure to open `monitor` (part of the Android SDK) and check for any clues there. Even if the error doesn't make sense to you, it'll help if you post it along with the issue you'll create.
|
130
|
+
|
131
|
+
Not all Android platforms work well with dex-oracle. Some of them just crap out when trying to execute arbitrary DEX files. If you're having trouble with Segfaults or driver crashes, try using Android 4.4.2 API level 19 with ARM.
|
132
|
+
|
133
|
+
It's possible that a plugin sees a pattern it thinks is obfuscation but is actually some code it shouldn't execute. This seems unlikely because the obfuscation patterns are really unusual, but it is possible. If you're finding a particular plugin is causing problems and you're sure the app isn't protected by that particular obfuscator, i.e. the app is not DexGuarded but the DexGuard plugin is trying to execute stuff, just disable it.
|
134
|
+
|
100
135
|
## More Information
|
101
136
|
|
102
|
-
[TetCon 2016 Android Deobfuscation Presentation](http://www.slideshare.net/tekproxy/tetcon-2016)
|
137
|
+
1. [TetCon 2016 Android Deobfuscation Presentation](http://www.slideshare.net/tekproxy/tetcon-2016)
|
data/bin/dex-oracle
CHANGED
@@ -29,7 +29,7 @@ optparse = OptionParser.new do |opts|
|
|
29
29
|
options[:device_id] = id
|
30
30
|
end
|
31
31
|
|
32
|
-
opts.on('-t', '--timeout N',
|
32
|
+
opts.on('-t', '--timeout N', Integer,
|
33
33
|
"ADB command execution timeout in seconds, default=\"#{options[:timeout]}\"") do |id|
|
34
34
|
options[:timeout] = id
|
35
35
|
end
|
data/dex-oracle.gemspec
CHANGED
@@ -22,7 +22,7 @@ EOF
|
|
22
22
|
[:development, 'rspec-mocks', '~> 3.4', '>= 3.4.0'],
|
23
23
|
]
|
24
24
|
|
25
|
-
exclude_files = Dir['driver/build/**/*'] + Dir['driver/bin/**/*'] + Dir['sandbox/**/*'] +
|
25
|
+
exclude_files = Dir['driver/build/**/*'] + Dir['driver/bin/**/*'] + Dir['sandbox/**/*'] + %w(driver/build driver/bin sandbox)
|
26
26
|
s.files = Dir['**/*'] - exclude_files
|
27
27
|
s.test_files = Dir['test/**/*'] + Dir['spec/**/*']
|
28
28
|
s.executables = Dir['bin/*'].map { |f| File.basename(f) }
|
@@ -20,7 +20,6 @@ import com.google.gson.GsonBuilder;
|
|
20
20
|
public class Driver {
|
21
21
|
|
22
22
|
private static final String DRIVER_DIR = "/data/local";
|
23
|
-
|
24
23
|
private static final String OUTPUT_HEADER = "===ORACLE DRIVER OUTPUT===\n";
|
25
24
|
private static final String EXCEPTION_LOG = DRIVER_DIR + "/od-exception.txt";
|
26
25
|
private static final String OUTPUT_FILE = DRIVER_DIR + "/od-output.json";
|
@@ -66,8 +65,8 @@ public class Driver {
|
|
66
65
|
}
|
67
66
|
|
68
67
|
private static void showUsage() {
|
69
|
-
System.out.println("Usage: export CLASSPATH=/data/local/od.zip; app_process /system/bin org.cf.driver.
|
70
|
-
System.out.println(" export CLASSPATH=/data/local/od.zip; app_process /system/bin org.cf.driver.
|
68
|
+
System.out.println("Usage: export CLASSPATH=/data/local/od.zip; app_process /system/bin org.cf.driver.Driver <class> <method> [<parameter type>:<parameter value json>]");
|
69
|
+
System.out.println(" export CLASSPATH=/data/local/od.zip; app_process /system/bin org.cf.driver.Driver @<json file>");
|
71
70
|
}
|
72
71
|
|
73
72
|
public static void main(String[] args) {
|
data/lib/dex-oracle/driver.rb
CHANGED
@@ -12,14 +12,13 @@ class Driver
|
|
12
12
|
UNESCAPES = {
|
13
13
|
'a' => "\x07", 'b' => "\x08", 't' => "\x09",
|
14
14
|
'n' => "\x0a", 'v' => "\x0b", 'f' => "\x0c",
|
15
|
-
'r' => "\x0d", 'e' => "\x1b", '\\' =>
|
16
|
-
'"' => "
|
17
|
-
}
|
15
|
+
'r' => "\x0d", 'e' => "\x1b", '\\' => '\\',
|
16
|
+
'"' => '"', "'" => "'"
|
17
|
+
}.freeze
|
18
18
|
UNESCAPE_REGEX = /\\(?:([#{UNESCAPES.keys.join}])|u([\da-fA-F]{4}))|\\0?x([\da-fA-F]{2})/
|
19
19
|
|
20
|
-
OUTPUT_HEADER = '===ORACLE DRIVER OUTPUT==='
|
21
|
-
|
22
|
-
DRIVER_CLASS = 'org.cf.oracle.Driver'
|
20
|
+
OUTPUT_HEADER = '===ORACLE DRIVER OUTPUT==='.freeze
|
21
|
+
DRIVER_CLASS = 'org.cf.oracle.Driver'.freeze
|
23
22
|
|
24
23
|
def initialize(device_id, timeout = 60)
|
25
24
|
@device_id = device_id
|
@@ -27,36 +26,61 @@ class Driver
|
|
27
26
|
|
28
27
|
device_str = device_id.empty? ? '' : "-s #{@device_id} "
|
29
28
|
@adb_base = "adb #{device_str}%s"
|
30
|
-
@
|
29
|
+
@driver_dir = get_driver_dir
|
30
|
+
unless @driver_dir
|
31
|
+
logger.error 'Unable to find writable driver directory. Make sure /data/local or /data/local/tmp exists and is writable.'
|
32
|
+
exit -1
|
33
|
+
end
|
34
|
+
logger.debug "Using #{@driver_dir} as driver directory ..."
|
35
|
+
@cmd_stub = "export CLASSPATH=#{@driver_dir}/od.zip; app_process /system/bin #{DRIVER_CLASS}"
|
31
36
|
|
32
37
|
@cache = {}
|
33
38
|
end
|
34
39
|
|
35
40
|
def install(dex)
|
36
|
-
|
41
|
+
has_java = Utility.which('java')
|
42
|
+
raise 'Unable to find Java on the path.' unless has_java
|
37
43
|
|
38
44
|
begin
|
39
45
|
# Merge driver and target dex file
|
40
46
|
# Congratulations. You're now one of the 5 people who've used this tool explicitly.
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
tf = Tempfile.new(
|
47
|
+
raise "#{Resources.dx} does not exist and is required for DexMerger" unless File.exist?(Resources.dx)
|
48
|
+
raise "#{Resources.driver_dex} does not exist" unless File.exist?(Resources.driver_dex)
|
49
|
+
logger.debug("Merging input DEX (#{dex.path}) and driver DEX (#{Resources.driver_dex}) ...")
|
50
|
+
tf = Tempfile.new(%w(oracle-driver .dex))
|
45
51
|
cmd = "java -cp #{Resources.dx} com.android.dx.merge.DexMerger #{tf.path} #{dex.path} #{Resources.driver_dex}"
|
46
|
-
exec(
|
52
|
+
stdout, stderr = exec(cmd)
|
53
|
+
# Ideally, it says something like this:
|
54
|
+
# Merged dex #1 (36 defs/87.9KiB)
|
55
|
+
# Merged dex #2 (1225 defs/1092.7KiB)
|
56
|
+
# Result is 1261 defs/1375.0KiB. Took 0.2s
|
57
|
+
# But it might say this:
|
58
|
+
# Exception in thread "main" com.android.dex.DexIndexOverflowException: Cannot merge new index 65776 into a non-jumbo instruction!
|
59
|
+
if stderr.start_with?('Exception in thread "main"')
|
60
|
+
logger.error("Failure to merge input DEX and driver DEX:\n#{stderr}")
|
61
|
+
if stderr.include?('DexIndexOverflowException')
|
62
|
+
logger.error("Your input DEX inexplicably contains const-string and const-string/jumbo. This probably means someone fucked with it. In any case, it means DexMerge is failing because there are too many strings.\nTry this: baksmali the DEX, replace all const-string instructions with const-string/jumbo, then recompile with smali and use that DEX as input. Sorry, I don't want to do this for you. It's too complicated.")
|
63
|
+
end
|
64
|
+
exit -1
|
65
|
+
end
|
66
|
+
tf.close
|
47
67
|
|
48
68
|
# Zip merged dex and push to device
|
49
69
|
logger.debug('Pushing merged driver to device ...')
|
50
|
-
tz = Tempfile.new(
|
51
|
-
|
52
|
-
|
70
|
+
tz = Tempfile.new(%w(oracle-driver .zip))
|
71
|
+
# Could pass tz to create_zip, but Windows doesn't let you rename if file open
|
72
|
+
# And zip internally renames files when creating them
|
73
|
+
tempzip_path = tz.path
|
74
|
+
tz.close
|
75
|
+
Utility.create_zip(tempzip_path, 'classes.dex' => tf)
|
76
|
+
adb("push #{tz.path} #{@driver_dir}/od.zip")
|
53
77
|
rescue => e
|
54
|
-
puts "Error installing
|
78
|
+
puts "Error installing driver: #{e}\n#{e.backtrace.join("\n\t")}"
|
55
79
|
ensure
|
56
|
-
tf.close
|
57
|
-
tf.unlink
|
58
|
-
tz.close
|
59
|
-
tz.unlink
|
80
|
+
tf.close if tf
|
81
|
+
tf.unlink if tf
|
82
|
+
tz.close if tz
|
83
|
+
tz.unlink if tz
|
60
84
|
end
|
61
85
|
end
|
62
86
|
|
@@ -71,15 +95,14 @@ class Driver
|
|
71
95
|
# If you slam an emulator or device with too many app_process commands,
|
72
96
|
# it eventually gets angry and segmentation faults. No idea why.
|
73
97
|
# This took many frustrating hours to figure out.
|
74
|
-
if retries
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
raise e
|
81
|
-
end
|
98
|
+
raise e if retries > 3
|
99
|
+
|
100
|
+
logger.debug("Driver execution failed. Taking a quick nap and retrying, Zzzzz ##{retries} / 3 ...")
|
101
|
+
sleep 5
|
102
|
+
retries += 1
|
103
|
+
retry
|
82
104
|
end
|
105
|
+
|
83
106
|
output
|
84
107
|
end
|
85
108
|
|
@@ -87,17 +110,15 @@ class Driver
|
|
87
110
|
push_batch_targets(batch)
|
88
111
|
retries = 1
|
89
112
|
begin
|
90
|
-
drive("#{@cmd_stub} @#{
|
113
|
+
drive("#{@cmd_stub} @#{@driver_dir}/od-targets.json", true)
|
91
114
|
rescue => e
|
92
|
-
if retries
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
raise e
|
100
|
-
end
|
115
|
+
raise e if retries > 3 || !e.message.include?('Segmentation fault')
|
116
|
+
|
117
|
+
# Maybe we just need to retry
|
118
|
+
logger.debug("Driver execution segfaulted. Taking a quick nap and retrying, Zzzzz ##{retries} / 3 ...")
|
119
|
+
sleep 5
|
120
|
+
retries += 1
|
121
|
+
retry
|
101
122
|
end
|
102
123
|
pull_batch_outputs
|
103
124
|
end
|
@@ -107,7 +128,7 @@ class Driver
|
|
107
128
|
target = {
|
108
129
|
className: method.class.tr('/', '.'),
|
109
130
|
methodName: method.name,
|
110
|
-
arguments:
|
131
|
+
arguments: build_arguments(method.parameters, args)
|
111
132
|
}
|
112
133
|
# Identifiers are used to map individual inputs to outputs
|
113
134
|
target[:id] = Digest::SHA256.hexdigest(target.to_json)
|
@@ -118,20 +139,33 @@ class Driver
|
|
118
139
|
private
|
119
140
|
|
120
141
|
def push_batch_targets(batch)
|
121
|
-
target_file = Tempfile.new(
|
142
|
+
target_file = Tempfile.new(%w(oracle-targets .json))
|
122
143
|
target_file << batch.to_json
|
123
144
|
target_file.flush
|
124
145
|
logger.info("Pushing #{batch.size} method targets to device ...")
|
125
|
-
adb("push #{target_file.path} #{
|
146
|
+
adb("push #{target_file.path} #{@driver_dir}/od-targets.json")
|
126
147
|
target_file.close
|
127
148
|
target_file.unlink
|
128
149
|
end
|
129
150
|
|
151
|
+
def get_driver_dir
|
152
|
+
# On some older devices, /data/local is world writable
|
153
|
+
# But on other devices, it's /data/local/tmp
|
154
|
+
%w(/data/local /data/local/tmp).each do |dir|
|
155
|
+
stdout = adb("shell -x ls #{dir}")
|
156
|
+
next if stdout == "ls: #{dir}: Permission denied"
|
157
|
+
next if stdout == "ls: #{dir}: No such file or directory"
|
158
|
+
return dir
|
159
|
+
end
|
160
|
+
|
161
|
+
nil
|
162
|
+
end
|
163
|
+
|
130
164
|
def pull_batch_outputs
|
131
165
|
output_file = Tempfile.new(['oracle-output', '.json'])
|
132
166
|
logger.debug('Pulling batch results from device ...')
|
133
|
-
adb("pull #{
|
134
|
-
adb("shell rm #{
|
167
|
+
adb("pull #{@driver_dir}/od-output.json #{output_file.path}")
|
168
|
+
adb("shell rm #{@driver_dir}/od-output.json")
|
135
169
|
outputs = JSON.parse(File.read(output_file.path))
|
136
170
|
outputs.each { |_, (_, v2)| v2.gsub!(/(?:^"|"$)/, '') if v2.start_with?('"') }
|
137
171
|
logger.debug("Pulled #{outputs.size} outputs.")
|
@@ -146,21 +180,19 @@ class Driver
|
|
146
180
|
retries = 1
|
147
181
|
begin
|
148
182
|
status = Timeout.timeout(@timeout) do
|
149
|
-
if
|
150
|
-
|
183
|
+
if silent
|
184
|
+
Open3.popen3(cmd) { |_, stdout, stderr, _| [stdout.read, stderr.read] }
|
151
185
|
else
|
152
|
-
|
186
|
+
[`#{cmd}`, '']
|
153
187
|
end
|
154
188
|
end
|
155
189
|
rescue => e
|
156
|
-
if retries
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
raise e
|
163
|
-
end
|
190
|
+
raise e if retries > 3
|
191
|
+
|
192
|
+
logger.debug("ADB command execution timed out, retrying #{retries} ...")
|
193
|
+
sleep 5
|
194
|
+
retries += 1
|
195
|
+
retry
|
164
196
|
end
|
165
197
|
end
|
166
198
|
|
@@ -170,14 +202,14 @@ class Driver
|
|
170
202
|
if exit_code != 0
|
171
203
|
# Non zero exit code would only imply adb command itself was flawed
|
172
204
|
# app_process, dalvikvm, etc. don't propigate exit codes back
|
173
|
-
|
205
|
+
raise "Command failed with #{exit_code}: #{full_cmd}\nOutput: #{full_output}"
|
174
206
|
end
|
175
207
|
|
176
208
|
# Successful driver run should include driver header
|
177
209
|
# Otherwise it may be a Segmentation fault or Killed
|
178
210
|
logger.debug("Full output: #{full_output.inspect}")
|
179
211
|
header = output_lines[0]
|
180
|
-
|
212
|
+
raise "app_process execution failure, output: '#{full_output}'" if header != OUTPUT_HEADER
|
181
213
|
|
182
214
|
output_lines[1..-2].join("\n").rstrip
|
183
215
|
end
|
@@ -192,10 +224,10 @@ class Driver
|
|
192
224
|
# The driver writes any actual exceptions to the filesystem
|
193
225
|
# Need to check to make sure the output value is legitimate
|
194
226
|
logger.debug('Checking if execution had any exceptions ...')
|
195
|
-
exception = adb("shell cat #{
|
227
|
+
exception = adb("shell cat #{@driver_dir}/od-exception.txt").strip
|
196
228
|
unless exception.end_with?('No such file or directory')
|
197
|
-
adb("shell rm #{
|
198
|
-
|
229
|
+
adb("shell rm #{@driver_dir}/od-exception.txt")
|
230
|
+
raise exception
|
199
231
|
end
|
200
232
|
logger.debug('No exceptions found :)')
|
201
233
|
|
@@ -207,20 +239,13 @@ class Driver
|
|
207
239
|
end
|
208
240
|
|
209
241
|
def adb(cmd)
|
210
|
-
|
211
|
-
exec(full_cmd).rstrip
|
242
|
+
adb_with_stderr(cmd)[0]
|
212
243
|
end
|
213
244
|
|
214
|
-
def
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
elsif Regexp.last_match[2] # escape \u0000 unicode
|
219
|
-
[Regexp.last_match[2].hex].pack('U*')
|
220
|
-
elsif Regexp.last_match[3] # escape \0xff or \xff
|
221
|
-
[Regexp.last_match[3]].pack('H2')
|
222
|
-
end
|
223
|
-
end
|
245
|
+
def adb_with_stderr(cmd)
|
246
|
+
full_cmd = @adb_base % cmd
|
247
|
+
stdout, stderr = exec(full_cmd, false)
|
248
|
+
[stdout.rstrip, stderr.rstrip]
|
224
249
|
end
|
225
250
|
|
226
251
|
def build_command(class_name, method_name, parameters, args)
|
@@ -228,23 +253,39 @@ class Driver
|
|
228
253
|
class_name.gsub!('$', '\$') # inner classes
|
229
254
|
method_name.gsub!('$', '\$') # synthetic method names
|
230
255
|
target = "'#{class_name}' '#{method_name}'"
|
231
|
-
target_args =
|
256
|
+
target_args = build_arguments(parameters, args)
|
232
257
|
"#{@cmd_stub} #{target} #{target_args * ' '}"
|
233
258
|
end
|
234
259
|
|
235
|
-
|
236
|
-
|
237
|
-
|
260
|
+
private
|
261
|
+
|
262
|
+
def unescape(str)
|
263
|
+
str.gsub(UNESCAPE_REGEX) do
|
264
|
+
if Regexp.last_match[1]
|
265
|
+
if Regexp.last_match[1] == '\\'
|
266
|
+
Regexp.last_match[1]
|
267
|
+
else
|
268
|
+
UNESCAPES[Regexp.last_match[1]]
|
269
|
+
end
|
270
|
+
elsif Regexp.last_match[2] # escape \u0000 unicode
|
271
|
+
[Regexp.last_match[2].hex].pack('U*')
|
272
|
+
elsif Regexp.last_match[3] # escape \0xff or \xff
|
273
|
+
[Regexp.last_match[3]].pack('H2')
|
274
|
+
end
|
238
275
|
end
|
239
276
|
end
|
240
277
|
|
241
|
-
def
|
278
|
+
def build_arguments(parameters, args)
|
279
|
+
parameters.map.with_index { |o, i| build_argument(o, args[i]) }
|
280
|
+
end
|
281
|
+
|
282
|
+
def build_argument(parameter, argument)
|
242
283
|
if parameter[0] == 'L'
|
243
284
|
java_type = parameter[1..-2].tr('/', '.')
|
244
285
|
if java_type == 'java.lang.String'
|
245
286
|
# Need to unescape smali string to get the actual string
|
246
287
|
# Converting to bytes just avoids any weird non-printable characters nonsense
|
247
|
-
argument = "[#{
|
288
|
+
argument = "[#{unescape(argument).bytes.to_a.join(',')}]"
|
248
289
|
end
|
249
290
|
"#{java_type}:#{argument}"
|
250
291
|
else
|