/
main.go
700 lines (629 loc) · 20.1 KB
/
main.go
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
/*
____________ ___ ____ ___
/ __/ ___/ _ | / _ \ / _/__ / _/__
_\ \/ /__/ __ |/ ___/ _/ // _ \/ _/ _ \
/___/\___/_/ |_/_/ /___/_//_/_/ \___/
SCAP Info
(c) fG! 2024 - reverser@put.as - https://reverse.put.as
A small utility to extract version information from Apple EFI SCAP files.
*/
package main
import (
"bytes"
"crypto"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/binary"
"encoding/hex"
"encoding/json"
"encoding/pem"
"flag"
"fmt"
"github.com/gdbinit/scap_info/pkg/guid"
"github.com/schollz/progressbar/v3"
"io/ioutil"
"math/big"
"os"
"regexp"
"strings"
"path/filepath"
)
// version info to be modified runtime by -X flag
var (
Version = "1.0"
Build = "0"
Time = "none"
GitHash = "none"
verbose bool
)
const (
EFI_FVH_SIGNATURE = 0x4856465F
CAPSULE_GUID = "3B6686BD-0D76-4030-B70E-B5519E2FC5A0"
FFS1_GUID = "7A9354D9-0468-444A-81CE-0BF617D890DF"
EFI_CERT_TYPE_RSA2048_SHA256_GUID = "A7717414-C616-4977-9420-844712A735BF"
EFIBIOSID_GUID = "C3E36D09-8294-4B97-A857-D5288FE33E28"
AppleRomInformation_GUID = "B535ABF6-967D-43F2-B494-A1EB8E21A28E"
ApplePubKey = "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw+dIytnNOEMp4Q4lqR5D\n4adi/1Ka3leMk1vd+bE/IXnUhV5vyJ6eKcoSUX0X36Htzgvr8Op7Rh/+YdlOK99y\nwZb4ms01NrZEBkAU2uJaFdtrsIUuy9EgkWMY0czeo8hMku10P8F20LrKkg0/zzFY\nr/cx+IzgYjGCqO1n5lBRX3V0WQnwfUFfVfwVo1ZU0RjFWkYtN6Os2ghhLz8/ZXF2\nHvzLzCma7pmzpP1iEsz/9e83osM06HEZH34cMZYOAQpU6G+j9i5taQXhzVdzJBCj\n6wxrTe/avp9ZvxYYdYx1HNVs74UdHA6qHFWON6wQjakImGPSDi5+S/R17Gb+az79\nzwIDAQAB\n-----END RSA PUBLIC KEY-----"
ApplePubKeySHA256 = "bcc5659cd17be6b85bb9dc971c0370aa7e47e159f86ca1cdbe2c5b948e68a45e"
)
type EFI_FV_BLOCK_MAP_ENTRY struct {
NumBlocks uint32
Length uint32
}
type FirmwareVolumeFixedHeader struct {
Zero [16]uint8
FileSystemGUID guid.GUID
Length uint64
Signature uint32
Attributes uint32 // UEFI PI spec volume 3.2.1 EFI_FIRMWARE_VOLUME_HEADER
HeaderLen uint16
Checksum uint16
ExtHeaderOffset uint16
Reserved uint8
Revision uint8
FvBlockMap [2]EFI_FV_BLOCK_MAP_ENTRY
}
type EFICapsuleHeader struct {
CapsuleGuid guid.GUID
HeaderSize uint32
Flags uint32
ImageSize uint32
}
// IntegrityCheck holds the two 8 bit checksums for the file header and body separately.
type IntegrityCheck struct {
Header uint8
File uint8
}
// FileHeader represents an EFI File header.
type FileHeader struct {
GUID guid.GUID // This is the GUID of the file.
Checksum IntegrityCheck
Type FVFileType
Attributes uint8
Size [3]uint8
State uint8
}
// SectionHeader represents an EFI_COMMON_SECTION_HEADER as specified in
// UEFI PI Spec 3.2.4 Firmware File Section
type SectionHeader struct {
Size [3]uint8
Type uint8
}
// FVFileType represents the different types possible in an EFI file.
type FVFileType uint8
// UEFI FV File types.
const (
FVFileTypeAll FVFileType = iota
FVFileTypeRaw
FVFileTypeFreeForm
FVFileTypeSECCore
FVFileTypePEICore
FVFileTypeDXECore
FVFileTypePEIM
FVFileTypeDriver
FVFileTypeCombinedPEIMDriver
FVFileTypeApplication
FVFileTypeSMM
FVFileTypeVolumeImage
FVFileTypeCombinedSMMDXE
FVFileTypeSMMCore
FVFileTypeSMMStandalone
FVFileTypeSMMCoreStandalone
FVFileTypeOEMMin FVFileType = 0xC0
FVFileTypeOEMMax FVFileType = 0xDF
FVFileTypeDebugMin FVFileType = 0xE0
FVFileTypeDebugMax FVFileType = 0xEF
FVFileTypePad FVFileType = 0xF0
FVFileTypeFFSMin FVFileType = 0xF0
FVFileTypeFFSMax FVFileType = 0xFF
)
type EFI_CERT_BLOCK_RSA_2048_SHA256 struct {
HashType guid.GUID
PublicKey [256]uint8
Signature [256]uint8
}
type AppleRomInfo struct {
Model string `json:"model,omitempty"` // Model or BIOS ID
EFIVersion string `json:"efiversion,omitempty"` // EFI Version - not present on older
Date string `json:"date,omitempty"` // Date
Revision string `json:"revision,omitempty"` // Revision
ROM string `json:"romversion,omitempty"` // ROM Version
Compiler string `json:"compiler,omitempty"` // Compiler - not present on older
}
type EFIBIOSId struct {
Version string `json:"version"`
}
type OutputInfo struct {
AppleROM AppleRomInfo `json:"applerom,omitempty"`
EFI EFIBIOSId `json:"efi"`
SHA256 string `json:"sha256"`
Size int `json:"size"`
}
// Debug print if verbose flag is set
func Debugf(format string, args ...interface{}) {
if verbose {
fmt.Printf(format, args...)
}
}
// Checksum16 does a 16 bit checksum of the byte slice passed in.
func Checksum16(buf []byte) (uint16, error) {
r := bytes.NewReader(buf)
buflen := len(buf)
if buflen%2 != 0 {
return 0, fmt.Errorf("byte slice does not have even length, not able to do 16 bit checksum. Length was %v",
buflen)
}
var temp, sum uint16
for i := 0; i < buflen; i += 2 {
if err := binary.Read(r, binary.LittleEndian, &temp); err != nil {
return 0, err
}
sum += temp
}
return 0 - sum, nil
}
// Read3Size reads a 3-byte size and returns it as a uint64
func Read3Size(size [3]uint8) uint64 {
return uint64(size[2])<<16 |
uint64(size[1])<<8 | uint64(size[0])
}
func reverseByteOrder(data []byte) {
length := len(data)
for i := 0; i < length/2; i++ {
data[i], data[length-i-1] = data[length-i-1], data[i]
}
}
// Align aligns an address
func Align(val uint64, base uint64) uint64 {
return (val + base - 1) & ^(base - 1)
}
// Align8 aligns an address to 8 bytes
func Align8(val uint64) uint64 {
return Align(val, 8)
}
func ValidateFirmwareVolumeChecksum(buf []byte) (bool, error) {
fvh, err := GetFirmwareVolumeHeader(buf)
if err != nil {
return false, err
}
// store the checksum since we will overwrite it
srcChecksum := fvh.Checksum
// make a copy to a structure so that we can reset the checksum and compute it again
// XXX: is there a better way to do this in Go? Royal pain all this copy back and forth
// to just access the field instead of hardcoding an offset
// reset the checksum
fvh.Checksum = 0
// now copy the modified structure copy back to a bytes buffer that we can checksum
var copyBuf bytes.Buffer
err = binary.Write(©Buf, binary.LittleEndian, &fvh)
if err != nil {
return false, err
}
dataToChecksum := copyBuf.Bytes()
// fmt.Println("Firmare Volume Header Data to checksum", dataToChecksum, len(dataToChecksum))
// validate the checksum
headerCRC, err := Checksum16(dataToChecksum)
if err != nil {
return false, err
}
if headerCRC != srcChecksum {
return false, fmt.Errorf("checksum doesn't match")
}
Debugf("Computed Header CRC: 0x%x\n", headerCRC)
Debugf("Header Checksum: 0x%x\n", srcChecksum)
return true, nil
}
func ValidateSignature(buf []byte) (bool, error) {
header, err := GetEFICapsuleHeader(buf)
if err != nil {
return false, err
}
fvh, err := GetFirmwareVolumeHeader(buf)
if err != nil {
return false, err
}
rsaHeader := EFI_CERT_BLOCK_RSA_2048_SHA256{}
rsaData := buf[fvh.Length+uint64(header.HeaderSize):]
reader := bytes.NewReader(rsaData)
err = binary.Read(reader, binary.LittleEndian, &rsaHeader)
// fmt.Println(rsaHeader.HashType)
if rsaHeader.HashType.String() != EFI_CERT_TYPE_RSA2048_SHA256_GUID {
return false, fmt.Errorf("hash type not supported")
}
// we need to reverse the original data byte order
reverseByteOrder(rsaHeader.PublicKey[:])
reverseByteOrder(rsaHeader.Signature[:])
pubKeyHash := sha256.Sum256(rsaHeader.PublicKey[:])
if hex.EncodeToString(pubKeyHash[:]) != ApplePubKeySHA256 {
return false, fmt.Errorf("Unexpected public key. Tampered or damaged file?")
}
// fmt.Println(len(rsaHeader.PublicKey), rsaHeader.PublicKey)
// fmt.Println(len(rsaHeader.Signature), rsaHeader.Signature)
modulus := new(big.Int).SetBytes(rsaHeader.PublicKey[:])
exponentStr := "65537"
exponent, success := new(big.Int).SetString(exponentStr, 10)
if !success {
return false, fmt.Errorf("Invalid exponent")
}
// Create the RSA public key
publicKey := rsa.PublicKey{
N: modulus,
E: int(exponent.Int64()),
}
// Encode the public key to PEM format
pubASN1, err := x509.MarshalPKIXPublicKey(&publicKey)
if err != nil {
return false, err
}
pubPEM := pem.EncodeToMemory(&pem.Block{
Type: "RSA PUBLIC KEY",
Bytes: pubASN1,
})
// Print or use the PEM-encoded public key
Debugf("Public key (PEM format):\n")
Debugf("%s\n", string(pubPEM))
// Hash the data and verify the signature
// the whole firmware volume is what is signed
hash := sha256.Sum256(buf[header.HeaderSize : uint64(header.HeaderSize)+fvh.Length])
Debugf("[+] Firmware volume SHA256: %s\n", hex.EncodeToString(hash[:]))
// verify the signature
err = rsa.VerifyPKCS1v15(&publicKey, crypto.SHA256, hash[:], rsaHeader.Signature[:])
if err != nil {
return false, err
}
Debugf("[+] Signature is valid!\n")
return true, nil
}
// returns a EFICapsuleHeader structure
func GetEFICapsuleHeader(buf []byte) (EFICapsuleHeader, error) {
reader := bytes.NewReader(buf)
header := EFICapsuleHeader{}
err := binary.Read(reader, binary.LittleEndian, &header)
if err != nil {
return EFICapsuleHeader{}, err
}
if header.CapsuleGuid.String() != CAPSULE_GUID {
return EFICapsuleHeader{}, fmt.Errorf("target file is not an EFI Capsule")
}
return header, nil
}
func GetFirmwareVolumeHeader(buf []byte) (FirmwareVolumeFixedHeader, error) {
capsuleHeader, err := GetEFICapsuleHeader(buf)
if err != nil {
return FirmwareVolumeFixedHeader{}, err
}
/* the volumes start after the header */
fvData := buf[capsuleHeader.HeaderSize:]
fvh := FirmwareVolumeFixedHeader{}
reader := bytes.NewReader(fvData)
err = binary.Read(reader, binary.LittleEndian, &fvh)
if err != nil {
return FirmwareVolumeFixedHeader{}, err
}
if fvh.Signature != EFI_FVH_SIGNATURE {
return FirmwareVolumeFixedHeader{}, fmt.Errorf("not a firmware volume")
}
return fvh, nil
}
func GetSectionData(buf []byte, myguid string) ([]byte, error) {
header, err := GetEFICapsuleHeader(buf)
if err != nil {
return []byte{}, err
}
fvh, err := GetFirmwareVolumeHeader(buf)
if err != nil {
return []byte{}, err
}
fvBodyLen := fvh.Length - uint64(fvh.HeaderLen)
pos := uint64(fvh.HeaderLen)
fvData := buf[header.HeaderSize:]
for pos < fvBodyLen {
filePtr := fvData[pos:]
reader := bytes.NewReader(filePtr)
fh := FileHeader{}
err = binary.Read(reader, binary.LittleEndian, &fh)
if err != nil {
return []byte{}, err
}
// the ones that we are interested in - we expect it to be FreeForm type
if fh.GUID.String() == myguid && fh.Type == FVFileTypeFreeForm {
sectionData := filePtr[binary.Size(FileHeader{}):]
return sectionData, nil
}
// move to next item - we need to take care of alignment
pos += Align8(Read3Size(fh.Size))
// XXX: we are not detecting the last one and loop exiting via size check
}
return []byte{}, fmt.Errorf("guid not found")
}
func GetEFIBiosID(buf []byte) (EFIBIOSId, error) {
sectionData, err := GetSectionData(buf, EFIBIOSID_GUID)
if err != nil {
fmt.Printf("[-] ERROR: Failed to retrieve EFI Bios ID section: %v\n", err)
return EFIBIOSId{}, err
}
reader := bytes.NewReader(sectionData)
sectionHeader := SectionHeader{}
sectionHeaderSize := binary.Size(SectionHeader{})
err = binary.Read(reader, binary.LittleEndian, §ionHeader)
if err != nil {
fmt.Println("[-] ERROR: Failed to read section header.")
// XXX: what do we do?
}
// fmt.Println(Read3Size(sectionHeader.Size))
// the raw data is after the section header
rawData := sectionData[sectionHeaderSize:Read3Size(sectionHeader.Size)]
// fmt.Println(string(rawData))
inputText := string(rawData)
// Find the indices of "$IBIOSI$" and "Copyright"
startIndex := strings.Index(inputText, "$IBIOSI$")
endIndex := strings.Index(inputText, "Copyright")
if startIndex != -1 && endIndex != -1 {
// Extract the text between "$IBIOSI$" and "Copyright"
finalText := inputText[startIndex+len("$IBIOSI$") : endIndex]
// TrimSpace doesn't work so we go the hard way
finalText = strings.TrimLeft(finalText, "\x20\x00")
// the string is utf-16 and all the bytes will show up in the JSON
// so we remove them the dumb way
// XXX: this is ugly :P
finalTextBytes := []byte(finalText)
cleanText := make([]byte, 0)
for i := 0; i < len(finalTextBytes); i++ {
if finalTextBytes[i] != 0 {
cleanText = append(cleanText, finalTextBytes[i])
}
}
ret := EFIBIOSId{
Version: string(cleanText),
}
return ret, nil
} else {
return EFIBIOSId{}, fmt.Errorf("bios id not found")
}
}
func GetAppleRomInfo(buf []byte) (AppleRomInfo, error) {
sectionData, err := GetSectionData(buf, AppleRomInformation_GUID)
if err != nil {
// fmt.Printf("[-] ERROR: Failed to retrieve Apple Rom Info section: %v\n", err)
return AppleRomInfo{}, err
}
reader := bytes.NewReader(sectionData)
sectionHeader := SectionHeader{}
sectionHeaderSize := binary.Size(SectionHeader{})
err = binary.Read(reader, binary.LittleEndian, §ionHeader)
if err != nil {
// fmt.Println("[-] ERROR: Failed to read section header.")
return AppleRomInfo{}, err
// XXX: what do we do?
}
// fmt.Println(Read3Size(sectionHeader.Size))
// the raw data is after the section header
rawData := sectionData[sectionHeaderSize:Read3Size(sectionHeader.Size)]
// fmt.Println(string(rawData))
inputText := string(rawData)
// ChatGPT territory here... why bother with regexps ever again :-)
// Split the input text into lines
lines := strings.Split(inputText, "\n")
// Define the regular expression pattern to match pairs of fields
pattern := `\s+([A-Za-z\s]+):\s+([^\n]+)`
// Compile the regular expression pattern
re := regexp.MustCompile(pattern)
// Create a map to store extracted fields and their values
fields := make(map[string]string)
// Process lines skipping the first line
startIndex := 1 // Skip the first line ("Apple ROM Version")
for i := startIndex; i < len(lines); i++ {
line := lines[i]
// Find all matches in the current line
matches := re.FindStringSubmatch(line)
// Extract and store pairs of fields and values
if len(matches) == 3 {
field := strings.TrimSpace(matches[1])
value := strings.TrimSpace(matches[2])
fields[field] = value
}
}
// Display the extracted fields and values
// We want "BIOS ID" and "ROM Version" for file naming
// for field, value := range fields {
// fmt.Printf("%s -> %s\n", field, value)
// }
ret := AppleRomInfo{
Model: fields["Model"],
EFIVersion: fields["EFI Version"],
Date: fields["Date"],
Revision: fields["Revision"],
ROM: fields["ROM Version"],
Compiler: fields["Compiler"],
}
// older versions use BIOS ID instead of Model
if val, ok := fields["BIOS ID"]; ok {
ret.Model = val
}
return ret, nil
}
func AnalyseFile(input_file string) (OutputInfo, error) {
f, err := os.Open(input_file)
if err != nil {
panic(err)
}
defer f.Close()
input_buf, err := ioutil.ReadAll(f)
header, err := GetEFICapsuleHeader(input_buf)
if err != nil {
return OutputInfo{}, err
}
Debugf("------ Capsule Header ------\n")
Debugf("GUID: %s\n", header.CapsuleGuid)
Debugf("Full Size: %d\n", header.ImageSize)
Debugf("Header Size: %d\n", header.HeaderSize)
Debugf("Flags: %d\n", header.Flags)
Debugf("Capsule Size: %d\n", header.ImageSize-header.HeaderSize)
/* the volumes start after the header */
fvh, err := GetFirmwareVolumeHeader(input_buf)
if err != nil {
fmt.Printf("[-] ERROR: failed to read: %v\n", err)
return OutputInfo{}, err
}
Debugf("------ Firmware Volume Header ------\n")
// Debugf("%#v\n", fvh)
Debugf("Zero: %v\n", fvh.Zero)
Debugf("GUID: %s\n", fvh.FileSystemGUID)
Debugf("Length: %d\n", fvh.Length)
Debugf("Signature: %x\n", fvh.Signature)
Debugf("Attributes: %x\n", fvh.Attributes)
Debugf("Header len: %d\n", fvh.HeaderLen)
Debugf("Checksum: %x\n", fvh.Checksum)
Debugf("Extended offset: %d\n", fvh.ExtHeaderOffset)
Debugf("Reserved: %x\n", fvh.Reserved)
Debugf("Revision: %d\n", fvh.Revision)
Debugf("----------------------------------\n")
valid, err := ValidateFirmwareVolumeChecksum(input_buf)
if err != nil {
fmt.Printf("[-] ERROR: Firmware Volume checksum failed: %v", err)
return OutputInfo{}, err
}
valid, err = ValidateSignature(input_buf)
if err != nil {
fmt.Printf("[-] ERROR: Signature verification failed: %v", err)
return OutputInfo{}, err
}
if !valid {
fmt.Println("[-] ERROR: Invalid signature detected.")
return OutputInfo{}, err
}
// now we can proceed parsing the firmware volume contents
// we are interested in two GUIDs: EFIBIOSID_GUID and AppleRomInformation_GUID
// the first one appears to always exist while the second not
//
// layout is:
// SCAP Header (0x50 bytes)
// Firmware Volume Header (0x48 bytes)
// Followed by File Header
// And then Section Headers and data
biosid, err := GetEFIBiosID(input_buf)
rominfo, err := GetAppleRomInfo(input_buf)
scapHash := sha256.Sum256(input_buf)
// fmt.Println("[+] Target hash:", hex.EncodeToString(scapHash[:]))
output := OutputInfo{
AppleROM: rominfo,
EFI: biosid,
SHA256: hex.EncodeToString(scapHash[:]),
Size: len(input_buf),
}
return output, nil
}
func AnalyseFolder(input_folder string) ([]OutputInfo, error) {
var err error
output := make([]OutputInfo, 0)
// who doesn't love a spinner!
bar := progressbar.Default(-1, "processing")
err = filepath.Walk(input_folder, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
if info.Mode().IsRegular() {
newentry, err2 := AnalyseFile(path)
if err2 == nil {
output = append(output, newentry)
}
bar.Add(1)
}
return nil
})
if err != nil {
fmt.Printf("[-] Error: walking through target folder: %s\n", err.Error())
}
return output, nil
}
// prettyJSON returns an indentified JSON bytes.Buffer
func prettyJSON(v any) (bytes.Buffer, error) {
var out bytes.Buffer
outputJson, err := json.Marshal(v)
if err != nil {
fmt.Printf("[-] ERROR: JSON failure: %v\n", err)
return out, err
}
err = json.Indent(&out, outputJson, "", " ")
if err != nil {
fmt.Printf("json indent error\n")
return out, err
}
// fmt.Printf("%s\n", out.Bytes())
return out, nil
}
func printJSON(input any) {
buf, err := prettyJSON(input)
if err != nil {
fmt.Printf("[-] ERROR: Failed to prettify JSON\n")
return
}
fmt.Println("")
fmt.Println("------------------ CUT HERE ------------------")
fmt.Printf("%s\n", buf.Bytes())
fmt.Println("------------------ CUT HERE ------------------")
}
func saveJSON(input any, output string) error {
buf, err := prettyJSON(input)
if err != nil {
fmt.Printf("[-] ERROR: Failed to prettify JSON\n")
return err
}
err = os.WriteFile(output, buf.Bytes(), 0644)
if err != nil {
fmt.Errorf("[-] ERROR: Failed to write JSON file: %v\n", err)
return err
}
return nil
}
func main() {
fmt.Println(" ____________ ___ ____ ___")
fmt.Println(" / __/ ___/ _ | / _ \\ / _/__ / _/__")
fmt.Println(" _\\ \\/ /__/ __ |/ ___/ _/ // _ \\/ _/ _ \\")
fmt.Println(" /___/\\___/_/ |_/_/ /___/_//_/_/ \\___/")
fmt.Println("(c) fG! 2024 - reverser@put.as - https://reverse.put.as")
fmt.Printf("SCAP Info v%s.%s generated %s from hash %s\n", Version, Build, Time, GitHash)
var input string
var output string
flag.StringVar(&input, "i", "", "Input SCAP file")
flag.StringVar(&output, "o", "", "Output JSON file")
flag.BoolVar(&verbose, "v", false, "Verbose output")
flag.Parse()
if input == "" {
fmt.Println("[-] ERROR: Missing input file.")
return
}
stat, err := os.Stat(input)
if err != nil {
fmt.Printf("[-] ERROR: Failed to stat file: %v\n", err)
os.Exit(1)
}
if mode := stat.Mode(); mode.IsRegular() {
data, err := AnalyseFile(input)
if err != nil {
fmt.Printf("[-] ERROR: Failed to analyse file: %v\n", err)
os.Exit(1)
}
if output != "" {
saveJSON(data, output)
} else {
printJSON(data)
}
} else if mode.IsDir() {
data, err := AnalyseFolder(input)
if err != nil {
fmt.Printf("[-] ERROR: Failed to analyse folder: %v\n", err)
os.Exit(1)
}
if output != "" {
saveJSON(data, output)
} else {
printJSON(data)
}
} else {
fmt.Printf("[-] ERROR: Not sure what the hell target is!\n")
os.Exit(1)
}
fmt.Printf("[+] All done!\n")
}