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.
package main
import (
"bufio"
"bytes"
"fmt"
"log"
"os"
)
// Automate error checking
func CE(x error) {
if x != nil {
log.Fatal(x)
}
}
func VerifyZipSig(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 file
CE(x) // Check for error
defer 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 size
for l := int64(0); l < stat.Size(); l++ { // itterate over the size
b, x := buffer.ReadByte() // read the byte
CE(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 error
if 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 point
func main() {
if len(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.
package main
import (
"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 errors
func CE(msg string, x error) {
if x != nil {
log.Fatal(msg + " -> " + fmt.Sprint(x))
}
}
// Carving function
func Carve(file string) {
in, x := ioutil.ReadFile(file) // Read the file into bytes
CE("Could not read the input file ", x) // Check error
var start, end int // define start and end points
for i := 0; i < len(in)-len(startb); i++ { // itterate over the length
if string(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 length
if string(in[i:i+len(endB)]) == string(endB) { // calculate the end
end = i
break
}
} // check if the start and end are within bounds
if start >= end {
fmt.Println("File signature not found")
os.Exit(0)
}
outputFile, x := os.Create("output.jpg") // create output file
CE("Could not CREATE the output file ", x) // check for error
defer outputFile.Close() // close file at the end of the brick
_, x = outputFile.Write(in[start:end]) // write the cut start to the cutting end point
CE("Could not WRITE output file ", x) // check for the error
fmt.Println("File carved successfully") // output the write message OK
}
func main() {
if len(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.
package main
import (
"fmt"
"os"
"strings"
)
func main() {
// 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 representation
var hexCodes []string
for _, 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
package main
import (
"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
)
func CE(x error) {
if x != nil {
log.Fatal(x)
}
}
func main() {
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.