Skip to content

Commit

Permalink
Added inline compression of memory image (#57)
Browse files Browse the repository at this point in the history
To get good quality memory images it is important to acquire the image
as quickly as possible to avoid smear.

The following algorithms are supported

1. None - no compression
2. S2 - this algorithm gains about 50% compression over none but very
   little loss of speed. The result is slightly faster than none.
3. Snappy - this is a reasonable trade off getting compression
   slightly less than gzip but about twice the time as none.
4. Gzip - This method is very slow and so not recommended but it is
   compatible with the traditional gzip tool

Also added an extract command to convert the compressed images to raw
images later.
  • Loading branch information
scudette authored Jul 11, 2024
1 parent c824d20 commit acdf919
Show file tree
Hide file tree
Showing 19 changed files with 1,175 additions and 112 deletions.
138 changes: 138 additions & 0 deletions go-winpmem/cmd/acquire.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package main

import (
"io/ioutil"
"os"
"time"

"github.com/Velocidex/WinPmem/go-winpmem"
"github.com/alecthomas/kingpin"
)

var (
acquire = app.Command("acquire", "Acquire a memory image")

service_name = acquire.Flag("service_name", "Name of the service to create").
Default("winpmem").String()

driver_path = acquire.Flag("driver_path", "Where to store the driver").
String()

filename = acquire.Arg("filename",
"Output path to write image to").String()

nosparse = acquire.Flag("nosparse", "Disable sparse output file").Bool()
progress = acquire.Flag("progress", "Show progress").Bool()

compression = acquire.Flag("compression", "Type of compression to apply").
Default("none").PlaceHolder("snappy|gzip").String()
)

func doAcquire() error {
logger := winpmem.NewLogger(*verbose)

if *progress {
logger.SetProgress(1024)
}

var err error
var fd *os.File

if *driver_path == "" {
fd, err = ioutil.TempFile("", "*.sys")
if err != nil {
return err
}

defer func() {
logger.Info("Removing driver from %v", fd.Name())
err := os.Remove(fd.Name())
if err != nil {
logger.Info("Error removing %v: %v",
fd.Name(), err)
}
}()

*driver_path = fd.Name()

} else {
fd, err = os.OpenFile(*driver_path,
os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err
}
}

driver_code, err := winpmem.Winpmem_x64()
if err != nil {
return err
}

fd.Write([]byte(driver_code))
fd.Close()

logger.Info("Writing driver to %v", *driver_path)

err = winpmem.InstallDriver(*driver_path, *service_name, logger)

defer winpmem.UninstallDriver(
*driver_path, *service_name, logger)

imager, err := winpmem.NewImager(`\\.\pmem`, logger)
if err != nil {
return err
}
defer imager.Close()

// We only support this mode now - it is the most reliable.
imager.SetMode(winpmem.PMEM_MODE_PTE)

logger.Info("Memory Info:\n")
logger.Info(imager.Stats().ToYaml())

// We do not need to take the image - we are done.
if *filename == "" {
return nil
}

// Sparse writing is only possible with no compression.
if *compression != "" && *compression != "none" {
*nosparse = true
}

if !*nosparse {
logger.Info("Setting sparse output file %v", *filename)
imager.SetSparse()
}

out_fd, err := winpmem.CreateFileForWriting(!*nosparse, *filename)
if err != nil {
return err
}
defer out_fd.Close()

start := time.Now()
defer func() {
logger.Info("Completed imaging in %v", time.Now().Sub(start))
}()

compressed_writer, closer, err := winpmem.GetCompressor(*compression, out_fd)
if err != nil {
return err
}
defer closer()

return imager.WriteTo(compressed_writer)
}

func init() {
command_handlers = append(command_handlers, func(command string) bool {
switch command {
case acquire.FullCommand():
kingpin.FatalIfError(doAcquire(), "acquire")
default:
return false
}
return true
})
}
73 changes: 73 additions & 0 deletions go-winpmem/cmd/decompress.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package main

import (
"os"
"time"

"github.com/Velocidex/WinPmem/go-winpmem"
"github.com/alecthomas/kingpin"
)

var (
decompress = app.Command("extract", "Decompress an image.")
image = decompress.Arg("image", "Path to the image to decompress").
Required().String()

decompress_output_filename = decompress.Arg("filename",
"Output path to write image to").Required().String()
)

func doDecompress() error {
fd, err := os.Open(*image)
if err != nil {
return err
}
defer fd.Close()

header := make([]byte, 10)
n, err := fd.Read(header)
if err != nil {
return err
}

fd.Seek(0, os.SEEK_SET)

decompressed_fd, err := winpmem.GetDecompressor(header[:n], fd)
if err != nil {
return err
}

out_fd, err := winpmem.CreateFileForWriting(true, *decompress_output_filename)
if err != nil {
return err
}
defer out_fd.Close()

logger := &DecompressionLogger{Logger: winpmem.NewLogger(*verbose)}
return winpmem.CopyAndLog(decompressed_fd, out_fd, logger)
}

func init() {
command_handlers = append(command_handlers, func(command string) bool {
switch command {
case decompress.FullCommand():
kingpin.FatalIfError(doDecompress(), "decompress")
default:
return false
}
return true
})
}

type DecompressionLogger struct {
winpmem.Logger
count int
}

func (self *DecompressionLogger) Progress(pages int) {
self.count += pages
if self.count%20000 == 0 {
self.Info("%v: Decompressed %v Mb", time.Now().Format(time.RFC3339),
self.count*winpmem.PAGE_SIZE/1024/1024)
}
}
110 changes: 8 additions & 102 deletions go-winpmem/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,124 +2,30 @@ package main

import (
_ "embed"
"io/ioutil"
"os"
"time"

winpmem "github.com/Velocidex/WinPmem/go-winpmem"
"github.com/alecthomas/kingpin"
)

var (
app = kingpin.New("winpmem", "A Windows Memory Acquisition Tool.")

service_name = app.Flag("service_name", "Name of the service to create").
Default("winpmem").String()

driver_path = app.Flag("driver_path", "Where to store the driver").
String()

filename = app.Arg("filename",
"Output path to write image to").String()

nosparse = app.Flag("nosparse", "Disable sparse output file").Bool()
progress = app.Flag("progress", "Show progress").Bool()

verbose = app.Flag("verbose", "More vebose information").Short('v').Bool()

command_handlers []CommandHandler
)

type CommandHandler func(command string) bool

func main() {
app.HelpFlag.Short('h')
app.UsageTemplate(kingpin.CompactUsageTemplate)

args := os.Args[1:]
kingpin.MustParse(app.Parse(args))

err := doRun()
kingpin.FatalIfError(err, "")
}

func doRun() error {
logger := winpmem.NewLogger(*verbose)
command := kingpin.MustParse(app.Parse(os.Args[1:]))

if *progress {
logger.SetProgress(1024)
}

var err error
var fd *os.File

if *driver_path == "" {
fd, err = ioutil.TempFile("", "*.sys")
if err != nil {
return err
}

defer func() {
logger.Info("Removing driver from %v", fd.Name())
err := os.Remove(fd.Name())
if err != nil {
logger.Info("Error removing %v: %v",
fd.Name(), err)
}
}()

*driver_path = fd.Name()

} else {
fd, err = os.OpenFile(*driver_path,
os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err
for _, command_handler := range command_handlers {
if command_handler(command) {
break
}
}

driver_code, err := winpmem.Winpmem_x64()
if err != nil {
return err
}

fd.Write([]byte(driver_code))
fd.Close()

logger.Info("Writing driver to %v", *driver_path)

err = winpmem.InstallDriver(*driver_path, *service_name, logger)

defer winpmem.UninstallDriver(
*driver_path, *service_name, logger)

imager, err := winpmem.NewImager(`\\.\pmem`, logger)
if err != nil {
return err
}
defer imager.Close()

// We only support this mode now - it is the most reliable.
imager.SetMode(winpmem.PMEM_MODE_PTE)

logger.Info("Memory Info:\n")
logger.Info(imager.Stats().ToYaml())

// We do not need to take the image - we are done.
if *filename == "" {
return nil
}

if !*nosparse {
logger.Info("Setting sparse output file %v", *filename)
}

out_fd, err := winpmem.CreateFileForWriting(!*nosparse, *filename)
if err != nil {
return err
}
defer out_fd.Close()

start := time.Now()
defer func() {
logger.Info("Completed imaging in %v", time.Now().Sub(start))
}()

return imager.WriteTo(out_fd)
}
Loading

0 comments on commit acdf919

Please sign in to comment.