11name : Windows.Forensics.RDPCache
22author : Matt Green - @mgreen27
33description : |
4- This artifact views and enables simplified upload of RDP
5- cache files.
6-
7- Filters include User regex to target a user and Accessor to target
8- vss via ntfs_vss.
9-
10- Best combined with:
11-
12- - Windows.EventLogs.RDPAuth to collect RDP focused event logs.
13- - Windows.Registry.RDP to collect user RDP mru and server info
4+ This artifact parses, views and enables simplified upload of RDP
5+ cache files.
6+
7+ By default the artifact will parse .BIN RDPcache files.
8+
9+ Filters include User regex to target a user and Accessor to target
10+ vss via ntfs_vss.
11+
12+ Best combined with:
13+
14+ - Windows.EventLogs.RDPAuth to collect RDP focused event logs.
15+ - Windows.Registry.RDP to collect user RDP mru and server info
1416
1517reference :
1618 - https://github.com/ANSSI-FR/bmc-tools
@@ -25,18 +27,141 @@ parameters:
2527 default : .
2628 description : Regex filter of user to target. StartOf(^) and EndOf($)) regex may behave unexpectanly.
2729 type : regex
30+ - name : ParseCache
31+ description : If selected will parse .BIN RDPcache files.
32+ type : bool
33+ - name : Workers
34+ default : 100
35+ type : int
36+ description : Number of workers to use for ParseCache
2837 - name : UploadRDPCache
38+ description : If selected will upload raw cache files. Can be used for offline processing/preservation.
2939 type : bool
3040
3141sources :
32- - query : |
42+ - name : TargetFiles
43+ description : RDP BitmapCache files in scope.
44+ query : |
3345 LET results = SELECT OSPath, Size, Mtime, Atime, Ctime, Btime
3446 FROM glob(globs=RDPCacheGlob,accessor=Accessor)
3547 WHERE OSPath =~ UserRegex
36-
48+
3749 LET upload_results = SELECT *, upload(file=OSPath) as CacheUpload
3850 FROM results
39-
51+
4052 SELECT * FROM if(condition= UploadRDPCache,
4153 then= upload_results,
42- else= results )
54+ else= results )
55+
56+ - name : Parsed
57+ description : Parsed RDP BitmapCache files.
58+ query : |
59+ LET PROFILE = '''[
60+ ["BIN_CONTAINER", 0, [
61+ [Magic, 0, String, {length: 8, term_hex : "FFFFFF" }],
62+ [Version, 8, uint32],
63+ [CachedFiles, 12, Array, {
64+ "type": "rgb32b",
65+ "count": 10000,
66+ "max_count": 2000,
67+ "sentinel": "x=>x.__Size < 15",
68+ }],
69+ ]],
70+ ["rgb32b","x=>x.__Size",[
71+ [__key1, 0, uint32],
72+ [__key1, 4, uint32],
73+ ["Width", 8, "uint16"],
74+ ["Height", 10, "uint16"],
75+ [DataLength, 0, Value,{ value: "x=> 4 * x.Width * x.Height"}],
76+ [DataOffset, 0, Value,{ "value": "x=>x.StartOf + 12"}],
77+ ["__Size", 0, Value,{ "value": "x=>x.DataLength + 12"}],
78+ ["Index", 0, Value,{ "value": "x=>count() - 1 "}],
79+ ]]]'''
80+
81+ LET parse_rgb32b(data) = SELECT
82+ _value as Offset,
83+ _value + 3 as EndOffset,
84+ len(list=data) as Length,
85+ data[(_value):(_value + 3)] + unhex(string="FF") as Buffer
86+ FROM range(step=4,end=len(list=data))
87+
88+ LET fix_bmp(data) = SELECT
89+ _value as Offset,
90+ _value + 255 as EndOffset,
91+ join(array=data[ (_value):(_value + 256 ) ],sep='') as Buffer
92+ FROM range(step=256, end= len(list=data) )
93+ ORDER BY Offset DESC
94+
95+ LET parse_container = SELECT * OSPath,Name,Size as FileSize,
96+ read_file(filename=OSPath,length=12) as Header,
97+ parse_binary(filename=OSPath,profile=PROFILE,struct='BIN_CONTAINER') as Parsed
98+ FROM foreach(row={
99+ SELECT * FROM glob(globs=RDPCacheGlob,accessor=Accessor)
100+ WHERE OSPath =~ '\.bin$'
101+ AND OSPath =~ UserRegex
102+ AND NOT IsDir
103+ })
104+
105+ LET find_index_differential = SELECT *, 0 - Parsed.CachedFiles.Index[0] as IndexDif
106+ FROM parse_container
107+
108+ LET parse_cache = SELECT * FROM foreach(row=find_index_differential, query={
109+ SELECT OSPath, IndexDif,
110+ OSPath.Dirname + ( OSPath.Basename + '_' + format(format='%04v',args= Index + IndexDif ) + '.bmp' ) as BmpName,
111+ FileSize,Header,Width,Height,DataLength,DataOffset
112+ FROM foreach(row=Parsed.CachedFiles)
113+ })
114+
115+ LET extract_data = SELECT *
116+ FROM foreach(row=parse_cache,query={
117+ SELECT
118+ OSPath,BmpName,FileSize,Header,Width,Height,DataLength,DataOffset,
119+ join(array=parse_rgb32b(data=read_file(filename=OSPath,offset=DataOffset,length=DataLength)).Buffer,sep='') as Data
120+ FROM scope()
121+ }, workers=Workers)
122+
123+ -- change endianess for unint32
124+ LET pack_lt_l(data) = unhex(string=join(array=[
125+ format(format='%02x',args=unhex(string=format(format='%08x',args=data))[3]),
126+ format(format='%02x',args=unhex(string=format(format='%08x',args=data))[2]),
127+ format(format='%02x',args=unhex(string=format(format='%08x',args=data))[1]),
128+ format(format='%02x',args=unhex(string=format(format='%08x',args=data))[0])
129+ ],sep=''))
130+
131+ -- build bmp file, adding appropriate header
132+ LET build_bmp(data,width,height) = join(array=[
133+ "BM",
134+ pack_lt_l(data=len(list=data) + 122),
135+ unhex(string="000000007A0000006C000000"),
136+ pack_lt_l(data=width),
137+ pack_lt_l(data=height),
138+ unhex(string="0100200003000000"),
139+ pack_lt_l(data=len(list=data)),
140+ unhex(string="000000000000000000000000000000000000FF0000FF0000FF000000000000FF"),
141+ " niW",
142+ unhex(string="00" * 36),
143+ unhex(string="000000000000000000000000"),
144+ data
145+ ], sep='')
146+
147+ SELECT * FROM if(condition= ParseCache,
148+ then={
149+ SELECT
150+ BmpName, Header, Width, Height, DataLength, DataOffset,
151+ upload(
152+ file=build_bmp(data=join(array=fix_bmp(data=Data).Buffer,sep=''),
153+ width=Width, height=Height),
154+ name=BmpName,
155+ accessor='data' ) as BmpUpload,
156+ OSPath as SourceFile
157+ FROM extract_data
158+ ORDER BY BmpName
159+ },
160+ else= Null )
161+
162+
163+ column_types :
164+ - name : BmpUpload
165+ type : upload_preview
166+ - name : CacheUpload
167+ type : upload_preview
0 commit comments