-
Notifications
You must be signed in to change notification settings - Fork 491
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This function dumps a PE file from memory. This is useful for extracting injected PE files.
- Loading branch information
Showing
10 changed files
with
1,035 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
package sparse | ||
|
||
import ( | ||
"bufio" | ||
"bytes" | ||
"fmt" | ||
"io" | ||
"sync" | ||
|
||
"www.velocidex.com/golang/velociraptor/accessors" | ||
"www.velocidex.com/golang/velociraptor/accessors/zip" | ||
actions_proto "www.velocidex.com/golang/velociraptor/actions/proto" | ||
"www.velocidex.com/golang/velociraptor/json" | ||
"www.velocidex.com/golang/velociraptor/utils" | ||
vql_subsystem "www.velocidex.com/golang/velociraptor/vql" | ||
vfilter "www.velocidex.com/golang/vfilter" | ||
) | ||
|
||
type RangedReaderPath struct { | ||
JsonlRanges string `json:"jsonl"` | ||
} | ||
|
||
func parseIndexRanges(serialized []byte) (*actions_proto.Index, error) { | ||
arg := &RangedReaderPath{} | ||
err := json.Unmarshal(serialized, arg) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
index := &actions_proto.Index{} | ||
|
||
if arg.JsonlRanges != "" { | ||
reader := bufio.NewReader(bytes.NewReader([]byte(arg.JsonlRanges))) | ||
for { | ||
row_data, err := reader.ReadBytes('\n') | ||
if err != nil || len(row_data) == 0 { | ||
return index, nil | ||
} | ||
|
||
item := &actions_proto.Range{} | ||
err = json.Unmarshal(row_data, item) | ||
if err == nil { | ||
index.Ranges = append(index.Ranges, item) | ||
} | ||
} | ||
} | ||
|
||
return index, nil | ||
} | ||
|
||
type RangedReader struct { | ||
mu sync.Mutex | ||
size int64 | ||
offset int64 | ||
|
||
// A file handle to the underlying file. | ||
handle accessors.ReadSeekCloser | ||
reader_at io.ReaderAt | ||
} | ||
|
||
func (self *RangedReader) Read(buf []byte) (int, error) { | ||
self.mu.Lock() | ||
defer self.mu.Unlock() | ||
|
||
n, err := self.reader_at.ReadAt(buf, self.offset) | ||
self.offset += int64(n) | ||
|
||
// Range is past the end of file | ||
return n, err | ||
} | ||
|
||
func (self *RangedReader) Seek(offset int64, whence int) (int64, error) { | ||
self.mu.Lock() | ||
defer self.mu.Unlock() | ||
|
||
switch whence { | ||
case 0: | ||
self.offset = offset | ||
case 1: | ||
self.offset += offset | ||
case 2: | ||
self.offset = self.size | ||
} | ||
|
||
return int64(self.offset), nil | ||
} | ||
|
||
func (self RangedReader) Close() error { | ||
return self.handle.Close() | ||
} | ||
|
||
func (self RangedReader) LStat() (accessors.FileInfo, error) { | ||
return &SparseFileInfo{size: self.size}, nil | ||
} | ||
|
||
func GetRangedReaderFile(full_path *accessors.OSPath, scope vfilter.Scope) ( | ||
zip.ReaderStat, error) { | ||
if len(full_path.Components) == 0 { | ||
return nil, fmt.Errorf("Ranged accessor expects a JSON sparse definition.") | ||
} | ||
|
||
// The Path is a serialized ranges map. | ||
index, err := parseIndexRanges([]byte(full_path.Components[0])) | ||
if err != nil { | ||
scope.Log("Ranged accessor expects ranges as path, for example: '[{Offset:0, Length: 10},{Offset:10,length:20}]'") | ||
return nil, err | ||
} | ||
|
||
pathspec := full_path.PathSpec() | ||
|
||
err = vql_subsystem.CheckFilesystemAccess(scope, pathspec.DelegateAccessor) | ||
if err != nil { | ||
scope.Log("%v: DelegateAccessor denied", err) | ||
return nil, err | ||
} | ||
|
||
accessor, err := accessors.GetAccessor(pathspec.DelegateAccessor, scope) | ||
if err != nil { | ||
scope.Log("%v: did you provide a PathSpec?", err) | ||
return nil, err | ||
} | ||
|
||
fd, err := accessor.Open(pathspec.GetDelegatePath()) | ||
if err != nil { | ||
scope.Log("sparse: Failed to open delegate %v: %v", | ||
pathspec.GetDelegatePath(), err) | ||
return nil, err | ||
} | ||
|
||
// Devices can not be stat'ed | ||
size := int64(0) | ||
if len(index.Ranges) > 0 { | ||
last := index.Ranges[len(index.Ranges)-1] | ||
size = last.FileOffset + last.FileLength | ||
} | ||
|
||
return &RangedReader{ | ||
handle: fd, | ||
size: size, | ||
reader_at: &utils.RangedReader{ | ||
ReaderAt: utils.MakeReaderAtter(fd), | ||
Index: index, | ||
}, | ||
}, nil | ||
} | ||
|
||
func init() { | ||
accessors.Register("ranged", zip.NewGzipFileSystemAccessor( | ||
accessors.MustNewPathspecOSPath(""), GetRangedReaderFile), | ||
`Reconstruct sparse files from idx and base`) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
name: Windows.Memory.PEDump | ||
description: | | ||
This artifact dumps a PE file from memory and uploads the file to | ||
the server. | ||
NOTE: The output is not exactly the same as the original binary: | ||
1. Relocations are not fixed | ||
2. Due to ASLR the base address of the binary will not be the same as the original. | ||
The result is usully much better than the binaries dumped from a | ||
physical memory image (using e.g. Volatility) because reading | ||
process memory will page in any mmaped pages as we copy them | ||
out. Therefore we do not expect to have holes in the produced binary | ||
as is often the case in memory analysis. | ||
parameters: | ||
- name: Pid | ||
type: int | ||
description: The pid to dump | ||
- name: BaseOffset | ||
type: int | ||
description: | | ||
The base offset to dump from memory. If not provided, we dump | ||
all pe files from the PID. | ||
- name: FilenameRegex | ||
default: .+exe$ | ||
description: Applies to the PE mapping filename to upload | ||
|
||
sources: | ||
- query: | | ||
LET GetFilename(MappingName, BaseOffset) = if( | ||
condition=MappingName, | ||
then=format(format="dump_%#x_%s", args=[BaseOffset, basename(path=MappingName)]), | ||
else=format(format="dump_%#x", args=BaseOffset)) | ||
SELECT format(format="%#x", args=Address) AS Address, Size, MappingName, | ||
State, Type, Protection, ProtectionMsg, read_file( | ||
accessor="process", | ||
filename=format(format="/%d", args=Pid), | ||
offset=Address, | ||
length=10) AS Header, | ||
upload(file=pe_dump(pid=Pid, base_offset=Address), | ||
name=GetFilename(MappingName=MappingName, BaseOffset=Address)) AS Upload | ||
FROM vad(pid=9604) | ||
WHERE Header =~ "^MZ" AND MappingName =~ FilenameRegex |
Binary file not shown.
Oops, something went wrong.