gho

package module
v1.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 5, 2026 License: MIT Imports: 11 Imported by: 0

README

gho

Go Reference Go Report Card

Pure Go library and CLI tool for parsing Norton Ghost GHO disk image files.

No C dependencies. No CGo. Single binary.

Features

  • Parse GHO file/partition headers and record structure
  • Decompress Fast LZ (Z1) compressed partitions
  • Create GHO images from raw partition data
  • Fixup GHO headers (ghofixup.exe equivalent — CD/span flag modification with PRNG cipher)
  • Extract MBR/Track 0 data and partition table
  • Stream decompression (constant memory, arbitrary image sizes)
  • Supports Ghost 11.x–12.x format

Install

# Library
go get github.com/nyarime/gho

# CLI tool
go install github.com/nyarime/gho/cmd/gho@latest

CLI Usage

# Show image info
gho info disk.gho

# Extract all partitions to a directory
gho extract disk.gho output/

# Create a GHO image from a partition image (+ optional MBR)
gho create output.gho partition.img mbr.bin

# Modify header flags (ghofixup.exe equivalent)
gho fixup disk.gho cd      # Set spanned/CD bit
gho fixup disk.gho cd-     # Clear spanned/CD bit
gho fixup disk.gho span    # Toggle CD flag

Example output:

GHO Image Summary
  File Type:   1 (1=single, 9=span)
  Compression: 2 (Fast/Z1)
  Image ID:    0x12345678
  MBR Partitions: 1
    P0: type=0x83 LBA=2016 size=102240 (49.9 MB)
  Data Partitions: 1
    Partition 0: 3 spans, 42816212 bytes compressed data

Decompressing partition 0...
Saved partition 0: output/partition-0.img (51.4 MB)

Library Usage

package main

import (
    "bytes"
    "fmt"
    "log"

    "github.com/nyarime/gho"
)

func main() {
    img, err := gho.Open("disk.gho")
    if err != nil {
        log.Fatal(err)
    }
    defer img.Close()

    // Print summary
    fmt.Print(img.Summary())

    // Access MBR partition table
    for i, p := range img.MBRPartitions() {
        fmt.Printf("Partition %d: type=%#x, LBA=%d, size=%d sectors\n",
            i, p.Type, p.LBAStart, p.LBASize)
    }

    // Decompress partition 0 to memory
    var buf bytes.Buffer
    if err := img.DecompressPartition(0, &buf); err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Decompressed: %d bytes\n", buf.Len())
}
Create a GHO Image
w, _ := gho.Create("output.gho", gho.CompressionNone)
w.WriteTrack0(mbrData, 63)  // MBR + boot sectors
w.WritePartition(partFile)    // raw partition data (io.Reader)
w.Close()
Modify Header Flags (ghofixup)
// Set CD/spanned bit (like ghofixup.exe cd)
gho.ModifyHeader("disk.gho", gho.FixupCD)

// Toggle span flag
gho.ModifyHeader("disk.gho", gho.FixupSpan)

GHO Format

Norton Ghost GHO files store disk/partition images with the following structure:

┌──────────────────────────────────────┐
│  File Header (512 bytes)             │  Magic: FE EF, compression type, ID
├──────────────────────────────────────┤
│  Record: Track 0 (type 0x0006)       │  6-byte header + MBR + boot sectors
├──────────────────────────────────────┤
│  Record: Partition (type 0x0603)     │  20-byte partition descriptor
├──────────────────────────────────────┤
│  FEEF Partition Header (512 bytes)   │  Per-partition compression settings
├──────────────────────────────────────┤
│  Compressed Blocks                   │  [2B len][block data]...
│  ├─ Block: 2B stored_len + data      │  32KB decompressed per block
│  ├─ Block: ...                       │
│  └─ Block: ...                       │
├──────────────────────────────────────┤
│  Record: Continuation (type 0x0703)  │  Additional data spans
├──────────────────────────────────────┤
│  (optional FEEF + more blocks)       │
├──────────────────────────────────────┤
│  Record: End (type 0x0023)           │  End of image marker
└──────────────────────────────────────┘

Compression: Each block is 32KB decompressed. The first byte of block data indicates the type:

  • 0x01 → Uncompressed (raw data at offset 4)
  • Other → Fast LZ compressed (custom LZ77 variant with 4096-entry hash table)

Records: Every record has a 10-byte header: [4B type][4B magic 0x012F18D8][2B body_len]

Fast LZ Algorithm

The Fast LZ decompressor was reverse-engineered from Norton Ghost 11.5.1. It's a custom LZ77 variant using:

  • 16-bit control words (bit 0 = literal, bit 1 = match reference)
  • 4096-entry hash table with the hash function: h = ((-24993 * (b2 ^ (16 * (b1 ^ (16 * b0))))) >> 4) & 0xFFF
  • 2-byte match tokens encoding hash index + extra length
  • Minimum match length of 3 bytes

Supported Formats

Ghost Version Compression Status
11.x–12.x None (Z0) ✅ Read + Write
11.x–12.x Fast LZ (Z1)
11.x–12.x High/zlib (Z3–Z9) ✅ Read + Write
Encrypted images CRC-16 cipher ✅ Read + Write
Span files (.ghs) Multi-file ✅ Read

Contributing

Contributions welcome! Areas that need work:

  • Span file writing: Creating multi-file (.ghs) span images
  • Full disk images: Currently supports partition-level images; whole-disk with MBR rebuild is planned

License

MIT — see LICENSE.

Credits

Reverse-engineered from Norton Ghost 11.5.1 by Nyarime.

Part of the Nyarc firmware analysis toolkit.

Documentation

Overview

Package gho implements Norton Ghost GHO image format parsing.

GHO files contain disk/partition images created by Symantec Ghost (versions 11.x-12.x). The format supports Fast LZ (Z1), High/zlib (Z2-Z9), and no compression modes, with optional CRC-16 stream cipher encryption.

File structure:

[512B File Header (FE EF 01 02)]
[Record type 6: Track 0 / MBR data]
[Record type 0x0603: Partition descriptor (20B body)]
[512B FEEF Partition Header (FE EF 02 02)]
[Compressed blocks: 2B stored_len + block_data]
[Record type 0x0703: Continuation (20B body)]
[512B FEEF Header]
[More compressed blocks...]
[Record type 0x23: End record (24B body)]

Compressed block format:

stored_len = LE uint16 (includes the 2-byte length field itself)
comp_len = stored_len - 2
block_data[0] == 1: uncompressed, output = block_data[4:comp_len]
block_data[0] != 1: Fast LZ compressed

Record format:

[4B type (LE uint32, low 16 = type code, high 16 = flags)]
[4B magic (0x012F18D8)]
[2B body_len (LE uint16)]
[body_len bytes body]

Index

Constants

View Source
const (
	// FileMagic is the first 2 bytes of GHO file header and FEEF partition header.
	FileMagic = 0xEFFE

	// RecordMagic appears at offset 4 in every record header.
	RecordMagic = 0x012F18D8

	// HeaderSize is the size of file header and FEEF partition header.
	HeaderSize = 512

	// RecordHeaderSize is the fixed size of a record header.
	RecordHeaderSize = 10

	// BlockSize is the default decompressed block size.
	BlockSize = 32768

	// MaxStoredLen is the maximum valid stored length for a block.
	MaxStoredLen = 33002 // BlockSize + 4 (uncompressed header) + 2 (stored_len)

	// FastLZHashSize is the hash table size for Fast LZ.
	FastLZHashSize = 4096
)
View Source
const (
	RecordTypeTrack0       = 0x0006 // Track 0 / MBR data
	RecordTypePartition    = 0x0603 // Partition descriptor
	RecordTypeContinuation = 0x0703 // Partition data continuation
	RecordTypeEnd          = 0x0023 // End of image
)

Record types (low 16 bits of the type field).

View Source
const (
	CompressionNone  = 0
	CompressionOld   = 1 // Not supported
	CompressionFast  = 2 // Fast LZ (Z1)
	CompressionHigh3 = 3 // High compression (Z3, zlib)
	CompressionHigh4 = 4
	CompressionHigh5 = 5
	CompressionHigh6 = 6
	CompressionHigh7 = 7
	CompressionHigh8 = 8
	CompressionHigh9 = 9
)

Compression types (byte 3 of file header).

View Source
const (
	FixupCD    = "cd"   // Set spanned bit at file offset 584
	FixupCDOff = "cd-"  // Clear spanned bit at file offset 584
	FixupSpan  = "span" // Toggle CD flag at header offset 55
)

Fixup mode constants for ModifyHeader.

Variables

View Source
var (
	ErrInvalidMagic           = errors.New("gho: invalid file magic")
	ErrInvalidRecordMagic     = errors.New("gho: invalid record magic")
	ErrUnsupportedCompression = errors.New("gho: unsupported compression type")
	ErrCorruptBlock           = errors.New("gho: corrupt compressed block")
	ErrTruncated              = errors.New("gho: unexpected end of data")
)

Errors

View Source
var ErrBadPassword = errors.New("gho: invalid password")

ErrBadPassword is returned when the password is empty or decryption fails.

Functions

func FastLZCompress added in v1.1.0

func FastLZCompress(src []byte) []byte

FastLZCompress compresses src using the Ghost Fast LZ (Z1) algorithm.

The compressor maintains exact hash table synchronization with the decompressor (FastLZDecompress). Every hash table update in the compressor mirrors what the decompressor would do when processing the compressed stream.

Returns the compressed block data including the 4-byte header.

func FastLZDecompress

func FastLZDecompress(data []byte, compLen int, dst []byte) (int, error)

FastLZDecompress decompresses a Ghost Fast LZ (Z1) compressed block.

The algorithm is a custom LZ77 variant with a 4096-entry hash table. Each hash entry stores a pointer to a previous position in the output buffer.

Block format:

  • byte[0] == 1: uncompressed, output = data[4:compLen]
  • byte[0] != 1: compressed with 16-bit control words

Compressed format uses 16-bit control words where:

  • bit 0 = literal byte (copy from input)
  • bit 1 = match reference (2-byte token: hash_idx + extra_len)

Hash function: h = ((-24993 * (b2 ^ (16 * (b1 ^ (16 * b0))))) >> 4) & 0xFFF

Reversed from Norton Ghost 11.5.1 sub_4DDD70 via IDA.

func IsEncrypted added in v1.2.0

func IsEncrypted(header []byte) bool

IsEncrypted checks if a GHO file header indicates encryption is enabled. Ghost sets a specific flag pattern when encryption is used. The encryption indicator is at header byte 12 (after ID field), bit 0.

func ModifyHeader added in v1.1.0

func ModifyHeader(path string, mode string) error

ModifyHeader modifies a GHO file's header flags, replicating the functionality of Norton Ghost's ghofixup.exe utility.

Modes:

  • "cd": Set the spanned/CD bit at file offset 584
  • "cd-": Clear the spanned/CD bit at file offset 584
  • "span": Toggle the CD flag byte at header offset 55

The header is decrypted, modified, and re-encrypted using the Ghost PRNG cipher. Only the necessary portion of the file is read and written back.

Reversed from ghofixup.exe (PDB: c:\depot\ghost\gsstrunk\ghost\utilityapps\ghofixup\).

func ZlibCompress added in v1.2.0

func ZlibCompress(src []byte, level int) []byte

ZlibCompress compresses src using zlib at the given level (3-9).

The output includes the 4-byte header expected by the GHO block format. If compression doesn't reduce size, an uncompressed block is returned.

func ZlibDecompress added in v1.2.0

func ZlibDecompress(data []byte, compLen int, dst []byte) (int, error)

ZlibDecompress decompresses a Ghost High/zlib (Z3-Z9) compressed block.

Block format is identical to Fast LZ: byte[0] == 1 means uncompressed. Otherwise the data (starting at offset 4) is zlib-compressed.

Types

type CRC16Cipher added in v1.2.0

type CRC16Cipher struct {
	// contains filtered or unexported fields
}

CRC16Cipher implements the Ghost CRC-16 stream cipher used for GHO encryption.

Ghost uses a CRC-16 based stream cipher where each byte of plaintext is XORed with the low byte of a running CRC-16 state. The CRC state is updated with each plaintext byte, creating a password-dependent keystream.

The password is used to initialize the CRC-16 state by feeding each password byte through the CRC update function.

Reversed from Norton Ghost 11.5.1 encryption routines.

func NewCRC16Cipher added in v1.2.0

func NewCRC16Cipher(password string) (*CRC16Cipher, error)

NewCRC16Cipher creates a new CRC-16 stream cipher initialized with the given password.

func (*CRC16Cipher) Decrypt added in v1.2.0

func (c *CRC16Cipher) Decrypt(data []byte)

Decrypt decrypts data in-place using the CRC-16 stream cipher. Each byte is XORed with the low byte of the CRC state, then the CRC state is updated with the decrypted (plaintext) byte.

func (*CRC16Cipher) Encrypt added in v1.2.0

func (c *CRC16Cipher) Encrypt(data []byte)

Encrypt encrypts data in-place using the CRC-16 stream cipher. Each plaintext byte updates the CRC state, then is XORed with the low byte of the previous CRC state.

func (*CRC16Cipher) Reset added in v1.2.0

func (c *CRC16Cipher) Reset(password string)

Reset reinitializes the cipher with a new password.

type FileHeader

type FileHeader struct {
	Magic       uint16    // 0xEFFE
	FileType    byte      // 0x01=first file, 0x09=span file
	Compression byte      // Compression type (0-9)
	ID          uint32    // CRC/timestamp identifier
	Flags       [3]byte   // bytes 8-10
	Raw         [512]byte // Full raw header
}

FileHeader represents the 512-byte GHO file header.

func ParseFileHeader

func ParseFileHeader(data []byte) (*FileHeader, error)

ParseFileHeader parses a 512-byte file header.

type Image

type Image struct {
	Header     *FileHeader
	Track0     []byte          // Raw Track 0 data (MBR at offset 6)
	Track0Hdr  Track0Header    // 6-byte mini-header
	Partitions []PartitionInfo // Partition descriptors
	EndRecord  *Record
	// contains filtered or unexported fields
}

Image represents a parsed GHO image file.

func Open

func Open(path string) (*Image, error)

Open opens and parses a GHO image file.

func OpenSpanned added in v1.2.0

func OpenSpanned(primaryPath string) (*Image, error)

OpenSpanned opens a multi-file GHO image (primary .gho + span .ghs files).

Norton Ghost splits large images into multiple files:

  • image.gho (FileType=0x01) — primary file with headers and initial data
  • image.ghs (FileType=0x09) — span continuation files

The span files are auto-discovered in the same directory as the primary file, sorted by filename (which Ghost names sequentially).

OpenSpanned uses a zero-copy multi-file reader — no temporary files are created and the span files are not loaded into memory.

func (*Image) Close

func (img *Image) Close() error

Close closes the underlying file and cleans up temporary span files.

func (*Image) DecompressPartition

func (img *Image) DecompressPartition(partIdx int, w io.Writer) error

DecompressPartition decompresses all blocks of a partition and writes to w. If the image is encrypted, SetPassword must be called first.

func (*Image) IsEncrypted added in v1.2.0

func (img *Image) IsEncrypted() bool

IsEncrypted returns true if the image header indicates encryption.

func (*Image) MBRPartitions

func (img *Image) MBRPartitions() []MBRPartitionEntry

MBRPartitions returns parsed MBR partition entries from Track 0.

func (*Image) SetPassword added in v1.2.0

func (img *Image) SetPassword(password string)

SetPassword sets the decryption password for encrypted GHO images. Must be called before DecompressPartition on encrypted images.

func (*Image) Summary

func (img *Image) Summary() string

Summary returns a human-readable summary of the GHO image.

func (*Image) Verify added in v1.4.0

func (img *Image) Verify(partIdx int) error

Verify checks the integrity of a partition by decompressing all blocks without writing output. Returns nil if all blocks decompress successfully.

type MBRPartitionEntry

type MBRPartitionEntry struct {
	Status   byte
	CHSStart [3]byte
	Type     byte
	CHSEnd   [3]byte
	LBAStart uint32
	LBASize  uint32
}

MBRPartitionEntry represents a single MBR partition table entry.

func ParseMBRPartitions

func ParseMBRPartitions(mbr []byte) []MBRPartitionEntry

ParseMBRPartitions extracts partition table entries from a 512-byte MBR.

type PartitionHeader

type PartitionHeader struct {
	Magic       uint16
	SubType     byte
	Compression byte
	ID          uint32
	Flags       [3]byte
	Raw         [512]byte
}

PartitionHeader represents the 512-byte FEEF partition header.

func ParsePartitionHeader

func ParsePartitionHeader(data []byte) (*PartitionHeader, error)

ParsePartitionHeader parses a 512-byte FEEF partition header.

type PartitionInfo

type PartitionInfo struct {
	Descriptor *Record          // Type 0x0603 record
	Header     *PartitionHeader // FEEF partition header
	Spans      []Span           // Data spans (may span multiple continuation records)
	DescBody   [20]byte         // Raw descriptor body
}

PartitionInfo holds information about a single partition in the image.

func (*PartitionInfo) TotalCompressedSize added in v1.4.0

func (p *PartitionInfo) TotalCompressedSize() int64

TotalCompressedSize returns the total compressed data size across all spans.

type Record

type Record struct {
	Type    uint32 // Full type (low 16 = type code, high 16 = flags)
	Magic   uint32 // Should be RecordMagic
	BodyLen uint16
	Offset  int64 // File offset of this record
}

Record represents a GHO record header.

func ParseRecord

func ParseRecord(data []byte, offset int64) (*Record, error)

ParseRecord reads a record header from data.

func (Record) TypeCode

func (r Record) TypeCode() uint16

TypeCode returns the record type code (low 16 bits).

type Span

type Span struct {
	DataStart int64 // File offset where compressed blocks begin
	DataEnd   int64 // File offset where this span ends (next record)
}

Span represents a contiguous range of compressed blocks in the file.

type Track0Header

type Track0Header struct {
	Unknown1 byte   // Usually 0x06
	Sectors  byte   // Sectors per track (e.g., 126)
	Unknown2 uint32 // Usually 0
}

Track0Header is the 6-byte mini-header before the actual MBR in Track 0 data.

type Writer added in v1.1.0

type Writer struct {
	// contains filtered or unexported fields
}

Writer creates a new GHO image file.

Usage:

w, _ := Create("output.gho", CompressionFast)
w.WriteTrack0(mbrData, 63)
w.WritePartition(partitionImageReader)
w.Close()

func Create added in v1.1.0

func Create(path string, compression byte) (*Writer, error)

Create creates a new GHO image file for writing.

func NewWriter added in v1.1.0

func NewWriter(w io.WriteSeeker, compression byte) (*Writer, error)

NewWriter wraps an io.WriteSeeker as a GHO writer.

func (*Writer) Close added in v1.1.0

func (gw *Writer) Close() error

Close writes the end record and closes the underlying writer (if it's a file).

func (*Writer) SetID added in v1.1.0

func (gw *Writer) SetID(id uint32) error

SetID sets the image ID and rewrites it in the file header. Can be called at any point before Close.

func (*Writer) SetPassword added in v1.4.0

func (gw *Writer) SetPassword(password string)

SetPassword enables CRC-16 encryption for all subsequent blocks.

func (*Writer) WritePartition added in v1.1.0

func (gw *Writer) WritePartition(r io.Reader) error

WritePartition reads partition data from r and writes it as compressed GHO blocks.

func (*Writer) WriteTrack0 added in v1.1.0

func (gw *Writer) WriteTrack0(track0Data []byte, sectors byte) error

WriteTrack0 writes the MBR / Track 0 data as a type-6 record. track0Data should be the raw MBR + boot sectors (typically 512 * sectors bytes). sectors is the sector count field in the Track 0 mini-header (e.g. 63 or 126).

Directories

Path Synopsis
cmd
gho command
Command gho is a CLI tool for inspecting, extracting, creating, and fixing Norton Ghost GHO disk images.
Command gho is a CLI tool for inspecting, extracting, creating, and fixing Norton Ghost GHO disk images.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL