In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-03-29 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Network Security >
Share
Shulou(Shulou.com)05/31 Report--
This article is about how to analyze Chrome 1day vulnerability CVE-2021-21224. The editor thinks it is very practical, so I share it with you. I hope you can get something after reading this article. Let's take a look at it.
1. Timeline
April 4, 2021-tr0y4 submits this vulnerability in Chromium issue tracker
April 12, 2021-Chromium fixes this vulnerability by exposing the relevant poc in addition to the patch
April 14, 2021-domestic researcher frust93717815 disclosed the exp of this vulnerability [1] [2], affecting the stable version of Chrome browser without sandboxing.
April 20, 2021-Chrome releases updates and thanks, corresponding to CVE number CVE-2021-21224.
Second, background
The flaw occurs in the optimization compiler TurboFan of v8, specifically in the Simplified-Lowering phase of JIT. For an introduction to TurboFan, please refer to "Introduction to TurboFan" [3]. For details about the Simplefied-Lowering phase, please refer to the analysis of CVE-2020-16040 [4]. In addition, this article uses Turbolizer to show the sea of nodes graph of different optimization phases of TurboFan.
Here's a brief introduction to sea of nodes, which is the program representation of the TurboFan runtime. When TurboFan optimizes the code, the whole code is stored in the form of graph, and each node is a node, including mathematical operations, loading, storage, calls, constants and so on. The specific information for each node is as follows:
Each node has a restriction type and a feedback type, the former can be understood as the type of node after initialization, and the latter is the real type of feedback during the actual optimization process. The representation of Node represents the representation type of the node, as shown below:
Analysis of loopholes and patches
According to commit information, the vulnerability occurs within the RepresentationChanger:: GetWord32RepresentationFor function. The call stack of this function is shown in figure 1, and the call to this function mainly occurs in the Simplified-Lowering phase:
Figure 1. GetWord32RepresentationFor call stack
The specific patch shown in figure 2 is the Representation type of the incoming node, and the output_type is the feedback type,use_info of the current node and the successor node information of the current node (mainly used to distinguish between 32-bit and 64-bit). So the logic before repair is to satisfy one of two situations: one is that the feedback type of the current node is Signed32 or UnSigned32;, and the other is that the feedback type of the current node is SafeInteger and its successor node is used as 32-bit use (IsUsedAsWord32).
The repaired logic can be updated if one of the following three conditions is met: first, the feedback type of the current node is Signed32;; second, the current node feedback type is UnSigned32 and the type of successor node is None;; third, the current node is SafeInteger and the successor node is used as 32-bit.
Figure 2. Patch information
Comparing the logic before and after the repair, it can be found that when the feedback type of the current node is UnSinged32, the op cannot be modified directly, and the subsequent node type needs to be guaranteed to be None before it can be modified. The vulnerability exploits this condition: when the current node satisfies UnSigned32, the successor node type is not None (the exp relay node type is SingdeSmall, which is a signed number as shown in figure 3), and finally op is assigned to TruncateInt64ToInt32.
Therefore, all numbers passed to the current TruncateInt64ToInt32 node are directly converted to a signed 32-bit number, and integer underflows occur if the passed number happens to use the corresponding 32-bit symbol bit (such as 0xffffffff used in exp).
Figure 3. Use_info of successor node
Loophole recurrence
In order to verify the vulnerability, this article experimented with the v8 code from the previous version of the patch, and the commit hash is f87baad0f8b3f7fda43a01b9af3dd940dd09a9a3. For ease of understanding, this article simplifies the foo function in exp as follows:
Here we compare the graph results of Simplified Lowering and its previous phase EscapeAnalysis to show the changes of turbolizer graph before and after repair. As shown in figure 1, node 70 corresponds to the return result of Max in the EscapeAnalysis phase, which continues with the next subtraction operation (SpeculativeSafeIntegerSubtract) as the input of node 51. After the Simplified Lowering phase, a new Node 95 TruncateInt64ToInt32 is inserted between Node 70 and Node 51, which directly converts the output of Node 70 into a 32-bit signed number, and then subtracts.
Looking back at the example used in this section, the return value of Max is 0xffffffff, that is, y=0xffffffff. After passing through the TruncateInt64ToInt32 node, y is converted to a 32-bit signed number, that is,-1, resulting in an integer underflow. So the final result of z through the sign function is 1.
Figure 4. Repair the turbolizer graph of the pre-EscapeAnalysis phase
Figure 5. Repair the turbolizer graph of the pre-Simplified Lowering phase
The fixed turbolizer graph is shown in figure 6, which shows that the original TruncateInt64ToInt32 node is now CheckedUint64ToInt32, which is checked when converting 64-bit unsigned numbers to 32-bit signed numbers to avoid integer overflow.
Figure 6. Turbolizer graph in post-repair Simplified Lowering phase
Fourth, vulnerability exploitation analysis 1. Vulnerability exploitation
Integer overflow alone does not implement RCE, similar to the previous exploit of CVE-2020-16040 [5] and CVE-2021-21220 [6]. The author of this exploit combines the shift function to obtain an array of length-1 to implement OOB.
The flaw in exp is triggered by the foo function, and the following code retains only the portion of the original exp that fetches an array of length-1. The result of each line of code execution is marked in the comment, and you can see that the actual length of the last declared arr array is 1, but the length becomes-1 directly after the shift operation:
The reason for the above situation is explained in detail through the analysis of each node in the turbolizer graph (Simplified-Lowering phase).
Figure 7 is the key node contained in the 2-4 lines of code. First of all, the first two lines of code determine that the range of x is (- 1 Math.max 4294967295). Then in the Math.max function, x is first compared with 0 to take the maximum value, so the updated range is (0, 4294967295), and then compared with-1 to take the maximum value, the range is still (0, 0).
Figure 7. Output result of Math.max function
After that, the value of Node 90 will be converted to Int32 and then subtracted. The corresponding code is 0mury in line 5, and the corresponding range is (- 4294967295). It is important to note that the scope here is not the real scope, there will be subsequent updates to feedback type will further update this scope, but this does not affect the subsequent results. The Math.sign function will return + 1 0/-0/NaN based on the positive or negative of the parameters. Since +-0 will not affect the final result, this article will not discuss it too much. Because the value of 0mury is less than or equal to zero, the output of this function can only be (- 1), that is, the range of z is (- 1). Then to line 6, the array is declared, and the boundary of the array is checked (the length must be greater than zero), so the range of z and 0 is intersected to get the length of the final array.
The Shift operation removes an element from the array and reduces the array length by one. Since the length of the array can only be 0, jit directly fixes the length of the array after shift to 0-1 =-1 in the optimization process, which is why the length of the array before shift is 1 and the length becomes-1 after shift shift. This actually takes advantage of a bug that exists in the shift function, that is, the function does not check the boundaries of the array and subtracts one operation directly regardless of the length of the original array. Since the length of the array is 0, the length after shift is fixed to-1, which is written directly into the code optimized by jit. (in figure 9, the length is directly assigned to 0xfffffffe, that is,-2. After debugging, it is found that the length value stored in jsarry in memory has moved 1 bit to the left, in order to ensure that all the numbers in its memory end with 0 and the pointer ends with 1. You can refer to the memory layout of objects of type JS [8]), so the final length of the array is-1.
Figure 8. Boundary check results
It is also important to note that because the shift operation also removes an element from the array, if the array declared on line 6 does not contain any elements, the remove element operation will cause v8 to crash directly. The overflow in exp just declares an array of length 1, which satisfies this restriction. This also explains another problem, if you do not take advantage of integer overflow vulnerability, directly declare an array of length 0, then according to the optimized jit code can also get an array of length-1? In fact, the jit code validates the length before shift. As shown in figure 9, rdi is the length of the array. If the length is 0, it will skip the array length update and return directly, so you can't get an array of length-1.
Figure 9. Boundary check and length assignment before calling shift
Of course, Google also fixed the bug in an update on April 15th. In this patch, shift and similar pop functions do boundary checking first after calculating the length of the new array, basically eliminating similar uses.
Figure 10. [turbofan] Harden ArrayPrototypePop and ArrayPrototypeShift patch
two。 Memory read and write
1) access the rwarr array out of bounds
Exp declares an array of length 2 and an ArrayBuffer of length 0x1000 after the arr array, and then modifies the value of corrput_arr [12]. Here, the length of the rwarr array is modified by overflow.
According to the debugging information, we can know that the position of the element in corrput_arr is in 0x18b80828216c, the length of each element is 4 bytes, and the position of element in rwarr is 0x18b8082821a8. According to the distance between the two, minus the first eight bytes of corrput_arr (map and length, respectively), you can calculate the length of the rwarr array corresponding to the 13th element of the corrput_arr array. By changing the length to a larger value (such as 0x22444), you can achieve out-of-bounds access to the rwarr array.
2) cross the boundary to access corrupt_buff
SetbackingStore is to set the value of backing_store in corrput_buf, and leakObjLow is to disclose the information at address o.
The first element set by rwarr has a value of 5.1 and a length of 8 bytes, so the elements in the array are all 8 bytes, so the location of rwarr [4] is in 0x18b8082821a98+8+4*8=0x18b8082821ac0, and the address plus 4 is the address of corrupt_buff 's backing_store, so here you also need to use rwarr [5] to modify the high four bytes of rwarr [4] and the low four bytes of rwarr [5], respectively. The exp then declares a Dataview from the corrupt_buff, and the backing_store records the memory address of the actual DataView. If we change the backing_store pointer to the memory address we want to write, then when we call a similar instruction view.setUint32 (0, poc, true), we actually write poc to the specified memory address, thus writing to any address.
The leakObjLow function uses the slot property of corrupt_buff to change the attribute to an object o, then the address of o will be written to the memory range where corrupt_buff is located, and then use the overflow of rwarr to access the value to achieve leakage.
3. Code execution
The method of code execution takes advantage of the RWX segment of WASM. Code execution can be achieved by divulging the address of the RWX segment, writing the shellcode, and finally calling the export function of WASM.
The above is how to analyze the Chrome 1day vulnerability CVE-2021-21224. The editor believes that there are some knowledge points that we may see or use in our daily work. I hope you can learn more from this article. For more details, please follow the industry information channel.
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.