In the first part of this series I showed how, using AFL++, I found multiple vulnerabilities in three open-source File Transfer Protocol (FTP) servers: Pure-FTPd, Bftpd, and ProFTPd. I also detailed the code changes required to perform the fuzzing as well as three of the most interesting vulnerabilities that resulted from this work.
In this second installment, I’ll delve into the research conducted on FreeRDP. FreeRDP is the widely used open source implementation of the Remote Desktop Protocol (RDP). Similar to the last post, we will document the entire fuzzing process from start to finish!
Let’s get fuzzing!
Putting ourselves in context
In April 2020, the GitHub Security Lab had an internal three-day hackathon with the goals of finding and fixing as many vulnerabilities as possible in Apache Guacamole. For those who aren’t familiar, Apache Guacamole is a browser application that allows you to access remote machines through standard protocols like VNC, RDP, and SSH. It’s become increasingly popular especially in the current work-from-home environment.
Although the hackathon only lasted three days, it planted the seeds for this follow-up fuzzing effort. During the hackathon, each team member was focused on a different part of the code. In my case, I chose to focus on the Remote Desktop Protocol (RDP) protocol.
If there is still someone who haven’t heard of RDP, it is a protocol that allows a user to remotely manage a Windows machine using a graphical interface as if they were actually sitting in front of the desktop.
Results
As a result of this research, I reported the following twelve bugs in FreeRDP:
CVE | GHSL | Description | PoC |
---|---|---|---|
CVE-2020-13396 | GHSL-2020-100 | OOB Read in ntlm_read_ChallengeMessage |
PoC |
CVE-2020-13397 | GHSL-2020-101 | NULL dereference in FreeRDP FIPS routines | PoC |
CVE-2020-13398 | GHSL-2020-102 | Heap overflow in FreeRDP crypto_rsa_common |
PoC |
CVE-2020-11099 | GHSL-2020-103 | OOB read vulnerability in license_read_new_or_upgrade_license_packet |
PoC |
CVE-2020-11097 | GHSL-2020-104 | OOB read vulnerability in FreeRDP ntlm_av_pair_get |
PoC |
CVE-2020-11098 | GHSL-2020-105 | OOB read vulnerability in FreeRDP glyph_cache_put |
PoC |
CVE-2020-4030 | GHSL-2020-106 | Integer signedness mismatch leading to OOB read in FreeRDP | |
CVE-2020-11096 | GHSL-2020-107 | OOB read vulnerability in FreeRDP update_read_cache_bitmap_v3_order |
PoC |
CVE-2020-11095 | GHSL-2020-124 | OOB read vulnerability in FreeRDP update_recv_primary_order |
PoC |
CVE-2020-4032 | GHSL-2020-125 | Integer signedness mismatch vulnerability in FreeRDP leads to OOB read | PoC |
CVE-2020-4033 | GHSL-2020-128 | OOB read vulnerability in FreeRDP RLEDECOMPRESS |
PoC |
CVE-2020-4031 | GHSL-2020-129 | Use-After-Free in gdi_SelectObject |
PoC |
Creating a “custom” input case
Before we start fuzzing, we need to have (at least) one representative input case. By representative I mean a serialized RDP packet trace that reaches a high rate of code coverage.
While the quickest option is to reuse previously captured packet data (e.g. from Wireshark), in this case I opted to create my own input case from scratch. And when I say from scratch, I literally mean from scratch using just GDB and a hex editor.
Although this method is obviously slower and more painstaking, it has the advantage that it forces us to become intimately familiar with our target code base. Since fuzzing network targets generally requires some degree of code modification to be effective, the knowledge we acquire when handcrafting our RDP packets will make subsequent code modifications much easier for us.
Another important advantage of hand crafting our input case packets is that it differentiates us from the standard layouts and structures implemented by official clients. By building our own input case from scratch we can more easily build packet structures that are outside of the norm and may yield better fuzzing results.
PDU
Next, I will go through some of the most important structures in the RDP protocol.
First, we should view an RDP connection as a sequence of Protocol Data Units (PDUs). These PDUs can be of two types:
- TPKT packets (Slow-Path): A TPKT header is of fixed length 4, and the following X.224 TPDU is at least three bytes long. Therefore, the minimum TPKT length is 7, and the maximum TPKT length is 65535. Because the TPKT length includes the TPKT header (4 bytes), the maximum X.224 TPDU length is 65531.
- Fast-Path packets: Fast-Path inspects server output packets from the first byte with the goal of improving bandwidth. The TPKT Header, X.224 TPDU, and MCS Send Data Indication are replaced; the Security Header is collapsed into the fast-path output header; and the Share Data Header is replaced by a new fast-path format.
In short, Slow-Path packet always starts with a TPKT header, which starts with byte 0x03, while Fast-Path packets zero out the first two least significant bits of the first byte.
Cases where NLA mode is enabled must also be taken into account. Network Level Authentication (NLA) is a feature of RDP that requires the connecting user to authenticate themselves before a session is established with the server. In these cases, we must also know the TSRequest structure. Its structure is as follows:
The TSRequest
structure is the most-used structure by the CredSSP client and CredSSP server. It contains the SPNEGO tokens and may contain Kerberos/New Technology LAN Manager (NTLM) messages that are passed between the client and server, and either the public key authentication messages that are used to bind to the TLS session or the client credentials that are delegated to the server. The TSRequest
message is always sent over the TLS-encrypted channel between the client and server in a CredSSP Protocol exchange.
RDP states
RDP is a complex protocol which involves a lot of different requests and states until a connection is fully established. This means checking a large number of protocol constraints.
Since a picture is worth a thousand words, here is a picture that describes the RDP connection process:
You can find more information about the details of the RDP protocol at: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/023f1e69-cfe8-4ee6-9ee0-7e759fb4e4ee
The FreeRDP implementation of the RDP protocol defines the following states through the CONNECTION_STATE
enum:
enum CONNECTION_STATE
{
CONNECTION_STATE_INITIAL,
CONNECTION_STATE_NEGO,
CONNECTION_STATE_NLA,
CONNECTION_STATE_MCS_CONNECT,
CONNECTION_STATE_MCS_ERECT_DOMAIN,
CONNECTION_STATE_MCS_ATTACH_USER,
CONNECTION_STATE_MCS_CHANNEL_JOIN,
CONNECTION_STATE_RDP_SECURITY_COMMENCEMENT,
CONNECTION_STATE_SECURE_SETTINGS_EXCHANGE,
CONNECTION_STATE_CONNECT_TIME_AUTO_DETECT,
CONNECTION_STATE_LICENSING,
CONNECTION_STATE_MULTITRANSPORT_BOOTSTRAPPING,
CONNECTION_STATE_CAPABILITIES_EXCHANGE,
CONNECTION_STATE_FINALIZATION,
CONNECTION_STATE_ACTIVE
};
All the structures defined above, are reflected in our fuzzing dictionary.
Changes made in the code for fuzzing
As we saw above, the RDP protocol implements many integrity checks and restrictions for the purposes of avoiding unexpected failures due to errors in the data stream.
However, for our purpose, such integrity checks would be counterproductive. Our fuzzer will constantly mutate data stream bytes and trigger checksum failures, which would normally cause the RDP connection to abort. To address these limitations, we have to make some changes to the FreeRDP code.
Let’s explore some of the changes I’ve had to make in more detail.
Disabling RC4 encoding
First I disabled the RC4 encryption during the “licensing exchange” stage. License transfers from the server to the client take place in this phase — the client stores the license and on subsequent connections sends the license to the server for validation.
The reason to disable RC4 is that fuzzers such as AFL are unable to find bugs on encrypted streams. This is because flip bit/byte mutations usually corrupt encrypted streams. So, generated inputs will be invalid and may not be decrypted correctly.
Disabling TLS encoding
Just as we have done with RC4 encryption, and for similar reasons, we will also disable any TLS encryption on the RDP connection. This includes both handshake and certificate validation.
Disabling MAC checking
A Message Authentication Code (MAC) is a cryptographic checksum on data that uses a session key to detect both accidental and intentional modifications of the data.
Since the fuzzer’s mutations will likely introduce MAC check failures, we should also disable those.
Disabling NTLM signature verification
NTLM is the authentication protocol used on networks that include systems running the Windows operating system. RDP protocol uses either NTLM or Kerberos to perform its authentication.
In my case, I mainly focused on NTLM authentication. And since the NTLM protocol includes signature verification, any change made by the fuzzer in the NTLM section will cause an error in the authentication process. So we disable NTLM signature verification as well:
winpr/libwinpr/sspi/NTLM/ntlm.c
Disabling Nhash verification
When Network Level Authentication (NLA) is enabled, we also need to disable SHA256 digest comparisons:
Disabling multi-threading
In the previous installment of this series, we discussed how we generally want to avoid forking target processes since AFL can not handle multi-process targets well.
Although this limitation doesn’t apply to multi-threaded applications, multiple threads in the target process may negatively affect the AFL “stability” score. This is primarily due to the non-deterministic execution order of program instructions, and this in turn will lead to worse code coverage.
For this reason, I recommend that whenever possible, you should limit thread concurrency to a minimum. Below I show some changes made in the FreeRDP code for this purpose:
channels/drdynvc/client/drdynvc_main.c
channels/cliprdr/client/cliprdr_main.c
Other minor changes
There are some additional minor changes that were made to the FreeRDP code that are mostly in line with the points we already discussed in the previous post in this series.
You can visit my FreeRDP_FUZZ repository for a detailed record of all the changes made to effectively fuzz the FreeRDP library: Browse the commits
Let’s go fuzzing
Now that we have shown the changes made in the code, we can now explain the fuzzing process itself.
First of all, we need to configure and build the project for fuzzing with AFL:
cmake -G "Eclipse CDT4 - Unix Makefiles" -DCHANNEL_URBDRC=ON -DWITH_FFMPEG=ON -DWITH_CUPS=ON -DWITH_PULSE=ON -DWITH_FAAC=ON -DWITH_FAAD2=ON -DWITH_GSM=ON -DWITH_JPEG=ON -DWITH_MBEDTLS=ON -DCMAKE_C_COMPILER=afl-clang-fast -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined -fno-sanitize-recover=all -g" -DCMAKE_C_FLAGS="-fsanitize=address,undefined -fno-sanitize-recover=all -g" -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address -fno-sanitize-recover=all" DCMAKE_INSTALL_PREFIX=/home/antonio/Downloads/FreeFuzzing/FreeRDP/extLibs/FreeRDP/install/ -DCMAKE_MODULE_LINKER_FLAGS="-fsanitize=address,undefined -fno-sanitize-recover=all" -DCMAKE_BUILD_TYPE=Debug,ASAN,UBSAN -DWITH_SSE2=ON -DMONOLITHIC_BUILD=ON -DBUILD_SHARED_LIBS=OFF .
cmake --build . -j 4
The relevant part is that I’ve enabled Address Sanitizer and Undefined Behavior Sanitizer and that I’ve set “afl-clang-fast” as the default compiler. I’ve also disabled shared libs and have chosen the monolithic build option. And, finally, I’ve enabled the maximum possible external functionalities (DCHANNEL_URBDRC
, DWITH_CUPS
, DWITH_PULSE
, DWITH_FAAC
, DWITH_GSM
, DWITH_JPEG
, DWITH_MBEDTLS
).
As you can see in the following code snippet, we’re going to invoke the xfreerdp executable in our fuzzing process:
./client/X11/xfreerdp /v:127.0.0.1 /p:whatever /log-level:TRACE /relax-order-checks +glyph-cache +bitmap-cache +menu-anims @@
In order to increase the FreeRDP code coverage, I’ve also enabled the following command-line options:
log-level:TRACE
: Set the default log level to maximum level/relax-order-checks
: Do not check if a RDP order was announced during capability exchange, only use when connecting to a buggy serverglyph-cache
: Enable glyph cache (experimental function)bitmap-cache
: Enable bitmap cachemenu-anims
: Enable menu animations
I also provided the following dictionary to the fuzzer: RDP.dict
Based on the above, my final command line to fuzz FreeRDP with AFL looks like:
ASAN_OPTIONS=verbosity=3,detect_leaks=0,abort_on_error=1,symbolize=0,debug=true,check_initialization_order=true,detect_stack_use_after_return=true,strict_string_checks=true,detect_invalid_pointer_pairs=2 afl-fuzz -t 1500 -m none -i ./AFL/afl_in/ -o './AFL/afl_out' -x './AFL/dictionaries/Basic.txt' -- ./client/X11/xfreerdp /v:127.0.0.1 /p:whatever /log-level:TRACE /relax-order-checks +glyph-cache +bitmap-cache +menu-anims @@
Acknowledgements
I would like to thank akallabeth for their cooperation and professionalism in the process of fixing the bugs. I can say that it has been a pleasure working with you and working on improving FreeRDP security :)
Take a look at the tools and references I used throughout this post for further reading: