Skip to content

Commit

Permalink
test: implement a test for the critical exploit
Browse files Browse the repository at this point in the history
  • Loading branch information
reednaa committed Mar 20, 2024
2 parents 6634b0a + 05ffca4 commit 87d5f3e
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 1 deletion.
181 changes: 181 additions & 0 deletions test/IncentivizedMessageEscrow/reemitAck/AnyAckExploit.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import { IncentivizedMockEscrow } from "../../../src/apps/mock/IncentivizedMockEscrow.sol";
import { IIncentivizedMessageEscrow } from "../../../src/interfaces/IIncentivizedMessageEscrow.sol";
import { TestCommon } from "../../TestCommon.t.sol";
import { ICrossChainReceiver } from "../../../src/interfaces/ICrossChainReceiver.sol";


contract TargetExploit is ICrossChainReceiver {

event ACK(bytes);

IIncentivizedMessageEscrow immutable MESSAGE_ESCROW;

constructor(IIncentivizedMessageEscrow messageEscrow_) {
MESSAGE_ESCROW = messageEscrow_;
}

function submitMessage(
bytes32 destinationIdentifier,
bytes calldata destinationAddress,
bytes calldata message,
IIncentivizedMessageEscrow.IncentiveDescription calldata incentive,
uint64 deadline
) external payable returns(uint256 gasRefund, bytes32 messageIdentifier) {
(gasRefund, messageIdentifier) = MESSAGE_ESCROW.submitMessage{value: msg.value}(
destinationIdentifier,
destinationAddress,
message,
incentive,
deadline
);
}

function receiveAck(bytes32 /* destinationIdentifier */, bytes32 /* messageIdentifier */, bytes calldata acknowledgement) external {
emit ACK(acknowledgement);
}

function receiveMessage(bytes32 /* sourceIdentifierbytes */, bytes32 /* messageIdentifier */, bytes calldata /* fromApplication */, bytes calldata /* message */) pure external returns(bytes memory acknowledgement) {
return hex"";
}
}

contract DestinationHelper is ICrossChainReceiver {
IIncentivizedMessageEscrow immutable MESSAGE_ESCROW;

constructor(IIncentivizedMessageEscrow messageEscrow_) {
MESSAGE_ESCROW = messageEscrow_;
}

function submitMessage(
bytes32 destinationIdentifier,
bytes calldata destinationAddress,
bytes calldata message,
IIncentivizedMessageEscrow.IncentiveDescription calldata incentive,
uint64 deadline
) external payable returns(uint256 gasRefund, bytes32 messageIdentifier) {
(gasRefund, messageIdentifier) = MESSAGE_ESCROW.submitMessage{value: msg.value}(
destinationIdentifier,
destinationAddress,
message,
incentive,
deadline
);
}

function receiveAck(bytes32 destinationIdentifier, bytes32 messageIdentifier, bytes calldata acknowledgement) pure external {}

function receiveMessage(bytes32 /* sourceIdentifierbytes */, bytes32 /* messageIdentifier */, bytes calldata /* fromApplication */, bytes calldata message) pure external returns(bytes memory acknowledgement) {
return message;
}

}

contract ReemitAckAnywhereExploitTest is TestCommon {

TargetExploit targetToExploit;

DestinationHelper destinationHelper;

function setUp() override public {
super.setUp();

targetToExploit = new TargetExploit(escrow);
destinationHelper = new DestinationHelper(escrow);

// We need to set escrow as an approved sender and destination on targetToExplot
// We need to set escrow as an approved caller on destinationHelper.
vm.prank(address(targetToExploit));
escrow.setRemoteImplementation(_DESTINATION_IDENTIFIER, abi.encode(address(escrow)));

vm.prank(address(destinationHelper));
escrow.setRemoteImplementation(_DESTINATION_IDENTIFIER, abi.encode(address(this)));
}

function test_exploit_target_contract() public {
// This explot is about using the fact that anyone can call a destination escrow.

// We will start by sending a message from TargetExplot:

// vm.recordLogs();
(, bytes32 messageIdentifier) = targetToExploit.submitMessage{value: _getTotalIncentive(_INCENTIVE)}(
_DESTINATION_IDENTIFIER,
abi.encodePacked(
bytes1(0x14),
bytes32(0),
abi.encode(address(targetToExploit))
),
hex"04e110",
_INCENTIVE,
0
);

// Now we need to construct a message to attack with.

bytes memory explotMessage = abi.encodePacked(
bytes1(0),
bytes32(messageIdentifier), // Remember, we can set anything.
abi.encodePacked(
uint8(20),
bytes32(0),
abi.encode(address(targetToExploit))
), // We need to set this to the contract we want to exploit.
abi.encodePacked(
uint8(20),
bytes32(0),
abi.encode(address(destinationHelper))
), // Our application so we know it goes through
bytes8(0), // deadline, don't care about
bytes6(uint48(600000)), // max gas, sets set high.
abi.encodePacked(
hex"deaddeaddeaddead"
)
);

bytes memory nonLegitMessage = abi.encodePacked(
bytes32(uint256(uint160(address(this)))),
_DESTINATION_IDENTIFIER,
_DESTINATION_IDENTIFIER,
explotMessage
);

(uint8 v, bytes32 r, bytes32 s) = signMessageForMock(nonLegitMessage);
bytes memory nonLegitMessageContext = abi.encode(v, r, s);

// lets execute the non-legit message

vm.recordLogs();
escrow.processPacket(nonLegitMessageContext, nonLegitMessage, bytes32(abi.encode(address(0))));
Vm.Log[] memory entries = vm.getRecordedLogs();

(, , bytes memory ackMessage) = abi.decode(entries[1].data, (bytes32, bytes, bytes));

bytes memory preparedExploitMessage = this.memorySlice(ackMessage, 64);

vm.recordLogs();

vm.expectRevert(abi.encodeWithSignature("CannotRetryWrongMessage(bytes32,bytes32)", bytes32(0), keccak256(preparedExploitMessage)));
escrow.reemitAckMessage(
_DESTINATION_IDENTIFIER,
abi.encode(address(escrow)),
preparedExploitMessage
);

// The message fails above so we can't continue. But if we could, this is what we would do.

// (, , bytes memory reAckMessage) = abi.decode(entries[1].data, (bytes32, bytes, bytes));

// bytes memory reAckMessageWithSender = abi.encodePacked(
// bytes32(uint256(uint160(address(escrow)))),
// reAckMessage
// );

// (v, r, s) = signMessageForMock(reAckMessageWithSender);
// bytes memory reAckMessageContext = abi.encode(v, r, s);

// escrow.processPacket(reAckMessageContext, reAckMessageWithSender, bytes32(abi.encode(address(0))));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import { TestCommon } from "../TestCommon.t.sol";
import { TestCommon } from "../../TestCommon.t.sol";

contract ReemitAckMessageTest is TestCommon {
event Message(
Expand Down

0 comments on commit 87d5f3e

Please sign in to comment.