Velocidex / go-ntfs

An NTFS file parser in Go
Apache License 2.0
64 stars 22 forks source link

LZNT1 decompress error #96

Closed Tupler closed 2 months ago

Tupler commented 2 months ago

image

But RtlDecompressBuffer can decompress normally.

Tupler commented 2 months ago

this is my code

package main

import (
    "encoding/binary"
    "errors"
    "log"
    "os"
)

/*
Decompression support for the LZNT1 compression algorithm.

Reference:
http://msdn.microsoft.com/en-us/library/jj665697.aspx
(2.5 LZNT1 Algorithm Details)

https://github.com/libyal/reviveit/
https://github.com/sleuthkit/sleuthkit/blob/develop/tsk/fs/ntfs.c
*/

var (
    COMPRESSED_MASK = uint16(1 << 15)
    SIGNATURE_MASK  = uint16(3 << 12)
    SIZE_MASK       = uint16(1<<12) - 1

    shiftTooLargeError = errors.New(
        "Decompression error - shift is too large")
    blockTooSmallError = errors.New("Block too small!")
)

func get_displacement(offset uint16) byte {
    result := byte(0)
    for {
        if offset < 0x10 {
            return result
        }

        offset >>= 1
        result += 1
    }
}

func LZNT1Decompress(in []byte) ([]byte, error) {

    // Index into the in buffer
    i := 0
    out := []byte{}

    for {
        if len(in) < i+2 {
            break
        }
        uncompressed_chunk_offset := len(out)
        block_offset := i

        block_header := binary.LittleEndian.Uint16(in[i:])

        i += 2

        size := int(block_header & SIZE_MASK)
        block_end := block_offset + size + 3

        if size == 0 {
            break
        }

        if len(in) < i+size {
            return nil, blockTooSmallError
        }

        if block_header&COMPRESSED_MASK != 0 {
            for i < block_end {
                header := uint8(in[i])

                i++

                for mask_idx := uint8(0); mask_idx < 8 && i < block_end; mask_idx++ {
                    if (header & 1) == 0 {

                        out = append(out, in[i])
                        i++

                    } else {
                        pointer := binary.LittleEndian.Uint16(in[i:])
                        i += 2

                        displacement := get_displacement(
                            uint16(len(out) - uncompressed_chunk_offset - 1))
                        symbol_offset := int(pointer>>(12-displacement)) + 1
                        symbol_length := int(pointer&(0xFFF>>displacement)) + 2
                        start_offset := len(out) - symbol_offset
                        for j := 0; j < symbol_length+1; j++ {
                            idx := start_offset + j
                            if idx < 0 || idx >= len(out) {

                                return out, shiftTooLargeError
                            }
                            out = append(out, out[idx])
                        }
                    }
                    header >>= 1
                }
            }

            // Block is not compressed.
        } else {
            out = append(out, in[i:i+size+1]...)
            i += size + 1
        }

    }

    return out, nil
}
func main() {

    source, err := os.ReadFile("PX_dec.TXT")
    if err != nil {
        log.Fatalln(err)
    }

    result, err := LZNT1Decompress(source)
    if err != nil {
        panic(err)
    }

    if err = os.WriteFile("PXX.TXT", result, 0666); err != nil {
        log.Fatalln(err)
    }

}