dex-oracle 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.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +4 -0
  3. data/Gemfile.lock +56 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +102 -0
  6. data/bin/dex-oracle +98 -0
  7. data/dex-oracle.gemspec +43 -0
  8. data/driver/build.gradle +52 -0
  9. data/driver/gradle/wrapper/gradle-wrapper.jar +0 -0
  10. data/driver/gradle/wrapper/gradle-wrapper.properties +6 -0
  11. data/driver/gradlew +160 -0
  12. data/driver/gradlew.bat +90 -0
  13. data/driver/src/main/java/org/cf/oracle/Driver.java +134 -0
  14. data/driver/src/main/java/org/cf/oracle/FileUtils.java +35 -0
  15. data/driver/src/main/java/org/cf/oracle/StackSpoofer.java +42 -0
  16. data/driver/src/main/java/org/cf/oracle/options/InvocationTarget.java +40 -0
  17. data/driver/src/main/java/org/cf/oracle/options/TargetParser.java +121 -0
  18. data/lib/dex-oracle/driver.rb +255 -0
  19. data/lib/dex-oracle/logging.rb +32 -0
  20. data/lib/dex-oracle/plugin.rb +87 -0
  21. data/lib/dex-oracle/plugins/string_decryptor.rb +59 -0
  22. data/lib/dex-oracle/plugins/undexguard.rb +155 -0
  23. data/lib/dex-oracle/plugins/unreflector.rb +85 -0
  24. data/lib/dex-oracle/resources.rb +13 -0
  25. data/lib/dex-oracle/smali_field.rb +21 -0
  26. data/lib/dex-oracle/smali_file.rb +64 -0
  27. data/lib/dex-oracle/smali_input.rb +81 -0
  28. data/lib/dex-oracle/smali_method.rb +33 -0
  29. data/lib/dex-oracle/utility.rb +37 -0
  30. data/lib/dex-oracle/version.rb +3 -0
  31. data/lib/oracle.rb +61 -0
  32. data/res/driver.dex +0 -0
  33. data/res/dx.jar +0 -0
  34. data/spec/data/helloworld.apk +0 -0
  35. data/spec/data/helloworld.dex +0 -0
  36. data/spec/data/plugins/bytes_decrypt.smali +18 -0
  37. data/spec/data/plugins/class_forname.smali +14 -0
  38. data/spec/data/plugins/multi_bytes_decrypt.smali +28 -0
  39. data/spec/data/plugins/string_decrypt.smali +14 -0
  40. data/spec/data/plugins/string_lookup_1int.smali +14 -0
  41. data/spec/data/plugins/string_lookup_3int.smali +18 -0
  42. data/spec/data/smali/helloworld.smali +17 -0
  43. data/spec/dex-oracle/driver_spec.rb +82 -0
  44. data/spec/dex-oracle/plugins/string_decryptor_spec.rb +25 -0
  45. data/spec/dex-oracle/plugins/undexguard_spec.rb +69 -0
  46. data/spec/dex-oracle/plugins/unreflector_spec.rb +29 -0
  47. data/spec/dex-oracle/smali_field_spec.rb +15 -0
  48. data/spec/dex-oracle/smali_file_spec.rb +41 -0
  49. data/spec/dex-oracle/smali_input_spec.rb +90 -0
  50. data/spec/dex-oracle/smali_method_spec.rb +19 -0
  51. data/spec/spec_helper.rb +9 -0
  52. data/update_driver +5 -0
  53. metadata +195 -0
@@ -0,0 +1,28 @@
1
+ .class public Lorg/cf/MultiBytesDecrypt;
2
+ .super Ljava/lang/Object;
3
+
4
+ .method public static doStuff()V
5
+ .locals 3
6
+
7
+ const-string v0, "string1"
8
+
9
+ new-instance v1, Ljava/lang/String;
10
+
11
+ invoke-static {v0}, Lorg/cf/MultiBytesDecrypt;->doThing1(Ljava/lang/String;)[B
12
+
13
+ move-result-object v2
14
+
15
+ const-string v3, "string2"
16
+
17
+ invoke-static {v2, v3}, Lorg/cf/MultiBytesDecrypt;->doThing2([BLjava/lang/String;)[B
18
+
19
+ move-result-object v2
20
+
21
+ invoke-static {v2}, Lorg/cf/MultiBytesDecrypt;->doThing3([B)[B
22
+
23
+ move-result-object v2
24
+
25
+ invoke-direct {v1, v2}, Ljava/lang/String;-><init>([B)V
26
+
27
+ return-void
28
+ .end method
@@ -0,0 +1,14 @@
1
+ .class public Lorg/cf/StringDecrypt;
2
+ .super Ljava/lang/Object;
3
+
4
+ .method public static doStuff()V
5
+ .locals 1
6
+
7
+ const-string v0, "encrypted"
8
+
9
+ invoke-static {v0}, Lorg/cf/StringDecrypt;->decrypt(Ljava/lang/String;)Ljava/lang/String;
10
+
11
+ move-result-object v0
12
+
13
+ return-void
14
+ .end method
@@ -0,0 +1,14 @@
1
+ .class public Lorg/cf/StringLookup;
2
+ .super Ljava/lang/Object;
3
+
4
+ .method public static doStuff()V
5
+ .locals 1
6
+
7
+ const/4 v0, 0x0
8
+
9
+ invoke-static {v0}, Lorg/cf/StringLookup;->lookup(I)Ljava/lang/String;
10
+
11
+ move-result-object v0
12
+
13
+ return-void
14
+ .end method
@@ -0,0 +1,18 @@
1
+ .class public Lorg/cf/StringLookup;
2
+ .super Ljava/lang/Object;
3
+
4
+ .method public static doStuff()V
5
+ .locals 3
6
+
7
+ const/4 v0, 0x0
8
+
9
+ const/4 v1, 0x1
10
+
11
+ const/4 v2, 0x2
12
+
13
+ invoke-static {v0, v1, v2}, Lorg/cf/StringLookup;->lookup(III)Ljava/lang/String;
14
+
15
+ move-result-object v0
16
+
17
+ return-void
18
+ .end method
@@ -0,0 +1,17 @@
1
+ .class public LHelloWorld; # COMMENT;
2
+ .super Ljava/lang/Object; # YEAH ;
3
+ .implements Lsome/Interface1;
4
+ .implements Lsome/Interface2;
5
+
6
+ .field public static final someField:Z
7
+
8
+ .method public static main([Ljava/lang/String;)V
9
+ .locals 2
10
+
11
+ sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
12
+ const-string v1, "hello,world!"
13
+ invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
14
+
15
+ return-void
16
+ .end method
17
+
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ describe Driver do
4
+ let(:temp_file) { instance_double('Tempfile') }
5
+ let(:class_name) { 'some/Klazz' }
6
+ let(:method_signature) { 'run(III)V' }
7
+ let(:args) { [1, 2, 3] }
8
+ let(:batch_id) { '8ea0a5c705617449899c85cec2435356e8be83d6829e12ff109ab0c44c4156c6' }
9
+ let(:batch_item) { { className: 'some.Klazz', methodName: 'run', arguments: %w(I:1 I:2 I:3), id: batch_id } }
10
+ let(:driver) do
11
+ allow(temp_file).to receive(:path).and_return('/fake/tmp/file')
12
+ allow(temp_file).to receive(:unlink)
13
+ allow(temp_file).to receive(:close)
14
+ allow(temp_file).to receive(:flush)
15
+ allow(temp_file).to receive(:<<)
16
+ allow(Tempfile).to receive(:new).and_return(temp_file)
17
+ allow(File).to receive(:open).and_yield(temp_file)
18
+ allow(File).to receive(:read)
19
+ allow(JSON).to receive(:parse)
20
+ Driver.new(device_id)
21
+ end
22
+ let(:driver_stub) { 'export CLASSPATH=/data/local/od.zip; app_process /system/bin org.cf.oracle.Driver' }
23
+
24
+ describe '#make_target' do
25
+ let(:device_id) { '' }
26
+ let(:make_target) { driver.make_target(class_name, method_signature, *args) }
27
+
28
+ subject { make_target }
29
+ it { should eq batch_item }
30
+ end
31
+
32
+ describe '#run_batch' do
33
+ let(:device_id) { '' }
34
+ let(:batch) { [batch_item] }
35
+ let(:run_batch) { driver.run_batch(batch) }
36
+
37
+ subject { run_batch }
38
+ it do
39
+ expect(temp_file).to receive(:<<).with(batch.to_json).ordered
40
+ expect(driver).to receive(:adb).with("push #{temp_file.path} /data/local/od-targets.json")
41
+ expect(driver).to receive(:drive).with("#{driver_stub} @/data/local/od-targets.json", true)
42
+ allow(JSON).to receive(:parse) { {} }
43
+ expect(driver).to receive(:adb).with("pull /data/local/od-output.json #{temp_file.path}")
44
+ expect(driver).to receive(:adb).with('shell rm /data/local/od-output.json')
45
+ subject
46
+ end
47
+ end
48
+
49
+ describe '#run' do
50
+ context 'with a device id' do
51
+ let(:device_id) { '1234abcd' }
52
+
53
+ context 'with integer arguments' do
54
+ subject { driver.run(class_name, method_signature, *args) }
55
+ it do
56
+ allow(driver).to receive(:drive)
57
+ expect(driver).to receive(:drive).with("#{driver_stub} 'some.Klazz' 'run' I:1 I:2 I:3")
58
+ subject
59
+ end
60
+ end
61
+ end
62
+
63
+ context 'without a device id' do
64
+ let(:device_id) { '' }
65
+
66
+ context 'with string argument' do
67
+ let(:class_name) { 'string/Klazz' }
68
+ let(:method_signature) { 'run(Ljava/lang/String;)V' }
69
+ let(:args) { 'hello string' }
70
+
71
+ subject { driver.run(class_name, method_signature, args) }
72
+ it do
73
+ allow(driver).to receive(:drive)
74
+ expect(driver).to receive(:drive).with(
75
+ "#{driver_stub} 'string.Klazz' 'run' java.lang.String:[104,101,108,108,111,32,115,116,114,105,110,103]"
76
+ )
77
+ subject
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe StringDecryptor do
4
+ let(:data_path) { 'spec/data/plugins' }
5
+ let(:driver) { instance_double('Driver') }
6
+ let(:smali_files) { [SmaliFile.new(file_path)] }
7
+ let(:method) { smali_files.first.methods.first }
8
+ let(:batch) { { id: '123' } }
9
+ let(:plugin) { StringDecryptor.new(driver, smali_files, [method]) }
10
+
11
+ describe '#process' do
12
+ subject { plugin.process }
13
+
14
+ context 'with string_decrypt.smali' do
15
+ let(:file_path) { "#{data_path}/string_decrypt.smali" }
16
+ let(:batch_item) { ["const-string v0, \"encrypted\"\n\n invoke-static {v0}, Lorg/cf/StringDecrypt;->decrypt(Ljava/lang/String;)Ljava/lang/String;\n\n move-result-object v0", 'v0'] }
17
+
18
+ it do
19
+ expect(driver).to receive(:make_target).with('org/cf/StringDecrypt', 'decrypt(Ljava/lang/String;)', 'encrypted').and_return(batch)
20
+ expect(Plugin).to receive(:apply_batch).with(driver, { method => { batch => [batch_item] } }, kind_of(Proc))
21
+ subject
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+
3
+ describe Undexguard do
4
+ let(:data_path) { 'spec/data/plugins' }
5
+ let(:driver) { instance_double('Driver') }
6
+ let(:smali_files) { [SmaliFile.new(file_path)] }
7
+ let(:method) { smali_files.first.methods.first }
8
+ let(:batch) { { id: '123' } }
9
+ let(:plugin) { Undexguard.new(driver, smali_files, [method]) }
10
+
11
+ describe '#process' do
12
+ subject { plugin.process }
13
+
14
+ context 'with string_lookup_3int.smali' do
15
+ let(:file_path) { "#{data_path}/string_lookup_3int.smali" }
16
+ let(:batch_item) { ["const/4 v0, 0x0\n\n const/4 v1, 0x1\n\n const/4 v2, 0x2\n\n invoke-static {v0, v1, v2}, Lorg/cf/StringLookup;->lookup(III)Ljava/lang/String;\n\n move-result-object v0", 'v0'] }
17
+
18
+ it do
19
+ expect(driver).to receive(:make_target).with('org/cf/StringLookup', 'lookup(III)', 0, 1, 2).and_return(batch)
20
+ expect(Plugin).to receive(:apply_batch).with(driver, { method => { batch => [batch_item] } }, kind_of(Proc))
21
+ subject
22
+ end
23
+ end
24
+
25
+ context 'with string_lookup_1int.smali' do
26
+ let(:file_path) { "#{data_path}/string_lookup_1int.smali" }
27
+ let(:batch_item) { ["const/4 v0, 0x0\n\n invoke-static {v0}, Lorg/cf/StringLookup;->lookup(I)Ljava/lang/String;\n\n move-result-object v0", 'v0'] }
28
+
29
+ it do
30
+ expect(driver).to receive(:make_target).with('org/cf/StringLookup', 'lookup(I)', 0).and_return(batch)
31
+ expect(Plugin).to receive(:apply_batch).with(driver, { method => { batch => [batch_item] } }, kind_of(Proc))
32
+ subject
33
+ end
34
+ end
35
+
36
+ context 'with bytes_decrypt.smali' do
37
+ let(:file_path) { "#{data_path}/bytes_decrypt.smali" }
38
+ let(:batch_item) { ["const-string v0, \"asdf\"\n\n invoke-virtual {v0}, Ljava/lang/String;->getBytes()[B\n\n move-result-object v0\n\n invoke-static {v0}, Lorg/cf/BytesDecrypt;->decrypt([B)Ljava/lang/String;\n\n move-result-object v0", 'v0'] }
39
+
40
+ it do
41
+ expect(driver).to receive(:make_target).with('org/cf/BytesDecrypt', 'decrypt([B)', [97, 115, 100, 102]).and_return(batch)
42
+ expect(Plugin).to receive(:apply_batch).with(driver, { method => { batch => [batch_item] } }, kind_of(Proc))
43
+ subject
44
+ end
45
+ end
46
+
47
+ context 'with multi_bytes_decrypt.smali' do
48
+ let(:file_path) { "#{data_path}/multi_bytes_decrypt.smali" }
49
+ let(:batch_id) { '9e31b050ec6334ed1c9ec57686b5bd765bd811410bc240141dfc466ef0719bfd' }
50
+ let(:batch) { { id: batch_id } }
51
+ let(:batch_item) { ["const-string v0, \"string1\"\n\n new-instance v1, Ljava/lang/String;\n\n invoke-static {v0}, Lorg/cf/MultiBytesDecrypt;->doThing1(Ljava/lang/String;)[B\n\n move-result-object v2\n\n const-string v3, \"string2\"\n\n invoke-static {v2, v3}, Lorg/cf/MultiBytesDecrypt;->doThing2([BLjava/lang/String;)[B\n\n move-result-object v2\n\n invoke-static {v2}, Lorg/cf/MultiBytesDecrypt;->doThing3([B)[B\n\n move-result-object v2\n\n invoke-direct {v1, v2}, Ljava/lang/String;-><init>([B)V", 'v1'] }
52
+ let(:iv_bytes) { '[1,2,3]' }
53
+ let(:enc_bytes) { '[4,5,6]' }
54
+ let(:dec_bytes) { '[65]' }
55
+
56
+ it do
57
+ expect(driver).to receive(:run).with('org/cf/MultiBytesDecrypt', 'doThing1(Ljava/lang/String;)', 'string1').and_return(iv_bytes)
58
+ expect(driver).to receive(:run).with('org/cf/MultiBytesDecrypt', 'doThing2([BLjava/lang/String;)', iv_bytes, 'string2').and_return(enc_bytes)
59
+ expect(driver).to receive(:run).with('org/cf/MultiBytesDecrypt', 'doThing3([B)', enc_bytes).and_return(dec_bytes)
60
+ expect(Plugin).to receive(:apply_outputs).with(
61
+ { batch_id => %w(success A) },
62
+ { method => { batch => [batch_item] } },
63
+ kind_of(Proc)
64
+ )
65
+ subject
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe Unreflector do
4
+ let(:data_path) { 'spec/data/plugins' }
5
+ let(:driver) { instance_double('Driver') }
6
+ let(:smali_files) { [SmaliFile.new(file_path)] }
7
+ let(:method) { smali_files.first.methods.first }
8
+ let(:plugin) { Unreflector.new(driver, smali_files, [method]) }
9
+
10
+ describe '#process' do
11
+ subject { plugin.process }
12
+
13
+ context 'with class_forname.smali' do
14
+ let(:file_path) { "#{data_path}/class_forname.smali" }
15
+ let(:batch_id) { 'f8dc3df1acedbee81e5ff984eb026b76eb5e4bdf6d401fb12e081b8bcdb0cd55' }
16
+ let(:batch) { { id: batch_id } }
17
+ let(:batch_item) { ["const-string v0, \"android.content.Intent\"\n\n invoke-static {v0}, Ljava/lang/Class;->forName(Ljava/lang/String;)Ljava/lang/Class;\n\n move-result-object v0", 'v0'] }
18
+
19
+ it do
20
+ expect(Plugin).to receive(:apply_outputs).with(
21
+ { batch_id => ['success', 'Landroid/content/Intent;'] },
22
+ { method => { batch => [batch_item] } },
23
+ kind_of(Proc)
24
+ )
25
+ subject
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe SmaliField do
4
+ context 'with simple field' do
5
+ let(:class_name) { 'Lorg/cfg/MyClass;' }
6
+ let(:field_signature) { 'someField:Ljava/lang/Object;' }
7
+ let(:smali_field) { SmaliField.new(class_name, field_signature) }
8
+ subject { smali_field }
9
+
10
+ its(:class) { should eq class_name }
11
+ its(:name) { should eq 'someField' }
12
+ its(:type) { should eq 'Ljava/lang/Object;' }
13
+ its(:descriptor) { should eq "#{class_name}->#{field_signature}" }
14
+ end
15
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe SmaliFile do
4
+ let(:data_path) { 'spec/data/smali' }
5
+
6
+ context 'the hello world smali' do
7
+ let(:file_path) { "#{data_path}/helloworld.smali" }
8
+ let(:smali_file) { SmaliFile.new(file_path) }
9
+ let(:method_body) do
10
+ <<-EOF
11
+
12
+ .locals 2
13
+
14
+ sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
15
+ const-string v1, "hello,world!"
16
+ invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
17
+
18
+ return-void
19
+ EOF
20
+ end
21
+
22
+ subject { smali_file }
23
+ its(:class) { should eq 'LHelloWorld;' }
24
+ its(:super) { should eq 'Ljava/lang/Object;' }
25
+ its(:interfaces) { should eq ['Lsome/Interface1;', 'Lsome/Interface2;'] }
26
+ its(:fields) { should eq [SmaliField.new('LHelloWorld;', 'someField:Z')] }
27
+ its(:methods) { should eq [SmaliMethod.new('LHelloWorld;', 'main([Ljava/lang/String;)V', method_body)] }
28
+
29
+ describe '#update' do
30
+ subject { smali_file.content }
31
+ it 'should update modified methods' do
32
+ allow(File).to receive(:open)
33
+ method = smali_file.methods.first
34
+ method.modified = true
35
+ method.body = "\nreturn-void\n"
36
+ smali_file.update
37
+ should eq ".class public LHelloWorld; # COMMENT;\n.super Ljava/lang/Object; # YEAH ;\n.implements Lsome/Interface1;\n.implements Lsome/Interface2;\n\n.field public static final someField:Z\n\n.method public static main([Ljava/lang/String;)V\nreturn-void\n.end method\n\n"
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+
3
+ describe SmaliInput do
4
+ let(:data_path) { 'spec/data' }
5
+ let(:temp_dir) { '/fake/tmp/dir' }
6
+ let(:temp_file) { '/fake/tmp/file' }
7
+
8
+ context 'for input that must be disassembled with baksmali' do
9
+ let(:smali_input) do
10
+ allow(Dir).to receive(:mktmpdir).and_return(temp_dir)
11
+ allow(Tempfile).to receive(:new).and_return(temp_file)
12
+ allow(SmaliInput).to receive(:which).and_return('baksmali')
13
+ allow(SmaliInput).to receive(:exec)
14
+ allow(SmaliInput).to receive(:update_apk)
15
+ allow(SmaliInput).to receive(:extract_dex)
16
+ allow(FileUtils).to receive(:cp)
17
+ SmaliInput.new(file_path)
18
+ end
19
+
20
+ subject { smali_input }
21
+
22
+ context 'with an apk' do
23
+ let(:file_path) { "#{data_path}/helloworld.apk" }
24
+ its(:out_apk) { should eq 'helloworld_oracle.apk' }
25
+ its(:out_dex) { should eq temp_file }
26
+ its(:dir) { should eq temp_dir }
27
+ its(:temp_dir) { should be true }
28
+ its(:temp_dex) { should be true }
29
+ end
30
+
31
+ context 'with a dex' do
32
+ let(:file_path) { "#{data_path}/helloworld.dex" }
33
+ its(:out_apk) { should be nil }
34
+ its(:out_dex) { should eq 'helloworld_oracle.dex' }
35
+ its(:dir) { should eq temp_dir }
36
+ its(:temp_dir) { should be true }
37
+ its(:temp_dex) { should be false }
38
+ end
39
+ end
40
+
41
+ context 'for input that must be disassembled without baksmali' do
42
+ let(:file_path) { "#{data_path}/helloworld.dex" }
43
+ let(:smali_input) do
44
+ allow(Dir).to receive(:mktmpdir).and_return(temp_dir)
45
+ allow(SmaliInput).to receive(:which).and_return(nil)
46
+ allow(SmaliInput).to receive(:exec)
47
+ SmaliInput.new(file_path)
48
+ end
49
+
50
+ subject { smali_input }
51
+ it 'raises' do
52
+ expect raise_error
53
+ end
54
+ end
55
+
56
+ context 'for unrecognized input type' do
57
+ let(:file_path) { "#{data_path}/helloworld.smali" }
58
+ let(:smali_input) do
59
+ allow(Dir).to receive(:mktmpdir).and_return(temp_dir)
60
+ SmaliInput.new(file_path)
61
+ end
62
+
63
+ subject { smali_input }
64
+
65
+ it 'raises' do
66
+ expect raise_error
67
+ end
68
+ end
69
+
70
+ context 'for a directory' do
71
+ let(:file_path) { "#{data_path}/smali" }
72
+ let(:smali_input) do
73
+ allow(FileUtils).to receive(:rm_rf)
74
+ allow(SmaliInput).to receive(:compile)
75
+ SmaliInput.new(file_path)
76
+ end
77
+
78
+ subject { smali_input }
79
+
80
+ its(:out_apk) { should be nil }
81
+ its(:out_dex) { should be nil }
82
+ its(:dir) { should eq file_path }
83
+ its(:temp_dir) { should be false }
84
+ its(:temp_dex) { should be true }
85
+ it do
86
+ expect(SmaliInput).to receive(:compile)
87
+ subject
88
+ end
89
+ end
90
+ end