-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathGolangTypeRecovery.java
1227 lines (1100 loc) · 42.2 KB
/
GolangTypeRecovery.java
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
//Recovers Golang types within the given binary
//@author Max 'Libra' Kersten of Trellix' Advanced Research Center, based on the work by padorka@cujoai (https://github.com/getCUJO/ThreatIntel/blob/master/Scripts/Ghidra/type_extract.py)
//@category Golang
//@keybinding
//@menupath
//@toolbar
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Data;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.symbol.Reference;
public class GolangTypeRecovery extends GhidraScript {
/**
* A boolean which defines if logging should be enabled. When prioritising
* speed, one might not be interested in getting all messages, but rather only
* the concluding message, along with potential error messages. As such, this
* boolean specifies if more logging should be enabled or disabled.</br>
* </br>
* The default value of this field is <code>true</code>.
*/
private static final boolean ENABLE_LOGGING = true;
/**
* The numeric value for the function kind, as defined in the Golang language
* specification
*
* @see <a href="https://go.dev/src/reflect/type.go">The Golang source code for
* the Type class, see the constant list under <code>type Kind uint</code>
* for the complete list</a>
*/
private static final int KIND_FUNCTION = 0x13;
/**
* The numeric value for the interface kind, as defined in the Golang language
* specification
*
* @see <a href="https://go.dev/src/reflect/type.go">The Golang source code for
* the Type class, see the constant list under <code>type Kind uint</code>
* for the complete list</a>
*/
private static final int KIND_INTERFACE = 0x14;
/**
* The numeric value for the pointer kind, as defined in the Golang language
* specification
*
* @see <a href="https://go.dev/src/reflect/type.go">The Golang source code for
* the Type class, see the constant list under <code>type Kind uint</code>
* for the complete list</a>
*/
private static final int KIND_POINTER = 0x16;
/**
* The numeric value for the struct kind, as defined in the Golang language
* specification
*
* @see <a href="https://go.dev/src/reflect/type.go">The Golang source code for
* the Type class, see the constant list under <code>type Kind uint</code>
* for the complete list</a>
*/
private static final int KIND_STRUCT = 0x19;
/**
* The default PC Quantum size (minimal instruction size), used in x86, x86_64,
* and WASM
*
* @see <a href=
* "https://github.com/golang/gofrontend/blob/master/libgo/goarch.sh">Golang
* architecture documentation</a>
*/
private static final int INSTRUCTION_SIZE_ONE = 1;
/**
* The PC Quantum size (minimal instruction size), used in RISCV, RISCV x64,
* S390, S390X, SH, and SHbe
*
* @see <a href=
* "https://github.com/golang/gofrontend/blob/master/libgo/goarch.sh">Golang
* architecture documentation</a>
*/
private static final int INSTRUCTION_SIZE_TWO = 2;
/**
* The PC Quantum size (minimal instruction size), used in ALPHA, ARM, ARMbe,
* M68K, MIPS, MIPSle, MIPS64p32, MIPS64p32le, MIPS64, MIPS64le, NIOS2, PPC,
* PPC64, PPC64le, SPARC, and SPARC64
*
* @see <a href=
* "https://github.com/golang/gofrontend/blob/master/libgo/goarch.sh">Golang
* architecture documentation</a>
*/
private static final int INSTRUCTION_SIZE_FOUR = 4;
/**
* The size of a pointer on X86
*/
private static final int POINTER_SIZE_X86 = 4;
/**
* The size of a pointer on X64
*/
private static final int POINTER_SIZE_X64 = 8;
/**
* The human readable version strings that are to be used to determine the
* version of a Golang binary. These will be converted to byte strings during
* the script's runtime, and then stored in the {@link #versions} variable. The
* sole purpose of this additional step is to more easily update this script
* when fixing bugs and/or adding support for later Golang versions.
*/
private static final String[] HUMAN_READABLE_VERSIONS = { "go1.20", "go1.19", "go1.18", "go1.17", "go1.16",
"go1.15", "go1.14", "go1.13", "go1.12", "go1.11", "go1.10", "go1.9", "go1.8", "go1.7", "go1.6", "go1.5",
"go1.4", "go1.3", "go1.2" };
/**
* Byte strings of the versions in the {@link #HUMAN_READABLE_VERSIONS}
* variable. This variable is to be initialised during runtime.
*/
private String[] versions;
/**
* The Golang version of the analysed binary, stored globally for later use to
* avoid looking for the version multiple times. This value is one of the
* {@link #versions}, which is more easily read in its human readable format as
* defined within the {@link #HUMAN_READABLE_VERSIONS}.
*/
private String currentVersion;
/**
* The magic values for the pclntab, as found in the Golang source code. Note
* that version <code>1.2</code> is less than <code>1.16</code>, since the value
* after the dot is to be seen as an incrementing value number, not as the usual
* mathematical way of writing. The values in the array are for the following
* Golang versions, in order:
*
* <ol>
* <li>Version 1.2</li>
* <li>Version 1.16</li>
* <li>Version 1.18</li>
* <li>Version 1.20</li>
* </ol>
*
* @see <a href="https://go.dev/src/debug/gosym/pclntab.go">Golang pclntab
* source code</a>
*/
private static String[] pclntab_magic = { "\\xfb\\xff\\xff\\xff\\x00\\x00", "\\xfa\\xff\\xff\\xff\\x00\\x00",
"\\xf0\\xff\\xff\\xff\\x00\\x00", "\\xf1\\xff\\xff\\xff\\x00\\x00" };
/**
* The size of a pointer, in bytes, within the currently loaded binary
*/
private int binaryPointerSize;
/**
* A hash set of addresses with recovered types, used to avoid recovering the
* same type twice.<br>
* <br>
* The hash set is used, instead of an {@link java.util.ArrayList}, as its more
* efficient. Both the {@link java.util.HashSet#contains(Object)} and
* {@link java.util.HashSet#add(Object)} methods function in <code>O(1)</code>,
* in contrast to the inner workings of other types, which often work in
* <code>O(n)</code>.<br>
* <br>
* Testing locally shows that the time to complete the script, on a test sample
* which contains 3047 to-be recovered types, is nearly three times as much when
* using an {@link java.util.ArrayList}, compared to the
* {@link java.util.HashSet}. The specifics obviously depend on the used
* hardware, but it shows that the hash set implementation is more efficient.
*/
private static HashSet<Address> recoveredTypes = new HashSet<>();
/**
* The script's main function. This script can be executed normally and
* headless.
*/
@Override
protected void run() throws Exception {
// Initialise the byte string versions, based on the human readable ones
versions = createVersionByteStrings();
// Declares and initialises the executable format of the current program
String executableFormat = currentProgram.getExecutableFormat();
/*
* Initialises the global variable to store the pointer size of the currently
* analysed binary in
*/
binaryPointerSize = currentProgram.getDefaultPointerSize();
// Optionally print the executable format
log("Executable format: " + executableFormat);
/*
* Defines the address array which is to be initialised differently, depending
* on the file's type
*/
Address[] objects;
// If the file is of the PE format
if (executableFormat.equalsIgnoreCase("Portable Executable (PE)")) {
// Initialise the objects array
objects = scanPeFile();
} else if (executableFormat.equalsIgnoreCase("Executable and Linking Format (ELF)")) { // If the file is of the
// ELF format
// Initialise the objects array
objects = scanElfFile();
} else if (executableFormat.equalsIgnoreCase("Mac OS X Mach-O")) {
objects = scanMachoFile();
} else {
// Inform the analyst of the error
printerr("Incorrect file format: \"" + executableFormat + "\"");
// Exit the script
return;
}
/*
* If the objects array is null, an error occurred earlier on, of which the
* analyst is already informed via an earlier printerr statement. As such, this
* function simply needs to return early to end the script's execution.
*/
if (objects == null) {
return;
}
// Find the Golang version within the given binary
currentVersion = findVersion();
// Check if a version was found
if (currentVersion == null) {
// Inform the analyst of the failure to find a version
printerr("No Golang version could be found in the binary!");
// Return early as the version is required later on
return;
}
/*
* Regardless if the file is of the PE or ELF format, the returned addresses are
* the same. As such, the array can be used in the same way. To easier read the
* function call, read it as:
*
* getAllTypes(typelinks, endOfTypelinks, type);
*/
recoverAllTypes(objects[0], objects[1], objects[2]);
// Print the number of recovered types for the analyst
println("Types recovered: " + recoveredTypes.size());
}
/**
* A wrapper function for the
* {@link ghidra.app.script.GhidraScript#println(String)} which is only called
* if the {@link #ENABLE_LOGGING} is <code>true</code>. The logging that is
* (potentially) passing through this function, is meant as optional logging.
* The final conclusion, as well as the logging of any error messages, should be
* printed via direct calls. The easy-to-omit nature of optional messages speeds
* up automated analysis by limiting the number of print calls.
*
* @param message
*/
private void log(String message) {
if (ENABLE_LOGGING) {
println(message);
}
}
/**
* Gets the value of the bytes as a little endian short
*
* @param bytes the value to read
* @return the value of the bytes as a little endian short
*/
private short getShort(byte[] bytes) {
// Allocate a buffer of two bytes
ByteBuffer buffer = ByteBuffer.allocate(2);
// Set the order
buffer.order(ByteOrder.LITTLE_ENDIAN);
// Place the first byte
buffer.put(bytes[0]);
// Place the second byte
buffer.put(bytes[1]);
// Get the value as a short, in little endian format
return buffer.getShort(0);
}
/**
* Ingests the human readable versions and converts them into byte strings. The
* byte strings can be used to searched for using
* {@link #findBytes(Address, String)} and overloads thereof.<br>
* <br>
* An example of a byte string is <code>\xfb\xff\xff\xff\x00\x00</code>.
*
* @return the byte strings of the given versions
*/
private String[] createVersionByteStrings() {
// Declare and initialise the output variable
String[] byteStrings = new String[HUMAN_READABLE_VERSIONS.length];
// Loop over all human readable strings
for (int i = 0; i < HUMAN_READABLE_VERSIONS.length; i++) {
/*
* Convert each human readable string into a byte string, and store it in the
* output variable
*/
byteStrings[i] = createByteString(HUMAN_READABLE_VERSIONS[i]);
}
// Return the newly created byte strings
return byteStrings;
}
/**
* Returns a byte string of the given string, assuming the given input is UTF-8
* encoded
*
* @param input the UTF-8 encoded string
* @return a byte string of the given input
*/
private String createByteString(String input) {
// Get the bytes from the given input
byte[] bytes = input.getBytes(Charset.forName("UTF-8"));
// Declare the byte string and initialise it as an empty string
String byteString = "";
// Iterate over all bytes
for (byte currentByte : bytes) {
// Get the current byte in string form
String byteStringByte = Integer.toHexString(currentByte);
/*
* If the length of the current string is only a single character, prepend a
* zero to ensure two digits are present for the given byte in string form
*/
if (byteStringByte.length() == 1) {
byteStringByte = "0" + byteStringByte;
}
/*
* Prepend "\x" to byte string's current byte, which is then added to the
* complete byte string
*/
byteString += "\\x" + byteStringByte;
}
// Return the complete byte string
return byteString;
}
/**
* Gets the address at the given address, essentially dereferencing the given
* pointer
*
* @param address the address to dereference
* @return the dereferenced address
* @throws MemoryAccessException if the given address cannot be dereferenced
*/
private Address getAddressAt(Address address) throws MemoryAccessException {
return toAddr(Long.toHexString(getInt(address)));
}
/**
* Removes data at the given location, which is required to retype certain
* variables
*
* @param address the address to remove the type from
* @param length the length of the data to remove the typing from
* @throws Exception if the address is invalid or null
*/
private void removeData(Address address, int length) throws Exception {
// Iterate for the given length
for (int i = 0; i < length; i++) {
/*
* Remove the data at the given address plus the offset of the length, starting
* at 0
*/
removeDataAt(address.add(i));
}
// Return if no exception is thrown
}
/**
* Gets the sections that match (case insensitive) the given section name
*
* @param sectionName the section name to match, matching is case insensitive
* @return the starting addresses of all sections which match the given section
* name
*/
private Address[] getSection(String sectionName) {
// Iterate over all memory blocks
for (MemoryBlock block : getMemoryBlocks()) {
// If the block's name matches the given section name, case insensitive
if (block.getName().equalsIgnoreCase(sectionName)) {
// Get the start of the block
Address start = block.getStart();
// Get the end of the block
Address end = block.getEnd();
/*
* Optionally print a message for the analyst with the block's name, start
* address, and end address
*/
log(String.format("%s [start: 0x%x, end: 0x%x]", block.getName(), start.getOffset(), end.getOffset()));
// Return the start and end addresses
return new Address[] { start, end };
}
}
// Print a message for the analyst indicating the section couldn't be found
printerr("Section \"" + sectionName + "\" not found!");
// Return null in the case of an error
return null;
}
/**
* Returns the current version from any of the given versions, based on its
* presence in the binary. The first version that is found is used.
*
* @return the byte string version that is found
*/
private String findVersion() {
// Iterate over all versions in the global variable
for (int i = 0; i < versions.length; i++) {
// Declare a local variable for the currently iterated version
String version = versions[i];
// Find the address of the first occurrence of the current version
Address[] addresses = findBytes(null, version, 1000);
// Check if any results were found
if (addresses == null || addresses.length == 0) {
// If not, continue to the next attempt to find the version
continue;
}
/*
* If the address is not null, inform the analyst with the human readable
* version, for all results
*/
for (Address address : addresses) {
// Optionally print the Golang version
log("Golang version found at 0x" + Long.toHexString(address.getOffset()) + " : "
+ HUMAN_READABLE_VERSIONS[i]);
}
/*
* Return the current version based on the first match. This can be wrong, which
* is why all matches are printed to the analyst
*/
return version;
}
// Return null in the case of a failure to find the version
return null;
}
/**
* Until and including Golang 1.16, the length offset was two bytes in size.
* From Golang 1.17 onwards, the length offset uses a variable length.<br>
* <br>
* For versions until and including 1.16, the second byte is used, which causes
* the script to potentially miss long strings.<br>
* <br>
* For versions 1.17 or later, the first byte is used, which causes the script
* to potentially miss long strings.
*
* @return the length offset
*/
private int getLengthOffset() {
/*
* Store the length of the current version byte string locally, which avoids
* calling the <code>length()<code> function several times
*/
int currentVersionLength = currentVersion.length();
/*
* Get the first digit of the version (which is the one but last byte in the
* byte string), where the "1." part is omitted as its not required
*/
String firstDigit = currentVersion.substring(currentVersionLength - 6, currentVersionLength - 4);
// Convert the value into a hexadecimal integer
int integer = Integer.parseInt(firstDigit, 16);
// Convert the value into a character
char c = (char) integer;
/*
* Declare and initialise the variable to store the version number in, in string
* format
*/
String versionNumberString = "" + c;
/*
* Get the last digit of the version (which is the one but last byte in the byte
* string), where the "1." part is omitted as its not required
*/
String lastDigit = currentVersion.substring(currentVersionLength - 2, currentVersionLength);
// Convert the value into a hexadecimal integer
integer = Integer.parseInt(lastDigit, 16);
// Convert the value into a character
c = (char) integer;
// Append the number
versionNumberString = versionNumberString + c;
// Convert the version number into an integer
int versionNumber = Integer.parseInt(versionNumberString);
// Compare the version to the versions of 1.17 or later
if (versionNumber >= 17) {
// Return one, as only one byte will be used
return 1;
}
/*
* If none of the >= 1.17 versions matches, assume the version is lower than
* that, and thus the second byte is used
*/
return 2;
}
/**
* Looks for the pclntab within the binary, without looking for a section with
* the same name, but rather for a magic value within the binary
*
* @param pclntab_magic the byte strings of pclntab magic strings to look for
* @return Returns an object array where the pclntab address is at index 0, and
* the matching magic byte string is at index 1. The magic value can be
* null!
* @throws MemoryAccessException if the pclntab memory cannot be accessed
*/
private Object[] findPclntabPE() throws MemoryAccessException {
// Iterate over all plcntab magic byte strings
for (String magic : pclntab_magic) {
// Search through the program for the given byte string
Address[] results = findBytes(null, magic, 1000);
// Ensure the results are not null nor empty
if (results == null || results.length == 0) {
// If no matches are found, continue
continue;
}
// Iterate over all results
for (int i = 0; i < results.length; i++) {
// Get the currently iterated address
Address tempAddress = results[i];
// If the returned value is not found
if (tempAddress == null) {
// Move to the next result in the address array
continue;
}
// Check if the match is aligned with the pclntab structure
if (isPclntab(tempAddress)) {
// Optionally inform the analyst of the finding
log("pclntab found!");
// Return the pclntab's address and the respective magic value
return new Object[] { tempAddress, magic };
}
}
}
// If nothing can be found, inform the analyst and then return null
printerr("The pclntab could not be found!");
return null;
}
/**
* Checks if the given address matches the pclntab structure pattern
*
* @param address the address to check
* @return true if the structure matches, false if not
* @throws MemoryAccessException if the memory at the given address (and a few
* bytes more) cannot be accessed
*/
private boolean isPclntab(Address address) throws MemoryAccessException {
// Get the PC Quantum Size value
byte pcQuantum = getByte(address.add(6));
// Get the pointer size from the binary
byte pclntabPointerSize = getByte(address.add(7));
// Verify the validity of the values
if ((pcQuantum != INSTRUCTION_SIZE_ONE && pcQuantum != INSTRUCTION_SIZE_TWO
&& pcQuantum != INSTRUCTION_SIZE_FOUR)
|| (pclntabPointerSize != POINTER_SIZE_X86 && pclntabPointerSize != POINTER_SIZE_X64)) {
// Return false if the conditions aren't met
return false;
}
// Return true if the conditions are met
return true;
}
/**
* Checks if the given address is the start of the module data
*
* @param address the address to check
* @param magic the magic value of the module data from the pclntab
* @return true if the given address is the address of the module data, false if
* not
* @throws MemoryAccessException if the memory at the given address, and offsets
* thereof, aren't accessible for any reason
*/
private boolean isModuleData(Address address, String magic) throws MemoryAccessException {
// Declare the offset
int offset;
/*
* Check if the magic value of Golang version 1.2 is in-use, as the offset
* differs in this case
*/
if (magic.equalsIgnoreCase("\\xfb\\xff\\xff\\xff\\x00\\x00")) { // Golang version 1.2 magic value
offset = 12;
} else {
offset = 22;
}
// Get the text address based on the given address and the offset
Address text = getAddressAt(address.add(offset * binaryPointerSize));
// Get the memory block that corresponds with the .text section
MemoryBlock textBlock = currentProgram.getMemory().getBlock(".text");
/*
* Verify the module data by ensuring the offset from the module data matches
* the offset of the .text block
*/
if (text != null && textBlock != null && text.getOffset() == textBlock.getStart().getOffset()) {
log("Module data found!");
return true;
}
// Return false if the offset does not match, or if there is no .text section
return false;
}
/**
* Finds the address of the module data, based on the given pclntab address and
* the pclntab magic value
*
* @param pclntab the address of the pclntab
* @param magic the magic value of the pclntab
* @return the address of the module data
* @throws MemoryAccessException if the pclntab, or offsets thereof, aren't
* accessible for any reason
*/
private Address findModuleData(Address pclntab, String magic) throws MemoryAccessException {
// Declare the module data address
Address moduleData;
// Get all references to the pclntab
Reference[] pclntabReferences = getReferencesTo(pclntab);
// Iterate over all references
for (Reference reference : pclntabReferences) {
// Get the reference-making address
moduleData = reference.getFromAddress();
// Check if the address matches the module data structure
if (isModuleData(moduleData, magic)) {
// Optionally print the module data's address
log("Module data address: " + moduleData.toString(false, true));
// Return the address once a match is found
return moduleData;
}
}
/*
* If no such reference was found, a search through the whole program is to be
* started for the usage of the pclntab address, which is referenced in the
* module data. Searching only through specific sections is possible, but since
* this function is only called once in the beginning, and the improved accuracy
* outweighs the marginally extra time that is required
*/
/*
* Create a string of the inverted and hexadecimal representation of the pclntab
* address
*/
String invertedHexPclntab = Long.toHexString(Long.reverseBytes(pclntab.getOffset()));
/*
* If the binary size is four, the string needs to be 8 characters long (four
* times two characters, meaning four time two bytes).
*/
if (binaryPointerSize == 4) {
invertedHexPclntab = invertedHexPclntab.substring(0, 8);
}
/*
* If the pointer size is not four, its assumed to be eight, which is the
* default value of the long representation already, meaning no change has to be
* made
*/
String byteStringPclntab = "";
// Iterate over all characters, in sets of two per iteration
for (int i = 0; i < invertedHexPclntab.length(); i = i + 2) {
// Create the byte in string form, which is appended to the byte string variable
byteStringPclntab += "\\x" + invertedHexPclntab.charAt(i) + invertedHexPclntab.charAt(i + 1);
}
/*
* Get all results for the given byte string, starting at the program's
* beginning
*/
Address[] results = findBytes(null, byteStringPclntab, 1000);
// If the results are null or empty, return early
if (results != null && results.length > 0) {
// Iterate over all results
for (int i = 0; i < results.length; i++) {
// Temporarily store the current result in the module data variable
moduleData = results[i];
// Check if the current address is the start of the module data structure
if (isModuleData(moduleData, magic)) {
// Return the value if that is the case
return moduleData;
}
}
}
// Inform the analyst of the missing value
printerr("The module data cannot be found!");
// Return null if no match is found
return null;
}
/**
* Gets the typeStart, typeEnd, typeLinks, and numberOfTypes variables based on
* the given module data address and the plcntab magic value.<br>
* <br>
* Returns an array of objects which contains the following types and values, in
* order:<br>
* <ol>
* <li>Address typeStart</li>
* <li>Address typeEnd</li>
* <li>Address typeLinks</li>
* <li>int numberOfTypes</li>
* </ol>
*
* @param moduleData the address of the module data
* @param magic the plcntab magic value
* @param pointerSize the size of the pointer in bytes
* @return in order Address type, Address etype, Address typelinks, int ntypes
* @throws MemoryAccessException if the value at the module data address, or
* offsets thereof, cannot be accessed for any
* reason
*/
private Object[] getTypeLinks(Address moduleData, String magic) throws MemoryAccessException {
// Declaration of the two offsets
int offset;
int offset2;
// The offsets differ in Golang 1.2
if (magic.equalsIgnoreCase("\\xfb\\xff\\xff\\xff\\x00\\x00")) { // Golang 1.2 magic value
offset = 25;
offset2 = 30;
} else if (magic.equalsIgnoreCase("\\xfa\\xff\\xff\\xff\\x00\\x00")
|| magic.equalsIgnoreCase("\\xf0\\xff\\xff\\xff\\x00\\x00")) { // Golang 1.16 and Golang 1.18
// respectively
offset = 35;
offset2 = 42;
} else if (magic.equalsIgnoreCase("\\xf1\\xff\\xff\\xff\\x00\\x00")) { // Golang 1.20
offset = 37;
offset2 = 44;
} else {
printerr("No pclntab magic value matched, thus making the offsets unknown!");
return null;
}
// Get the required values
Address typeStart = getAddressAt(moduleData.add(offset * binaryPointerSize));
Address typeEnd = getAddressAt(moduleData.add((offset + 1) * binaryPointerSize));
Address typeLinks = getAddressAt(moduleData.add(offset2 * binaryPointerSize));
int numberOfTypes = getInt(moduleData.add((offset2 + 1) * binaryPointerSize));
// Return the values in an object array
return new Object[] { typeStart, typeEnd, typeLinks, numberOfTypes };
}
/**
* Recover the types, based on the type's address and the type's location
*
* @param typeAddress
* @param type
* @return
* @throws Exception
*/
private Address recoverTypes(Address typeAddress, Address type) throws Exception {
try {
// Check if the type is already recovered
if (recoveredTypes.contains(typeAddress)) {
// If the type is already recovered, optionally notify the analyst
log(String.format("Type already recovered at 0x%x", typeAddress.getOffset()));
// Return the type's address
return typeAddress;
}
// Gets the length offset
int lengthOffset = getLengthOffset();
// Add the current type to the list of recovered types
recoveredTypes.add(typeAddress);
// Declare and initialise the temporary offset variable
int tempOffset = 2 * binaryPointerSize + 4;
// Declare and initialise the temporary address
Address tempAddress = typeAddress.add(tempOffset);
// Get the byte at the given address
byte b = getByte(tempAddress);
// Declare and initialise the uncommon flag
byte tflagUncommon = (byte) (b & 0x01);
// Get the byte for the extra star flag
b = getByte(typeAddress.add(2 * binaryPointerSize + 4));
// Declare and initialise the extra star far
byte tflagExtraStar = (byte) (b & 0x02);
// Get the byte for the kind
b = getByte(typeAddress.add(2 * binaryPointerSize + 7));
// Declare and initialise the kind
byte kind = (byte) (b & 0x1F);
// Get the type name offset
int nameOffset = getInt(typeAddress.add(4 * binaryPointerSize + 8));
// Get the type name length
byte nameLength = getByte(type.add(nameOffset + lengthOffset));
// Get the name's address
Address nameAddress = type.add(nameOffset + lengthOffset + 1);
// Remove the data type(s) from the given addresses
removeData(nameAddress, nameLength);
// Create an ASCII string at the given address with the given length
Data name = createAsciiString(nameAddress, nameLength);
// Declare the variable
String nameType;
// Get the type's name
if (tflagExtraStar == 1) {
nameType = ((String) name.getValue()).substring(1);
} else {
nameType = ((String) name.getValue());
}
// Optionally print the name of the type
log("Recovered: \"" + nameType + "\"");
// Create a label at the given address for the given name, excluding spaces
createLabel(typeAddress, nameType.replace(" ", "_"), true);
/*
* The function type (funcType) is structured as shown below. More information
* can be found here: https://go.dev/src/reflect/type.go
*
* struct {
*
* funcType uncommonType
*
* [2]*rtype // [0] is in, [1] is out
*
* }
*
* type funcType struct {
*
* rtype
*
* inCount uint16
*
* outCount uint16 //the top bit is set if it is the last input parameter
*
* }
*/
if (kind == KIND_FUNCTION) {
byte[] bytes = getBytes(typeAddress.add(4 * binaryPointerSize + 8 + 8), 2);
short inCount = getShort(bytes);
byte[] outBytes = getBytes(typeAddress.add(4 * binaryPointerSize + 8 + 8 + 2), 2);
// the top bit is set if it is the last input parameter
int lastInput = outBytes[1] & 0x80;
outBytes[1] = (byte) (outBytes[1] & 0x7F);
int outCount = getShort(outBytes);
List<String> inputs = new ArrayList<>();
List<String> outputs = new ArrayList<>();
for (int i = 0; i < inCount; i++) {
Address input = getAddressAt(typeAddress.add(4 * binaryPointerSize + 8 + 8 + binaryPointerSize
+ tflagUncommon * 16 + i * binaryPointerSize));
recoverTypes(input, type);
inputs.add(getSymbolAt(input).getName());
}
for (int i = 0; i < outCount; i++) {
Address output = getAddressAt(typeAddress.add(4 * binaryPointerSize + 8 + 8 + binaryPointerSize
+ tflagUncommon * 16 + inCount * binaryPointerSize + i * binaryPointerSize));
recoverTypes(output, type);
outputs.add(getSymbolAt(output).getName());
}
if (lastInput == 0x80 && inputs.size() > 0) {
String comment = "func(";
for (String string : inputs) {
comment += string + ", ";
}
comment = comment.substring(0, comment.length() - 2);
comment += ") (";
for (String string : outputs) {
comment += string + ", ";
}
comment = comment.substring(0, comment.length() - 2) + ")";
setPreComment(typeAddress, comment);
}
}
/*
* Interface type interfaceType represents an interface type.
*
* type interfaceType struct {
*
* rtype pkgPath name // import path methods
*
* []imethod // sorted by hash
*
* }
*
* method represents a method on an interface type
*
* type imethod struct {
*
* name nameOff // name of method
*
* typ typeOff // .(*FuncType) underneath
*
* }
*/
if (kind == KIND_INTERFACE) {
Address imethodField = getAddressAt(typeAddress.add(5 * binaryPointerSize + 8 + 8));
List<String> methods = new ArrayList<>();
int methodLength = getInt(typeAddress.add(6 * binaryPointerSize + 8 + 8));
for (int i = 0; i < methodLength; i++) {
int imethodNameOffset = getInt(imethodField);
nameLength = getByte(type.add(imethodNameOffset + lengthOffset));
nameAddress = type.add(imethodNameOffset + lengthOffset + 1);
removeData(nameAddress, nameLength);
name = createAsciiString(nameAddress, nameLength);
String temp = (String) name.getValue();
setEOLComment(imethodField, temp);
createLabel(imethodField, temp.replace(" ", "_"), true);
int newTypeOffset = getInt(imethodField.add(4));
Address newType = type.add(newTypeOffset);
recoverTypes(newType, type);
imethodField = imethodField.add(8);
methods.add(name.getValue() + " " + getSymbolAt(newType).getName());
String comment = "type " + nameType + " interface{" + "\n\t" + "\n\t";
for (int j = 0; j < methods.size(); j++) {
comment += methods.get(j) + "\n\t";
}
comment += "\n" + "}";
setPreComment(typeAddress, comment);
}
}
/*
* Pointer type is used to represent a pointer type
*
* type ptrType struct {
*
* rtype
*
* elem *rtype // pointer element (pointed at) type
*
*
* }
*/
if (kind == KIND_POINTER) {
Address newAddress = toAddr(
Integer.toHexString(getInt(typeAddress.add(4 * binaryPointerSize + 8 + 8))));
recoverTypes(newAddress, type);
}
/*
* Struct type structType represents a struct type.
*
* type structType struct {
*
* rtype
*
* pkgPath name
*
* fields []structField // sorted by offset
*
* }