In a recent incident response project, we had the chance to virtually look over the attackers’ shoulder and observe their activities. The attackers used the Remote Desktop Protocol (RDP) for lateral movement within the compromized environment and beyond (MITRE techniques T1570, T1021). As a matter of fact, RDP creates cache files that contain tiles of the transferred screen recording data. While this fact is well-known and there are existing tools, we found it worth reporting because of two different aspects:
- On the one hand, we want to raise awareness for this valuable piece of evidence, explain how it works, how tooling works and how it can be used. In this particular case, the analysis of those cache files yielded valuable insights into the attackers’ activity and allowed further measures.
- On the other hand, we found it exciting to look over the attacker’s shoulder, see the desktop as they saw it, and the commands they typed. We want to share parts of those insights as far as we are able to show them publicly.
The Incident
We investigated an incident with a customer who acted as a service provider for his customers and was using RDP to connect to their customers’ machines. We already knew the attacker had admin privileges on the terminal server used to launch these outgoing RDP connections. There was evidence that the attacker had also launched outgoing RDP connections: network flow logs, the logs of their terminal server management software, and one of the targeted customers already had alerts about a suspicious .bat
file appearing on their machines. Our customer wanted to know more about the attacker targeting their customers.
We leveraged a quite valuable RDP connection artifact to better answer the question. Originally designed to optimize performance by caching screen contents for smoother user experiences, the Bitmap Cache contains fragments of the remote display rendered in the course of RDP sessions. Analysis of the stored bitmap fragments allowed us to gain a first-person view of the threat actor’s activities.
about:RDP
The Remote Desktop Protocol (RDP) is widely used to connect to and control another computer over a network, providing full access to its desktop and resources. First introduced in Windows NT 4.0, it is based on and extends the T-120 protocol standards governed by the International Telecommunication Union Telecommunication Standardization Sector (ITU-T).
Microsoft publishes the RDP specification as part of its Open Specifications program1 under the [MS-RDPBCGR]
abbreviation (Remote Desktop Protocol: Basic Connectivity and Graphics Remoting)2. As defined in [MS-RDPBCGR]
, RDP is designed to:
[…] facilitate user interaction with a remote computer system by transferring graphics display information from the remote computer to the user and transporting input from the user to the remote computer, where it may be injected locally.
The RDP protocol is further extended by multiple other protocols3:
- Licensing Extension
[MS-RDPELE]
- Session Selection Extension
[MS-RDPEPS]
- Graphics Device Interface (GDI) Acceleration Extensions
[MS-RDPEGDI]
- Desktop Composition Extension
[MS-RDPEDC]
- Remote Programs Virtual Channel Extension
[MS-RDPERP]
- NSCodec Extension
[MS-RDPNSC]
- RemoteFX Codec Extension
[MS-RDPRFX]
As graphics remoting is bandwidth-intensive, [MS-RDPEGDI]
aims to reduce the amount of transferred data by encoding the drawing operations that produce an image instead of encoding the actual image. Additionally, RDP traffic is further reduced by employing bitmap caches to avoid the repeated transmission of identical graphical elements over the network.
Bitmap Caches – Theory
Bitmap caches store graphic bitmaps during an RDP session between the client and server. The rationale behind their use is simple: the server should not resend bitmaps already known to the client.
Each cache is designed to hold bitmaps of a specific size in pixels, referred to as the “tile size”. When a bitmap exceeds the size of a single cache entry, the server employs a tiling algorithm to divide the bitmap into smaller tiles that fit within the cache. These tiles are then stored individually in the cache.
The contents of bitmap caches, stored on the client side, are updated by means of the server sending Cache Bitmap – Revision 1/2/3 Secondary Drawing Orders (SDO) messages to the client4. First-revision SDO cache bitmap messages only support memory-based bitmap caching (Revision 1 bitmap caches), whereas the second- and third-revision SDO messages support persistent disk caching (Revision 2 bitmap caches). The in-memory bitmap cache is not reused past an RDP session’s end. In contrast, the persistent bitmap cache is valid beyond the end of a particular session and is reused for future ones.
As seen in Figure 1, persistent caching is enabled by default in the Microsoft Terminal Service Client (mstsc.exe
), also known as Remote Desktop Connection.
Revision 2 bitmap caches enable support for persistent disk caching via the so-called persistent bitmap cache key. Cache bitmap revision 2 and 3 SDO messages (i.e., CACHE_BITMAP_REV2_ORDER
and CACHE_BITMAP_REV3_ORDER
) contain a 64-bit key derived from a cryptographic hash of the bitmap contents associated with each bitmap. On subsequent connections, the client sends these keys so the server can maintain its cache of bitmap keys already known to the client.
Bitmap Caches – In Practice
Depending on the Windows version in use, the bitmap cache files are stored in:
- Until Windows XP:
%USERPROFILE%\Local Settings\Application Data\Microsoft\Terminal Server Client\Cache
- Windows Vista, 7 and above:
%USERPROFILE%\AppData\Local\Microsoft\Terminal Server Client\Cache
Two file types with the following naming could be encountered: bcache*.bmc
and Cache*.bin
.
The former is an older file format which stores cached bitmaps of different image quality, measured in Bits Per Pixel (BPP), in distinct files. Namely, graphical elements with 8, 16, and 32 BPP are stored in bcache2.bmc
, bcache22.bmc
, and bcache24.bmc
, respectively. bcache*.bmc
files are known to reach a size of up to 20 MB and are created alongside Cache*.bin
files on machines running Windows 75.
The newer file format (present on Windows 7 and above) is particularly interesting. It stores 64 x 64-pixel bitmaps with up to 32 BPP. The first bitmap cache file name to be created is Cache0000.bin
, and subsequent files are “incremented by one” (e.g., Cache0001.bin, Cache0002.bin, etc.). Each file is known to reach a size of up to 100 MB.
Consider the below excerpt from the hexdump
output of an example Cache0000.bin
persistent bitmap cache from a Windows 11 machine:
$ hexdump -C Cache0000.bin | head
00000000 52 44 50 38 62 6d 70 00 06 00 00 00 1e c8 e1 a9 |RDP8bmp.........|
00000010 e2 b5 cc 89 40 00 40 00 ff ff ff ff ff ff ff ff |....@.@.........|
00000020 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
*
000000e0 ff ff ff ff ff ff ff ff ff ff ff ff b9 b9 e8 ff |................|
000000f0 23 23 cb ff 0d 0d c5 ff 0d 0d c5 ff 0d 0d c5 ff |##..............|
00000100 0d 0d c5 ff 0d 0d c5 ff 0d 0d c5 ff 0d 0d c5 ff |................|
00000110 0d 0d c5 ff 0d 0d c5 ff ff ff ff ff ff ff ff ff |................|
00000120 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
*
[...]
Cache*.bin
files have a fixed 12-byte header. The first 8 bytes constitute the file signature: the character string RDP8bmp
, terminated by a null byte6. The remaining 4 bytes hold versioning information.
Cache entries follow the header directly, meaning the first begins at offset 0xC
. Each graphical element has a 12-byte header which precedes the bitmap file contents:
- the first 8 bytes encode the persistent bitmap cache key (e.g.,
1e c8 e1 a9 e2 b5 cc 89
), - the next 2 bytes store the bitmap width (e.g.,
40 00
), and - the last 2 bytes hold the bitmap height (e.g.,
40 00
).
The raw bitmap bytes follow the header. These can be directly copied into a BMP file according to the specifications of the BMP file format (i.e., from bottom to top and from left to right). The stored bitmap graphics are in 32 BPP format. We can check this by calculating the offset of the second graphical element. For this, we need to take the following into account:
0xC
(12 decimal) bytes for the container file (.bin
) header0xC
(12 decimal) bytes for the first bitmap header0x4000
(16384 decimal) bytes for the first bitmap file contents- 64 bytes (width) * 64 bytes (height) * 4 bytes (32 bits per pixel) = 16384 bytes
Therefore, we should find the start of the second element at 0xC
+ 0xC
+ 0x4000
= 0x4018
. As can be seen from the expanded hexdump
output excerpt of the same Cache0000.bin
example file below, the header of the second bitmap does indeed begin at 0x4018
. The first 8 bytes hold the key (i.e., 58 64 3d 42 99 23 0d 7e
), followed by another 4 bytes for the width and height of the graphical element (i.e., 40 00 40 00
).
$ hexdump -C Cache0000.bin
00000000 52 44 50 38 62 6d 70 00 06 00 00 00 1e c8 e1 a9 |RDP8bmp.........|
00000010 e2 b5 cc 89 40 00 40 00 ff ff ff ff ff ff ff ff |....@.@.........|
00000020 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
*
000000e0 ff ff ff ff ff ff ff ff ff ff ff ff b9 b9 e8 ff |................|
000000f0 23 23 cb ff 0d 0d c5 ff 0d 0d c5 ff 0d 0d c5 ff |##..............|
00000100 0d 0d c5 ff 0d 0d c5 ff 0d 0d c5 ff 0d 0d c5 ff |................|
00000110 0d 0d c5 ff 0d 0d c5 ff ff ff ff ff ff ff ff ff |................|
00000120 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
*
000001e0 ff ff ff ff ff ff ff ff c1 c1 ec ff 23 23 ca ff |............##..|
000001f0 0d 0d c5 ff 52 52 d5 ff 16 16 c7 ff 23 23 ca ff |....RR......##..|
00000200 4b 4b d4 ff 1d 1d c8 ff 15 15 c7 ff 49 49 d3 ff |KK..........II..|
[...]
00003420 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
*
000034e0 ff ff ff ff ff ff ff ff ff ff ff ff c6 ff ff ff |................|
000034f0 49 49 87 ff 49 49 49 ff 87 49 49 ff ff ff c6 ff |II..III..II.....|
00003500 ff ff ff ff ff ff ff ff 49 87 c6 ff 49 49 49 ff |........I...III.|
00003510 49 49 49 ff ff c6 87 ff ff ff ff ff ff ff ff ff |III.............|
00003520 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
*
000035f0 49 87 c6 ff 49 49 49 ff c6 87 49 ff ff ff ff ff |I...III...I.....|
00003600 ff ff ff ff ff ff ff ff 65 a9 e2 ff 49 49 49 ff |........e...III.|
00003610 87 49 49 ff ff ff c6 ff ff ff ff ff ff ff ff ff |.II.............|
00003620 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
*
00004010 ff ff ff ff ff ff ff ff 58 64 3d 42 99 23 0d 7e |........Xd=B.#.~|
00004020 40 00 40 00 0d 0d c5 ff 0d 0d c5 ff 0d 0d c5 ff |@.@.............|
[...]
You’re Only as Good as Your Tools…
Now that we understand and know where to look for RDP bitmap cache files, what’s next? Well, of course, we need a way to parse the cache files to extract the contained bitmap images.
Fortunately, the French National Agency for Information Systems Security (ANSSI) has developed and open-sourced an excellent tool for the job: BMC-Tools
7. It parses the RDP bitmap cache (supports both bcache*.bmc
and Cache*.bin
file formats) and outputs the RDP session fragments as BMP files into a directory of choice.
Here is what a BMC-Tools
invocation looks like:
> python .\bmc-tools.py -s "C:\Users\ernw\AppData\Local\Microsoft\Terminal Server Client\Cache" -d .\ernw_rdpuzzle\ -vv
[+++] Processing a directory...
[---] File 'C:\Users\ernw\AppData\Local\Microsoft\Terminal Server Client\Cache\Cache0000.bin' has been found.
[---] Subsequent header version: 6.
[===] Successfully loaded 'C:\Users\ernw\AppData\Local\Microsoft\Terminal Server Client\Cache\Cache0000.bin' as a .BIN container.
[+++] 100 tiles successfully extracted so far.
[+++] 200 tiles successfully extracted so far.
[+++] 300 tiles successfully extracted so far.
[+++] 400 tiles successfully extracted so far.
[+++] 500 tiles successfully extracted so far.
[+++] 600 tiles successfully extracted so far.
[+++] 700 tiles successfully extracted so far.
[+++] 800 tiles successfully extracted so far.
[+++] 900 tiles successfully extracted so far.
[+++] 1000 tiles successfully extracted so far.
[+++] 1100 tiles successfully extracted so far.
[...]
[+++] 5400 tiles successfully extracted so far.
[+++] 5500 tiles successfully extracted so far.
[+++] 5600 tiles successfully extracted so far.
[+++] 5700 tiles successfully extracted so far.
[+++] 5800 tiles successfully extracted so far.
[+++] 5900 tiles successfully extracted so far.
[+++] 6000 tiles successfully extracted so far.
[+++] 6100 tiles successfully extracted so far.
[+++] 6200 tiles successfully extracted so far.
[+++] 6300 tiles successfully extracted so far.
[+++] 6400 tiles successfully extracted so far.
[===] 6418 tiles successfully extracted in the end.
[===] Successfully exported 6418 files.
[...]
Parts of the contents of the ernw_rdpuzzle
directory are shown below.
Additionally, BMC-Tools
can create collages of the extracted BMP files per bitmap cache by specifying the --bitmap
command line argument as shown in Figure 2.
At this point, the limitations of this artifact become apparent: The cache entries contain the bitmaps of transmitted tiles but no further information. We therefore don’t know:
- Where on the screen a tile was located.
- When a tile was added to the cache.
- What session a tile belonged to.
Another tool is needed to effectively assemble the RDP session fragments into meaningful images. This time around, the German Federal Office for Information Security (BSI) has come through for us by providing the right tool for the task at hand – RdpCacheStitcher
8.
RdpCacheStitcher
features a graphical user interface, as well as tile placement heuristics, to ease and speed up the puzzling of RDP session screen fragments back together. Figure 3 shows a partially reconstructed frame from an RDP session.
In this instance (a test case on one of our lab machines), it was possible to reassemble parts of the Windows taskbar and an opened command prompt. The following valuable data can be obtained from the reconstructed image:
- there was activity at least on January 13th, 2025, at 16:53,
- the remote machine is running Windows 10 (OS Build 19045.4651),
- commands
who
andwhoami
were executed, and - the logged-in user was
rdpuzzle
.
While this is a contrived example, a wealth of information can be gleaned from the RDP bitmap cache in real-world engagements, such as accessed applications, opened files, and viewed file system locations (e.g., directories, network shares, etc.).
Additionally, since frequently-occurring bitmap fragments from the rendered screen contents are cached, the longer the RDP session, the more information can be gained. Moreover, the RDP bitmap cache stores cross-session image fragments. Thus, it is possible to get glimpses of threat actor activity over a prolonged period of time, albeit with a significant manual effort.
Real World Cases
So, how well does this work outside the lab? The following screenshots were reconstructed in actual incidents.
In the first example, it was possible to partly reconstruct a terminal window opened by the attacker. We can not only see that certutil.exe was used to try to download a file, but we were also able to read the URL the file came from – a useful IoC.
In another example it was possible to reconstruct the complete terminal window with all its content – here a task list output.
Here we can see that the attacker opened a web browser in private mode.
Of the sites visited by the attacker, we can reconstruct this login page.
A file named svchost.exe
is copied to C:\Users\Public\Downloads
.
And we can observe further RDP activity.
Limitations
As already touched upon, RDP bitmap cache analysis is not a silver bullet for reconstructing malicious activity on compromised systems.
For one, the bitmap cache is designed for performance optimization purposes. It cannot fully capture user activity since it stores select fragments of on-screen content. Furthermore, the rendered content might not necessarily stem from processes running on the remote system. For instance, a rendering of a video showing an opened command prompt may mislead analysts regarding the commands executed on the investigated machine.
Another limitation is, that the RDP cache is stored on the host that initiated the session, as opposed to the target machine (i.e., the RDP server). Therefore, the initiating machine must be available for analysis. If an attacker establishes an RDP session from their machine, collecting the bitmap cache is not possible, as it is not stored on the target host.
And, as with many artifacts, luck also can be a factor: You cannot influence what data is saved in the cache and for how long. So it might be that the puzzled-together cache tiles reveal a useful IoC or other insights into the incident. However, if you are unlucky, all there is to find are fragments of desktop wallpapers.
Conclusion
Despite the mentioned limitations, the RDP bitmap cache can provide valuable context to a investigation. Reconstructed screen fragments can uncover concrete actions that were not logged or directly evident from the analysis of other sources (e.g., Windows event logs).
There is a lot to be gained from correlating reconstructed threat actor activity with more traditional evidence sources (e.g., RDP-related registry entries, network logs, etc.), especially in lateral movement scenarios.
In our particular case, the effort was worth it to gain valuable insights and we hope you might find it useful, too.
Cheers!
Samer Al-Bakhlul, Justus Hoffmann, and Lucas Wenzel