diff --git a/artifacts/definitions/Windows/Forensics/RDPCache.yaml b/artifacts/definitions/Windows/Forensics/RDPCache.yaml index 5fc71b1914a..3d8ab36d000 100644 --- a/artifacts/definitions/Windows/Forensics/RDPCache.yaml +++ b/artifacts/definitions/Windows/Forensics/RDPCache.yaml @@ -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 @@ -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 ) \ No newline at end of file + 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 \ No newline at end of file