File forensics is quite a large part of digital forensics, sometimes, you do not need to run the biggest tool to detect a file as you may just want something that can detect specific file headers or even specific signature. The list of programs in this module will allow you to inspect or categorize specific files much easily!
File Forensics | Locating ZIP / Archives inside of images
Locating ZIP files is something that always confuses people, why is it required? why would a digital forensics expert need to know how to do it? In the case of malware or even general scenarios, we may need to check if ZIP's exist in images as images may be used to sneak past security measures. To do so, we can use the following code.
packagemainimport ("bufio""bytes""fmt""log""os")// Automate error checkingfuncCE(x error) {if x !=nil { log.Fatal(x) }}funcVerifyZipSig(k string) { sig :=`\x4b\x03\x04`// ZIP / Archive signature (0x4b,0x03, 0x04) fmt.Printf("[%s]---[%s] Is being checked for a ZIP signature \n", "Debug", sig) f, x := os.Open(k) // Open fileCE(x) // Check for errordefer f.Close() // Close the file when the block of code is done executing buffer := bufio.NewReader(f) // create a new reader stat, _ := f.Stat() // stat the file to grab the files sizefor l :=int64(0); l < stat.Size(); l++ { // itterate over the size b, x := buffer.ReadByte() // read the byteCE(x) // check for an error if b == '\x50' { // check if the byte is 0x50 ( if the first byte is '\x50' before performing the more expensive buffer.Peek(3) operation, the code can quickly identify that a potential ZIP signature might exist in the file. This check helps to reduce unnecessary calls to buffer.Peek(3) and improve the efficiency of the overall search process. )
BS :=make([]byte, 3) // Create the storage and buffer for the signature BS, x = buffer.Peek(3) CE(x) // Check errorif bytes.Equal(BS, []byte{'\x4b', '\x03', '\x04'}) { fmt.Println("File [ ", k, " ] Contains a ZIP file") } // Check and make sure that the ZIP signature accutally exists } }}// Main programatic entry pointfuncmain() {iflen(os.Args) ==0 { // os arguments fmt.Printf("Usage: %s image_file...", os.Args[0]) } else { for _, f := range os.Args[1:] { // get all files from the argument list 'go run main.go file1.png file2.png file3.jpg ....'
VerifyZipSig(f) // call function } }}
This program works by scanning over each byte within the file or image and then compares it to the byte array for the signature of a ZIP file. If it is found it will say that the given file followed by its name does in fact contain a zip signature. If the file does contain a ZIP signature you can use 7z to extract the ZIP file.
File Forensics | Cuting out sections of images
When it comes to digital forensics specifically files, it may come in handy one day where you need to just slice an image in half or even cut out otherwise known as carve a part of a file out to make sure you are parsing a specific bit. This allows you to be much more percise when it comes to your investigations. The program below will do so but will allow you to cut from a starting point to an ending point.
packagemainimport ("fmt""io/ioutil""log""os")// Stop cutting at...var endB = []byte{0x00, 0x00}var startb = []byte{0x5b, 0xce, 0xb7,0x14, 0x0c, 0x00,0x00, 0x00, 0x0c,0x00, 0x00, 0x00,0x0d, 0x00, 0x00,0x00,} // Define the starting bytes to cut// Automate errorsfuncCE(msg string, x error) {if x !=nil { log.Fatal(msg +" -> "+ fmt.Sprint(x)) }}// Carving functionfuncCarve(file string) { in, x := ioutil.ReadFile(file) // Read the file into bytesCE("Could not read the input file ", x) // Check errorvar start, end int// define start and end pointsfor i :=0; i <len(in)-len(startb); i++ { // itterate over the lengthifstring(in[i:i+len(startb)]) ==string(startb) { // calculate the start start = i +len(startb)break } }for i :=len(in) -len(endB); i >0; i-- { // itterate over the lengthifstring(in[i:i+len(endB)]) ==string(endB) { // calculate the end end = ibreak } } // check if the start and end are within boundsif start >= end { fmt.Println("File signature not found") os.Exit(0) } outputFile, x := os.Create("output.jpg") // create output fileCE("Could not CREATE the output file ", x) // check for errordefer outputFile.Close() // close file at the end of the brick _, x = outputFile.Write(in[start:end]) // write the cut start to the cutting end pointCE("Could not WRITE output file ", x) // check for the error fmt.Println("File carved successfully") // output the write message OK}funcmain() {iflen(os.Args) ==0 { fmt.Println("Usage: "+ os.Args[0] +" input_file.jpg") } else {Carve(os.Args[1]) }}
This program you will need to tweak a bit, but here is how we might use this tool. Say we have a picture that has a ZIP file inside of it. We can demo this with an image dump shown below.
we can take specific bytes we want to carve out and run this program. Say we want to carve anything from f1 3f 00 00 00 7e 00 00 00 16 00 24 00 00 00 00 to 0x00 which is the end of the file in this case. We can replace our current hex with the following
var endB = []byte{0x00}var startb = []byte{0xf1, 0x3f, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x16, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00} // Define the starting bytes to cut
When we run the program and get our output we should get something like
This is not really a file forensics thing but may become helpful to you. Sometimes, manually converting thousands of hex points for forensics, shellcode etc can be quite boring. The following utility will allow you to convert these bytes into actual hex.
packagemainimport ("fmt""os""strings")funcmain() {// Input code input := os.Args[1]// Split the input by space to get individual codes codes := strings.Split(input, " ")// Convert each code to its hex representationvar hexCodes []stringfor _, code :=range codes { hexCode := fmt.Sprintf("0x%s", code) hexCodes =append(hexCodes, hexCode) }// Print the hex representation fmt.Println(strings.Join(hexCodes, ", "))}
Testing
When we test the program with go run hexutil.go "f1 3f 00 00 00 7e 00 00 00 16 00 24 00 00 00 00" we get 0xf1, 0x3f, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x16, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00
File Forensics | Getting mime type
Sometimes, the OS can not detect a specific file type, this is where file checking tools come in handy. Of course, tools like file exist on linux to get the file type, but this tool may work in any other environment where you may not be on linux or in some wild instance where file is not directly implemented. The program below will catch a file's mime type based on structured data.
This list can be improved but this is a good example of how you might do this.
File Forensics | PE File forensics
File forensics may not just be for specific file groups but rather file types. Take this program that parsers and grabs values for PE files
packagemainimport ("debug/pe""encoding/binary""fmt""io""log""os")var ( SizeofOptionalHeader32 =uint16(binary.Size(pe.OptionalHeader32{})) SizeofOptionalHeader64 =uint16(binary.Size(pe.OptionalHeader64{})) X32 pe.OptionalHeader32 X64 pe.OptionalHeader64)funcCE(x error) {if x !=nil { log.Fatal(x) }}funcmain() { f, x := os.Open(os.Args[0])CE(x) PEFILE, x := pe.NewFile(f)CE(x)defer f.Close()defer PEFILE.Close() HEAD :=make([]byte, 96) so :=make([]byte, 4) _, x = f.Read(HEAD)CE(x) peoffset :=int64(binary.LittleEndian.Uint32(HEAD[0x3c:])) f.ReadAt(so[:], peoffset) sr := io.NewSectionReader(f, 0, 1<<63-1) _, x = sr.Seek(peoffset+4, io.SeekStart)CE(x) binary.Read(sr, binary.LittleEndian, &PEFILE.FileHeader)switch PEFILE.FileHeader.SizeOfOptionalHeader {case SizeofOptionalHeader32: binary.Read(sr, binary.LittleEndian, &X32)case SizeofOptionalHeader64: binary.Read(sr, binary.LittleEndian, &X64) } fmt.Printf("Magic byte : %s%s\n", string(HEAD[0]), string(HEAD[1])) fmt.Printf("LFA_NEW Value : %s\n", string(so)) fmt.Printf("Architecture : %#x\n", PEFILE.FileHeader.Machine) fmt.Printf("Section Count : %#x\n", PEFILE.FileHeader.NumberOfSections) fmt.Printf("Sizeof Op Header : %#x\n", PEFILE.FileHeader.SizeOfOptionalHeader) fmt.Printf("Num of section field offsets : %#x\n", peoffset+6) fmt.Printf("Section Table offset : %#x\n", peoffset+0xF8)for _, sn :=range PEFILE.Sections { fmt.Printf("|>>> Discovered Section %s\n", sn.Name) fmt.Printf("\tSection Characteristics | %#x\n", sn.Characteristics) fmt.Printf("\tSection Virtual Size | %#x\n", sn.VirtualSize) fmt.Printf("\tSection Virtual Offset | %#x\n", sn.VirtualAddress) fmt.Printf("\tSection Raw Size | %#x\n", sn.Size) fmt.Printf("\tSection Raw Offset to Data |%#x\n", sn.Offset) fmt.Printf("\tSection Append Offset (Next Section) | %#x\n", sn.Offset+sn.Size) } fmt.Printf("Entry Point | %#x\n", X32.AddressOfEntryPoint) fmt.Printf("Image Base | %#x\n", X32.ImageBase) fmt.Printf("Size of image | %#x\n", X32.SizeOfImage) fmt.Printf("Section alignment | %#x\n", X32.SectionAlignment) fmt.Printf("File attachment | %#x\n", X32.FileAlignment) fmt.Printf("File Characteristics | %#x\n", PEFILE.FileHeader.Characteristics) fmt.Printf("Size of headers | %#x\n", X32.SizeOfHeaders) fmt.Printf("Checksum | %#x\n", X32.CheckSum) fmt.Printf("Machine | %#x\n", PEFILE.FileHeader.Machine) fmt.Printf("Subsystem | %#x\n", X32.Subsystem) fmt.Printf("DLL Characteristics | %#x\n", X32.DllCharacteristics)}
There are many more advanced ways we can parse this information but this is another good example of how go allows you to quickly execute operations like this.