Man-in-the-Browser in Google Chrome: Part 2 – Locating SSL_Write and SSL_Read

This second part of the Chromium Man-in-the-Browser series will take into consideration higher level wrappers such as SSL_Write and SSL_Read which are targeted by attackers to tamper secure SSL/TLS communications, respectively, before they get encrypted and after they get decrypted.

Targeting those wrappers in favour of lower-level ones discussed in part 1 have proved to be a successful strategy for malware developers as the SSL VMT structure can change at any time, making automatic lookup ineffective. The order of the methods contained in it can change, methods can be replaced or completely removed. For instance, since the first stable version of Chrome 64, ssl3_read_app_data and ssl_read_close_notify were removed from SSL VMT making harder or impossible for malware authors to tamper incoming encrypted traffic1.

Pattern Search Lookup

Pattern searching, also known as signature scanning, is a two-step process which involves firstly the extraction of the function signature from a binary and then to use the same to locate the otherwise-hidden function. A signature is a set of bytes (instructions) which uniquely identify a function. A signature can match zero, one or multiple addresses but it is fundamental to have a set with enough bytes to correctly identify only the targeted function. If the signature has too many bytes, the higher are the chances that any small change to the code will make matching not possible.

As Chromium is an Open Source project, it is possible to read its source code and locate the attack points by looking directly at its source code. Google’s portfolio became more complex and, since March 2015, Chromium/Chrome switched to BoringSSL which is a OpenSSL fork which meets the company needs 2.

By browsing Chromium source code repository it is trivial to find the functions to target, SSL_Write and SSL_Read, contained in ssl_lib.cc in BoringSSL source code. An extract of the disassembly of both functions, taken from chrome.dll module in .text section, are displayed in Figure 1.

By placing a breakpoint on each of these functions and by analysing the parameters passed by the caller, it is possible to intercept or manipulate the data sent and read from the server. In both functions, it is only necessary to break the execution of the code and read the memory location pointed by the second parameter passed to the function (in x86_64, rdx register) to locate the buffer. Figure 2 shows an example of sensitive data submitted to a bank with a POST request (by hooking SSL_Write).

Figure 2: Memory dump of the pointer to a buffer passed to SSL_Write routine during a login to a bank with SSL/TLS enabled

The first column in Figure 1 shows the opcodes (actual instructions in the binary) which are supposed to stay the same (after the code is recompiled) if no major change is done to this specific part of the code. The second column is just a textual representation of the opcodes in x64 Assembly code.

The signature scanning algorithm, takes as input the following three parameters:

  • Starting Address: the address from which the search should start from
  • Size: the size of data, in bytes, to search in
  • Byte Pattern: It is an ASCII based pattern of bytes which also support wildcards (“?” character) to match any byte
  • Byte Pattern Length: size in bytes of the byte pattern to match

As the SSL/TLS functions are contained in the chrome.dll module, the starting address is the virtual address where the module is loaded in memory and the size is the module size. To construct a reliable and resilient byte pattern which is able to match the same function after an update, it is fundamental to only match instruction opcodes and not operands as they could vary between builds.

FunctionByte PatternSize in Bytes
SSL_Write
(short)
41 56 56 57 55 53 48 83 EC 40 44 89 C6 4814
SSL_Write
(medium)
41 56 56 57 55 53 48 83 EC 40 44 89 C6 48
89 D7 48 89 CB 48 8B ?? ?? ?? ?? ?? 48 31
E0 48 89 44 24 38 48 8B 41 30 C7 80 B0 00
42
SSL_Write
(long)
41 56 56 57 55 53 48 83 EC 40 44 89 C6 48
89 D7 48 89 CB 48 8B ?? ?? ?? ?? ?? 48 31
E0 48 89 44 24 38 48 8B 41 30 C7 80 B0 00
00 00 01 00 00 00 E8 ?? ?? ?? ?? E8 ?? ??
?? ?? 48 83 BB 98 00 00 00 00 74 28 C7 44
70
FunctionByte PatternSize in Bytes
SSL_Read
(short)
56 57 48 83 EC 28 48 89 CF E8 ?? ?? ?? ??
89 C6 85 C0 7E
19
SSL_Read
(medium)
56 57 48 83 EC 28 48 89 CF E8 ?? ?? ?? ??
89 C6 85 C0 7E 2C 48 8B 47 30 48 63 CE 48
8B 50 78 48 29 CA 72 25 48 01 48 70 48 89
50 78
44
SSL_Read
(long)
56 57 48 83 EC 28 48 89 CF E8 ?? ?? ?? ??
89 C6 85 C0 7E 2C 48 8B 47 30 48 63 CE 48
8B 50 78 48 29 CA 72 25 48 01 48 70 48 89
50 78 48 8B 4F 30 48 83 79 78 00 75 09 48
83 C1 50 E8 ?? ?? ?? ?? 89 F0 48 83 C4 28
5F 5E C3
73

Choosing a byte pattern which is too short could result in matching multiple subroutines which are not related to the target function. On the other hand, choosing a long byte pattern has the drawback of being too specific to the build, so any little modification to the code in the subsequent builds could render ineffective the pattern match. The challenge is to find a byte pattern which is just right to be robust and effective for multiple builds. Three byte patterns of different lengths will be tested against each function to test the robustness and the accuracy of each of them.

The size of the shortest byte pattern is the minimum amount of bytes needed to correctly identify the target function on the oldest Chromium version tested (75.0.3770.100), without duplicate matches. The medium and long versions of the patterns are included to experiment with various lengths in case, in newer builds or versions, the shortest pattern matches multiple subroutines.

Results and Evaluation

Below, the results obtained by running the pattern search algorithm on the same virtual address space as the target Chromium process. The patterns have been tested to correctly perform lookups with x64 build version of Chromium (75.0.3770.100), released on 2019-06-18, and will be tested on newer versions (stable release channel) up to the latest version at the time of writing (84.0.4147.89) to observe the results.

The column “Target Found” indicates whether or not, among the n number of occurrences, the correct target was found. The column “Number of Occurrences” indicates the number of matches at different addresses for the given byte pattern. If the target is not found, its value must be zero. If the target is found, its value could be one if there is an exact match or more if multiple subroutines match the bytes in the pattern. A match is successful if the target subroutine is found and if it is unique (i.e. number of occurrences must be 1).

The experiments showed that the memory pattern search approach was not robust enough to accurately locate attack points after specific changes in target functions’ code. Nevertheless, out of the three patterns the short-size byte pattern resulted to be the most accurate, even though in some instances the target function was found but the pattern was not unique to it resulting in false positives. The virtual method table lookup method (discussed in part 1) showed more consistent results in finding the internal SSL table.

As key methods such as ssl3_read_app_data and ssl_read_close_notify were removed from the SSL VMT, malware authors shifted their focus to higher-level functions. This was a necessary move as the SSL VMT has an history of being edited or reordered quite often. Consequently, even though the VMT lookup method results were positive, further work is needed to obtain higher-level wrappers addresses especially for read operation. A possible solution would be to obtain those subroutines addresses by redirecting all the methods contained in the VMT to a common handler by applying an hook on the SSL VMT. The role of the common handler would be to check the stack to obtain SSL_Read by tracing the calls order and by checking the arguments of each of the possible candidate.

SSL_Read

Chromium VersionTarget FoundNumber of occurrences
75.0.3770.100Yes1
76.0.3809.100Yes1
77.0.3865.90Yes1
78.0.3904.97Yes1
79.0.3945.88Yes1
80.0.3987.87Yes2
81.0.4044.92Yes2
83.0.4103.97No1
84.0.4147.89No1
Results of Pattern Search on SSL_Read function using short-size byte pattern
Chromium VersionTarget FoundNumber of occurrences
75.0.3770.100Yes1
76.0.3809.100Yes1
77.0.3865.90Yes1
78.0.3904.97Yes1
79.0.3945.88Yes1
80.0.3987.87No0
81.0.4044.92No0
83.0.4103.97No0
84.0.4147.89No0
Results of Pattern Search on SSL_Read function using medium-size byte pattern
Chromium VersionTarget FoundNumber of occurrences
75.0.3770.100Yes1
76.0.3809.100Yes1
77.0.3865.90Yes1
78.0.3904.97Yes1
79.0.3945.88Yes1
80.0.3987.87No0
81.0.4044.92No0
83.0.4103.97No0
84.0.4147.89No0
Results of Pattern Search on SSL_Read function using long-size byte pattern

SSL_Write

Chromium VersionTarget FoundNumber of occurrences
75.0.3770.100Yes1
76.0.3809.100Yes1
77.0.3865.90Yes1
78.0.3904.97Yes1
79.0.3945.88Yes2
80.0.3987.87Yes2
81.0.4044.92Yes1
83.0.4103.97Yes1
84.0.4147.89Yes1
Results of Pattern Search on SSL_Write function using short-size byte pattern
Chromium VersionTarget FoundNumber of occurrences
75.0.3770.100Yes1
76.0.3809.100Yes1
77.0.3865.90Yes1
78.0.3904.97No0
79.0.3945.88No0
80.0.3987.87No0
81.0.4044.92No0
83.0.4103.97No0
84.0.4147.89No0
Results of Pattern Search on SSL_Write function using medium-size byte pattern
Chromium VersionTarget FoundNumber of occurrences
75.0.3770.100Yes1
76.0.3809.100Yes1
77.0.3865.90Yes1
78.0.3904.97No0
79.0.3945.88No0
80.0.3987.87No0
81.0.4044.92No0
83.0.4103.97No0
84.0.4147.89No0
Results of Pattern Search on SSL_Write function using long-size byte pattern

Appendix

Pattern Search Algorithm

void* SignatureScan(unsigned char* baseAddr, unsigned int size,  unsigned char* sigBytes, unsigned int sigLen) {
    unsigned char* basePtr = baseAddr;
    unsigned char* endPtr = baseAddr + size;
    
    while (basePtr < endPtr) {
        for (int i = 0; i < sigLen; i++) {
            if (sigBytes[i] != '?' && sigBytes[i] != basePtr[i]) {
                break;
            }
        }
        
        if (i == sigLen + 1) {
            return (void *) basePtr;
        }
        
        basePtr = basePtr + sizeof(unsigned char*);
    }
}

References

  1. VB2017 paper and update: Browser attack points still abused by banking trojans
  2. BoringSSL – The Chromium Projects

Leave a Comment