Introduction
In this post, I will introduce CVE-2017-3000, a CVE assigned to us last year. We analyse the weak implementation of the PRNG in Flash Player, which is used for constant blinding in its JIT compiler. We find two methods circumventing the constant blinding. Furthermore, we give a detailed exploitation plan on how to insert desired value into JIT code even if constant blinding is in place as demonstrated on the cover page. In this post, I will give details on the design of the PRNG and full exploit based on CVE-2015-5122.
Testing Environment
The full exploit is tested against flashplayer18_0r0_203_win_sa_debug.exe on Window7 SP1 32bit.
Attacking Scenario
Constant blinding was proposed to mitigate JIT-spray attack. In short, JIT-spray attempts to insert desired code gadget (constant value) in code heap for further exploitation, like code reuse attacks. Constant blinding is supposed to prevent the value of a constant number appearing in memory. The most common solution is to generate a secret cookie and XOR the constant number with the secret cookie to get a new number. At the moment of emitting JIT code, the new number will be moved into a register first. The register will be then XORed with the secret cookie to restore the original number.
For example, for a piece of code a = 0xc3585a59, its JIT code should look like below:
mov ecx, 0xccf1d312 xor ecx, 0xfa9894b mov dword ptr [edx+0x14],ecx
If the secret value is predictable for the attacker, the attacker could release desired value in executable region therefore failing constant blinding.
According to the work of Li Haifei[1], the work flow of invoking a method is given as following graph
Therefore, the work flow of a normal execution in Flash Player looks like the following one:
//source code: fun1() { a = 0x41414141; b = 0x42424242; } fun2() { c = 0x43434343; d = 0x44444444; } fun1(); fun2(); work flow of Flash Player: 1. generate JIT code for fun1 (1)Generate a random number Key1 (2)Retrieve 0x41414141 from data region (3)Emit binary code mov edx, (0x41414141 ^ Key1) (4)Emit binary code xor edx, (Key1) (5)Retrieve 0x42424242 from data region (6)Emit binary code mov edx, (0x42424242 ^ Key1) (6)Emit binary code xor edx, (Key1) 2. generate JIT code for fun2 (1)Generate a random number Key2 (2)Retrieve 0x43434343 from data region (3)Emit binary code mov edx, (0x43434343 ^ Key2) (4)Emit binary code xor edx, (Key2) (5)Retrieve 0x44444444 from data region (6)Emit binary code mov edx, (0x44444444 ^ Key2) (7)Emit binary code xor edx, (Key2)
I call the function, which contains large constant number that needs to be blinded, as constant releasing function. In the example given above, fun1 and fun2 are both constant releasing function.
To be more specific, the compiler needs to retrieve the target value in memory (with RW), xor it with secret value and then emit the value in executable region. In flash player, the jit code for user-defined function is generated one by one, and the secret cookie in a single function is the same one. Assuming under the information disclosure vulnerability, attacking flow of the attacker can be:
1) Call a few constant releasing function containing large number
2) Read the secret value and predict the future secret value
3) Prepare another constant releasing function, which contains a large number, say it A. Locate the value A in RW region and modify its value according to the future secret value
4) Call the constant releasing function after all preparation. It will then release the value in executable region.
And the overall workflow can be generalised as following:
//source code fun1() { a = 0x41414141; b = 0x42424242; } fun2() { c = 0x43434343; d = 0x44444444; } fun3() { e = 0x45454545; f = 0x46464646; } evilCode() { } fun1(); fun2(); evilCode(); fun3(); 1. fun1: generate JIT code for fun1 (1)Generate a random number Key1 (2)Retrieve 0x41414141 from data region (3)Emit binary code mov edx, (0x41414141 ^ Key1) (4)Emit binary code xor edx, (Key1) (5)Retrieve 0x42424242 from data region (6)Emit binary code mov edx, (0x42424242 ^ Key1) (6)Emit binary code xor edx, (Key1) 2. fun2: generate JIT code for fun2 (1)Generate a random number Key2 (2)Retrieve 0x43434343 from data region (3)Emit binary code mov edx, (0x43434343 ^ Key2) (4)Emit binary code xor edx, (Key2) (5)Retrieve 0x44444444 from data region (6)Emit binary code mov edx, (0x44444444 ^ Key2) (7)Emit binary code xor edx, (Key2) 3. evilCode: read and write memory under attacker's control (1)Search memory to find Key1 and Key2 (2)Retrieve seed value of Key1 and Key2, and predict the value of Key3 (3)Search memory to find 0x45454545 and 0x46464646, which will be used in fun3 (4)Modify those two values to be (0xc35841 ^ Key3) and (0xc35941 ^ Key3) 4. fun3: generate JIT code for fun3 (1)Generate a random number Key2 (2)Retrieve (0xc35841 ^ Key3) from data region (3)Emit binary code mov edx, (0xc35841 ^ Key3 ^ Key3 = 0xc35841) (4)Emit binary code xor edx, (Key3) (5)Retrieve (0xc35941 ^ Key3) from data region (6)Emit binary code mov edx, (0xc35941 ^ Key3 ^ Key3 = 0xc35941) (7)Emit binary code xor edx, (Key3)
Attacking PRNG
After introducing the attacking scenario and exploitation plan, we will introduce the implementation of the PRNG for constant blinding in the first section. In the second section, we will introduce the attack simply based on information disclosure. In the third section, we will introduce the attack based on cryptanalysis.
Introduction of PRNG
After introducing the attacking scenario of this CVE, now let’s turn to focus on attacking the PRNG. With some reverse-engineering effort on Flash Player 24.0.0.183, we get the logic of the PRNG as following code in C-style.
int RandomFastNext(int &uValue, int uXorMask) { if(uValue & 1) { uValue = (uValue>>1) ^ uXorMask; } else { uValue = uValue>>1; } return uValue; } int RandomPureHash(int seed) { int ans; seed = ((seed << 13) ^ seed ) - (seed >> 21); ans = (seed*(seed * seed * 0x3d73 + 0x0c0ae5) - 0x2df722f3 ) & 0x7fffffff; ans += seed; ans = ((ans << 13) ^ ans ) - (ans >> 21); return ans; } int GenerateRandomNumber(int &uValue, int uXorMask) { long aNum = RandomFastNext(uValue, uXorMask); aNum = RandomPureHash(aNum * 71); cout<<"Calculate number:"<<(aNum & 0x7fffffff)<<endl; return aNum & 0x7fffffff; }
Variable uValue, uXorMask are maintained in a structure. uXorMask is a fixed value with 0x48000000. More details could be found in [2]. uValue is the seed value we want to recover.
Attack on Memory Information Disclosure
With information disclosure vulnerability, attacker can search whole memory space and check each value against the leaked cookie. However, such attack is impractical. Since the default timeout of FlashPlayer is 15 seconds [3], attacker cannot afford time to check the value via the hash calculation. However, since uValue and uXorMask are maintained in a data structure. With a close look at uValue in memory, we find that it is locating adjacent to two constant numbers 0x4800000000 and 0x7fffffff. Attacker can easily use those two values as signature to locate the address of uValue.
Attack on Cryptanalysis
Another method is reverse the seed value from the disclosed secret cookie.
The hash function is intended to make attacker unable to reverse the seed value at a reasonable time cost. However from our analysis, the hash function is insufficient in confusion and diffusion enabling attacker to reverse the seed value. The core of the hash function lies in function RandomPureHash, which can be separated into three sub functions. The first and third one are identical bit operating functions. The second one is a polynomial function.
Poly function: g(n) = (c3 * n3 + c2 * n + c1 ) & 0x7fffffff + n;
Bit operation: f(n) = ((n << 13) xor n) – (n >> 21)
To reverse the polynomial function, the attacker just needs to reverse the input bitwise starting from the least significant bit of the output. Since it’s a polynomial function, higher bit of the input cannot influence lower bit of the result. It’s easy to reverse the input with backtracking method.
To reverse the bit operating function, the process is a little complicated. The attacker can divide the input into 3 parts: higher part, middle part and lower part. Starting from left to right, higher part corresponds to bit 1-11, middle part corresponds to bit 12-21 and lower part corresponds to bit 22-32. And we are supposed to reverse the function: target + (value » 21) = (value « 13) ^ value The attacker can first find all possible pairs of (higher part, lower part) that fit the function above. Then brute force all the possible values of middle part.
The reorganised bit operation is given below:
Final Exploit
I upload the full exploit, demonstrated as cover page, to github [4]. More details about my attack can be found in the code.
Conclusion
Basically speaking, this is a very academic work assuming there exist some code diversification technique, like G-Free, to eliminate all possible gadgets in the static code and attackers have to craft the gadget from the JIT code to construct an exploit. However, such assumption is still not real at present. A far more important lesson learned from this CVE is that self-implemented PRNG is ill-considered in most of the time and lack of review from professional crypto expert.
Reference
[1] https://recon.cx/2012/schedule/attachments/43_Inside_AVM_REcon2012.pdf
[2] https://github.com/adobe-flash/avmplus/blob/65a05927767f3735db37823eebf7d743531f5d37/core/MathUtils.cpp
[3] https://forums.adobe.com/thread/719280
[4] https://github.com/dangokyo/CVE-2017-3000
[…] [ Popular Software ] Adobe Flash Player 信息泄露漏洞分析(CVE-2017-3000): https://dangokyo.me/2018/08/26/analysis-on-cve-2017-3000/ […]
LikeLike