Skip to content

Commit cf44cd7

Browse files
committed
Add indexed string + class lookups
Reference sample: d3becbee846560d0ffa4f3cda708d69a98dff92785b7412d763f810c51c0b091
1 parent 6bb6f0c commit cf44cd7

File tree

6 files changed

+205
-0
lines changed

6 files changed

+205
-0
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
require_relative '../logging'
2+
require_relative '../utility'
3+
4+
class IndexedClassLookup < Plugin
5+
attr_reader :optimizations
6+
7+
include Logging
8+
include CommonRegex
9+
10+
CLASS_DECRYPT = Regexp.new(
11+
'^[ \t]*(' +
12+
(CONST_NUMBER_CAPTURE + '\s+') +
13+
'(?:\:[^\n]+\n\s*)?' + # May have a label between const and invoke
14+
'invoke-static \{\2\}, L([^;]+);->([^\(]+\(I\))Ljava/lang/Class;' \
15+
'\s+' +
16+
MOVE_RESULT_OBJECT + ')'
17+
)
18+
19+
MODIFIER = -> (original, output, out_reg) do
20+
# Put the labels back if any were removed
21+
labels = original.split("\n").select { |l| l.lstrip.start_with?(':') }
22+
labels << "\n " unless labels.empty?
23+
"#{labels.join("\n")}const-class #{out_reg}, #{output.split('').collect { |e| e.inspect[1..-2] }.join}"
24+
end
25+
# Sometimes class name lookup doesn't work and null is returned
26+
FILTER = -> (_, output, out_reg) { output == 'null' }
27+
28+
def initialize(driver, smali_files, methods)
29+
@driver = driver
30+
@smali_files = smali_files
31+
@methods = methods
32+
@optimizations = Hash.new(0)
33+
end
34+
35+
def process
36+
method_to_target_to_contexts = {}
37+
@methods.each do |method|
38+
logger.info("Decrypting indexed classes #{method.descriptor}")
39+
target_to_contexts = {}
40+
target_to_contexts.merge!(decrypt_classes(method))
41+
target_to_contexts.map { |_, v| v.uniq! }
42+
method_to_target_to_contexts[method] = target_to_contexts unless target_to_contexts.empty?
43+
end
44+
45+
made_changes = false
46+
made_changes |= Plugin.apply_batch(@driver, method_to_target_to_contexts, MODIFIER, FILTER)
47+
48+
made_changes
49+
end
50+
51+
private
52+
53+
def decrypt_classes(method)
54+
target_to_contexts = {}
55+
matches = method.body.scan(CLASS_DECRYPT)
56+
@optimizations[:class_lookups] += matches.size if matches
57+
matches.each do |original, _, index, class_name, method_signature, out_reg|
58+
target = @driver.make_target(
59+
class_name, method_signature, index.to_i(16)
60+
)
61+
target_to_contexts[target] = [] unless target_to_contexts.key?(target)
62+
target_to_contexts[target] << [original, out_reg]
63+
end
64+
65+
target_to_contexts
66+
end
67+
end
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
require_relative '../logging'
2+
require_relative '../utility'
3+
4+
class IndexedStringLookup < Plugin
5+
attr_reader :optimizations
6+
7+
include Logging
8+
include CommonRegex
9+
10+
STRING_DECRYPT = Regexp.new(
11+
'^[ \t]*(' +
12+
(CONST_NUMBER_CAPTURE + '\s+') +
13+
'invoke-static \{\2\}, L([^;]+);->([^\(]+\(I\))Ljava/lang/String;' \
14+
'\s+' +
15+
MOVE_RESULT_OBJECT + ')'
16+
)
17+
18+
MODIFIER = -> (_, output, out_reg) { "const-string #{out_reg}, \"#{output.split('').collect { |e| e.inspect[1..-2] }.join}\"" }
19+
20+
def initialize(driver, smali_files, methods)
21+
@driver = driver
22+
@smali_files = smali_files
23+
@methods = methods
24+
@optimizations = Hash.new(0)
25+
end
26+
27+
def process
28+
method_to_target_to_contexts = {}
29+
@methods.each do |method|
30+
logger.info("Decrypting indexed strings #{method.descriptor}")
31+
target_to_contexts = {}
32+
target_to_contexts.merge!(decrypt_strings(method))
33+
target_to_contexts.map { |_, v| v.uniq! }
34+
method_to_target_to_contexts[method] = target_to_contexts unless target_to_contexts.empty?
35+
end
36+
37+
made_changes = false
38+
made_changes |= Plugin.apply_batch(@driver, method_to_target_to_contexts, MODIFIER)
39+
40+
made_changes
41+
end
42+
43+
private
44+
45+
def decrypt_strings(method)
46+
target_to_contexts = {}
47+
matches = method.body.scan(STRING_DECRYPT)
48+
@optimizations[:string_lookups] += matches.size if matches
49+
matches.each do |original, index, class_name, method_signature, out_reg|
50+
target = @driver.make_target(
51+
class_name, method_signature, index.to_i(16)
52+
)
53+
target_to_contexts[target] = [] unless target_to_contexts.key?(target)
54+
target_to_contexts[target] << [original, out_reg]
55+
end
56+
57+
target_to_contexts
58+
end
59+
end
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.class public Lorg/cf/ClassLookup;
2+
.super Ljava/lang/Object;
3+
4+
.method public static doStuff()V
5+
.locals 1
6+
7+
const v0, 0x19189b0e
8+
9+
:some_label
10+
invoke-static {v0}, Lxjmurla/gqscntaej/bfdiays/g;->c(I)Ljava/lang/Class;
11+
12+
move-result-object v0
13+
14+
return-void
15+
.end method
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 v0, 0x320fb210
8+
9+
invoke-static {v0}, Lxjmurla/gqscntaej/bfdiays/f;->a(I)Ljava/lang/String;
10+
11+
move-result-object v0
12+
13+
return-void
14+
.end method
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
require 'spec_helper'
2+
3+
describe IndexedClassLookup 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) { IndexedClassLookup.new(driver, smali_files, [method]) }
10+
11+
describe '#process' do
12+
subject { plugin.process }
13+
14+
context 'with class_lookup.smali' do
15+
let(:file_path) { "#{data_path}/class_lookup.smali" }
16+
let(:batch_item) { ["const v0, 0x19189b0e\n\n :some_label\n invoke-static {v0}, Lxjmurla/gqscntaej/bfdiays/g;->c(I)Ljava/lang/Class;\n\n move-result-object v0", 'v0'] }
17+
18+
it do
19+
expect(driver).to receive(:make_target).with('xjmurla/gqscntaej/bfdiays/g', 'c(I)', 421042958).and_return(batch)
20+
expect(Plugin).to receive(:apply_batch).with(driver, { method => { batch => [batch_item] } }, kind_of(Proc), kind_of(Proc))
21+
subject
22+
end
23+
end
24+
end
25+
end
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
require 'spec_helper'
2+
3+
describe IndexedStringLookup 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) { IndexedStringLookup.new(driver, smali_files, [method]) }
10+
11+
describe '#process' do
12+
subject { plugin.process }
13+
14+
context 'with string_lookup.smali' do
15+
let(:file_path) { "#{data_path}/string_lookup.smali" }
16+
let(:batch_item) { ["const v0, 0x320fb210\n\n invoke-static {v0}, Lxjmurla/gqscntaej/bfdiays/f;->a(I)Ljava/lang/String;\n\n move-result-object v0", 'a(I)'] }
17+
18+
it do
19+
expect(driver).to receive(:make_target).with('0x320fb210', 'xjmurla/gqscntaej/bfdiays/f', 0).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 commit comments

Comments
 (0)