1
+ // SPDX-License-Identifier: UNLICENSED
2
+ pragma solidity ^ 0.8.13 ;
3
+
4
+ import "forge-std/Test.sol " ;
5
+ import { IncentivizedMockEscrow } from "../../../src/apps/mock/IncentivizedMockEscrow.sol " ;
6
+ import { IIncentivizedMessageEscrow } from "../../../src/interfaces/IIncentivizedMessageEscrow.sol " ;
7
+ import { TestCommon } from "../../TestCommon.t.sol " ;
8
+ import { ICrossChainReceiver } from "../../../src/interfaces/ICrossChainReceiver.sol " ;
9
+
10
+
11
+ contract TargetExploit is ICrossChainReceiver {
12
+
13
+ event ACK (bytes );
14
+
15
+ IIncentivizedMessageEscrow immutable MESSAGE_ESCROW;
16
+
17
+ constructor (IIncentivizedMessageEscrow messageEscrow_ ) {
18
+ MESSAGE_ESCROW = messageEscrow_;
19
+ }
20
+
21
+ function submitMessage (
22
+ bytes32 destinationIdentifier ,
23
+ bytes calldata destinationAddress ,
24
+ bytes calldata message ,
25
+ IIncentivizedMessageEscrow.IncentiveDescription calldata incentive ,
26
+ uint64 deadline
27
+ ) external payable returns (uint256 gasRefund , bytes32 messageIdentifier ) {
28
+ (gasRefund, messageIdentifier) = MESSAGE_ESCROW.submitMessage {value: msg .value }(
29
+ destinationIdentifier,
30
+ destinationAddress,
31
+ message,
32
+ incentive,
33
+ deadline
34
+ );
35
+ }
36
+
37
+ function receiveAck (bytes32 /* destinationIdentifier */ , bytes32 /* messageIdentifier */ , bytes calldata acknowledgement ) external {
38
+ emit ACK (acknowledgement);
39
+ }
40
+
41
+ function receiveMessage (bytes32 /* sourceIdentifierbytes */ , bytes32 /* messageIdentifier */ , bytes calldata /* fromApplication */ , bytes calldata /* message */ ) pure external returns (bytes memory acknowledgement ) {
42
+ return hex "" ;
43
+ }
44
+ }
45
+
46
+ contract DestinationHelper is ICrossChainReceiver {
47
+ IIncentivizedMessageEscrow immutable MESSAGE_ESCROW;
48
+
49
+ constructor (IIncentivizedMessageEscrow messageEscrow_ ) {
50
+ MESSAGE_ESCROW = messageEscrow_;
51
+ }
52
+
53
+ function submitMessage (
54
+ bytes32 destinationIdentifier ,
55
+ bytes calldata destinationAddress ,
56
+ bytes calldata message ,
57
+ IIncentivizedMessageEscrow.IncentiveDescription calldata incentive ,
58
+ uint64 deadline
59
+ ) external payable returns (uint256 gasRefund , bytes32 messageIdentifier ) {
60
+ (gasRefund, messageIdentifier) = MESSAGE_ESCROW.submitMessage {value: msg .value }(
61
+ destinationIdentifier,
62
+ destinationAddress,
63
+ message,
64
+ incentive,
65
+ deadline
66
+ );
67
+ }
68
+
69
+ function receiveAck (bytes32 destinationIdentifier , bytes32 messageIdentifier , bytes calldata acknowledgement ) pure external {}
70
+
71
+ function receiveMessage (bytes32 /* sourceIdentifierbytes */ , bytes32 /* messageIdentifier */ , bytes calldata /* fromApplication */ , bytes calldata message ) pure external returns (bytes memory acknowledgement ) {
72
+ return message;
73
+ }
74
+
75
+ }
76
+
77
+ contract ReemitAckAnywhereExploitTest is TestCommon {
78
+
79
+ TargetExploit targetToExploit;
80
+
81
+ DestinationHelper destinationHelper;
82
+
83
+ function setUp () override public {
84
+ super .setUp ();
85
+
86
+ targetToExploit = new TargetExploit (escrow);
87
+ destinationHelper = new DestinationHelper (escrow);
88
+
89
+ // We need to set escrow as an approved sender and destination on targetToExplot
90
+ // We need to set escrow as an approved caller on destinationHelper.
91
+ vm.prank (address (targetToExploit));
92
+ escrow.setRemoteImplementation (_DESTINATION_IDENTIFIER, abi.encode (address (escrow)));
93
+
94
+ vm.prank (address (destinationHelper));
95
+ escrow.setRemoteImplementation (_DESTINATION_IDENTIFIER, abi.encode (address (this )));
96
+ }
97
+
98
+ function test_exploit_target_contract () public {
99
+ // This explot is about using the fact that anyone can call a destination escrow.
100
+
101
+ // We will start by sending a message from TargetExplot:
102
+
103
+ // vm.recordLogs();
104
+ (, bytes32 messageIdentifier ) = targetToExploit.submitMessage {value: _getTotalIncentive (_INCENTIVE)}(
105
+ _DESTINATION_IDENTIFIER,
106
+ abi.encodePacked (
107
+ bytes1 (0x14 ),
108
+ bytes32 (0 ),
109
+ abi.encode (address (targetToExploit))
110
+ ),
111
+ hex "04e110 " ,
112
+ _INCENTIVE,
113
+ 0
114
+ );
115
+
116
+ // Now we need to construct a message to attack with.
117
+
118
+ bytes memory explotMessage = abi.encodePacked (
119
+ bytes1 (0 ),
120
+ bytes32 (messageIdentifier), // Remember, we can set anything.
121
+ abi.encodePacked (
122
+ uint8 (20 ),
123
+ bytes32 (0 ),
124
+ abi.encode (address (targetToExploit))
125
+ ), // We need to set this to the contract we want to exploit.
126
+ abi.encodePacked (
127
+ uint8 (20 ),
128
+ bytes32 (0 ),
129
+ abi.encode (address (destinationHelper))
130
+ ), // Our application so we know it goes through
131
+ bytes8 (0 ), // deadline, don't care about
132
+ bytes6 (uint48 (600000 )), // max gas, sets set high.
133
+ abi.encodePacked (
134
+ hex "deaddeaddeaddead "
135
+ )
136
+ );
137
+
138
+ bytes memory nonLegitMessage = abi.encodePacked (
139
+ bytes32 (uint256 (uint160 (address (this )))),
140
+ _DESTINATION_IDENTIFIER,
141
+ _DESTINATION_IDENTIFIER,
142
+ explotMessage
143
+ );
144
+
145
+ (uint8 v , bytes32 r , bytes32 s ) = signMessageForMock (nonLegitMessage);
146
+ bytes memory nonLegitMessageContext = abi.encode (v, r, s);
147
+
148
+ // lets execute the non-legit message
149
+
150
+ vm.recordLogs ();
151
+ escrow.processPacket (nonLegitMessageContext, nonLegitMessage, bytes32 (abi.encode (address (0 ))));
152
+ Vm.Log[] memory entries = vm.getRecordedLogs ();
153
+
154
+ (, , bytes memory ackMessage ) = abi.decode (entries[1 ].data, (bytes32 , bytes , bytes ));
155
+
156
+ bytes memory preparedExploitMessage = this .memorySlice (ackMessage, 64 );
157
+
158
+ vm.recordLogs ();
159
+
160
+ vm.expectRevert (abi.encodeWithSignature ("CannotRetryWrongMessage(bytes32,bytes32) " , bytes32 (0 ), keccak256 (preparedExploitMessage)));
161
+ escrow.reemitAckMessage (
162
+ _DESTINATION_IDENTIFIER,
163
+ abi.encode (address (escrow)),
164
+ preparedExploitMessage
165
+ );
166
+
167
+ // The message fails above so we can't continue. But if we could, this is what we would do.
168
+
169
+ // (, , bytes memory reAckMessage) = abi.decode(entries[1].data, (bytes32, bytes, bytes));
170
+
171
+ // bytes memory reAckMessageWithSender = abi.encodePacked(
172
+ // bytes32(uint256(uint160(address(escrow)))),
173
+ // reAckMessage
174
+ // );
175
+
176
+ // (v, r, s) = signMessageForMock(reAckMessageWithSender);
177
+ // bytes memory reAckMessageContext = abi.encode(v, r, s);
178
+
179
+ // escrow.processPacket(reAckMessageContext, reAckMessageWithSender, bytes32(abi.encode(address(0))));
180
+ }
181
+ }
0 commit comments