EternalBlue Series Part 5: Vulnerability analysis
by h3xduck
21 August 20213. Comprehensive analysis of the vulnerability
Welcome to my second favorite section of this eternalblue series, where we will now join all pieces of the puzzle gathered until this point:
We know from the FEAs research that…
- The server allocates memory in the heap for the FEAlist in NT format
- The server first needs to check that the FEAlist size which came from the client in the cbLIst attribute corresponds to the real FEAlist size once counting the FEAs in the list.
- That if the value of cbList is wrong, the server will not reject the transaction, but rather will correct itself the value of cbList.
And from reverse-engineering we know that…
- There is some subtraction going on which truncates the value to 16-bits, therefore losing the upper 16 bits of information. We know that this is the vulnerability because we read it from the metasploit exploit, but how exactly is losing 16 bits inside a variable a vulnerability?
Now, to help with this study I present you the pseudocode of the SrvOs2FeaListToNt and SrvOs2FeaListSizeToNt functions in which we are interested (SrvOs2FeaListSizeToNt being the one we decompiled with Ghidra on part2, and you can also decompile SrvOs2FeaListToNt in case you want to obtain the pseudocode by yourself):
(This relates to the decompiled code of before, see how p0s2FeaList is our old param_1, and pCurrentOs2FeaRecord is piVar1).
Okay, focusing on the pseudocode above, let’s follow the execution of the function when it is called:
- Instruction 3 - First, the server wants to know how large the buffer should be allocated in the heap for the NT Buffer, which will contain the FEAlist in NT format. For this, it calls the SrvOs2FeaListSizeToNt function.
- Instruction 22 - We are now iterating through all the FEAs of the whole FEAlist. We maintain two variables here, pCurrentOs2FeaRecord which is simply a pointer to the current FEA and which at the end of the loop will point to the last FEA address; and NtFeaListSize, which is the sum of the sizes of each FEA. At the end of the loop we will return NtFeaListSize, effectively getting the correct buffer size to allocate as we desired.
- Instruction 29 - Here the loop has already ended, but we first check that the value contained in SizeOfListInBytes was accurate or the size of all the FEAs we iterated through was different. If it is correct, we just return the size of the NT buffer to allocate.
- Instruction 33 - If we are here it’s because the server detected that the size provided in SizeOfListInBytes does not match with the size of the list itself. As we mentioned, the server will proceed to overwrite the original value with the calculated size of the list subtracting the address at the start and at the end of the FEAlist. This subtraction, however, is the vulnerability, because it does not work as intended. The operands of the subtraction, and thus the result, is casted into a WORD from their original DWORD type. But as we saw when revising the code, when assigning the result to SizeOfListInBytes, we have lost 16 bytes of information. Thus the following situation is possible:
Element | Real HIGH WORD | Real LOW WORD | Values expected |
---|---|---|---|
FEA List size in bytes | 0000 | fff5 | 0000ff5f |
SizeOfListInBytes | 0001 | 0000 | 00010000 |
Overwritten SizeOfListInBytes | 0001 | fff5 | 0000ff5f |
Appreciate the difference between the actual real value and the value which was expected to be received, at least by the developers, when overwriting the value of SizeOfListInBytes. Since only the lower word of the dword which is SizeOfListInBytes is overwritten, then if an attacker purposely set the value of SizeOfListInBytes to one which does not correspond to the actual size of the FEAlist but in which the higher word has a value larger than 0 and in which the lower word is smaller than the lower word of the size of the FEAlist, then the value of SizeOfListInBytes will get larger when the server overwrites it instead of shrinking, which is what the server desired to do. Now, the value of SizeOfListInBytes is much larger than the one in NtFeaListSize and also larger to the one we returned to the main function as the size of the NT buffer to allocate!
Let’s then continue following the execution so that we can see what consequences this has:
- Instruction 3 - We are back from the subroutine, and we get as an argument the size of the NT buffer which we calculated. This value is completely right.
- Instruction 6 - We now allocate memory for the NT buffer, of size NtFeaListSize as we have mentioned before.
- Instruction 8 - Here it is the second part of the vulnerability. We are now calculating Os2FeaRecordsEndAddress, a pointer to the end of the FEAlist once the server has overwritten the value of the size in case it was wrong, adding the value of SizeOfListInBytes to the pointer to the start of the FEAList. Note that since SizeOfListInBytes was too large due to the bug, now Os2FeaRecordsEndAddress points to an address which is much larger than the one in NtFeaListSize!
- Instruction 12 - Now we start copying each FEA from the FEAlist into the NT buffer one by one, until we reach Os2FeaRecordsEndAddress (Instruction 14). However that value is larger than the NT buffer itself, so the copy will overflow the NT buffer in the heap.
There it is! We have found our buffer overflow, which is an excellent signal. Please revise all the previous and make sure you fully understand the vulnerability and how we reached this point because we are almost at the part when things get spicy with our exploit.
There is only one small aspect to take into consideration with this buffer overflow, which is that we need some way of “stopping” copying FEAs where we want, or otherwise we could hit some undesired heap memory. This is where the FEA flags play a role: Each FEA has a flag (we saw this when we saw the FEAs on part 4 of the series) which take a value either 0x00 or 0x80. The meaning of these flags does not actually matter for the vulnerability (they just indicate the importance of the FEA when transforming it into NT format) but what is relevant is that, as we see in instruction 10 above, we will stop copying the FEAs if notice some flag has an incorrect value. Therefore, we will put a FEA with a flag set different to 0x00 or 0x80 at the end of the overflowing buffer in order to stop the memcpy whenever we desire.
(As a curious note, the fact that the overflow ends by this incorrect flag makes the server return an error message to the client indicating it, which is in fact what many EternalBlue vulnerability checkers use to see if the system is exploitable, because if that error does not appear it means that the system was patched and the overflow is no longer possible thus not triggering that copy of the invalid FEA).
Going back to the vulnerability, some issues arise now, however: we are overflowing the NT buffer, but are we overwriting anything important because of that? Can we control what we overwrite? How do we take advantage of this overflow?. We will explore this matter and answer these questions. In the following sections, we will see some additional bugs which will help us exploit this vulnerability and, after that, I’ll present you with the full picture of eternalblue.
See you on the next part!
tags: stack - overflow - buffer - exploitation - shellcode - RCE - eternalblue - vulnerability - windows - eternalblue - SMBThis work is licensed under a Creative Commons Attribution 4.0 International License.