-
Notifications
You must be signed in to change notification settings - Fork 96
/
Copy pathapkfuscator.rb
executable file
·2235 lines (1918 loc) · 81.7 KB
/
apkfuscator.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# -*- coding: utf-8 -*-
# APKfuscator - A generic DEX file obfuscator and munger
# Copyright (C) 2012 Tim Strazzere <[email protected]>, <[email protected]>
# Lookout Mobile Security http://www.mylookout.com/
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
require 'rubygems'
require 'zlib' # Required for checksum (adler32)
require 'digest/sha1' # Required for signature
require 'lib/utilities.rb'
require 'pp'
class DexFile
DEX = 0
ODEX = 1
DEX_VER_35 = 35
DEX_VER_36 = 36
DEX_OPT_VER_36 = 36
DEX_MAGIC = "dex\n"
DEX_OPT_MAGIC = "dey\n"
DEX_DEP_MAGIC = "deps"
DEX_MAGIC_VERS_35 = "035\0" # API levels 13 and below
DEX_MAGIC_VERS_36 = "036\0" # API levels 14 and above
DEX_OPT_MAGIC_VERS = "036\0"
DEFAULT_HEADER_SIZE = '70'.to_i(16)
ENDIAN_TAG = '12345678'.to_i(16)
# Map Item Types
Header_Item = "0000".to_i(16)
String_Id_Item = "0001".to_i(16)
Type_Id_Item = "0002".to_i(16)
Proto_Id_Item = "0003".to_i(16)
Field_Id_Item = "0004".to_i(16)
Method_Id_Item = "0005".to_i(16)
Class_Def_Item = "0006".to_i(16)
Map_List = "1000".to_i(16)
Type_List = "1001".to_i(16)
Annotation_Set_Ref_List = "1002".to_i(16)
Annotation_Set_Item = "1003".to_i(16)
Class_Data_Item = "2000".to_i(16)
Code_Item = "2001".to_i(16)
String_Data_Item = "2002".to_i(16)
Debug_Info_Item = "2003".to_i(16)
Annotation_Item = "2004".to_i(16)
Encoded_Array_Item = "2005".to_i(16)
Annotations_Directory_Item = "2006".to_i(16)
# ACCESS_FLAGS for class def items
ACC_PUBLIC = "00001".to_i(16) # class, field, method, ic
ACC_PRIVATE = "00002".to_i(16) # field, method, ic
ACC_PROTECTED = "00004".to_i(16) # field, method, ic
ACC_STATIC = "00008".to_i(16) # field, method, ic
ACC_FINAL = "00010".to_i(16) # class, field, method, ic
ACC_SYNCHRONIZED = "00020".to_i(16) # method (only allowed on natives)
ACC_SUPER = "00020".to_i(16) # class (not used in Dalvik)
ACC_VOLATILE = "00040".to_i(16) # field
ACC_BRIDGE = "00040".to_i(16) # method (1.5)
ACC_TRANSIENT = "00080".to_i(16) # field
ACC_VARARGS = "00080".to_i(16) # method (1.5)
ACC_NATIVE = "00100".to_i(16) # method
ACC_INTERFACE = "00200".to_i(16) # class, ic
ACC_ABSTRACT = "00400".to_i(16) # class, method, ic
ACC_STRICT = "00800".to_i(16) # method
ACC_SYNTHETIC = "01000".to_i(16) # field, method, ic
ACC_ANNOTATION = "02000".to_i(16) # class, ic (1.5)
ACC_ENUM = "04000".to_i(16) # class, field, ic (1.5)
ACC_CONSTRUCTOR = "10000".to_i(16) # method (Dalvik only)
ACC_DECLARED_SYNCHRONIZED = "20000".to_i(16) # method (Dalvik only)
ACC_CLASS_MASK = (ACC_PUBLIC | ACC_FINAL | ACC_INTERFACE |
ACC_ABSTRACT | ACC_SYNTHETIC | ACC_ANNOTATION |
ACC_ENUM)
ACC_INNER_CLASS_MASK = (ACC_CLASS_MASK | ACC_PRIVATE | ACC_PROTECTED |
ACC_STATIC)
ACC_FIELD_MASK = (ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED |
ACC_STATIC | ACC_FINAL | ACC_VOLATILE |
ACC_TRANSIENT | ACC_SYNTHETIC | ACC_ENUM)
ACC_METHOD_MASK = (ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED |
ACC_STATIC | ACC_FINAL | ACC_SYNCHRONIZED |
ACC_BRIDGE | ACC_VARARGS | ACC_NATIVE |
ACC_ABSTRACT | ACC_STRICT | ACC_SYNTHETIC |
ACC_CONSTRUCTOR | ACC_DECLARED_SYNCHRONIZED)
class ArgumentError < RuntimeError; end
def initialize(file=nil, verbose=false)
@verbose = verbose
if(file.nil?)
raise RuntimeError.new 'Cannot open a nil file!'
end
if(@verbose)
Utilities.head_info ' loaded file [ ' + file + ' ]'
end
@dex_file = File.new(file, 'r+')
@dex_type = nil
@dex_ver = nil
@header = {
:magic => nil,
:magic_ver => nil,
:checksum => nil,
:signature => nil,
:file_size => nil,
:header_size => nil,
:endian_tag => nil,
# sections
:link_size => nil,
:link_offset => nil,
# map size is listed as first unsigned int (4) at the beginning,
# must reside inside the data section
:map_offset => nil,
:type_ids_size => nil,
:type_ids_offset => nil,
:proto_ids_size => nil,
:proto_ids_offset => nil,
:field_ids_size => nil,
:field_ids_offset => nil,
:method_ids_size => nil,
:method_ids_offset => nil,
:class_defs_size => nil,
:class_defs_offset => nil,
:data_size => nil,
:data_offset => nil
}
@map = {}
@sections = {}
get_header_information
get_map_information
get_sections # must be called after map has been read since that gives us the totals
end
#
# Nerf the size of the header -- must be divisible evenly by 4
#
def nerf_header_size(new_size=nil, file_to_inject=nil)
if(new_size.nil?)
new_size = 116
end
# Make sure it aligns properly with a u4
if(new_size % 4 != 0)
raise ArgumentError.new 'Unable to nerf the header size to something that is not divisible by 4!'
end
# If an injectable is passed, then lets save it to an object we can reference later
@injecting_file = file_to_inject
set_header('header_size', new_size)
end
#
# Nerf a code section with either a new class or modify something
#
# {
# :code => code_to_inject,
# :code_length => length_of_injected_code, # The length of opcodes
# :method_index => method_index_to_inject_at
# }
def nerf_code_section(code_nerf=nil)
if(code_nerf.nil?)
raise ArgumentError.new 'Unable to use a nil code_nerf object'
end
if(@code_nerf.nil?)
@code_nerf = []
end
@code_nerf << code_nerf
end
#
# Nerf something inside of the string section, either modifying something existing or adding something new
#
# {
# :match => string_to_change,
# :value => value_to_change_string_to
# }
def nerf_string_section(string_nerf=nil)
if(string_nerf.nil?)
raise ArgumentError.new 'Unable to use a nil string_nerf object'
end
if(@string_nerf.nil?)
@string_nerf = []
end
@string_nerf << string_nerf
end
def save_modified(file_name=nil)
if(file_name.nil?)
file_name = File.dirname(__FILE__) + 'modified'
end
if(@verbose)
Utilities.head_info 'Writing file [ ' + file_name + ' ] for (hopefully) [ ' + File.size?(@dex_file).to_s + ' ] bytes'
end
file = File.new(file_name, 'w+')
buffer = ''
# After the header - we should be able to calculate the size of each section below until we hit the data section
string_id_offset = get_header('header_size')
type_id_offset = string_id_offset + @sections[:string_id_list].length * 4
proto_id_offset = type_id_offset + @sections[:type_id_list].length * 4
field_id_offset = proto_id_offset + @sections[:proto_id_list].length * 12
method_id_offset = field_id_offset + @sections[:field_id_list].length * 8
class_def_offset = method_id_offset + @sections[:method_id_list].length * 8
map_offset = class_def_offset + @sections[:class_def_items].length * "20".to_i(16)
pre_data_length = map_offset + create_map.length
# The following sections could in theory have spaces between them, possibly be out of order also
string_id_list, string_data = create_string_id_list(pre_data_length)
if(string_id_list.length != get_header('string_ids_size') * 4)
Utilities.warn 'String Id section does not appear to be the proper size, expected [ ' + (get_header('string_ids_size') * 4).to_s
+ ' ] but was [ ' + string_id_list.length.to_s + ' ]'
end
# Set map object
map_item = get_section_from_map String_Id_Item
map_item[:size] = @sections[:string_id_list].length
map_item[:offset] = string_id_offset
# Set header offset
set_header('string_ids_size', @sections[:string_id_list].length)
set_header('string_ids_offset', string_id_offset)
# Append the string_id_list to the pre-data section buffer
buffer += string_id_list
# Append the string data to the data section
data = string_data
# Doesn't actually interface with the data section
# -- only resolves indexes directly from the strings table
type_id_list = create_type_id_list
if(type_id_list.length != get_header('type_ids_size') * 4)
Utilities.warn 'Type Id section does not appear to be the proper size!'
end
# Set map object
map_item = get_section_from_map Type_Id_Item
map_item[:size] = @sections[:type_id_list].length
map_item[:offset] = type_id_offset
# Set header offset
set_header('type_ids_size', @sections[:type_id_list].length)
set_header('type_ids_offset', type_id_offset)
# Append the type_id_list to the pre-data section buffer
buffer += type_id_list
proto_id_list, parameter_data = create_proto_id_list(pre_data_length + data.length)
if(proto_id_list.length != get_header('proto_ids_size') * 12)
Utilities.warn 'Proto Id section does not appear to be the proper size!'
end
# Set map object
map_item = get_section_from_map Proto_Id_Item
map_item[:size] = @sections[:proto_id_list].length
map_item[:offset] = proto_id_offset
# Set header offset
set_header('proto_ids_size', @sections[:proto_id_list].length)
set_header('proto_ids_offset', proto_id_offset)
# Append the proto_id_list to the pre-data section buffer
buffer += proto_id_list
# Append the parameter data to the data section
data += parameter_data
# Doesn't actually interface with data section
# -- resolves indexes of two types and a string
field_id_list = create_field_id_list
if(field_id_list.length != get_header('field_ids_size') * 8)
Utilities.warn 'Field Id section does not appear to be the proper size!'
end
# Set map object
map_item = get_section_from_map Field_Id_Item
map_item[:size] = @sections[:field_id_list].length
map_item[:offset] = field_id_offset
# Set header offset
set_header('field_ids_size', @sections[:field_id_list].length)
set_header('field_ids_offset', field_id_offset)
# Append the field_id_list to the pre-data section buffer
buffer += field_id_list
# Doesn't actually interface with data section
# -- resolves an index for a type, a proto and a string
method_id_list = create_method_id_list
if(method_id_list.length != get_header('method_ids_size') * 8)
Utilities.warn 'Method Id section does not appear to be the proper size!'
end
# Set map object
map_item = get_section_from_map Method_Id_Item
map_item[:size] = @sections[:method_id_list].length
map_item[:offset] = method_id_offset
# Set header offset
set_header('method_ids_size', @sections[:method_id_list].length)
set_header('method_ids_offset', method_id_offset)
# Append the method_id_list to the pre-data section buffer
buffer += method_id_list
# Heavy interaction with the data section
# class_def_data_block is going to end up containing;
# -- interfaces
# -- annotations [ annotation directories, annotation sets and annotation items ]
# -- class_data_items
# -- static_values
# -- code_items
# -- debug_items
class_def_items, class_def_data_block = create_class_def_items(pre_data_length + data.length)
if(class_def_items.length != get_header('class_defs_size') * "20".to_i(16))
Utilities.warn 'Class definition section does not appear to be the proper size!'
end
# Set map object
map_item = get_section_from_map Class_Def_Item
map_item[:size] = @sections[:class_def_items].length
map_item[:offset] = class_def_offset
# Set header offset
set_header('class_defs_size', @sections[:class_def_items].length)
set_header('class_defs_offset', class_def_offset)
# Append the class_def_items to the pre-data section buffer
buffer += class_def_items
# Append the data we've just generated
data += class_def_data_block
# Set map object
map_item = get_section_from_map Map_List
map_item[:size] = 1
map_item[:offset] = map_offset
# Fix the offset in the header for the map
set_header('map_offset', map_offset)
# Add a fresh map the to data section
data = create_map + data
# Add the data
buffer += data
# Set data offset and length
set_header('data_size', data.length)
set_header('data_offset', map_offset)
set_header('file_size', get_header('header_size') + buffer.length)
# Create and preprend the fixed header
buffer = create_header + buffer
signed_dex = sign_dex(buffer)
file.write(signed_dex)
file.close
if(@verbose)
Utilities.info 'Wrote [ ' + File.stat(file_name).size.to_s + ' ] bytes'
end
end
def sign_dex(buffer=nil)
# Check if it's nil or smaller than the minimum header size
if(buffer.nil? || buffer.length < 0x70)
raise ArgumentError.new 'Unable to sign a dex file which is nil!'
end
# Get sha1 of everything past where the sha1 lives
signature = Digest::SHA1.hexdigest(buffer[32..buffer.length])
# build the sha1 plus data beyond it
sha_buf = Utilities.hex_to_char_string(signature)
sha_buf += buffer[32..buffer.length]
# get the adler32 of the sha'ed buffer
checksum = Zlib.adler32(sha_buf, nil).to_i
# copy over the magic bytes of the dex file
signed = buffer[0..7]
signed += Utilities.swap_4(checksum)
signed += sha_buf
return signed
end
def get_full_header
return @header
end
def create_header
header = ''
header += @header[:magic].pack('H*')
header += @header[:magic_ver].pack('H*')
header += Utilities.swap_4(@header[:checksum])
header += Utilities.hex_to_char_string(@header[:signature])
header += Utilities.swap_4(@header[:file_size])
header += Utilities.swap_4(@header[:header_size])
header += Utilities.swap_4(@header[:endian])
header += Utilities.swap_4(@header[:link_size])
header += Utilities.swap_4(@header[:link_offset])
header += Utilities.swap_4(@header[:map_offset])
header += Utilities.swap_4(@header[:string_ids_size])
header += Utilities.swap_4(@header[:string_ids_offset])
header += Utilities.swap_4(@header[:type_ids_size])
header += Utilities.swap_4(@header[:type_ids_offset])
header += Utilities.swap_4(@header[:proto_ids_size])
header += Utilities.swap_4(@header[:proto_ids_offset])
header += Utilities.swap_4(@header[:field_ids_size])
header += Utilities.swap_4(@header[:field_ids_offset])
header += Utilities.swap_4(@header[:method_ids_size])
header += Utilities.swap_4(@header[:method_ids_offset])
header += Utilities.swap_4(@header[:class_defs_size])
header += Utilities.swap_4(@header[:class_defs_offset])
header += Utilities.swap_4(@header[:data_size])
header += Utilities.swap_4(@header[:data_offset])
if(@verbose && (get_header('header_size') - DEFAULT_HEADER_SIZE) != 0)
Utilities.info 'Padding the end of the header with [ ' + (get_header('header_size') - DEFAULT_HEADER_SIZE).to_s + ' ] bytes, since size has been nerfed!'
end
if(!@injecting_file.nil?)
# Inject the other dex file here if need be
extra_file = File.new(@injecting_file, 'r+')
dex_contents = extra_file.read(File.size?(extra_file))
# Inject other dex file and xor it
dex_contents.each_byte do |byte|
# header += Utilities.hex_to_char_string((byte).to_s(16))
header += Utilities.hex_to_char_string((byte ^ 0xd1).to_s(16))
end
else
(1..(get_header('header_size') - DEFAULT_HEADER_SIZE)).each do
header += Utilities.hex_to_char_string "00"
end
end
return header
end
# Contains offsets to the struct;
# {
# :string_size,
# :string_contents
# }
def create_string_id_list(expected_data_offset=nil)
if(expected_data_offset.nil?)
raise ArgumentError.new 'Unable to create a string_id_list or string_table without an offset!'
end
string_id_list = ''
string_section = ''
fix_map_item(String_Data_Item, @sections[:string_id_list].length, expected_data_offset)
@sections[:string_id_list].each do |index, string_id|
# Process any string_nerf objects we have
if(!@string_nerf.nil?)
@string_nerf.each do |string_nerf|
if(string_nerf[:match] == string_id[:string_contents])
string_id[:string_contents] = string_nerf[:value]
string_id[:string_size] = strings_id[:string_contents].length
end
end
end
# We need to a make the offset be the expected offset + the current length of the section
string_id_list += Utilities.swap_4(expected_data_offset + string_section.length)
string_section += Utilities::LEB128.write_uleb128(string_id[:string_size])
string_section += string_id[:string_contents]
# Need to have a null byte at the end
string_section += Utilities.hex_to_char_string "00"
end
string_section = Utilities.divisible_4(string_section)
return string_id_list, string_section
end
# Contains indexes into the string section, no offsets used
def create_type_id_list
type_id_list = ''
@sections[:type_id_list].each do |index, type_id|
type_id_list += Utilities.swap_4(type_id[:index])
end
return type_id_list
end
# Contains:
# - index to string section
# - index to type section
# - offset into data section for parameters
def create_proto_id_list(expected_data_offset=nil)
if(expected_data_offset.nil?)
raise ArgumentError.new 'Unable to create a proto_id_list or parameters_section without an expected offset!'
end
proto_id_list = ''
parameters_section = ''
# TODO : optimize more? We don't need to repeat items, which might be happening
items = 0
@sections[:proto_id_list].each do |index, proto_id|
# index into string section
proto_id_list += Utilities.swap_4(proto_id[:shorty_index])
# index into type section
proto_id_list += Utilities.swap_4(proto_id[:return_type_index])
# create parameters section, if necessary, for data section
if(proto_id[:parameters_offset] != 0)
proto_id_list += Utilities.swap_4(expected_data_offset + parameters_section.length)
items += 1
parameters_section += Utilities.swap_4(proto_id[:parameters][:size])
proto_id[:parameters][:parameter_indexes].each do |parameter_index|
parameters_section += Utilities.swap_2(parameter_index)
end
# Each parameter section should be divisble by 4
parameters_section = Utilities.divisible_4(parameters_section)
else
# if there is no parameters section, we need to fill still fill the rest of our object
proto_id_list += Utilities.hex_to_char_string "00000000"
end
end
fix_map_item(Type_List, items, expected_data_offset)
return proto_id_list, parameters_section
end
# Contains 2 indexes into the type table and one into the string table
def create_field_id_list
field_id_list = ''
@sections[:field_id_list].each do |index, field_id|
field_id_list += Utilities.swap_2(field_id[:class_index])
field_id_list += Utilities.swap_2(field_id[:type_index])
field_id_list += Utilities.swap_4(field_id[:name_index])
end
return field_id_list
end
# Contains an index into type table, proto table and the string table
def create_method_id_list
method_id_list = ''
@sections[:method_id_list].each do |index, method_id|
method_id_list += Utilities.swap_2(method_id[:class_index])
method_id_list += Utilities.swap_2(method_id[:proto_index])
method_id_list += Utilities.swap_4(method_id[:name_index])
end
return method_id_list
end
# class_def_data_block is going to end up containing;
# -- interfaces
# -- annotations [ annotation directories, annotation sets and annotation items ]
# -- class_data_items
# -- static_values
# -- code_items
# -- debug_items
def create_class_def_items(expected_data_offset=nil)
if(expected_data_offset.nil?)
raise ArgumentError.new 'Unable to create a class_def_items or a class_data_block without an expected offset!'
end
class_def_items = ''
# Create the interface data block and correct offsets
buffer = create_interface_block(expected_data_offset)
# Create the annotation data block and correct the offsets
buffer += create_annotation_block(expected_data_offset + buffer.length)
# Create the block for the actual class data and correct the offsets
buffer += create_class_data_block(expected_data_offset + buffer.length)
# Create the block for the actual static data and correct the offsets
buffer += create_static_data_block(expected_data_offset + buffer.length)
@sections[:class_def_items].each do |index, class_def|
# Index into the type table
class_def_items += Utilities.swap_4(class_def[:class_index])
class_def_items += Utilities.swap_4(class_def[:access_flags])
# Index into the type table
class_def_items += Utilities.swap_4(class_def[:superclass_index])
if(class_def[:interfaces_offset] != 0)
# This offset has already been fixed in the create_interface_block method
class_def_items += Utilities.swap_4(class_def[:interfaces_offset])
else
class_def_items += Utilities.hex_to_char_string "00000000"
end
# Index into the string table
class_def_items += Utilities.swap_4(class_def[:source_file_index])
if(class_def[:annotations_offset] != 0)
# This offset has already been fixed in the create_annotation_block method
class_def_items += Utilities.swap_4(class_def[:annotations_offset])
else
class_def_items += Utilities.hex_to_char_string "00000000"
end
if(class_def[:class_data_offset] != 0)
# This offset has already been fixed in the create_class_data_block method
class_def_items += Utilities.swap_4(class_def[:class_data_offset])
else
class_def_items += Utilities.hex_to_char_string "00000000"
end
if(class_def[:static_values_offset] != 0)
# This offset has already been fixed in the create_static_data_block
class_def_items += Utilities.swap_4(class_def[:static_values_offset])
else
class_def_items += Utilities.hex_to_char_string "00000000"
end
end
return class_def_items, buffer
end
# Create the interface block and correct the offsets for interfaces
def create_interface_block(expected_data_offset=nil)
if(expected_data_offset.nil?)
raise ArgumentError.new 'Unable to create an interface data block without an expected offset!'
end
buffer = ''
items = 0
@sections[:class_def_items].each do |index, class_def|
# If we have an interface offset already, then we need to modify it to point to
# the new offset we're going to create
if(class_def[:interfaces_offset] != 0)
items += 1
class_def[:interfaces_offset] = expected_data_offset + buffer.length
# Write the size
buffer += Utilities::swap_4(class_def[:interfaces][:size])
# Write the actual index into the type table
class_def[:interfaces][:interfaces_indexes].each do |type_index|
buffer += Utilities::swap_2(type_index)
end
buffer = Utilities.divisible_4(buffer)
end
end
# TODO: This is actual part of the type_list which has lots of parameters in it, so it should be added into that section
get_section_from_map(Type_List)[:size] += items
return buffer
end
# The block being created here is actually just:
# {
# :class_annotation_offset,
# :fields_size,
# :methods_size,
# :parameters_size
# methods {
# :method_index_offset,
# :annotation_offset
# }
# }
# So every item should be 16 bytes long
def create_annotation_block(expected_data_offset=nil)
if(expected_data_offset.nil?)
raise ArgumentError.new 'Unable to create an annotation data block without an expected offset!'
end
# We need to first create the annotation items
buffer = create_annotation_item_block(expected_data_offset)
# We need to first create the class annotation block
buffer += create_class_annotation_block(expected_data_offset + buffer.length)
annotation_block_offset = expected_data_offset + buffer.length
items = 0
@sections[:class_def_items].each do |index, class_def|
# Make sure we fix the annotation offset if need be
if(class_def[:annotations_offset] != 0)
items += 1
class_def[:annotations_offset] = expected_data_offset + buffer.length
buffer += Utilities::swap_4(class_def[:annotations][:annotations_set_offset])
buffer += Utilities::swap_4(class_def[:annotations][:fields_size])
buffer += Utilities::swap_4(class_def[:annotations][:methods_size])
buffer += Utilities::swap_4(class_def[:annotations][:parameters_size])
# TODO : Write fields
# TODO : Write methods
if(class_def[:annotations][:methods_size] != 0)
class_def[:annotations][:method_annotations].each do |method_annotation|
buffer += Utilities::swap_4(method_annotation[:method_index])
buffer += Utilities::swap_4(method_annotation[:annotation_offset])
end
end
# TODO : Write parameters
end
end
fix_map_item(Annotations_Directory_Item, items, annotation_block_offset)
return buffer
end
# The block being created here is actually just:
# {
# :size_annotation_set,
# :array_of_offset_of_annotations[size_annotation_set]
# }
# So each item is (4 + 4 * number of annotation sets) bytes in length
def create_class_annotation_block(expected_data_offset=nil)
if(expected_data_offset.nil?)
raise ArgumentError.new 'Unable to create a class annoation data block without an expected offset!'
end
buffer = ''
items = 0
@sections[:class_def_items].each do |class_def_index, class_def|
# Not everything will have annotations
if(!class_def[:annotations].nil? && !class_def[:annotations][:method_annotations].nil?)
# If we have a class annotation offset already, then we need to modify it to point to
# the new offset we're going to create
if(!class_def[:annotations][:method_annotations].nil?)
class_def[:annotations][:method_annotations].each do |method_annotation|
if(!method_annotation[:method_sets].nil?)
items += 1
method_annotation[:annotation_offset] = expected_data_offset + buffer.length
buffer += Utilities::swap_4(method_annotation[:method_sets][:annotation_sets_size])
# These offsets should have already been fixed by the create annotation item block method
method_annotation[:method_sets][:annotation_offset_items].each do |offset|
buffer += Utilities::swap_4(offset)
end
end
end
end
end
end
@sections[:class_def_items].each do |class_def_index, class_def|
# Not everything will have annotations
if(!class_def[:annotations].nil?)
# If we have a class annotation offset already, then we need to modify it to point to
# the new offset we're going to create
if(class_def[:annotations][:annotations_set_offset] != 0)
items += 1
class_def[:annotations][:annotations_set_offset] = expected_data_offset + buffer.length
buffer += Utilities::swap_4(class_def[:annotations][:annotation_sets][:annotation_sets_size])
# These offsets should have already been fixed by the create annotation item block method
class_def[:annotations][:annotation_sets][:annotation_offset_items].each do |offset|
buffer += Utilities::swap_4(offset)
end
end
end
end
fix_map_item(Annotation_Set_Item, items, expected_data_offset)
return buffer
end
def create_annotation_item_block(expected_data_offset=nil)
if(expected_data_offset.nil?)
raise ArgumentError.new 'Unable to create an annoation item data block without an expected offset!'
end
buffer = ''
items = 0
# Do method annotations
@sections[:class_def_items].each do |class_def_index, class_def|
# Not all class defs have method annotations
if(!class_def[:annotations].nil? && !class_def[:annotations][:method_annotations].nil?)
class_def[:annotations][:method_annotations].each do |method_annotation|
index = 0
if(!method_annotation[:method_sets].nil?)
method_annotation[:method_sets][:annotation_items].each do |item|
items += 1
# We need to correct the offset we have already, so it matches where we are going to put
# the actual data at
method_annotation[:method_sets][:annotation_offset_items][index] = expected_data_offset + buffer.length
buffer += item[:enum_visibility]
# Write the index into the type table
buffer += Utilities::LEB128.write_uleb128(item[:type_index])
# Write the size of how many encoded values there are
buffer += Utilities::LEB128.write_uleb128(item[:size])
# Write the encoded values, but be with name_index in order
item[:item].sort_by { |itm| itm[:name_index] }.each do |val|
# Write the name which is an index into the string table
buffer += Utilities::LEB128::write_uleb128(val[:name_index])
# Write the actual encoded value, this
buffer += Utilities::EncodedValue::write_encoded_value(val[:value_type], val[:value_arg], val[:data])
end
index += 1
end
end
end
end
end
# Do normal annotations
@sections[:class_def_items].each do |class_def_index, class_def|
# Not all class defs have annotations
if(!class_def[:annotations].nil? && !class_def[:annotations][:annotation_sets].nil?)
index = 0
class_def[:annotations][:annotation_sets][:annotation_items].each do |item|
items += 1
# We need to correct the offset we have already, so it matches where we are going to put
# the actual data at
class_def[:annotations][:annotation_sets][:annotation_offset_items][index] = expected_data_offset + buffer.length
buffer += item[:enum_visibility]
# Write the index into the type table
buffer += Utilities::LEB128.write_uleb128(item[:type_index])
# Write the size of how many encoded values there are
buffer += Utilities::LEB128.write_uleb128(item[:size])
# Write the encoded values, but be with name_index in order
item[:item].sort_by { |itm| itm[:name_index] }.each do |val|
# Write the name which is an index into the string table
buffer += Utilities::LEB128::write_uleb128(val[:name_index])
# Write the actual encoded value, this
buffer += Utilities::EncodedValue::write_encoded_value(val[:value_type], val[:value_arg], val[:data])
end
index += 1
end
end
end
buffer = Utilities.divisible_4(buffer)
fix_map_item(Annotation_Item, items, expected_data_offset)
return buffer
end
def create_class_data_block(expected_data_offset=nil)
if(expected_data_offset.nil?)
raise ArgumentError.new 'Unable to create a class data block without an expected offset!'
end
# Need to write the code items out first so we know the offsets
buffer = create_code_block(expected_data_offset)
items = 0
class_data_offset = expected_data_offset + buffer.length
@sections[:class_def_items].each do |class_def_index, class_def|
# Fix the class data offset, should always be present and need to be changed
if(class_def[:class_data_offset] != 0)
items += 1
class_def[:class_data_offset] = expected_data_offset + buffer.length
# Go into the actual class data section and rip out the data
class_data = @sections[:class_data_items][class_def_index]
# Write the header
buffer += Utilities::LEB128.write_uleb128(class_data[:header][:static_fields_size])
buffer += Utilities::LEB128.write_uleb128(class_data[:header][:instance_fields_size])
buffer += Utilities::LEB128.write_uleb128(class_data[:header][:direct_methods_size])
buffer += Utilities::LEB128.write_uleb128(class_data[:header][:virtual_methods_size])
# Write static fields if necessary
class_data[:static_fields].each do |static_item|
buffer += Utilities::LEB128.write_uleb128(static_item[:field_index_diff])
buffer += Utilities::LEB128.write_uleb128(static_item[:access_flags])
end
# Write instance fields if necessary
class_data[:instance_fields].each do |instance_data|
buffer += Utilities::LEB128.write_uleb128(instance_data[:field_index_diff])
buffer += Utilities::LEB128.write_uleb128(instance_data[:access_flags])
end
# Write direct methods if necessary
class_data[:direct_methods].each do |direct_method|
buffer += Utilities::LEB128.write_uleb128(direct_method[:method_index_diff])
buffer += Utilities::LEB128.write_uleb128(direct_method[:access_flags])
# This offset should have already been fixed by the create code block method
buffer += Utilities::LEB128.write_uleb128(direct_method[:code_offset])
end
# Write virtual methods if necessary
class_data[:virtual_methods].each do |virtual_method|
buffer += Utilities::LEB128.write_uleb128(virtual_method[:method_index_diff])
buffer += Utilities::LEB128.write_uleb128(virtual_method[:access_flags])
# This offset should have already been fixed by the create code block method
buffer += Utilities::LEB128.write_uleb128(virtual_method[:code_offset])
end
end
end
buffer = Utilities.divisible_4(buffer)
fix_map_item(Class_Data_Item, items, class_data_offset)
return buffer
end
def create_code_block(expected_output_offset=nil)
if(expected_output_offset.nil?)
raise ArgumentError.new 'Unable to create a code block without an expected offset!'
end
patch_data = nil
buffer = ''
items = 0
@sections[:class_data_items].each do |class_data_index, class_data|
# Direct methods
class_data[:direct_methods].each do |method|
items += 1
# See if there is anything to process in reguards to nerfing
if(!@code_nerf.nil?)
@code_nerf.each do |code_nerf|
if(method[:method_index_diff] == code_nerf[:method_index])
patch_data = code_nerf
end
end
end
# Correct the offset
method[:code_offset] = expected_output_offset + buffer.length
code = method[:code]
buffer += Utilities.swap_2(code[:registers_size])
buffer += Utilities.swap_2(code[:ins_size])
buffer += Utilities.swap_2(code[:outs_size])
buffer += Utilities.swap_2(code[:tries_size])
# TODO : After implementing the debug info we should write it out, right now this will strip it
buffer += Utilities.hex_to_char_string "00000000"
# Adjust the instruction_size if need be due to patch data
if(patch_data.nil?)
buffer += Utilities.swap_4(code[:instruction_size])
else
buffer += Utilities.swap_4(code[:instruction_size] + patch_data[:code_length])
buffer += Utilities.hex_to_char_string patch_data[:code]
end
code[:instructions].each do |instruction|
buffer += Utilities.swap_2(instruction)
end
# Ensure the instructions have proper padding
if(patch_data.nil?)
if(code[:instruction_size] % 2 != 0)
buffer += Utilities.hex_to_char_string "0000"
end
else
if((code[:instruction_size] + patch_data[:code_length]) % 2 != 0)
buffer += Utilities.hex_to_char_string "0000"
end
end
if(code[:tries_size] != 0)
# Fix (if need be) and write the try blocks
code[:try_items].each do |try|
if(patch_data.nil?)
buffer += Utilities.swap_4(try[:start_addr])
else
buffer += Utilities.swap_4(try[:start_addr] + patch_data[:code_length])
end
buffer += Utilities.swap_2(try[:instruction_count])
buffer += Utilities.swap_2(try[:handler_offset])
end
# Fix (if need be) and write the handler blocks
if(code[:handlers_size] != 0)
buffer += Utilities::LEB128.write_uleb128(code[:handlers_size])
code[:handlers].each do |handler|
buffer += Utilities::LEB128.write_uleb128(handler[:size])
handler[:handler_pairs].each do |pair|
buffer += Utilities::LEB128.write_uleb128(pair[:type_index])
if(patch_data.nil?)
buffer += Utilities::LEB128.write_uleb128(pair[:address])
else
buffer += Utilities::LEB128.write_uleb128(pair[:address] + patch_data[:code_length])
end
end
end