Skip to content

Commit

Permalink
Add RDPCache parser to RDPCache artifact (#50) (Velocidex#3651)
Browse files Browse the repository at this point in the history
* Add RDPCache parser to RDPCache artifact
  • Loading branch information
mgreen27 authored Jul 29, 2024
1 parent 52d59c1 commit f753569
Showing 1 changed file with 139 additions and 14 deletions.
153 changes: 139 additions & 14 deletions artifacts/definitions/Windows/Forensics/RDPCache.yaml
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
name: Windows.Forensics.RDPCache
author: Matt Green - @mgreen27
description: |
This artifact views and enables simplified upload of RDP
cache files.
Filters include User regex to target a user and Accessor to target
vss via ntfs_vss.
Best combined with:
- Windows.EventLogs.RDPAuth to collect RDP focused event logs.
- Windows.Registry.RDP to collect user RDP mru and server info
This artifact parses, views and enables simplified upload of RDP
cache files.
By default the artifact will parse .BIN RDPcache files.
Filters include User regex to target a user and Accessor to target
vss via ntfs_vss.
Best combined with:
- Windows.EventLogs.RDPAuth to collect RDP focused event logs.
- Windows.Registry.RDP to collect user RDP mru and server info
reference:
- https://github.com/ANSSI-FR/bmc-tools
Expand All @@ -25,18 +27,141 @@ parameters:
default: .
description: Regex filter of user to target. StartOf(^) and EndOf($)) regex may behave unexpectanly.
type: regex
- name: ParseCache
description: If selected will parse .BIN RDPcache files.
type: bool
- name: Workers
default: 100
type: int
description: Number of workers to use for ParseCache
- name: UploadRDPCache
description: If selected will upload raw cache files. Can be used for offline processing/preservation.
type: bool

sources:
- query: |
- name: TargetFiles
description: RDP BitmapCache files in scope.
query: |
LET results = SELECT OSPath, Size, Mtime, Atime, Ctime, Btime
FROM glob(globs=RDPCacheGlob,accessor=Accessor)
WHERE OSPath =~ UserRegex
LET upload_results = SELECT *, upload(file=OSPath) as CacheUpload
FROM results
SELECT * FROM if(condition= UploadRDPCache,
then= upload_results,
else= results )
else= results )
- name: Parsed
description: Parsed RDP BitmapCache files.
query: |
LET PROFILE = '''[
["BIN_CONTAINER", 0, [
[Magic, 0, String, {length: 8, term_hex : "FFFFFF" }],
[Version, 8, uint32],
[CachedFiles, 12, Array, {
"type": "rgb32b",
"count": 10000,
"max_count": 2000,
"sentinel": "x=>x.__Size < 15",
}],
]],
["rgb32b","x=>x.__Size",[
[__key1, 0, uint32],
[__key1, 4, uint32],
["Width", 8, "uint16"],
["Height", 10, "uint16"],
[DataLength, 0, Value,{ value: "x=> 4 * x.Width * x.Height"}],
[DataOffset, 0, Value,{ "value": "x=>x.StartOf + 12"}],
["__Size", 0, Value,{ "value": "x=>x.DataLength + 12"}],
["Index", 0, Value,{ "value": "x=>count() - 1 "}],
]]]'''
LET parse_rgb32b(data) = SELECT
_value as Offset,
_value + 3 as EndOffset,
len(list=data) as Length,
data[(_value):(_value + 3)] + unhex(string="FF") as Buffer
FROM range(step=4,end=len(list=data))
LET fix_bmp(data) = SELECT
_value as Offset,
_value + 255 as EndOffset,
join(array=data[ (_value):(_value + 256 ) ],sep='') as Buffer
FROM range(step=256, end= len(list=data) )
ORDER BY Offset DESC
LET parse_container = SELECT * OSPath,Name,Size as FileSize,
read_file(filename=OSPath,length=12) as Header,
parse_binary(filename=OSPath,profile=PROFILE,struct='BIN_CONTAINER') as Parsed
FROM foreach(row={
SELECT * FROM glob(globs=RDPCacheGlob,accessor=Accessor)
WHERE OSPath =~ '\.bin$'
AND OSPath =~ UserRegex
AND NOT IsDir
})
LET find_index_differential = SELECT *, 0 - Parsed.CachedFiles.Index[0] as IndexDif
FROM parse_container
LET parse_cache = SELECT * FROM foreach(row=find_index_differential, query={
SELECT OSPath, IndexDif,
OSPath.Dirname + ( OSPath.Basename + '_' + format(format='%04v',args= Index + IndexDif ) + '.bmp' ) as BmpName,
FileSize,Header,Width,Height,DataLength,DataOffset
FROM foreach(row=Parsed.CachedFiles)
})
LET extract_data = SELECT *
FROM foreach(row=parse_cache,query={
SELECT
OSPath,BmpName,FileSize,Header,Width,Height,DataLength,DataOffset,
join(array=parse_rgb32b(data=read_file(filename=OSPath,offset=DataOffset,length=DataLength)).Buffer,sep='') as Data
FROM scope()
}, workers=Workers)
-- change endianess for unint32
LET pack_lt_l(data) = unhex(string=join(array=[
format(format='%02x',args=unhex(string=format(format='%08x',args=data))[3]),
format(format='%02x',args=unhex(string=format(format='%08x',args=data))[2]),
format(format='%02x',args=unhex(string=format(format='%08x',args=data))[1]),
format(format='%02x',args=unhex(string=format(format='%08x',args=data))[0])
],sep=''))
-- build bmp file, adding appropriate header
LET build_bmp(data,width,height) = join(array=[
"BM",
pack_lt_l(data=len(list=data) + 122),
unhex(string="000000007A0000006C000000"),
pack_lt_l(data=width),
pack_lt_l(data=height),
unhex(string="0100200003000000"),
pack_lt_l(data=len(list=data)),
unhex(string="000000000000000000000000000000000000FF0000FF0000FF000000000000FF"),
" niW",
unhex(string="00" * 36),
unhex(string="000000000000000000000000"),
data
], sep='')
SELECT * FROM if(condition= ParseCache,
then={
SELECT
BmpName, Header, Width, Height, DataLength, DataOffset,
upload(
file=build_bmp(data=join(array=fix_bmp(data=Data).Buffer,sep=''),
width=Width, height=Height),
name=BmpName,
accessor='data' ) as BmpUpload,
OSPath as SourceFile
FROM extract_data
ORDER BY BmpName
},
else= Null )
column_types:
- name: BmpUpload
type: upload_preview
- name: CacheUpload
type: upload_preview

0 comments on commit f753569

Please sign in to comment.