File Forensics
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.
00000000 50 4b 03 04 14 03 00 00 00 00 85 91 bc 56 00 00 |PK...........V..|
00000010 00 00 00 00 00 00 00 00 00 00 0c 00 00 00 43 6f |..............Co|
00000020 6d 70 72 65 73 73 44 69 72 2f 50 4b 03 04 14 03 |mpressDir/PK....|
00000030 00 00 08 00 1a 93 b8 56 01 a1 cd f1 3f 00 00 00 |.......V....?...|
00000040 7e 00 00 00 16 00 00 00 43 6f 6d 70 72 65 73 73 |~.......Compress|
00000050 44 69 72 2f 62 61 6e 6e 65 72 2e 74 78 74 e3 04 |Dir/banner.txt..|
00000060 02 85 47 53 fa 1f 4d 69 7c 34 65 32 17 88 fb 68 |..GS..Mi|4e2...h|
00000070 4a 33 10 29 00 31 88 ab 00 04 20 49 04 9a 0d 12 |J3.).1.... I....|
00000080 d6 02 0a 03 31 88 04 72 21 7c b0 38 50 03 10 83 |....1..r!|.8P...|
00000090 09 88 04 5c 09 94 d6 82 99 0a 64 01 00 50 4b 03 |...\......d..PK.|
000000a0 04 14 03 00 00 08 00 87 7a bc 56 50 9b 38 0b 30 |........z.VP.8.0|
000000b0 00 00 00 89 00 00 00 14 00 00 00 43 6f 6d 70 72 |...........Compr|
000000c0 65 73 73 44 69 72 2f 6d 61 69 6e 2e 74 78 74 e3 |essDir/main.txt.|
000000d0 e2 04 02 05 05 85 e0 ec 4a 05 9f cc bc 54 05 cf |........J....T..|
000000e0 bc 92 d4 a2 82 a2 54 20 59 a3 10 a6 60 a0 67 a0 |......T Y...`.g.|
000000f0 67 0a 53 84 4a 53 09 14 a7 26 03 6d e3 02 00 50 |g.S.JS...&.m...P|
00000100 4b 01 02 3f 03 14 03 00 00 00 00 85 91 bc 56 00 |K..?..........V.|
00000110 00 00 00 00 00 00 00 00 00 00 00 0c 00 24 00 00 |.............$..|
00000120 00 00 00 00 00 10 80 ed 41 00 00 00 00 43 6f 6d |........A....Com|
00000130 70 72 65 73 73 44 69 72 2f 0a 00 20 00 00 00 00 |pressDir/.. ....|
00000140 00 01 00 18 00 80 c2 e1 71 b1 91 d9 01 80 b4 ba |........q.......|
00000150 6a b1 91 d9 01 80 c2 e1 71 b1 91 d9 01 50 4b 01 |j.......q....PK.|
00000160 02 3f 03 14 03 00 00 08 00 1a 93 b8 56 01 a1 cd |.?..........V...|
00000170 f1 3f 00 00 00 7e 00 00 00 16 00 24 00 00 00 00 |.?...~.....$....|
00000180 00 00 00 20 80 a4 81 2a 00 00 00 43 6f 6d 70 72 |... ...*...Compr|
00000190 65 73 73 44 69 72 2f 62 61 6e 6e 65 72 2e 74 78 |essDir/banner.tx|
000001a0 74 0a 00 20 00 00 00 00 00 01 00 18 00 80 bb 6a |t.. ...........j|
000001b0 8e 8e 8e d9 01 80 b3 bb 5e 8e 8e d9 01 00 ff 17 |........^.......|
000001c0 70 b1 91 d9 01 50 4b 01 02 3f 03 14 03 00 00 08 |p....PK..?......|
000001d0 00 87 7a bc 56 50 9b 38 0b 30 00 00 00 89 00 00 |..z.VP.8.0......|
000001e0 00 14 00 24 00 00 00 00 00 00 00 20 80 a4 81 9d |...$....... ....|
000001f0 00 00 00 43 6f 6d 70 72 65 73 73 44 69 72 2f 6d |...CompressDir/m|
00000200 61 69 6e 2e 74 78 74 0a 00 20 00 00 00 00 00 01 |ain.txt.. ......|
00000210 00 18 00 80 14 11 6d 99 91 d9 01 80 14 11 6d 99 |......m.......m.|
00000220 91 d9 01 80 c2 e1 71 b1 91 d9 01 50 4b 05 06 00 |......q....PK...|
00000230 00 00 00 03 00 03 00 2c 01 00 00 ff 00 00 00 00 |.......,........|
00000240 00 |.|
00000241
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
f1 3f 00 00 00 7e 00 00 00 16 00 24 00 00 00 00 |.?...~.....$....|
00000180 00 00 00 20 80 a4 81 2a 00 00 00 43 6f 6d 70 72 |... ...*...Compr|
00000190 65 73 73 44 69 72 2f 62 61 6e 6e 65 72 2e 74 78 |essDir/banner.tx|
000001a0 74 0a 00 20 00 00 00 00 00 01 00 18 00 80 bb 6a |t.. ...........j|
000001b0 8e 8e 8e d9 01 80 b3 bb 5e 8e 8e d9 01 00 ff 17 |........^.......|
000001c0 70 b1 91 d9 01 50 4b 01 02 3f 03 14 03 00 00 08 |p....PK..?......|
000001d0 00 87 7a bc 56 50 9b 38 0b 30 00 00 00 89 00 00 |..z.VP.8.0......|
000001e0 00 14 00 24 00 00 00 00 00 00 00 20 80 a4 81 9d |...$....... ....|
000001f0 00 00 00 43 6f 6d 70 72 65 73 73 44 69 72 2f 6d |...CompressDir/m|
00000200 61 69 6e 2e 74 78 74 0a 00 20 00 00 00 00 00 01 |ain.txt.. ......|
00000210 00 18 00 80 14 11 6d 99 91 d9 01 80 14 11 6d 99 |......m.......m.|
00000220 91 d9 01 80 c2 e1 71 b1 91 d9 01 50 4b 05 06 00 |......q....PK...|
00000230 00 00 00 03 00 03 00 2c 01 00 00 ff 00 00 00 00 |.......,........|
00000240 00 |.|
00000241
cut out in the resulting file.
File Forensics | Hex Conversion
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.
package main
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"strings"
)
type Signatures struct {
Sign string
Sufix string
Format string
}
var Sig = []Signatures{
{`62706C69`, `*.bplist`, `Binary Property List`},
{`006E1EF0`, `*.ppt`, `PPT`},
{`A0461DF0`, `*.ppt`, `PPT`},
{`ECA5C100`, `*.doc`, `Doc file`},
{`474946`, `*.gif`, `GIF files`},
{`GIF89a`, `*.gif`, `GIF files`},
{`FFD8FF`, `*.jpg`, `JPEG files`},
{`JFIF`, `*.jpg`, `JPEG files`},
{`504B03`, `*.zip`, `ZIP files`},
{`LfLe`, `*.evt`, `Event file`},
{`38425053`, `*.psd`, `Photoshop file`},
{`8BPS`, `*.psd`, `Photoshop file`},
{`4D5A`, `*.ocx`, `Active X`},
{`415649204C495354`, `*.avi`, `AVI file`},
{`AVI LIST`, `*.avi`, `AVI file`},
{`57415645666D7420`, `*.wav`, `WAV file`},
{`WAVEfmt`, `*.wav`, `WAV file`},
{`25504446`, `*.pdf`, `PDF files`},
{`%PDF`, `*.pdf`, `PDF files`},
{`000100005374616E64617264204A6574204442`, `*.mdb`, `Microsoft database`},
{`Standard Jet DB`, `*.mdb`, `Microsoft database`},
{`2142444E`, `*.pst`, `PST file`},
{`!BDN`, `*.pst`, `PST file`},
{`4D6963726F736F66742056697375616C2053747564696F20536F6C7574696F6E2046696C65`, `*.sln`, `Microsft SLN file`},
{`Microsoft Visual Studio Solution File`, `*.sln`, `Microsft SLN file`},
{`504B030414000600`, `*.docx`, `Microsoft DOCX file`},
{`504B030414000600`, `*.pptx`, `Microsoft PPTX file`},
{`504B030414000600`, `*.xlsx`, `Microsoft XLSX file`},
{`504B0304140008000800`, `*.xlsx`, `Java JAR file`},
{`0908100000060500`, `*.xls`, `XLS file`},
{`D0CF11E0A1B11AE1`, `*.msi`, `MSI file`},
{`D0CF11E0A1B11AE1`, `*.doc`, `DOC`},
{`D0CF11E0A1B11AE1`, `*.xls`, `Excel`},
{`D0CF11E0A1B11AE1`, `*.vsd`, `Visio`},
{`D0CF11E0A1B11AE1`, `*.ppt`, `PPT`},
{`0A2525454F460A`, `*.pdf`, `PDF file`},
{`.%%EOF.`, `*.pdf`, `PDF file`},
{`4040402000004040`, `*.hlp`, `HLP file`},
{`465753`, `*.swf`, `SWF file`},
{`FWS`, `*.swf`, `SWF file`},
{`CWS`, `*.swf`, `SWF file`},
{`494433`, `*.mp3`, `MP3 file`},
{`ID3`, `*.mp3`, `MP3 file`},
{`MSCF`, `*.cab`, `Cab file`},
{`0x4D534346`, `*.cab`, `Cab file`},
{`ITSF`, `*.chm`, `Compressed Help`},
{`49545346`, `*.chm`, `Compressed Help`},
{`4C00000001140200`, `*.lnk`, `Link file`},
{`4C01`, `*.obj`, `OBJ file`},
{`4D4D002A`, `*.tif`, `TIF graphics`},
{`MM`, `*.tif`, `TIF graphics`},
{`000000186674797033677035`, `*.mp4`, `MP4 Video`},
{`ftyp3gp5`, `*.mp4`, `MP4 Video`},
{`0x00000100`, `*.ico`, `Icon file`},
{`300000004C664C65`, `*.evt`, `Event file`},
{`Rar!`, `*.rar`, `RAR file`},
{`526172211A0700`, `*.rar`, `RAR file`},
{`52657475726E2D506174683A20`, `*.eml`, `EML file`},
{`Return-Path:`, `*.eml`, `EML file`},
{`6D6F6F76`, `*.mov`, `MOV file`},
{`moov`, `*.mov`, `MOV file`},
{`7B5C72746631`, `*.rtf`, `RTF file`},
{`{\rtf1`, `*.rtf`, `RTF file`},
{`89504E470D0A1A0A`, `*.png`, `PNG file`},
{`PNG`, `*.png`, `PNG file`},
{`C5D0D3C6`, `*.eps`, `EPS file`},
{`CAFEBABE`, `*.class`, `Java class file`},
{`D7CDC69A`, `*.WMF`, `WMF file`},
}
func main() {
if len(os.Args) == 0 {
fmt.Printf("Usage: %s unknownfile... ", os.Args[0])
} else {
for _, file := range os.Args[1:] {
f, _ := ioutil.ReadFile(file) // skip bad files
for _, v := range Sig {
if strings.HasSuffix(file, v.Sufix) || bytes.Contains(f, []byte(v.Sign)) {
fmt.Println("File [ " + file + " ] was picked up as a [ " + v.Format + " ] using symbol < " + fmt.Sprint([]byte(v.Sign)) + " > ")
continue
}
}
}
}
}
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.
Last updated