-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathTLSKeyHunter.java
More file actions
3488 lines (2780 loc) · 152 KB
/
TLSKeyHunter.java
File metadata and controls
3488 lines (2780 loc) · 152 KB
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
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.data.StringDataInstance;
import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Listing;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.listing.DataIterator;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.listing.Instruction;
import ghidra.program.model.listing.InstructionIterator;
import ghidra.program.model.pcode.*;
import java.util.List;
import java.util.Map;
import java.util.Set;
import ghidra.program.model.lang.Register;
import java.util.Iterator;
import java.util.AbstractMap;
import java.util.ArrayList;
import ghidra.program.model.scalar.Scalar;
import ghidra.program.model.lang.OperandType;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.symbol.*;
import ghidra.program.model.listing.Program;
import ghidra.util.task.TaskMonitor;
import ghidra.app.util.bin.BinaryReader;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
/*
* invocation:
* cd ghidra/ghidra_11.2_PUBLIC/support
* than:
* ./analyzeHeadless ~/ghidra_scripts TLSKeyHunter -import "<PATH to TLS Binary>" -overwrite -prescript MinimalAnalysisOption.java -postScript TLSKeyHunter.java
* To-Do:
* - Split this into severals files and refactor the code
* - Add support for Rust and Java (right now we aren't able to identify there the PRF/HKDF-functions)
* - Check if the identified function is the same as the one used for exp master
* like in OpenSSL where the exp master String is directly provided to the tls13_hkdf_expand and else the derive_secret is used before
mov r9, [rsp+1C8h+var_180]
lea rcx, exporter_master_secret_5 ; "exp master"
mov rdx, [rsp+1C8h+var_188]
call tls13_hkdf_expand
- In the cases the developer of libray decided to create on lib for the P_Hash (TLS 1.2) and the normal PRF (TLS 1.0-1.1) we will propbably find two provided prfs
- For NSS we print both and for MatrixSSL we have to do this (here we have to retry output)
- In GoTLS if we have a short pattern and the last instruction was a jbe (just below or equals) check than
-- if we find multpile references like here we should check if its are 3 --> if there take last ref and there get the last function invocation which should be call crypto_tls_pHash
-- than as always (In GoTLS the extends and normal master secret have different function wrappers)
- In Future releases we can check if the identified PRF is using MD5 or SHA1 --> than it is very likely the TLS 1.0-1.1 version. Sha256/Sha384 is TLS 1.2 with P-Hash
- The ARM64 register following does not work so good at least on ncryptsslp.dll
- https://github.com/SySS-Research/hallucinate/blob/main/java/src/main/java/gs/sy/m8/hallucinate/Agent.java and BYteBuddy are used for hooking Java-Programs
- The x64 version to identify the TLSDeriveSecret is working (ubfornatly Ghidra seems not to hace access to global debug symbols?) it find the following offsets:
[*] Function offset (Ghidra): 1800110EC (0x1800110EC) --> this is also the one for IDA so it seems that the IDA offset is here wrong
[*] Function offset (IDA with base 0x0): 17FF110EC (0x17FF110EC)
- chrome: we need to ensure if the label is not provided as an argument to a function we have to keep following the jmp instruction to find its invocation
For Go Binaries I can say if there are symbols looks for the symbol to crypto_tls_prf10
Otherwise I need to identify the crypto_tls_pHash
*/
public class TLSKeyHunter extends GhidraScript {
// Custom implementation of Pair class
public class Pair<K, V> {
private final K first;
private final V second;
public Pair(K first, V second) {
this.first = first;
this.second = second;
}
public K getFirst() {
return first;
}
public V getSecond() {
return second;
}
}
// Global variable
private static List<Pair<Function, Address>> globalFunctionAddressPairs = new ArrayList<>();
private static final String VERSION = "0.9.4.1";
private static boolean DEBUG_RUN = true;
private static boolean is_go_binary = false;
private static boolean is_rust_binary = false;
private static boolean found_hkdf_string_without_ref = false;
private static boolean found_prf_string_without_ref = false;
private static boolean is_windows_binary = false;
private boolean is_target_binary_a_rust_binary(){
SymbolTable symbolTable = currentProgram.getSymbolTable();
String[] rustSymbols = {
"rust_eh_personality",
"core::panicking::panic_fmt",
"alloc::alloc::alloc",
"std::rt::lang_start",
"_ZN3std2rt10lang_start",
"DW.ref.rust_eh_personality"
};
for (Symbol symbol : symbolTable.getAllSymbols(true)) {
for (String rustSymbol : rustSymbols) {
if (symbol.getName().contains(rustSymbol)) {
System.out.println("🚀 Rust Binary Detected! Symbol found: " + symbol.getName());
return true; // Stop early if Rust is confirmed
}
}
}
return false;
}
private void printTLSKeyHunterLogo() {
System.out.println("");
System.out.println("""
TLSKeyHunter
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⠾⠛⢉⣉⣉⣉⡉⠛⠷⣦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⠋⣠⣴⣿⣿⣿⣿⣿⡿⣿⣶⣌⠹⣷⡀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⣼⠁⣴⣿⣿⣿⣿⣿⣿⣿⣿⣆⠉⠻⣧⠘⣷⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⢰⡇⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠀⠀⠈⠀⢹⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⢸⡇⢸⣿⠛⣿⣿⣿⣿⣿⣿⡿⠃⠀⠀⠀⠀⢸⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠈⣷⠀⢿⡆⠈⠛⠻⠟⠛⠉⠀⠀⠀⠀⠀⠀⣾⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⣧⡀⠻⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣼⠃⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢼⠿⣦⣄⠀⠀⠀⠀⠀⠀⠀⣀⣴⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⣠⣾⣿⣦⠀⠀⠈⠉⠛⠓⠲⠶⠖⠚⠋⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⣠⣾⣿⣿⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⣠⣾⣿⣿⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⣾⣿⣿⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⣄⠈⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
""");
System.out.println("Identifying the TLS PRF and the HKDF function for extracting TLS key material using Frida.");
System.out.println("Version: " + VERSION + " by Anonymous\n");
}
// Utility function to append a byte to a byte array
private byte[] appendByte(byte[] original, byte value) {
byte[] result = new byte[original.length + 1];
System.arraycopy(original, 0, result, 0, original.length);
result[original.length] = value;
return result;
}
private String byteArrayToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X ", b));
}
return sb.toString().trim();
}
public Pair<Function, Address> traceDataSectionPointer(Program program, Address startAddress, int maxAttempts) {
Listing listing = program.getListing();
int addressSize = program.getAddressFactory().getDefaultAddressSpace().getPointerSize();
Address refAddress = null;
boolean print_go_debug = false;
if(TLSKeyHunter.is_go_binary){
System.out.println("We are beginning to trace...");
print_go_debug= true;
}
Address currentAddress = startAddress;
for (int attempt = 0; attempt < maxAttempts; attempt++) {
// Check if the current address contains a pointer to a function
Function function = listing.getFunctionAt(currentAddress);
ReferenceManager referenceManager = currentProgram.getReferenceManager();
ReferenceIterator references = referenceManager.getReferencesTo(currentAddress);
Reference reference = references.next();
if(reference != null){
refAddress = reference.getFromAddress();
Function function1 = getFunctionContaining(refAddress);
function = function1;
}
if (function != null && refAddress != null) {
System.out.println("[*] Found reference to function: " + function.getName() +
" at target address: " + currentAddress);
Pair<Function, Address> funcPair = new Pair<>(function,refAddress);
return funcPair; // Found the function reference
}
// Move one address size backward
currentAddress = currentAddress.subtract(addressSize);
if (currentAddress == null) {
System.out.println("[-] Reached invalid address while stepping back.");
break;
}
}
System.out.println("[*] No function reference found after " + maxAttempts + " attempts.");
return null; // No valid function reference found
}
private Address searchPatterns(Memory memory, Address start, Address end, byte[][] patterns) throws Exception {
for (byte[] pattern : patterns) {
Address foundAddress = searchForPattern(memory, start, end, pattern);
if (foundAddress != null) {
System.out.println("[*] Found pattern: " + byteArrayToHex(pattern) + " at: " + foundAddress);
return foundAddress;
}
}
return null;
}
private Address findHexStringInRodata(String targetString, boolean do_print_info_msg) {
Set<Function> functions = new HashSet<>();
//Pair<Function, Address> functionAddressPair;
Address referenceAddress = null;
byte[] targetBytes = targetString.getBytes(); // Convert the target string to bytes
Memory memory = currentProgram.getMemory();
MemoryBlock rodataBlock = memory.getBlock(".rodata"); // Locate the .rodata section
if (rodataBlock == null) {
rodataBlock = memory.getBlock(".rdata"); // Locate the .rodata section
if (rodataBlock == null) {
if(do_print_info_msg){
System.out.println("[-] .rodata section not found!");
}
return null;
}
}
Address start = rodataBlock.getStart();
Address end = rodataBlock.getEnd();
byte[] littleEndianPattern = new byte[targetBytes.length];
for (int i = 0; i < targetBytes.length; i++) {
littleEndianPattern[i] = targetBytes[targetBytes.length - 1 - i];
}
// Variants of the pattern
byte[] bigEndianWithNull = appendByte(targetBytes, (byte) 0x00);
byte[] bigEndianWithSpace = appendByte(targetBytes, (byte) 0x20);
byte[] littleEndianWithNull = appendByte(littleEndianPattern, (byte) 0x00);
byte[] littleEndianWithSpace = appendByte(littleEndianPattern, (byte) 0x20);
Address foundAddress = null;
try {
// First, search for the big-endian pattern
foundAddress = searchPatterns(memory, start, end,
new byte[][] {bigEndianWithNull, bigEndianWithSpace, targetBytes});
if (foundAddress != null && DEBUG_RUN && do_print_info_msg) {
System.out.println("[*] Found big-endian pattern at: " + foundAddress);
}
if(foundAddress == null){
// If not found, search for the little-endian pattern
foundAddress = searchPatterns(memory, start, end,
new byte[][] {littleEndianWithNull, littleEndianWithSpace, littleEndianPattern});
if (foundAddress != null && DEBUG_RUN && do_print_info_msg) {
System.out.println("[*] Found little-endian pattern at: " + foundAddress);
}
}
} catch (MemoryAccessException e) {
System.err.println("[-] Error accessing memory: " + e.getMessage());
} catch (Exception e) {
System.err.println("[-] Error in pattern identification: " + e.getMessage());
}
if(foundAddress != null){
if(do_print_info_msg){
System.out.println("[*] String found in .rodata section at address: " + foundAddress);
}
return foundAddress;
}else{
if(do_print_info_msg){
System.err.println("[-] Unable to find pattern in .rodata section as well: "+foundAddress);
}
}
return null;
}
private Address findHexStringInRodataWrapper(String stringToFind, boolean do_print_info_msg) {
if(do_print_info_msg){
System.out.println("[*] Searching for hex representation of: " + stringToFind);
}
return findHexStringInRodata(stringToFind, do_print_info_msg); // Assumes this method exists as per your script
}
private Address findStringInRodata() throws Exception {
Memory memory = currentProgram.getMemory();
MemoryBlock rodataBlock = memory.getBlock(".rodata");
if (rodataBlock == null) {
rodataBlock = memory.getBlock(".rdata"); // if we have a pe file
if (rodataBlock == null) {
System.err.println("[-] No .rodata section found.");
return null;
}
}
Address start = rodataBlock.getStart();
Address end = rodataBlock.getEnd();
// Define the byte pattern in both big-endian and little-endian
byte[] bigEndianPattern = {(byte) 0x63, (byte) 0x20, (byte) 0x68, (byte) 0x73,
(byte) 0x20, (byte) 0x74, (byte) 0x72, (byte) 0x61,
(byte) 0x66, (byte) 0x66, (byte) 0x69, (byte) 0x63};
byte[] littleEndianPattern = new byte[bigEndianPattern.length];
for (int i = 0; i < bigEndianPattern.length; i++) {
littleEndianPattern[i] = bigEndianPattern[bigEndianPattern.length - 1 - i];
}
// Variants of the pattern
byte[] bigEndianWithNull = appendByte(bigEndianPattern, (byte) 0x00);
byte[] bigEndianWithSpace = appendByte(bigEndianPattern, (byte) 0x20);
byte[] littleEndianWithNull = appendByte(littleEndianPattern, (byte) 0x00);
byte[] littleEndianWithSpace = appendByte(littleEndianPattern, (byte) 0x20);
// First, search for the big-endian pattern
Address foundAddress = searchPatterns(memory, start, end,
new byte[][] {bigEndianWithNull, bigEndianWithSpace, bigEndianPattern});
if (foundAddress != null) {
System.out.println("[*] Found big-endian pattern at: " + foundAddress);
return foundAddress;
}
// If not found, search for the little-endian pattern
foundAddress = searchPatterns(memory, start, end,
new byte[][] {littleEndianWithNull, littleEndianWithSpace, littleEndianPattern});
if (foundAddress != null) {
System.out.println("[*] Found little-endian pattern at: " + foundAddress);
return foundAddress;
}
System.out.println("[*] Pattern not found in .rodata section.");
return null;
}
private Address searchForPattern(Memory memory, Address start, Address end, byte[] pattern) {
Address current = start;
try {
while (current.compareTo(end) <= 0) {
byte[] memoryBytes = new byte[pattern.length];
memory.getBytes(current, memoryBytes);
if (java.util.Arrays.equals(memoryBytes, pattern)) {
return current; // Pattern found
}
current = current.add(1); // Increment address by 1 byte
}
} catch (MemoryAccessException e) {
println("Memory access error at: " + current);
}
return null; // Pattern not found
}
private List<Pair<Function, Address>> findFunctionReferences(Address dataRelRoAddress, String sectionName) {
List<Pair<Function, Address>> functionAddressPairs = new ArrayList<>();
boolean is_go_binary = TLSKeyHunter.is_go_binary;
ReferenceManager referenceManager = currentProgram.getReferenceManager();
ReferenceIterator references = referenceManager.getReferencesTo(dataRelRoAddress);
if(is_go_binary){
System.out.println("Do we have more references: "+references.hasNext());
System.out.println("Infos about finding: address="+dataRelRoAddress+" + sectioname="+sectionName);
Reference[] referencesArray = referenceManager.getReferencesFrom(dataRelRoAddress);
System.out.println("referenceManager.getReferencesFrom(dataRelRoAddress): "+referencesArray.length);
Reference[] referencesGo = getReferencesTo(dataRelRoAddress);
System.out.println("Found nth references in go: "+referencesGo.length);
}
if(references.hasNext() == false){
System.out.println("[*] We found string but it has no reference");
return functionAddressPairs;
}
while (references.hasNext()) {
Reference reference = references.next();
Address refAddress = reference.getFromAddress();
Function function = getFunctionContaining(refAddress);
if (function != null) {
System.out.println("[*] Found reference to "+sectionName+" at " + refAddress + " in function: " + function.getName());
functionAddressPairs.add(new Pair<>(function, refAddress));
}else{
Memory memory = currentProgram.getMemory();
MemoryBlock block = memory.getBlock(refAddress);
if (block != null) {
String blockName = block.getName();
// Determine if the address belongs to a data section
if (blockName.contains(".data") || blockName.contains(".rodata") || blockName.contains(".rdata")) {
if(DEBUG_RUN){
System.out.println("[!] The address is pointing to another data section:"+blockName+ " at address: "+refAddress);
}
Pair<Function, Address> dataPair = traceDataSectionPointer(currentProgram, refAddress,4);
if(dataPair.first != null && dataPair.second != null){
functionAddressPairs.add(dataPair);
}
}else {
System.out.println("The address is in an unknown section.");
}
} else {
System.out.println("No memory block found for address: " + refAddress);
}
}
}
return functionAddressPairs;
}
/**
* This is a workaround for the rare cases where we found that the .data.rel.ro section has a reference to the actually string in the .data section but
* Ghidra wasn't able to identify this reference but was able to identidy the target string. This happens for instance in the s2n library for the "s hs traffic" string
*
*
* @param stringAddress
* @return
*/
private Address findPointerInDataRelRo(Address stringAddress) {
Memory memory = currentProgram.getMemory();
MemoryBlock dataRelRo = memory.getBlock(".data.rel.ro");
if (dataRelRo == null) {
dataRelRo = memory.getBlock(".rdata");
if(dataRelRo == null) {
System.err.println("[-] No .data.rel.ro section found.");
return null;
}
}
Address start = dataRelRo.getStart();
Address end = dataRelRo.getEnd();
System.out.println("pointer size: "+get_pointer_size());
byte[] targetBytes = new byte[get_pointer_size()]; // Assuming 64-bit architecture; adjust for 32-bit
// Get the offset of the string address and convert it to bytes
long addrValue = stringAddress.getOffset(); // Retrieve the raw address value
if (currentProgram.getLanguage().isBigEndian()) {
System.out.println("Doing big endian research...");
// Big-endian: Address bytes as-is
for (int i = targetBytes.length - 1; i >= 0; i--) {
targetBytes[i] = (byte) (addrValue & 0xFF);
addrValue >>= 8;
}
} else {
System.out.println("Doing little endian research...");
for (int i = 0; i < targetBytes.length; i++) {
targetBytes[i] = (byte) (addrValue & 0xFF);
addrValue >>= 8;
}
}
while (start.compareTo(end) <= 0) {
try {
byte[] memoryBytes = new byte[targetBytes.length];
memory.getBytes(start, memoryBytes);
if (java.util.Arrays.equals(memoryBytes, targetBytes)) {
System.err.println("[*] Found reference to string address in .data.rel.ro: " + start);
return start; // Found the pointer in .data.rel.ro
}
} catch (MemoryAccessException e) {
System.err.println("[-] Memory access error at (findPointerInDataRelRo): " + start);
}
start = start.add(targetBytes.length); // Increment by pointer size
}
println("No reference found in .data.rel.ro for address: " + stringAddress);
return null;
}
/**
* Checks if a given function contains any call to a function with "printf" in its name.
* @param function The function to search within.
* @return true if a call to a "printf" function is found, false otherwise.
*/
private boolean hasPrintfCall(Function function, boolean is_indirect_call) {
Listing listing = currentProgram.getListing();
if(is_indirect_call){
return true; // because we behave the same when we have an indirect call
}
// Iterate over all instructions within the function's body
for (Instruction instruction : listing.getInstructions(function.getBody(), true)) {
if (instruction.getMnemonicString().toUpperCase().equals("CALL")) {
// Get all references from the CALL instruction
for (Reference ref : instruction.getReferencesFrom()) {
if (ref.getReferenceType() == RefType.UNCONDITIONAL_CALL) {
// Check if the reference points to a function
Function calledFunction = getFunctionAt(ref.getToAddress());
if (calledFunction != null && functionNameContainsPrintf(calledFunction.getName())) {
return true; // Found a "printf" call
}
}
}
}
}
return false; // No "printf" call found
}
/**
* Checks if a function name contains "printf".
* @param functionName The name of the function to check.
* @return true if "printf" is in the function name, false otherwise.
*/
private boolean functionNameContainsPrintf(String functionName) {
return functionName.toLowerCase().contains("printf") && !functionName.toLowerCase().startsWith("pr_");
}
public static String toSignedHexString(long number) {
// Get the absolute value of the number and convert to hex
String hexValue = Long.toHexString(Math.abs(number));
// Prepend "-" if the original number is negative
return (number < 0 ? "-" : "") + "0x" + hexValue;
}
// Function to decode a 64-bit value to ASCII
private String decodeToAscii(long value) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 8; i++) {
char c = (char) (value & 0xFF);
if (c < 32 || c > 126) { // Check if character is printable
return null; // Not a valid ASCII string
}
sb.append(c);
value >>= 8;
}
return sb.reverse().toString(); // Reverse due to little-endian storage
}
private Pair<String, Long> analyzeInstructionOperands(Instruction instruction, boolean is_load_op){
if(DEBUG_RUN){
System.out.println("[!] (analyzeInstructionOperands) instr.getNumOperands(): "+instruction.getNumOperands());
}
String base_register_name = "Unknown";
String hex_offset = "0";
Long hex_long_offset = (long) 0;
// Analyze the first operand (destination) to extract RBP and offset
if (instruction.getMnemonicString().toUpperCase().equals("MOV") && instruction.getNumOperands() > 0) {
Object[] opObjects = instruction.getOpObjects(0);
if(is_load_op){
opObjects = instruction.getOpObjects(1);
}
Register baseRegister = null;
long offset = 0;
for (Object opObject : opObjects) {
if (opObject instanceof Register) {
baseRegister = (Register) opObject;
base_register_name = baseRegister.getName();
if(DEBUG_RUN){
System.out.println("[!] Base register: " + base_register_name);
}
}
else if (opObject instanceof Scalar) {
offset = ((Scalar) opObject).getValue();
hex_offset = toSignedHexString(offset);
hex_long_offset = offset;
if(DEBUG_RUN){
System.out.println("[!] Offset: " + hex_offset);
}
}
}
if (baseRegister != null) {
if(DEBUG_RUN){
System.out.println("[!] Found base register " + base_register_name + " with offset " + hex_offset);
}
} else {
System.err.println("[-] No base register found in the instruction.");
}
}
//return "["+base_register_name+" +"+hex_offset+"]";
return new Pair<>(base_register_name, hex_long_offset);
}
// Function to check if the next instruction stores the value on the stack
private boolean isStackStorage(Instruction instr) {
//analyzeInstructionOperands(instr);
if (instr.getNumOperands() > 0) {
Object operand = instr.getOpObjects(0)[0];
//System.out.println("[!] operand instanceof Address : "+(operand instanceof Address));
if (operand instanceof Address) {
Address address = (Address) operand;
return address.isStackAddress();
}
if(operand instanceof Register){
Register reg = (Register) operand;
//System.out.println("[!] reg.isBaseRegister(): "+reg.isBaseRegister());
return reg.isBaseRegister();
}
}
return false;
}
private boolean is_target_binary_a_go_binary(){
Program program = getCurrentProgram();
Memory memory = program.getMemory();
// 1) Check for a dedicated ".gopclntab" block
MemoryBlock gopclntab = memory.getBlock(".gopclntab");
if (gopclntab != null) {
return true;
}
// 2) Optionally check for ".go.buildinfo"
MemoryBlock gobuild = memory.getBlock(".go.buildinfo");
if (gobuild != null) {
return true;
}
SymbolTable symbolTable = currentProgram.getSymbolTable();
String[] goSymbols = {
"x_cgo_munmap.cold",
"main.main",
"runtime.main",
"_cgo_wait_runtime_init_done",
"runtime.goexit1.abi0",
"runtime.gogetenv"
};
for (Symbol symbol : symbolTable.getAllSymbols(true)) {
for (String goSymbol : goSymbols) {
if (symbol.getName().contains(goSymbol)) {
System.out.println("🚀 Go Binary Detected! Symbol found: " + symbol.getName());
return true; // Stop early if Go is confirmed
}
}
}
return false;
}
public List<Pair<Function, Address>> FindStackStrings(boolean is_hkdf) {
List<Pair<Function, Address>> functionAddressPairs = new ArrayList<>();
Address referenceAddress = null;
Listing listing = currentProgram.getListing();
String stack_string = "s retsam";
if(is_hkdf){
stack_string = "art sh s";
}
// Iterate through all instructions
for (Instruction instr : listing.getInstructions(true)) {
// Look for "mov" instructions that load a 64-bit constant
if (instr.getMnemonicString().toLowerCase().equals("mov") &&
instr.getNumOperands() > 1 &&
instr.getOperandType(1) == OperandType.SCALAR) {
Scalar scalar = instr.getScalar(1);
if (scalar != null && scalar.bitLength() == 64) {
long value = scalar.getValue();
// Convert the 64-bit constant to ASCII if possible
String asciiString = decodeToAscii(value);
if (asciiString != null) {
if(asciiString.equals(stack_string)){
referenceAddress = instr.getAddress();
Function func = getFunctionContaining(referenceAddress);
if (func != null) {
functionAddressPairs.add(new Pair<>(func, referenceAddress));
}
System.out.println("[*] Found 64-bit constant at " + instr.getAddress() +
" with ASCII interpretation (Little Endian): " + asciiString + " in function "+func.getName().toUpperCase());
if(is_hkdf){
System.out.println("[*] Stack-String for \"s hs traffic\" found: "+instr);
}else{
System.out.println("[*] Stack-String for \"master secret\" found: "+instr);
}
// Check if this constant is stored on the stack
Instruction nextInstr = instr.getNext();
if (nextInstr != null && nextInstr.getMnemonicString().toLowerCase().equals("mov") &&
isStackStorage(nextInstr)) {
System.out.println("[*] Stored on stack at " + nextInstr.getAddress());
}
}
}
}
}
}
return functionAddressPairs;
}
private List<Pair<Function, Address>> findStringUsageWithOffset(String substringToFind) {
List<Pair<Function, Address>> functionAddressPairs = new ArrayList<>();
Listing listing = currentProgram.getListing();
DataIterator dataIterator = listing.getDefinedData(true);
while (dataIterator.hasNext()) {
Data data = dataIterator.next();
// Check if the data type is a string and contains the substring
if (data.getDataType().getName().equals("string")) {
String fullString = data.getValue().toString();
int substringOffset = fullString.indexOf(substringToFind);
if (substringOffset != -1) {
// Found the larger string containing the substring
Address baseAddress = data.getAddress();
Address targetAddress = baseAddress.add(substringOffset); // Calculate target address for substring
if (DEBUG_RUN) {
System.out.println("[!] Found larger string containing substring at: " + baseAddress);
System.out.println("[!] Substring target address: " + targetAddress);
}
// Get references to the calculated target address (substring's address within larger string)
Reference[] references = getReferencesTo(targetAddress);
for (Reference ref : references) {
Address referenceAddress = ref.getFromAddress(); // Reference address pointing to the substring's location
if (DEBUG_RUN) {
System.out.println("[!] Found reference to substring at: " + referenceAddress);
Instruction instruction = listing.getInstructionAt(referenceAddress);
System.out.println("[!] Instruction using substring: " + instruction);
}
// Find the function containing this reference
Function func = getFunctionContaining(referenceAddress);
if (func != null) {
functionAddressPairs.add(new Pair<>(func, referenceAddress));
}
}
}
}
}
return functionAddressPairs;
}
private List<Pair<Function, Address>> findStringUsage(String stringToFind) {
List<Pair<Function, Address>> functionAddressPairs = new ArrayList<>();
Address referenceAddress = null;
Listing listing = currentProgram.getListing();
DataIterator dataIterator = listing.getDefinedData(true);
while (dataIterator.hasNext()) {
Data data = dataIterator.next(); // toLowerCase ggfs. als weiter verbesserung?
if (data.getDataType().getName().equals("string") && data.getValue().toString().equals(stringToFind)) {
Reference[] references = getReferencesTo(data.getAddress());
if(references.length < 1){
if(DEBUG_RUN){
System.out.println("[!] String found but has no reference: "+data.getAddress());
}
Address dataRelRoPointer = findPointerInDataRelRo(data.getAddress());
functionAddressPairs = findFunctionReferences(dataRelRoPointer,".data.rel.ro");
return functionAddressPairs;
}
for (Reference ref : references) {
referenceAddress = ref.getFromAddress(); // Store the reference address
if(DEBUG_RUN){
System.out.println("[!] Found String at ref: "+referenceAddress);
Instruction instruction = listing.getInstructionAt(referenceAddress);
System.out.println("[!] Instruction used there: "+instruction);
}
Function func = getFunctionContaining(ref.getFromAddress());
if (func != null) {
functionAddressPairs.add(new Pair<>(func, referenceAddress));
}
}
}
}
return functionAddressPairs;
}
private int getArgumentIndexForStack(PcodeOp callPcodeOp, Varnode trackedVarnode, Register resultRegister) {
Instruction currentInstruction = currentProgram.getListing().getInstructionContaining(callPcodeOp.getSeqnum().getTarget());
int argumentIndex = -1;
int pushCount = 0; // Count the number of push operations
if(DEBUG_RUN){
System.out.println("[!] Start identifying the argument index of the provided 32bit instruction\nBeginning with: "+currentInstruction);
}
// first current instruction will of course the call invocation therefore we have to start with the first instruction before that
currentInstruction = currentInstruction.getPrevious();
while (currentInstruction != null && !currentInstruction.getFlowType().isCall()) {
if(DEBUG_RUN){
System.out.println("[!!] argument analyzer: "+currentInstruction);
}
// Iterate backwards to find push instructions before the call
if (currentInstruction.getMnemonicString().equalsIgnoreCase("PUSH")) {
PcodeOp[] pcodeOps = currentInstruction.getPcode();
for (PcodeOp pcodeOp : pcodeOps) {
Varnode input = pcodeOp.getInput(0); // PUSH instructions usually have one input
if (input != null && input.equals(trackedVarnode)) {
argumentIndex = pushCount; // Found the matching PUSH for the register
}
}
pushCount++; // Increment the number of arguments pushed onto the stack
}
// Move to the previous instruction (because pushes happen before the CALL)
currentInstruction = currentInstruction.getPrevious();
}
// If the trackedVarnode was found in a PUSH instruction, return the argument index
if (argumentIndex != -1) {
return argumentIndex;
}
// If not found in a PUSH instruction, return an error code (-1)
return -1;
}
private long getLongValueAtAddress(Address address) {
// Get the Memory object from the current program
Memory memory = currentProgram.getMemory();
// Read 8 bytes from the memory at the given address
byte[] bytes = new byte[8]; // Long is 8 bytes
try {
memory.getBytes(address, bytes);
} catch (MemoryAccessException e) {
// Handle error if memory access fails
System.err.println("[-] Failed to read memory at address: " + address);
return -1; // Return an error value (you could handle this differently)
}
// Convert the bytes to a long (assuming little-endian order)
long value = 0;
for (int i = 0; i < 8; i++) {
value |= (long) (bytes[i] & 0xFF) << (i * 8);
}
return value;
}
private Pair<Function, StringBuilder> getFunctionBytesAsStringBuilder(Function resolvedFunction, boolean is_hkdf, boolean do_we_have_two_master_sec_labels){
Pair<Integer, Instruction> lengthPair = getLengthUntilBranch(resolvedFunction, is_hkdf, do_we_have_two_master_sec_labels, false);
int numBytes = lengthPair.getFirst();
Pair<Instruction, Boolean> instPair = isFunctionReturnAJumpWithInst(resolvedFunction);
if(numBytes < 0){
if(instPair.getSecond()){
Reference reference = instPair.getFirst().getReferencesFrom()[0];
Address targetAddress = reference.getToAddress();
Function originalResolvedFunction = resolvedFunction;
resolvedFunction = getFunctionAt(targetAddress);
if(DEBUG_RUN){
System.out.println("[!] The function "+originalResolvedFunction.getName()+" is a wrapper (actually jumping) function of "+resolvedFunction.getName());
}
lengthPair = getLengthUntilBranch(resolvedFunction, is_hkdf, do_we_have_two_master_sec_labels, false);
numBytes = lengthPair.getFirst();
}else{
return null;
}
}
// Now invoke readBytes on the resolved function
Memory memory = currentProgram.getMemory();
byte[] resolvedByteData = readBytes(memory, resolvedFunction.getEntryPoint(), numBytes);
StringBuilder bytePattern = new StringBuilder();
for (byte b : resolvedByteData) {
bytePattern.append(String.format("%02X ", b & 0xFF)); // Ensure uppercase hex values
}
Pair<Function, StringBuilder> funcPair = new Pair<>(resolvedFunction,bytePattern);
return funcPair;
}
private Pair<Instruction, Boolean> isFunctionReturnAJumpWithInst(Function function) {
// Get the function body instructions
InstructionIterator instructions = currentProgram.getListing().getInstructions(function.getBody(), true);
// Initialize a flag to track if the function ends with a jump
boolean endsWithJump = false;
// Loop through all instructions of the function
Instruction lastInstruction = null;
while (instructions.hasNext()) {
lastInstruction = instructions.next();
}
// Now check if the last instruction is a jump
if (lastInstruction != null && isJumpInstruction(lastInstruction)) {
endsWithJump = true;
}
Pair<Instruction, Boolean> instPair = new Pair<>(lastInstruction,endsWithJump);
// Output the result
if (endsWithJump) {
if(DEBUG_RUN){
System.out.println("[!] Function " + function.getName() + " does not return and ends with a jump.");
}
return instPair;
} else {
if(DEBUG_RUN){
System.out.println("[!] Function " + function.getName() + " either returns or does not end with a jump.");
}
return instPair;
}
}
private static boolean isJumpInstruction(Instruction instruction) {
String mnemonic = instruction.getMnemonicString();
if (mnemonic.equalsIgnoreCase("jmp") || mnemonic.equalsIgnoreCase("call")) {
return true;
}